1.问题描述:
给定一个包含 (n+1)个整数的数组,数组中每个元素的值在闭区间[1,n]上,证明数组至少存在一个重复的数字,并且找出这个重复的数字。要求:空间复杂度O(1),时间复杂度小于O(n^2),不能修改数组(可以认为是只读)。
2.问题分析:
问题产生的根本原因在于要要在n个数位上放n+1个整数,则一定会溢出(重复),这道题的意义不在题目问题,而在于题目的要求——时间复杂度小于O(n^2)这就让我们直接摒弃了循环嵌套求解方法,这时候我们可能会想以空间换时间递归求解,然而空间复杂度O(1)的要求明确告诉我们此路不通。所以,该问题的目的是丰富我们看待问题的方式,以精简的代码从不同角度来解决问题。
3.动物领地法(自定义):
动物在声明自己领土主权完整的时候往往会用排泄物为自己的领地标记,而其他有操守的动物要是要是发现这些标记物,就会克制住自己的冲动,去其他地方觅食或者遛弯...... 个人认为以下的这种思维方式与动物领地原则高度契合,故名动物领地法:建立数值(1)与数组中某一位置的对应关系,对该位置的数值(2)打上“标记”,以此标记判断数值(1)是否重复,而它的复杂度仅为O(n)!一支穿云箭,千军万码来相见:
public int findDuplicate(int[] nums) {
int duplicate = -1;
for(int i=0; i < nums.length; i++){
if(nums[Math.abs(nums[i])-1]<0){
duplicate = Math.abs(nums[i]);
}else{
nums[Math.abs(nums[i])-1]=-nums[Math.abs(nums[i])-1];
}
}
for(int i=0; i< nums.length; i++){
nums[i] = Math.abs(nums[i]);
}
return duplicate;
}
4.龟兔赛跑,平分秋色
Floyd判圈法:正常情况下,龟和兔从同一点触发,两者的速度不同是无论如何都不会相遇的,如果是两者相遇则必定是进入了环形场。典型应用:检测一个链表是否有环,如果有确定环的起始节点:
public ListNode detectCycle(ListNode head) {
ListNode hare = head;
ListNode tortoise = head;
boolean isCycle = false;
while(hare!=null && hare.next!=null){
hare = hare.next.next;
tortoise = tortoise.next;
if(hare == tortoise){
isCycle = true;
break;
}
}
if(!isCycle)return null;
hare = head;
while(hare != tortoise){
hare = hare.next;
tortoise = tortoise.next;
}
return hare;
}
5.肖申克的救赎——链在心中
Floyd判圈法的主要对象是链表,而本文中的问题是数组,如何结合两者体现经典算法中的“经典” ?
回归问题本身:n个位置,n+1个数组元素,那么必有一个元素无法容身,此时如将元素的值作为链连起来,因为有重复值,所以必定会出现两个链指向同一个节点——环!救赎:重复问题变为判圈问题:
public int findDuplicate(int[] nums) {
int duplicate = -1;
if(nums.length==0)return duplicate;
int fast = nums[nums[0]];
int slow = nums[0];
while(fast != slow){
fast = nums[nums[fast]];
slow = nums[slow];
}
fast = 0;
while(fast != slow){
fast = nums[fast];
slow = nums[slow];
}
duplicate = fast;
return duplicate;
}