找重复:拈花微笑,飞叶伤人

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;
    }
写这个方法的时候还是很自信,可是注意没有,我最后使用了一个循环对数组进行复原,这与要求中的不能修改数组冲突,其实看一下括号中的说明,这个方法完全可行。那么为了在荒芜的大脑上开疆扩土,让我们领略一下经典算法:Floyd判圈法。

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;
    }

本文的重点在于如何迁移Floyd圈判法解决所遇到的问题,所以对此方法就不做过多介绍,当然有兴趣研究代码的话有助于你更清晰的理解该算法。

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;
    }
写法上可以看出数组是反着写的,但是当做链表操作的原理是没有区别的。数组和链表作为两种基本的数据结构各有千秋,对于Floyd法的迁移让人深刻体会到两者身上的优越性并非不可移植。性感的脑子从不纠结于数组和链表,就如同武侠世界里内功深厚的绝世高手从不拘泥于刀剑的使用,拈花飞叶即可伤人。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值