[剑指Offer] 3 数组中重复的数字

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

描述

在一个长度为n的数组里的所有数字都在0~(n-1)的范围内。
数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。
请找出数组中任意一个重复的数字。

  • 示例

输入:{2,3,1,0,2,5,3} 长度为7的数组
输出:2或者3 重复的数字

实现代码

class DuplicateNumber {

    /**
     * 暴力枚举 
     * 将数组中每个数与其他数对比 有重复就返回结果
     * 最差结果 n^2 最后两个重复
     */
    public static int bruteDuplicate(int[] arr, int length) {
        if (arr.length <= 0 || length <= 0) {
            return -1;
        }
        for (int i : arr) {
            if (i < 0 || i > length - 1) {
                return -1;
            }
        }

        for (int i = 0; i < length; i++) {
            for (int j = i+1; j < length; j++) {
                if (arr[i] == arr[j]) {
                    return arr[i];
                }
            }
        }

        return -1;
    }

    /**
     * 修改数组方法
     * 逐个排序,在排序过程中寻找重复数字
     * 从数组第一个数开始,将数字交换放入相应下标的位置,重复的数字必然会在交换过程中被发现
     * 最差结果为 n-1 数组内数字全部交换完
     * 
     * 有些类似桶方法,每个下标为一个桶,将元素放置到与下标相同的位置
     * 限制:连续的元素
     */
    public static int modifyArrayDuplicate(int[] arr, int length) {
        if (arr.length <= 0 || length <= 0) {
            return -1;
        }
        for (int i : arr) {
            if (i < 0 || i > length - 1) {
                return -1;
            }
        }

        for (int i = 0; i < length; i++) {
            // 将数组内数字和下标不相等的交换 直至发现数字重复
            while (arr[i] != i) {
                if (arr[i] == arr[arr[i]]) {
                    return arr[i];
                }

                // 交换
                int temp = arr[i];
                arr[i] = arr[temp];
                arr[temp] = temp;
            }
        }

        return -1;
    }
}
  • 测试代码
public class FindDuplicateNumber {

    public static void main(String[] args) {
        // 有多个重复数字
        unitTest(new int[]{2,3,1,0,2,5,3});
        // 有多个重复数字 偏后
        unitTest(new int[]{1,2,3,4,5,5,1});
        // 有一个重复数字 前后
        unitTest(new int[]{0,2,3,4,5,6,0});
        // 都是重复数字
        unitTest(new int[]{2,2,3,3});
        // 数组数字超出边界
        unitTest(new int[]{2,3,7,0,2,5,3});
        // 数组为空
        unitTest(new int[]{});
    }

    private static void unitTest(int[] array) {
        int length = array.length;
        printArray(array);
        System.out.println("暴力枚举" + DuplicateNumber.bruteDuplicate(array, length));
        System.out.println("桶排序" + DuplicateNumber.modifyArrayDuplicate(array, length));
    }

    private static void printArray(int[] arr) {
        System.out.print("array:\t" + arr.length + "\n");
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + "\t");
        }
        System.out.println();
    }
}

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

描述

在一个长度为n+1的数组里的所有数字都在1~n的范围内。
数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。
请找出数组中任意一个重复的数字,但不能修改输入的数组。

  • 示例

输入:{2,3,5,4,3,2,6,7} 长度为8的数组
输出:2或者3 重复的数字

  • 注意:

请勿将此题与上题联系
题目条件只是形似 但本质是不同
上一题是:有n个元素,有n种元素 个数和种类相同 可能出现不重复的
本题是:有n+1个元素,有n种元素 个数和种类不同 一定会出现重复的 鸽舍原理

思路

按区域元素出现频次,来辨别重复数字是否出现在该区域

鸽舍原理 n只鸽子,(n-1)个鸽舍,必有1个鸽舍有2只鸽子及以上

区域划分可以使用二分法
3_2_二分查找必有重复数字的数组

实现代码

class DuplicateNumber {

   /**
     * 不修改数组方法 1~n
     * 鸽舍原理 + 二分查找 -- [1,3] 这4个数字在数组中出现5次,则必有一个数字出现两次及以上
     */
    public static int binarySearchDuplicate(int[] arr, int length) {
        if (arr.length <= 0 || length <= 0) {
            return -1;
        }
        for (int i : arr) {
            if (i < 0 || i > length - 1) {
                return -1;
            }
        }

        int start = 0;
        int end = length - 1;
        // 通过二分遍历元素种类 得到它在数组中出现的次数
        while (end >= start) {
            int middle = ((end - start) >> 1) + start;
            int count = countRange(arr, length, 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;
    }

    /**
     * 找出[start,end]在数组中出现的次数
     */
    private static int countRange(int[] arr, int length, int start, int end) {
        int count = 0;
        for (int i : arr) {
            if (i >= start && i <= end) {
                count++;
            }
        }
        return count;
    }
}
  • 测试
public class FindDuplicateNumber {

    public static void main(String[] args) {
        // 有多个重复数字
        unitTest(new int[]{2,3,1,4,2,5,3});
        // 有多个重复数字 偏后
        unitTest(new int[]{1,2,3,4,5,5,1});
        // 有一个重复数字 前后
        unitTest(new int[]{1,2,3,4,5,6,1});
        // 都是重复数字
        unitTest(new int[]{2,2,3,3});
        // 数组数字超出边界
        unitTest(new int[]{2,3,7,0,2,5,3});
        // 数组为空
        unitTest(new int[]{});
    }

    private static void unitTest(int[] array) {
        int length = array.length;
        printArray(array);
        System.out.println("二分查找" + DuplicateNumber.binarySearchDuplicate(array, length));
    }

    private static void printArray(int[] arr) {
        System.out.print("array:\t" + arr.length + "\n");
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + "\t");
        }
        System.out.println();
    }
}
posted @ 2019-08-02 14:49  slowbird  阅读(192)  评论(0编辑  收藏  举报