【问题标题】:find duplicate number in an array在数组中查找重复的数字
【发布时间】:2016-01-17 08:41:02
【问题描述】:

我正在调试以下问题并发布我正在调试和研究的解决方案,解决方案或类似的解决方案已发布在几个论坛上,但我认为当 num[0] = 0 或一般 num 时解决方案存在错误[x] = x?我对么?如果我错了,请随时纠正我。

给定一个包含 n + 1 个整数的数组 nums,其中每个整数都介于 1 和 n(含)之间,证明至少存在一个重复数。假设只有一个重复号码,找到重复号码。

注意: 您不得修改数组(假设数组是只读的)。 您必须只使用常量,O(1) 额外空间。 您的运行时复杂度应小于 O(n2)。 数组中只有一个重复数,但可以重复多次。

int findDuplicate3(vector<int>& nums)
{
    if (nums.size() > 1)
    {
        int slow = nums[0];
        int fast = nums[nums[0]];
        while (slow != fast)
        {
            slow = nums[slow];
            fast = nums[nums[fast]];
        }

        fast = 0;
        while (fast != slow)
        {
            fast = nums[fast];
            slow = nums[slow];
        }
        return slow;
    }
    return -1;
}

【问题讨论】:

  • 现在我明白你的意思了。我认为对于 nums[i]==i 的这个问题,你必须从任何其他点开始。你可以有一个外部循环来检查 nums 的位置[i]!=i 然后你可以从那个位置继续。你认为这也会中断吗?
  • 如果 i!=0 则不会引起问题,因为慢速指针会卡在那个位置,而快速指针最终会到达相同的位置,while 循环将中断,并且当慢速仍然存在时,第二个 while 循环快速卡住会到达那里并找到重复项。但是当 nums[0] = 0 时,您需要单独处理这种情况。例如:int nums[] = {4,1,3,2,2} 这里 arr[1] ==1 但这不会导致问题,您可以使用笔和纸轻松解决。
  • 这个算法基本行不通,x[i]=i 是它的小问题。更大的问题是它永远不会在[1,2,3,0,4,4] 中看到重复项。
  • 当 arr[i] == i 或者 arr[i] 是重复的时候有两种可能性,那么没有问题,上面的算法可以正常工作。第二种可能性是 arr[i ] == i 并且 arr[i] 不是重复的(比如说 i 不为零,我们可以单独处理这种情况),那么我的主张是您永远不会通过上述算法到达位置 i。如果您觉得有什么不对那请提供并反例。(再次 i==0 情况单独处理)因为我不知道如何正式地说。
  • @Khatri 这个问题似乎有一个内部矛盾,它说x[0]==0 是一个问题,所以我取 0 是允许的。如果不是,那么算法可能没问题。

标签: c++ algorithm find


【解决方案1】:

从 1 到 N 的整数之和 = (N * (N + 1)) / 2。您可以使用它来查找重复项 - 将数组中的整数相加,然后从总和中减去上述公式。那是副本。

更新:上述解决方案基于(可能无效)假设输入数组由从 1 到 N 的值加上单个副本组成。

【讨论】:

  • 很聪明,keithmo,但你怎么证明有重复?
  • "数组中只有一个重复的数字,但可以重复多次。"但这个想法很棒。
  • 糟糕,我错过了“...重复不止一次”部分。我的错。
  • 嗨 keithmo,我认为你是正确的,只有一个重复的数字。问题本身就有问题。
  • 我想所描述的问题将允许,例如,N = 5 并且数组 = [1, 1, 5, 1, 1, 2]。如果是这样,那么我的解决方案将严重失败。
【解决方案2】:

下面是我的代码,它使用了弗洛伊德的循环寻找算法

#include <iostream>
#include <vector>
using namespace std;

int findDup(vector<int>&arr){
    int len = arr.size();
    if(len>1){
        int slow = arr[0];
        int fast = arr[arr[0]];
        while(slow!=fast){
            slow = arr[slow];
            fast = arr[arr[fast]];
        }
        fast = 0;
        while(slow!=fast){
            slow = arr[slow];
            fast = arr[fast];
        }
        return slow;
    }
    return -1;
}

int main() {
    vector<int>v = {1,2,2,3,4};
    cout<<findDup(v)<<endl;
    return 0;
}

评论这是因为不允许使用零,所以数组的第一个元素不是循环的一部分,因此我们找到的第一个循环的第一个元素同时引用循环内外。如果允许零,如果 arr[0] 在一个循环中,这将失败。例如,[0,1,1]。

【讨论】:

  • 你的 while 将在第一次迭代时返回 false
  • 我不明白为什么这是个问题?
  • 这是对上述算法的一个很好的解释。link这个算法被称为弗洛伊德的寻环算法,使用了rho形状的概念,你可以在链接中阅读更多细节。
  • 你的fast = arr[0](第9行)应该是fast = arr[arr[0]]
  • 这是有效的,因为不允许零,所以数组的第一个元素不是循环的一部分,所以我们找到的第一个循环的第一个元素在外部和内部都被引用周期。如果允许零,如果 arr[0] 在一个循环中,这将失败。例如,[0,1,1]。
