剑指offer 面试题3:数组中重复的数字

数组中重复的数字

题目描述

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

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

算法思想

Hash思想

用hash表。从头到尾顺序扫描数组的每个数字,每扫描到一个数字的时候,都可以用O(1)的时间判断哈希表里是否已经包含了该数字。如果该哈希表里还没有这个数字,就把它加入哈希表。如果哈希表里已经存在该数字,就找到一个重复的数字。算法时间复杂度为O(n),空间复杂度为O(n).

Java代码
public class Solution3 {

    /**
     * 输入合法性校验
     * @param array
     * @return boolean value
     */
    public static boolean isValidInput(int[] array) {
        boolean result = true;
        if (array.length == 0) {
            result = false;
        }
        for (int i = 0; i < array.length; i++) {
            if (array[i] < 0 || array[i] >= array.length) {
                result = false;
                break;
            }
        }
        return result;
    }

    /**
     * hash思想
     * @param array
     * @return repeat number,若不存在,返回-1;若是非法输入,返回-2
     * 时间复杂度O(n),空间复杂度O(n)
     */
    public static int findRepeatNumber(int[] array) {
        int result = -1;
        if(!isValidInput(array)) {
            result = -2;
            return result;
        }
        int[] hash = new int[array.length];
        for (int i = 0; i < hash.length; i++) {
            hash[i] = 0;
        }
        for (int i = 0; i < array.length; i++) {
            if (hash[array[i]] > 0) {
                result = array[i];
                break;
            }
            hash[array[i]]++;
        }
        return result;
    }

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

}
交换思想

因为数组中的数字都在0~n-1范围内。如果这个数组中没有重复的数字,那么当数组排序后数字i将出现在下标为i的位置。由于数组中有重复的数字,有些位置可能存在多个数字,有些位置可能没有数字。

从头到尾扫描这个数组中的每个数字。当扫描到下标为i的数字时,首先比较这个数字(用m表示)是不是等于i。如果是,则接着扫描下一个数字;如果不是,则再拿它和第m个数字进行比较。如果它和第m个数字相等,就找到了一个重复的数字(该数字在下标为i和m的位置都出现了);如果它和第m个数字不相等,就把第i个数字和第m个数字交换,把m放到属于它的位置。接下来再重复这个比较、交换过程,直到我们发现一个重复的数字。

Java代码
public class Solution3 {

    /**
     * 输入合法性校验
     * @param array
     * @return boolean value
     */
    public static boolean isValidInput(int[] array) {
        boolean result = true;
        if (array.length == 0) {
            result = false;
        }
        for (int i = 0; i < array.length; i++) {
            if (array[i] < 0 || array[i] >= array.length) {
                result = false;
                break;
            }
        }
        return result;
    }

    /**
     * 交换思想
     * @param array
     * @return repeat number,若不存在,返回-1;若是非法输入,返回-2
     * 时间复杂度O(n),空间复杂度O(1)
     */
    public static int duplicate(int[] array) {
        int result = -1;
        if (!isValidInput(array)) {
            result = -2;
            return result;
        }
        for (int i = 0; i < array.length; i++) {
            while(array[i] != i) {
                if(array[i] == array[array[i]]) {
                    result = array[i];
                    return result;
                }
                else {
                    int temp = array[i];
                    array[i] = array[temp];
                    array[temp] = temp;
                }
            }
        }
        return result;
    }


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

}

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

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

算法思想

Hash思想

因为题目要求不能修改数组,所以交换思想不能用。所以可以使用Hash思想,与题目一的Hash思想一样,不再赘述。时间复杂度O(n),空间复杂度O(n)。

二分查找思想

因为使用Hash思想,需要使用O(n)的辅助空间,若要求只能使用O(1)的空间,该如何查找呢?

为什么数组中会有重复的数字?假如没有重复的数字,那么1~n的范围里只有n个数字。由于数组里包含超过n个数字,所以一定包含了重复的数字。

我们把从1~n的数字从中间的数字m分为两部分,前面一半为1~m,后面一半为m+1~n。如果1~m的数字的数目超过m,那么这一半的区间里一定包含重复的数字;否则,另一半m+1~n的区间里一定包含重复的数字。这个过程和二分查找算法很类似,只是多了一步统计区间里数字的数目。

我们以长度为8的数组{2,3,5,4,3,2,6,7}为例分析查找的过程。根据题目要求,这个长度为8的所有数字都在1~7的范围内。中间的数字4把1~7的范围分为两段,一段是1~4,另一段是5~7。接下来我们统计1~4这4个数字在数组中出现的次数,它们一共出现了5次,因此这4个数字中一定有重复的数字。

接下来我们再把1~4的范围一分为二,一段是1、2两个数字,另一段是3、4两个数字。数字1或者2在数组中一共出现了两次。我们再统计3、4再数组中出现的次数,为3次。这意味着3、4两个数字中一定有一个重复了。我们再分别统计这两个数字在数组中出现的次数。接着我们发现数字3出现了两次,是一个重复的数字。

下面的代码按照二分查找的思路,如果输入长度为n的数组,那么函数countRange将被调用O(logn)次,每次需要O(n)的时间,因此总的时间复杂度是O(nlogn),空间复杂度为O(1)。和前面的需要O(n)辅助空间的Hash思想相比,这种算法相当于以时间换空间。

特别需要注意的是,这种算法不能保证找出所有重复的数字。例如,该算法不能找出数组{2,3,5,4,3,2,6,7}中重复的数字2。因为在1~2的范围里有1和2两个数字,这个范围的数字也出现了2次,此时我们用该算法不能确定是每个数字各出现一次还是某个数字出现了两次。

Java代码
public class Solution3_2 {

    /**
     * 输入合法性校验
     * @param array
     * @return boolean value
     */
    public static boolean isValidInput(int[] array) {
        boolean result = true;
        if (array.length == 0) {
            result = false;
        }
        for (int i = 0; i < array.length; i++) {
            if (array[i] < 0 || array[i] >= array.length) {
                result = false;
                break;
            }
        }
        return result;
    }

    /**
     * 统计数组中在某一范围内的数字的数目
     * @param array
     * @param start
     * @param end
     * @return 数组中在某一范围内的数字的数目
     * 时间复杂度O(n)
     */
    public static int countRange(int[] array, int start, int end) {
        int count = 0;
        if (array.length == 0) {
            return count;
        }
        for (int i = 0; i < array.length; i++) {
            if (array[i] >= start && array[i] <= end) {
                count++;
            }
        }
        return count;
    }

    /**
     * 二分查找思想
     * @param array
     * @return repeat number,若不存在,返回-1;若是非法输入,返回-2
     * 时间复杂度O(nlogn),空间复杂度O(1)
     */
    public static int getDuplication(int[] array) {
        int result = -1;
        if(!isValidInput(array)) {
            result = -2;
            return result;
        }

        int start = 1;
        int end = array.length - 1;
        while(end >= start) {
            int middle = ((end - start) >> 1) + start;
            int count = countRange(array, start, middle);
            if (end == start) {
                if (count > 1) {
                    result = start;
                }
                break;
            }
            if (count > (middle - start + 1)) {
                end = middle;
            }
            else {
                start = middle + 1;
            }
        }
        return result;
    }

    public static void main(String[] args) {
        int[] array = {2, 3, 5, 4, 3, 2, 6, 7};
        System.out.println(getDuplication(array));
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值