287. 寻找重复数

287. 寻找重复数

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

示例 1:

输入: [1,3,4,2,2]
输出: 2
示例 2:

输入: [3,1,3,4,2]
输出: 3
说明:

不能更改原数组(假设数组是只读的)。
只能使用额外的 O(1) 的空间。
时间复杂度小于 O(n2) 。
数组中只有一个重复的数字,但它可能不止重复出现一次。

解法一:二分查找(想到了乐鑫的变态笔试题)

由于该题的数组元素限制在1~n之中,所以使用二分查找重复元素;
确定那边区间存在重复元素,然后到该区间接着使用二分法;

以 [1, 2, 2, 3, 4, 5, 6, 7] 为例,一共 8 个数,n + 1 = 8,n = 7,根据题目意思,每个数都在 1 和 7 之间。

例如:区间 [1, 7] 的中位数是 4,遍历整个数组,统计小于等于 4 的整数的个数,至多应该为 4 个。换句话说,整个数组里小于等于 4 的整数的个数如果严格大于 4 个,就说明重复的数存在于区间 [1, 4],它的反面是:重复的数存在于区间 [5, 7]。

于是,二分法的思路是先猜一个数(有效范围 [left, right]里的中间数 mid),然后统计原始数组中小于等于这个中间数的元素的个数 cnt,如果 cnt 严格大于 mid,(注意我加了着重号的部分“小于等于”、“严格大于”)依然根据抽屉原理,重复元素就应该在区间 [left, mid] 里。

说明:下面的算法运行时间肯定不会高,因为这个算法是空间敏感的,「用时间换空间」是反常规做法,大家了解一下即可。

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int len = nums.size();
        int left = 1;//初始
        int right = len-1;
        while(left <= right)
        {
            int mid = left+(right-left)/2;
            int count = 0;
            for(auto num:nums)
            {              
                if(num <= mid)
                    count += 1;
            }
            if(count > mid)
                right = mid-1;
            else
                left = mid+1;
        }
        return left;
    }
};

复杂度分析:

时间复杂度:O(NlogN),二分法的时间复杂度为 O(logN),在二分法的内部,执行了一次 for 循环,时间复杂度为 O(N),故时间复杂度为 O(NlogN)。
空间复杂度:O(1),使用了一个 cnt 变量,因此空间复杂度为 O(1)。

解法二:找环——快慢指针

先看141. 环形链表——判断是否有环

给定一个链表,判断链表中是否有环。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
在这里插入图片描述

class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(head==NULL || head->next==NULL)
            return false;
        ListNode* slow = head;
        ListNode* fast = head;//快慢指针
        while(fast!=NULL && fast->next!=NULL){//指针合法
            slow = slow->next;//慢指针走一步
            fast = fast->next->next;//快指针走两步
            if(slow == fast){
                return true;//两指针相遇,即有环;没有环的话,是不可能相遇的
            }
        }
        return false;       
    }
};
再看142: 环形链表 II——找环的起点

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

说明:不允许修改给定的链表。

在这里插入图片描述

思路

在这里插入图片描述

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* slow=head;
        ListNode* fast=head;
        while(fast!=NULL && fast->next!=NULL){//保证指针合法
            slow=slow->next;
            fast=fast->next->next;
            if(slow==fast){//快慢指针相遇,表示有环
                slow=head;//慢指针返回头结点,两个指针同步向后走,再次相遇的结点就是环的入口
                while(slow!=fast){
                    slow=slow->next;
                    fast=fast->next;
                }
                return fast;
            }
        }
        return NULL;
    }
};
最后再细品本题:寻找重复数

对于数组中的每个元素,可以将它们看作next结点;
从理论上讲,数组中如果有重复的数,那么就会产生多对一的映射,这样,形成的链表就一定会有环路了,
综上
1.数组中有一个重复的整数 <——> 链表中存在环
2.找到数组中的重复整数 <——> 找到链表的环入口

至此,问题转换为142题。那么针对此题,快、慢指针该如何走呢。根据上述数组转链表的映射关系,可推出
142题中慢指针走一步slow = slow.next ——> 本题 slow = nums[slow]
142题中快指针走两步fast = fast.next.next ——> 本题 fast = nums[nums[fast]]

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int slow = 0;
        int fast = 0;
        slow = nums[slow];
        fast = nums[nums[fast]];//注意开始循环时,快慢指针不是在同一个起点了
        //判断是否有环,
        while(slow != fast){
            slow = nums[slow];
            fast = nums[nums[fast]];
        }
        //找到环,将慢指针返回起点,快慢指针同步前进,
        //再次相遇时,就是环的入口,即本题的而重复数
        slow =0;
        while(slow != fast)
        {
            fast = nums[fast];
            slow = nums[slow];
        }
        return slow;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值