[leetcode] findDuplicateNumber

FindDuplicateNumber

  • 问题描述:给定一个数组长度为N,里面元素的取值范围是1~N-1。所有会有一个数是重复的,请找到这个重复的数字并返回。
  • 解法1:暴力
    • 最直接的办法就是利用两重循环来寻找
    • 当碰到相同的数字的时候就返回
    • 时间复杂度是O(N)
  • 解法2: 二分法
    • 我们知道我们N个数是分布在1~N-1之间的。所有假设我们统计1~N/2, N/2-N-1之间的数字,如果哪一方的数字多余一半,则重复的数字就在其中。
    • 通过这样的方式我们可以二分的查找多余的数字
    • 时间复杂度是O(NlogN)
  • 解法3:
    • 我们尝试构造下面的数列:
      • X 0 = n u m s [ 0 ] X_0 = nums[0] X0=nums[0]
      • X 1 = n u m s [ X 0 ] X_1 = nums[X_0] X1=nums[X0]
      • X 2 = n u m s [ X 1 ] X_2 = nums[X_1] X2=nums[X1]
      • X i = n u m s [ X i − 1 ] X_i = nums[X_{i-1}] Xi=nums[Xi1]
    • 因为我们数字里存在重复的数字,所有一定存在一个 X i X_i Xi [ X 0 , X i − 1 ] [X_0,X_{i-1}] [X0,Xi1]之中出现过。形成如下所示的带环链表。
      cycle linked list
    • 接下来我们就是要找到入口前的一个点,他就是我们重复的数字。
    • 那么我们如何找呢?
      • 我们先利用两个point,一快一慢,快的走两步,慢的走一步。
      • 因为我们存在环,所有两个指针一定会相遇。
      • 假设两个相遇在某一点,则此时再让fast从头开始,然后slow和fast都每次走一步。
      • 两者相遇的时候就是我们的重复的数字
    • why?
      • 假设一开始两者相遇的时候slow的走了N步,则fast的走了2N步
      • 接下来我们分析一下这N步是由什么构成的?
        • 首先在进环前走了D步
        • 从环的起点到相遇点走了K步。
        • 整个环走了i圈,假设环的长度是C
        • 则N = D + K + C*i
      • 那么对于2N,我们同样有2N = D + K + C*j
      • 我们消去N可以得到2D + 2K + 2Ci = D + K + Cj =>D+K = C(j-i). 注意j>i是成立的。
      • 所有我们让一个point从头走D步。
      • 那么从相遇的点相当于走D=C(j-i)-K。
      • 相当于我们差K个step可以走满C(j-i+1)圈。
      • 因为我们相遇的点距离环的入口点是K。所有此时一定是相遇在起点的。
    • 上面我们分析了为什么可以相遇在起点。又因为我们两个point在经历起点前的一个点也一定是相同的,参照上图。
    • 所以我们可以得到当第一次两个相同的时候,就是我们的重复数字。
    • 如果说上面方法难以理解的话,我们可以在中加加上一步
      • 找到相遇的点
      • 计算环的长度L
      • 初始化两个point
      • 第一个point先走L步
      • 然后两个point同时走,当两个point相遇的时候就是环的入口点。
  • 三种方法的代码:
//
// Created by 梁栋 on 2019-05-16.
//
#include <vector>
#include <iostream>
using namespace std;
class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int size = nums.size();
        for(int i=0;i<size;i++){
            for(int j=i+1;j<size;j++)
                if(nums[i] == nums[j])
                    return nums[i];
        }
        return -1;
    }
    int findDuplicateV2(vector<int>& nums){
        int size = (int) nums.size();
        if(size > 1){
            int slow = nums[0];
            int fast = nums[nums[0]];
            while(slow != fast){
                slow = nums[slow];
                fast = nums[nums[fast]];
            }
            slow = 0;
            while(slow != fast){
                slow = nums[slow];
                fast = nums[fast];
            }
            return slow;
        }
        return -1;
    }
    int findDuplicateV3(vector<int>& nums){
        int size = (int) nums.size();
        if(size > 1){
            int leftCount = 0;
            int rightCount = 0;
            int left = 1;
            int right = size - 1;
            while(left < right){
                int mid = (right + left) / 2; //
                int count = 0;
                for(auto c: nums){
                    if(c <= mid){
                        count += 1;
                    }
                }
                if(count > mid){
                    right = mid;
                }else{
                    left = mid + 1;
                }
            }
            return left;
        }
        return -1;
    }
    static void solution(){
        Solution solution1;
        vector<int> nums = {1, 3, 4, 2, 2};
        cout<<solution1.findDuplicateV3(nums)<<endl;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值