数字中重复的数字(不修改数组且不开辟新空间)

以前作过这样的题,,以前的题是能够修改数组的,那么若是不能够修改数组,咱们如何来作这个题呢?html

1.题目

给定一个长度为 n+1 的数组nums,数组中全部的数均在 1∼n 的范围内,其中 n≥1。c++

请找出数组中任意一个重复的数,但不能修改输入的数组。数组

样例指针

给定 nums = [2, 3, 5, 4, 3, 2, 6, 7]。

返回 2 或 3。

思考题:若是只能使用 O(1) 的额外空间,该怎么作呢?code

题目来自于这里,有兴趣的能够作一下。htm

2.时间复杂度为O(nlogn)的解法

本身没想出来,看一位大佬写的题解blog

2.1原理

该解法的原理就是抽屉原理与分治。索引

抽屉原理:n+1个苹果放入到n个抽屉中,那么一定有一个抽屉至少有两个苹果ip

这道题咱们能够理解为,有n+1个位置,咱们要给这n+1个位置赋值,范围是1~n,那么一定有一个数要有两个位置。get

咱们能够对这些数进行分治(不是位置)。在范围1~n当中的数的个数加起来必定是n+1,一定大于n,咱们接下去缩小这个范围,当在这个范围中的数的个数加起来大于这个范围的长度,就说明这个范围中必定存在重复的数。依次下去,直到范围的长度缩到1,咱们就找到了重复的数了。

2.2代码

int duplicateInArray(vector<int>& nums) {
        int n = nums.size() - 1;
        int left = 1, right = n;
        while(left < right) {
        	int s = 0;
        	int mid = (left + right) / 2;
        	for(int i = 0; i <= n; i++) {
        		if(nums[i] >= left && nums[i] <= mid)
        			s++;
        	}
        	if(s > mid - left + 1)
        		right = mid;
        	else
        		left = mid + 1;
        }
        return right;
    }

3.时间复杂度为O(n)的解法

题解原地址,看完只能说,大佬不愧是大佬,膜拜。

3.1原理

在这里有一点前置的知识,谈一下我看完他们后的见解吧

3.1.1 如何判断一个链存在环

存在环,那么咱们就有一个相遇的问题,让一个快的指针和一个慢的指针去循环,它们就会相遇。

因此在这里,咱们让快指针每一次走两步,慢指针每一次走一步,若是有环存在,那么两个指针必定会相遇。

3.1.2 快指针与慢指针相遇的地点以及起点的关系

借一下原地址大佬的图

首先,快指针速度是慢指针的二倍,相同时间内,快指针的位移必定是慢指针的二倍。

慢指针在进入环后,走完一周以前,必定会与快指针相遇。

将上述两个位移化简得
b + (k - 2l)c = a
公式中k-2L必定是个整数,因此b + 整数倍的圈数 位置依然没有变,咱们能够让k = 2L,这样结果是不影响最终位置的。

要细讨论的话是分为三种状况,与a 和 c的大小有关系。(此处就不讨论了)

咱们即可以得出一个结论 a = 变化后的b 即相遇的地点与环起点相距b

3.1.3 将知识与题目结合

咱们能够将上述知识应用到本题当中。咱们能够将数组对应的数看成next值来处理,索引值看成值来处理,由抽屉原理得这个数组必定存在环。

咱们先从索引值为0的数开始,将数组对应的值看成next,让快指针移动两格,慢指针移动一格,相遇的地点即P点,接下来咱们再让快指针移动到0,而后一格一格的移动直到两个指针相遇,此时索引值就是那个重复的数字。

3.2代码

int duplicateInArray(vector<int>& nums) {
        int low = 0, fast = 0;
        while(low == 0 || low != fast) {
        	low = nums[low];
        	fast = nums[nums[fast]];
        }
        fast = 0;
        while(low != fast) {
        	low = nums[low];
        	fast = nums[fast];
        }
        return fast;
    }
相关文章
相关标签/搜索