【解决方案3】:

由于您不能使用任何额外的空间,因此将排除使用另一个哈希表。

现在,谈到对现有数组进行散列的方法,如果允许我们就地修改数组,就可以实现。

算法:

1) 从第一个元素开始。

2) 散列第一个元素并对散列的值应用一个变换。假设这个变换使值 -ve。

3)继续下一个元素。对元素进行哈希处理,在应用转换之前,检查是否已经应用了转换。

4) 如果是,则元素是重复的。

代码:

 for(i = 0; i < size; i++)
  {
    if(arr[abs(arr[i])] > 0)
      arr[abs(arr[i])] = -arr[abs(arr[i])];
    else
      cout<< abs(arr[i]) <<endl;
  }  

这种转换是必需的,因为如果我们要使用散列方法,那么在散列相同的键时必须存在冲突。

我想不出一种可以在没有任何额外空间且不修改数组的情况下使用散列的方法。

【讨论】:

  • 不能修改现有数组。
  • “我想不出一种方法可以在没有任何额外空间且不修改数组的情况下使用散列。”...agreed.如果要使用散列,那么数组将不得不修改,否则无法修改。
  • @basav,想法很聪明,同意 n.m.我们不应该修改数组。感谢分享。
【解决方案4】:
  1. 从指向第一个元素的两个指针开始:fastslow
  2. 将“移动”定义为 fast 增加 2 步(位置)和 slow 增加 1。
  3. 每次move后,检查slowfast是否指向同一个节点。
  4. 如果有一个循环,在某些时候他们会。这是因为在它们都进入循环之后,fast 的移动速度是 slow 的两倍,并且最终会“撞上”它。
  5. 假设他们在 k 次移动 之后相遇。这不一定是重复元素,因为它可能不是从循环外部到达的循环的第一个元素。
    将此元素称为 X
  6. 请注意,fast 已步进 2k 次,slow 已步进 k 次。
  7. 移回零。
  8. 重复fastslow每一步,在每一步之后进行比较。
  9. 请注意,在另一个 k 步之后,slow 将总共移动 2k 步,fast 总共移动 k 从头开始​​,所以它们都将再次指向 X
  10. 请注意,如果前面的步骤对它们两个都在循环中,那么它们都指向 X-1。如果前面的步骤仅在 slow 的循环中,则它们指向不同的元素。
  11. X-2、X-3、...
  12. 同上
  13. 因此,在接下来的过程中,它们第一次指向同一个元素是从循环外部到达的第一个循环元素,也就是您要查找的重复元素。

【讨论】:

  • 让我补充一点,这只是对@Khatri 算法的解释,这很好,您应该接受他的回答而不是我的回答。
  • 谢谢Dave,我再次阅读了你上面的cmets并做了一些练习,我想我的理解,“当有一个圆时,数组中必须有两个元素指向圆的入口点,和更快/更慢的跑步者在圆的入口点遇到(在你的算法中),这就是为什么开始圆的数组的索引是重复的元素,对吗?“,这正是你的意思吗?如果我错了,请随时纠正我。 :)
  • 较快/较慢在循环中第一次 SOMEWHERE 相遇,但不一定在第一个(因此重复)元素处。这就是您需要第二步的原因,在此您快速回到起点并前进并缓慢减速(一次 1 个)。此时我们知道fast一旦进入循环,它就会和slow指向同一个元素,所以我们可以用它来识别第一个元素,我们知道它一定是重复的元素。
  • @LinMa 如果k是快和慢第一次相遇的走法,那么在k快走之后,快走2k步,慢走k步。现在我们快速回到零并开始移动它每次移动 1 步而不是 2。从​​ 0 开始,在 k 1 步移动中它位于位置 k。慢从位置 k 开始,因此在另一个 k 1 步移动中它位于位置 2k。我们已经知道位置 2k 与位置 k 相同。
  • @LinMa 假设我们以每秒两次快速移动开始,每秒一次缓慢移动,并且它们在 k 秒后在循环中的某个位置相遇,我称之为 x。然后我们快速重置为零并将其速度更改为每秒移动一次。所以现在快和慢以相同的速度移动。再过 k 秒,他们都会回到 x。这里要注意的是,如果我们将时钟松开一秒,那么它们都会以相同的速度向后移动,所以只要快还在循环中,它就等于慢。
猜你喜欢
  • 1970-01-01
  • 2015-06-14
  • 1970-01-01
  • 2018-06-01
  • 2022-01-03
  • 1970-01-01
  • 2016-08-15
  • 2018-01-21
相关资源
最近更新 更多