算法面试题之不修改数组找出重复的数字

题目

在一个长度为n+1的数组里的所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但是不能修改输入的数组。例如,如果输入长度为8的数组{2,3,5,4,3,2,6,7},那么对应的输出是重复的数字2或者3。

解决思路

这题的难点在于不能修改输入的数组,下面由简到难讲述几个解决方案

思路1

由于不能修改输入的数组,我们可以创建一个长度为n+1的辅助数组,然后逐一把原数组的每个数字复制到辅助数组。如果原数组中被复制的数字是m,则把它复制到辅助数组中下标为m的位置。如果下标为m的位置上已经有数字了,则说明该数字重复了。由于使用了辅助空间,故该方案的空间复杂度是 O(n) 。下面是该算法的Java程序:

package TempFile;

/**
 * Created by 余沾.
 */
public class Test {

    /**
     * 利用辅助数组解决该问题
     */

    public int getDuplication(int[] arr)
    {
        int[] newArr = new int[arr.length];
        for(int i = 0;i < arr.length;i++)
        {
            if(arr[i] < 0 || arr[i] >= arr.length)
                throw new IllegalArgumentException("输入参数不合法");
            else
                newArr[i] = -1;
        }
        for(int i = 0;i < arr.length;i++)
        {
            if(newArr[arr[i]] != arr[i])
            {
                newArr[arr[i]] = arr[i];
            }else
                return arr[i];
        }
        return -1;
    }

    public static void main(String[] args) {

        Test test = new Test();
        int[] arr = {2,3,5,4,3,2,6,7};
        int value = test.getDuplication(arr);
    }
}

思路2

由于思路1的空间复杂度是 O(n) ,因此我们需要想办法避免使用辅助空间。我们可以想:如果数组中有重复的数,那么n+1个0~n范围内的数中,一定有几个数的个数大于1。那么,我们可以利用这个思路解决该问题。

我们把从1~n的数字从中间的数字m分为两部分,前面一半为1~m,后面一半为m+1~n。如果1~m的数字的数目等于m,则不能直接判断这一半区间是否包含重复的数字,反之,如果大于m,那么这一半的区间一定包含重复的数字;如果小于m,另一半m+1~n的区间里一定包含重复的数字。接下来,我们可以继续把包含重复的数字的区间一分为二,直到找到一个重复的数字。

由于如果1~m的数字的数目等于m,则不能直接判断这一半区间是否包含重复的数字,我们可以逐步减少m,然后判断1~m之间是否有重复的数,即,我们可以令m=m-1,然后再计算1~m的数字的数目是否等于m,如果等于m,再令m=m-1,如果大于m,则说明1~m的区间有重复的数,如果小于m,则说明m+1~n有重复的数,不断重复此过程。

下面是该算法的Java程序:

package TempFile;

/**
 * Created by 余沾.
 */
public class Test {

    /**
     * 避免使用辅助空间
     */
    public int getDuplication(int[] arr)
    {
        for(int i = 0;i < arr.length;i++)
        {
            if(arr[i] < 0 || arr[i] >= arr.length)
                throw new IllegalArgumentException("输入参数不合法");
        }
        int start = 0;
        int end = arr.length-1;
        int flag = 0;
        int middle = 0;
        while(end >= start)
        {
            if(flag == 0)
               middle = (end + start)/2;
            int count = countRange(arr,start,middle);
            if(end == start)
            {
                if(count > 1)
                    return start;
                else
                    break;
            }
            if(count > (middle-start+1))//说明(start,middle)这个区间有重复的数
            {
                end = middle;
                flag = 0;
            }else if(count == (middle-start+1))//不能判断(start,middle)这个区间有重复的数
            {
                middle = middle - 1;
                if(middle < start)//说明(start,middle)这个区间没有重复的数
                {
                    start = (start+end)/2 + 1;
                    flag = 0;
                }else
                    flag = 1;
            }else //说明(middle+1,end)这个区间有重复的数
            {
                start = middle + 1;
                flag = 0;
            }
        }
        return -1;
    }

    private int countRange(int[] arr, int start, int end)
    {
        int count = 0;
        for(int i = 0;i < arr.length;i++)
        {
            if(arr[i] >= start && arr[i] <= end)
                ++count;
        }
        return count;
    }

    public static void main(String[] args) {

        Test test = new Test();
        int[] arr = {0,3,5,4,1,2,6,7};
        int value = test.getDuplication(arr);
    }
}

上述代码按照二分查找的思路,如果输入长度为n的数组,那么函数countRange最多将被调用 O(logn) 次,每次需要 O(n) 的时间,因此总的时间复杂度是 O(nlogn) 。但是如果区间无重复的数,则时间复杂度将变为 O(n2)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值