美文网首页
《剑指offer第二版》题3:数组中重复的数字

《剑指offer第二版》题3:数组中重复的数字

作者: leilifengxingmw | 来源:发表于2022-04-05 11:22 被阅读0次

题目一:数组中重复的数字

在一个长度为n的数组里的所有数字都在0到n-1的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是重复的数字2或者3。

解题思路:

  1. 把数组排序,然后再遍历数组,如果存在 array[i]==array[i+1] 就说明存在重复数字,返回array[i]。

  2. 使用哈希表来解决,从头到尾扫描数组,如果哈希表中不存在这个数字,就加入哈希表,如果哈希表中存在这个数字,那么就找到了一个重复的数字。这个算法的时间复杂度是O(n),但它提高时间效率是以一个大小为O(n)的哈希表为代价的。

  3. 我们注意到数组中的数字都在0到n-1中。如果这个数组中没有重复的数字,那么当数组排序之后数字i将出现在下标为i的位置。由于数组中有重复的数字,有些位置可能存在多个数字,同时有些位置可能没有数字。

现在让我们重排这个数组,依然从头到尾一次扫描这个数组中的每个数字。当扫描到下标为i的数字时,首先比较这个数字(用m表示)是不是等于i。如果是,接着扫描下一个数字。

如果不是,再拿它和下标为m的数字进行比较。如果它和下标为m的数字相等,就找到了一个重复的数字(该数字在下标为i和m的位置都出现了)。如果它和下标为m的数字不相等,就把下标为i的数字和下标为m的数字交换,把m放到属于它的位置(也就是下标m上)。

接下来再重复这个比较、交换的过程,直到我们发现一个重复的数字。

以数组{2,3,1,0,2,5,3}为例来分析找到重复数字的步骤。

img1.png

数组的第0个数字是2,与它的下标不相等,于是把它和下标为2的数字1交换。交换之后的数组是{1,3,2,0,2,5,3}

img2.png

此时第0个数字是1,仍然与它的下标不相等,继续把它和下标为1的数字3交换,得到数组{3,1,2,0,2,5,3}

img3.png

接下来继续交换第0个数字3和第3个数字0,得到数组{0,1,2,3,2,5,3}。此时第0个数字的数值为0。

img4.png

接着扫描下一个数字。在接下来的几个数字中,下标为1,2,3的三个数字分别为1,2,3,它们的下标和数值都分别相等,因此不需要做任何操作。

接下来扫描到下标为4的数字2。由于它的数值与它的下标不相等,再比较它和下标为2的数字。注意到此时数组中下标为2的数字也是2,也就是数字在下标为2和下标为4的两个位置都出现了,因此找到一个重复的数字。

思路3算法实现

public class Test3 {

    public static void main(String[] args) {
        int[] numbers = {1, 3, 2, 0, 2, 5, 3};
        System.out.println(duplicate(numbers));

        int[] numbers1 = {1, 2, 3, 4, 7, 5, 6, 0};
        System.out.println(duplicate(numbers1));

        int[] numbers2 = {1, 10, 3};
        System.out.println(duplicate(numbers2));

        System.out.println(duplicate(null));
    }

    public static int duplicate(int[] numbers) {
        if (numbers == null || numbers.length < 1) {
            return -1;
        }
        /**
         * 判断输入是否在[0,numbers.length-1]之间
         */
        for (int number : numbers) {
            if (number < 0 || number >= numbers.length) {
                return -1;
            }
        }
        for (int i = 0; i < numbers.length; i++) {

            /**
             * 如果number[i]与i不同的时候一致交换,我们交换的原因是让number[i]等于它的下标
             */
            while (numbers[i] != i) {
                //如果i位置的数字和numbers[i]位置的数字相等,说明存在重复的数字
                if (numbers[i] == numbers[numbers[i]]) {
                    return numbers[i];
                } else {
                    //如果不同就交换
                    int temp = numbers[i];
                    numbers[i] = numbers[temp];
                    numbers[temp] = temp;
                }
            }
        }
        return -1;
    }
}

