287. Find the Duplicate Number

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:

  1. You must not modify the array (assume the array is read only).
  2. You must use only constant, O(1) extra space.
  3. Your runtime complexity should be less than O(n2).
  4. 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;

 类似题目:

442. Find All Duplicates in an Array

268. Missing Number

448. Find All Numbers Disappeared in an Array

 

posted @ 2017-10-16 16:55  daniel456  阅读(148)  评论(0编辑  收藏  举报