Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive), prove that at least one duplicate number must exist. Assume that there is only one duplicate number, find the duplicate one.
Note:
- You must not modify the array (assume the array is read only).
- You must use only constant, O(1) extra space.
- Your runtime complexity should be less than
O(n2)
. - There is only one duplicate number in the array, but it could be repeated more than once.
题意:有n+1个数字,范围从1到n,其中有一个数字会重复多次,用低于O(n2)的时间复杂度算法找出重复的数字,空间复杂的为O(1)。
解法一:思路是采用了二分法+抽屉远离。首先解释一下为什么用二分法,因为O(n2)时间复杂度不能A,所以往下应该是n*logn,很容易联想到二分法,因为其复杂度为logn。
抽屉原理是说假设你有11个苹果,要放进10个抽屉,那么至少有一个抽屉里是有两个苹果的。
对应到这题,1~n的n+1个数字,有1个数字会至少重复两次。
比如取数组为{1,2,2,3,4,5},一共6个数,范围是1~5,其中位数应该是(5+1)/2 = 3,那么,如果小于等于3的数的个数如果超过了3,那么重复的数字一定出现在[1,3]之间,否则出现在[4,5]之间。以该数组为例,中位数为3,小于等于3的数一共有4个,大于3的数有两个,所以重复的数字在[1,3]之间。
1 public int test_287(int[] nums) { 2 int low = 1, high = nums.length - 1; //low和high为数字的取值范围 3 while (low < high) { 4 int cnt = 0; //cnt为不大于中位数的数字个数 5 int mid = (low + high) / 2; 6 for (int i = 0; i < nums.length; i++) { 7 if (nums[i] <= mid) 8 cnt++; 9 } 10 if (cnt > mid) { 11 high = mid; //如果不大于mid的数字个数比mid多的话,则重复数字应该出现在[low, mid]之间 12 } else 13 low = mid + 1; //如果不大于mid的数字个数比mid少的话,说明重复的数字出现在后半段中[mid+1,high] 14 } 15 return low; 16 }
方法二:检测环路法
基本思想是将数组抽象为一条线和一个圆环,因为1~n 之间有n+1个数,所以一定有重复数字出现,所以重复的数字即是圆环与线的交汇点。然后设置两个指针,一个快指针一次走两步,一个慢指针一次走一步。当两个指针第一次相遇时,令快指针回到原点(0)且也变成一次走一步,慢指针则继续前进,再次回合时即是线与圆环的交汇点。
把数组抽象成线和圆环,举例来说,假设我们有一个数组是nums[]=[1,2,3,4,5,5,6,7],pf代表快指针,ps代表慢指针,初始ps指向nums[0],即1,pf指向nums[nums[0]],即2,行动一次后,ps指向nums[1],即2,pf指向nums[nums[2]],即4,再动一次,ps指向nums[2],即3,pf则指向了nums[nums[4]],即5;可以发现pf一旦指向5后便不会再动,因为nums[5]一直为5,直到ps慢慢追上,然后令pf从头开始,ps一直在5处停留,最后定会相遇在这里,而这里就是重复数字。这里举了个最简单的例子,是为了方便大家理解,实际上实际的圆环顺序与数组的顺序是没有关系的,不信可以自己在纸上画一画,当数组变成nums[]=[4,6,5,1,3,2,5,7]的样子,你会更加理解这个算法的!
1 if (nums.length > 1) { 2 int slow = nums[0]; 3 int fast = nums[nums[0]]; 4 while (slow != fast) { 5 slow = nums[slow]; 6 fast = nums[nums[fast]]; 7 } 8 9 fast = 0; 10 while (fast != slow) { 11 fast = nums[fast]; 12 slow = nums[slow]; 13 } 14 return slow; 15 } 16 return -1;
检查单链表是否有环路的方法 142. Linked List Cycle II
方法三:遍历过得元素置为负数。因为是从头到尾顺序遍历的,如果再次指向某个负数的时候,表示前面一定出现过同样的元素值导致此位置变成了负数
1 int n = nums.length; 2 if(n == 0) return -1; 3 for (int i=0;i<nums.length;i++) 4 { 5 int index = Math.abs(nums[i])-1; 6 if (nums[index] < 0 ) 7 { 8 return Math.abs(index+1); 9 }else 10 nums[index] = -nums[index]; 11 } 12 return -1;
类似题目: