如何在C语言中将数组重复元素移至末尾并保留唯一元素顺序?
数组去重并将重复元素移至末尾的问题修复
需求说明
- 返回处理后数组的唯一元素数量
- 维持唯一元素的相对顺序
- 时间复杂度O(nlogn)
- 最多使用1个辅助数组(归并排序的临时数组不计)
- 支持正负整数数组
示例输入:
int arr[] = {6, 2, 3, 6, 9, 2, 7, 8, 1};
预期输出:{6, 2, 3, 9, 7, 8, 1, 6, 2}(末尾重复元素顺序无要求)
当前问题
尝试通过复制原数组并排序、结合二分查找标记已使用元素的方案,但出现错误:部分唯一元素被误判为重复移至末尾。当前代码输出:
Number of duplicates: 2 { 6, 2, 9, 7, 8, 1, 2, 3, 6 }
问题代码
#include <stdio.h> #include <stdlib.h> #include <assert.h> int handleDuplicates(int* arr, int length); void mergeSort(int* arr, int startIndex, int endIndex); void merge(int* arr, int startIndex, int midIndex, int endIndex); int binarySearch(int* arr, int length, int value); void swap(int* x, int* y); int main() { int numbers[] = { 6, 2, 3, 6, 9, 2, 7, 8, 1 }; int arrayLength = sizeof(numbers) / sizeof(int); int index = 0; int duplicateCounter = handleDuplicates(numbers, arrayLength); printf("Number of duplicates: %d\n", duplicateCounter); printf("{ "); for (index = 0; index < arrayLength; index++) { printf("%d%s", numbers[index], index + 1 < arrayLength ? ", " : ""); } printf(" }\n"); return 0; } int handleDuplicates(int* arr, int length) { int index = 0, uniqueIndex = 0, duplicateIndex = 0, searchResult = 0, shiftIndex = 1; int* tempArray = (int*)malloc(length * sizeof(int)); if (tempArray == NULL) { printf("Memory allocation error.\n"); return -1; } for (index = 0; index < length; index++) { tempArray[index] = arr[index]; } mergeSort(tempArray, 0, length - 1); // Temp Array: { 1, 2, 2, 3, 6, 6, 7, 8, 9 } for (index = 0; index < length; index++) { if (index == 0 || index == length - 1 || (index + 1 < length && tempArray[index] != tempArray[index + 1])) swap(&tempArray[uniqueIndex++], &tempArray[index]); } // Temp Array: { 1, 2, 3, 6, 7, 8, 9, 2, 6 } for (index = 0; index < length; index++) { searchResult = binarySearch(tempArray, uniqueIndex, arr[index]); if (searchResult != -1) { tempArray[searchResult] = arr[0]; swap(&arr[duplicateIndex++], &arr[index]); } } // Array: { 6, 2, 6, 9, 7, 8, 1, 2, 3 } for (index = 0; index < length; index++) { if (index != 0 && arr[index] != arr[0]) { swap(&arr[shiftIndex++], &arr[index]); } } // Array: { 6, 2, 9, 7, 8, 1, 2, 3, 6 } free(tempArray); return length - uniqueIndex; } int binarySearch(int* arr, int length, int value) { int left = 0, right = length - 1, middle = 0; while (left <= right) { middle = left + (right - left) / 2; while (middle <= right && middle != 0 && arr[middle] == arr[0]) { middle++; } if (middle > right) break; if (arr[middle] == value) { return middle; } else if (arr[middle] < value) { left = middle + 1; } else { right = middle - 1; } } return -1; } void merge(int* array, int start, int mid, int end) { int i = start, j = mid + 1, k = 0; int* temp = (int*)malloc((end - start + 1) * sizeof(int)); assert(temp); while (i <= mid && j <= end) { if (array[i] < array[j]) { temp[k++] = array[i++]; } else { temp[k++] = array[j++]; } } while (j <= end) { temp[k++] = array[j++]; } while (i <= mid) { temp[k++] = array[i++]; } for (i = start, k = 0; i <= end; i++, k++) { array[i] = temp[k]; } free(temp); } void mergeSort(int* array, int start, int end) { int mid; if (start < end) { mid = (start + end) / 2; mergeSort(array, start, mid); mergeSort(array, mid + 1, end); merge(array, start, mid, end); } } void swap(int* a, int* b) { int temp = *a; *a = *b; *b = temp; }
问题分析与修复方案
核心问题
- 标记值冲突:用
arr[0]作为已使用标记,但该值是数组中的有效元素,会导致二分查找时误判有效元素为已标记项。 - 排序数组结构破坏:通过交换收集唯一元素时,打乱了排序数组的有序性,导致二分查找无法准确定位元素。
- 元素交换逻辑混乱:两次交换操作导致唯一元素的相对顺序被破坏,部分元素被错误移至末尾。
修复思路
- 统计元素出现次数:排序辅助数组后,遍历统计每个唯一元素的出现次数,避免破坏排序结构。
- 跟踪元素可用次数:复制计数数组,遍历原数组时,对每个元素若还有可用次数则保留在前方,否则视为重复项。
- 二分查找定位唯一元素:在有序数组中准确定位元素的起始位置,确保计数匹配正确。
修复后的代码
#include <stdio.h> #include <stdlib.h> int handleDuplicates(int* arr, int length); void mergeSort(int* arr, int start, int end); void merge(int* arr, int start, int mid, int end); int main() { int numbers[] = {6, 2, 3, 6, 9, 2, 7, 8, 1}; int arrayLength = sizeof(numbers) / sizeof(int); int uniqueCount = handleDuplicates(numbers, arrayLength); printf("Number of duplicates: %d\n", arrayLength - uniqueCount); printf("{ "); for (int i = 0; i < arrayLength; i++) { printf("%d%s", numbers[i], i + 1 < arrayLength ? ", " : ""); } printf(" }\n"); return 0; } int handleDuplicates(int* arr, int length) { if (length <= 0) return 0; // 创建并排序辅助数组 int* sortedArr = (int*)malloc(length * sizeof(int)); if (!sortedArr) { printf("Memory allocation error.\n"); return -1; } for (int i = 0; i < length; i++) { sortedArr[i] = arr[i]; } mergeSort(sortedArr, 0, length - 1); // 统计唯一元素及出现次数 int uniqueCount = 0; int* counts = (int*)malloc(length * sizeof(int)); if (!counts) { free(sortedArr); printf("Memory allocation error.\n"); return -1; } int prev = sortedArr[0]; counts[uniqueCount] = 1; uniqueCount++; for (int i = 1; i < length; i++) { if (sortedArr[i] != prev) { prev = sortedArr[i]; counts[uniqueCount] = 1; uniqueCount++; } else { counts[uniqueCount - 1]++; } } // 复制计数数组用于跟踪可用次数 int* tempCounts = (int*)malloc(uniqueCount * sizeof(int)); if (!tempCounts) { free(sortedArr); free(counts); printf("Memory allocation error.\n"); return -1; } for (int i = 0; i < uniqueCount; i++) { tempCounts[i] = counts[i]; } // 整理数组:保留首次出现的唯一元素 int uniquePos = 0; for (int i = 0; i < length; i++) { // 二分查找元素在有序数组中的起始位置 int left = 0, right = uniqueCount - 1; int idx = -1; while (left <= right) { int mid = left + (right - left) / 2; // 定位到唯一元素的第一个位置 while (mid > 0 && sortedArr[mid] == sortedArr[mid - 1]) mid--; if (sortedArr[mid] == arr[i]) { idx = mid; break; } else if (sortedArr[mid] < arr[i]) { left = mid + 1; } else { right = mid - 1; } } if (idx != -1 && tempCounts[idx] > 0) { // 保留该元素,维持原顺序 if (i != uniquePos) { int temp = arr[i]; arr[i] = arr[uniquePos]; arr[uniquePos] = temp; } tempCounts[idx]--; uniquePos++; } } // 释放内存 free(sortedArr); free(counts); free(tempCounts); return uniquePos; } void merge(int* array, int start, int mid, int end) { int i = start, j = mid + 1, k = 0; int* temp = (int*)malloc((end - start + 1) * sizeof(int)); if (!temp) { printf("Memory allocation error in merge.\n"); exit(1); } while (i <= mid && j <= end) { if (array[i] < array[j]) { temp[k++] = array[i++]; } else { temp[k++] = array[j++]; } } while (j <= end) { temp[k++] = array[j++]; } while (i <= mid) { temp[k++] = array[i++]; } for (i = start, k = 0; i <= end; i++, k++) { array[i] = temp[k]; } free(temp); } void mergeSort(int* array, int start, int end) { if (start < end) { int mid = start + (end - start) / 2; mergeSort(array, start, mid); mergeSort(array, mid + 1, end); merge(array, start, mid, end); } }
修复说明
- 排序与统计:通过排序辅助数组统计每个元素的出现次数,确保后续能准确跟踪元素是否还能保留。
- 有序数组复用:保持辅助数组的有序性,通过二分查找快速定位元素,保证O(nlogn)的时间复杂度。
- 元素顺序维持:遍历原数组时,仅将有可用次数的元素交换到前方,严格保留原数组中唯一元素的相对顺序。
- 内存安全:所有动态分配的内存均被正确释放,避免内存泄漏。
该方案处理示例输入后,输出符合预期:{6, 2, 3, 9, 7, 8, 1, 6, 2},满足所有需求。
内容的提问来源于stack exchange,提问作者Nature9376
相关产品推荐
相关产品推荐

