【问题标题】:C - Remove Duplicates from an ArrayC - 从数组中删除重复项
【发布时间】:2021-02-20 17:57:30
【问题描述】:

我对编程很陌生,我编写了一个代码来从数组中删除重复项,从逻辑上讲,它应该可以工作,但它没有......我在逻辑上对其进行了多次测试,这很有意义......

代码如下:

#include <stdio.h>

int rmDuplicates(int arr[], int n)
{
    int i, j;
    for (i = 0; i < n; i++) {
        if (arr[i] == arr[i + 1]) {
            for (j = i + 1; j < n - 1; j++) {
                arr[j] = arr[j + 1];
            }
            n--;
        }
        return n;
    }
}

int main()
{
    int n, i;
    scanf("%d", &n);
    int arr[n];
    for (i = 0; i < n; i++) {
        scanf("%d", &arr[i]);
    }

    n = rmDuplicates(arr, n);
    for (i = 0; i < n; i++) {
        printf("%d", arr[i]);
    }
    printf("\n%d", n);
    return 0;
}

【问题讨论】:

  • 如果你的输入是 1 2 3 4 1 你的输出应该是 1 2 3 4 但你的代码会输出 1 2 3 4 1
  • 只有在重复项是连续的情况下才会删除它们(因为 if(arr[i] == arr[i+1]) 测试);你可以接受吗?
  • 你只检查连续的数字,所以如果重复不连续,它就不起作用,在第一个循环中应该检查所有其他元素的第 0 个元素,然后重复该过程直到结束。
  • 'arr[i+1]' 当 i 等于 n-1 时 OOB 访问。

标签: arrays c data-structures duplicates


【解决方案1】:

你的“return n”放错地方了,第一个循环后就返回了。

for(i=0;i<n;i++) {
    if(arr[i] == arr[i+1]) {
        for(j=i+1;j<n-1;j++) {
            arr[j] = arr[j+1];                              
        }
        n--;
    }
    return n; // <---- this
}
// <-- should be here.

作为确认,如果我将return n; 移到循环之外,则代码有效。但它只删除 连续 重复项,因为您只检查 arr[i] 与其连续的 arr[i+1]。

(另外,循环应该在 n-1 处停止,否则 arr[n-1+1] 是 arr[n],它在数组之外)。

最后一个问题是,如果你有,比如说,

                   n
 ...5,..., 5, 5, 6
    i      j

然后你检查前 5 和第二个,发现它是重复的,然后将后面的所有内容移动一步,在第 j 个位置你将再次有一个 5,但现在 j 将增加,你将将前 5 个与第 6 个而不是第三个 5 进行测试,没有找到重复项:

                n
 ...5,..., 5, 6
    i         j

因此,当您找到匹配项时,您需要将 j 倒带 1 并重复该测试:

int rmDuplicates(int arr[], int n) {
    int i,j,k;
    for (i=0;i<n-1;i++) {
        for (j=i+1; j < n; j++) {
                if(arr[i] == arr[j]) {
                    n--;
                    for (k=j;k<n;k++) {
                        arr[k] = arr[k+1];
                    }
                    j--;
                }
        }
    }
    return n;
}

从性能上看,上述算法是O(n^2),即如果数组列表加倍,算法耗时是原来的四倍;如果是三倍,则需要九倍的时间。

因此,更好的算法是首先对数组进行就地排序,使1 3 2 7 2 3 5 变为1 2 2 3 3 5 7(成本为 O(n log n),增长更慢);然后你只需“压缩”跳过重复的数组,这是 O(n) 并得到你1 2 3 5 7

int i, j;
for (i = 0, j = 1; j < n;) {
    if (arr[i] == arr[j]) {
        j++;
        continue;
    }
    i++;
    if (j != (i+1)) {
        arr[i] = arr[j];
    }
    j++;
}
n = i+1;

【讨论】:

  • return 在这里没有意义,但这不是这里不删除重复的原因
  • @sravs 我相信是的;如果我将该指令移到循环之外,则代码可以工作(不完美,请参阅修改后的答案,但更好 - 它现在可以找到之前没有的连续重复项)。
  • @LSerni 非常感谢,但是为什么对数组进行排序是 O(n log n) 而不是 O(n) ?
  • 这解释起来有点复杂。它与数组的香农熵有关。您的未排序数组代表 n!可能的排列,它的熵是 log(n!)/log(2)。有一个公式说明 log(n!) 趋于 n (log n - 1),然后您需要 o(n log n) 位来唯一标识您的排序。由于每次比较都是一位信息(arr[i] 是否 > arr[j]),因此您最终需要 o(n log n) 次比较。
【解决方案2】:
size_t removeDups(int *arr, size_t size)
{
    if(arr && size > 1)
    {
        for(size_t current = 0; current < size - 1; current++)
        {
            size_t original_size = size;
            size_t copypos = current + 1;
            for(size_t cpos = current + 1; cpos < original_size; cpos++)
            {
                if(arr[current] == arr[cpos])
                {
                    if(cpos < original_size -1)
                    {
                        if(arr[current] != arr[cpos + 1])
                        {
                            arr[copypos++] = arr[cpos + 1];
                            cpos++;
                        }
                    }
                    size--;
                }
                else
                {
                    arr[copypos++] = arr[cpos];
                }
           }
        }
    }
    return size;
}

int main(void)
{
    int arr[] = {1,1,1,2,2,3,3,4,5,6,7,1,8,8,2,2,2,2};
    size_t size = sizeof(arr) / sizeof(arr[0]);

    size = removeDups(arr, size);
    for(size_t index = 0; index < size; index++)
    {
        printf("%d\n", arr[index]);
    }
}

【讨论】:

    猜你喜欢
    • 2019-03-15
    • 1970-01-01
    • 2012-03-25
    • 2010-09-05
    • 1970-01-01
    • 1970-01-01
    • 2011-06-29
    相关资源
    最近更新 更多