题目二:不修改数组找出重复的数字

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

解题思路:

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

思路1代码实现

    /**
     * 方法1,使用辅助数组来查找
     *
     * @param numbers 数组
     * @return -1 ,表示不存在重复的数
     */
    public static int duplicateUseAnotherArray(int[] numbers) {
        if (numbers == null || numbers.length < 1) {
            return -1;
        }
        /**
         * 判断输入是否在[1,numbers.length-1]之间
         */
        int[] newArray = new int[numbers.length];
        for (int i = 0; i < numbers.length; i++) {
            if (numbers[i] < 1 || numbers[i] >= numbers.length) {
                return -1;
            }
        }
        for (int i = 0; i < numbers.length; i++) {
            if (newArray[numbers[i]] != numbers[i]) {
                newArray[numbers[i]] = numbers[i];
            } else {
                return numbers[i];
            }
        }
        return -1;

    }

思路2 TODO 这个待研究

我们可以这样想:如果数组中有重复的数,那么n+1个[1,n]范围内的数中,一定有几个数的个数大于1。那么,我们可以利用这个思路解决该问题。

我们把从1到n的数字从中间的数字m分为两部分,前面一半为[1,m],后面一半为[m+1,n]。

  1. 如果[1,m]的数字的数目大于m,那么这一半的区间一定包含重复的数字。
  2. 如果[1,m]的数字的数目等于m,则不能直接判断这一半区间是否包含重复的数字。
  3. 如果小于m,另一半[m+1,n]的区间里一定包含重复的数字。接下来,我们可以继续把包含重复的数字的区间一分为二,直到找到一个重复的数字。

以长度为8的数组{2,3,5,4,3,2,6,7}为例分析查找过程。根据题目要求,数组中所有的数字在[1,7]之间。[1,7]之间的中间数字是1+(7-1)/2=4。4把[1,7]分为两段,[1,4],[5,7]。接下来统计[1,4]之间数字出现的次数是5次,所以重复的数字肯定在[1,4]这个区间内。

接下来把[1,4]区间一分为2,中间数字 1+(4-1)/2=2。2把[1,4]分为两段,[1,2],[3,4]。接下来统计区间[1,2]中数字出现的次数,总共两次(1没有出现,2出现了两次)。这个时候无法直接判断[1,2]中是否有重复的数字。

接下来统计区间[3,4]中数字出现的次数,一共是3次,那么说明区间[3,4]中用重复的数字。

接下来把[3,4]区间一分为2,中间数字 3+(4-3)/2=3。3把[3,4]分为两段,[3],[4],接下来统计区间[3]中数字重复的次数,发现出现了两次,找到了一个重复的数字就是3。

注意:这种算法不能保证找出所有重复的数字,例如2也是重复了两次。

思路2代码实现

    public static int getDuplication(int[] arr) {
        if (arr == null || arr.length == 0) {
            return -1;
        }
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] < 1 || arr[i] >= arr.length) {
                return -1;
            }
        }
        //区间开始的数字
        int start = 1;
        //区间结束的数字
        int end = arr.length - 1;
        while (end >= start) {
            //中间数字
            int middle = start + ((end - start) >> 1);
            //统计左半区间的数字出现的次数
            int count = countRange(arr, start, middle); 
            //区间中只有一个数字,循环要结束
            if (end == start) {
                if (count > 1) {
                    return start;
                } else {
                    break;
                }
            }
            if (count > (middle - start + 1)) {
                end = middle;
            } else {
                start = middle + 1;
            }
        }
        return -1;
    }

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

参考链接:

相关文章

网友评论

      本文标题:《剑指offer第二版》题3:数组中重复的数字

      本文链接:https://www.haomeiwen.com/subject/aaaqcltx.html