剑指Offer面试题:3.不修改数组找出重复的数字

一、题目

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

二、思路

方法一
创建一个新n+1的数组data,遍历原来的数组如 2 将2存到 data[2]中, 3存到data[3]中…. 等下下次一遍历到3 发现data[3]=3 说明重复了。
算法时间复杂度为 O(n),空间复杂度O(n)
方法二
使用二分法。 如:{2,3,5,4,3,2,6,7} 先二分,先统计在1-4里面的数字个数如果大于4则说明1~4里面有重复数字,否则5-7里面有重复数字。重复上面操作。
分析:二分查找logn ,但是getCount每个数组遍历一变 n,时间复杂度为O(nlogn),空间复杂度为O(1)。但是这个方法有个问题不能找出所有的重复元素。

如{2,2,4,4,5,5} 找到的元素会是4。

start=1,end=5,middle=3. 1-3中元素有2个则4~5有重复

start=4,end=5,middle=4。 4有2个元素

start=4,end=4,middle=4, 返回4。

三、解决问题

3.1 代码实现

方法一

/**
     * 创建一个新n+1的数组data,遍历原来的数组如 2 将2存到 data[2]中, 3存到data[3]中…. 等下下次一遍历到3 发现data[3]=3 说明重复了。
     * 算法时间复杂度为 O(n),空间复杂度O(n)
     * @param arr
     * @return
     */
    public int getDuplicate1(int[] arr) {
        if(null == arr || arr.length <= 0){
            System.out.println("数组输入无效!");
            return -1;
        }
        for(int num : arr){
            if (num < 1 || num > arr.length - 1){
                System.out.println("数组大小超出范围");
                return -1;
            }
        }
        int[] newArr = new int[arr.length];
        Map<Integer,Integer> map = new HashMap<>();
        for (int i = 0; i < newArr.length; i++) {
            newArr[i] = -1;
        }
        for (int i = 0; i < arr.length; i++) {
            // 即将该数组放到他在数组中和他角标相等的位置
            if (newArr[arr[i]] == arr[i]){
                map.put(arr[i],map.get(arr[i]) + 1);//重复的次数
            }else{
                newArr[arr[i]] = arr[i];
                map.put(arr[i],1);
            }
        }
        // 通用的Map迭代方式
        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            if (entry.getValue() > 1){
                System.out.println(entry.getKey()  +" : "+entry.getValue() );
                return entry.getKey();
            }
        }
        // JDK8的迭代方式
        /*map.forEach((key,value)->{
            if (value > 1){
                System.out.println(key +" : "+value);
            }
        });*/
        return -1;
    }

方法二

/**
     * 找到数组中一个重复的数字
     * 返回-1代表无重复数字或者输入无效
     *
     * 采用二分思想。我们把1~n的数字从中间的数字middle分为两部分,
     * 统计输入数组中1~middle的数字的数目,如果这部分数目大于middle,则说明此部分中含有重复数字,否则就是另外的一部分含有重复数字。
     * 依此类推,将区间内的数目和区间大小比较,直到区间的头尾相等,就找到了重复数字。
     *
     *
     * 二分查找logn ,但是getCount每个数组遍历一变 n,时间复杂度为O(nlogn),空间复杂度为O(1)。但是这个方法有个问题不能找出所有的重复元素。
     */
    public int getDuplicate(int[] arr) {
        if(null == arr || arr.length <= 0){
            System.out.println("数组输入无效!");
            return -1;
        }
        for(int num : arr){
            if (num < 1 || num > arr.length - 1){
                System.out.println("数组大小超出范围");
                return -1;
            }
        }
        int low = 1;
        int high = arr.length - 1;// high即为题目的n
        int mid,count;
        while (low <= high){
            mid = ((high - low) >> 1) + low;//右移动n位,相当于除以2^n
            //统计每区间里数字的数目
            count = countRange(arr,low,mid);
            /*System.out.println("low: " + low);
            System.out.println("mid: " + mid);
            System.out.println("high: " + high);*/
            //直到区间的头尾相等,就找到了重复数字
            if (low == high){
                if (count > 1){
                    return low;
                }else {
                    break;// 必有重复,应该不会出现这种情况吧?
                }
            }
            if (count > mid - low + 1){
                high = mid;//说明这个区间有重复元素
            }else {
                low = mid + 1;
            }
        }
        return -1;
    }

    /**
     * 返回在[low,high]范围中数字的个数
     * 查找数组中值位于low和high之间的元素个数
     * 函数countRange()将被调用O(logn)次,每次需要O(n)的时间。
     * @return
     */
    private int countRange(int[] arr, int low, int high) {
        if (null == arr){
            return 0;
        }
        int count = 0;
        for(int num : arr){
            if (num >= low && num <= high){
                count++;
            }
        }
        return count;
    }

3.2 单元测试

1.数组中带一个或多个重复数字

2.数组中不包含重复的数字

3.无效输入测试用例(空数组,数组数字越界等)

package SwordOffer;

import org.junit.Test;

import java.util.HashMap;
import java.util.Map;

/**
 * @Description 不修改数组找出重复的数字
 *
 * @author kankan
 * @creater 2019-10-28 9:20
 */
/*
 * 题目:在一个长度为n+1的数组里的所有数字都在1到n的范围内,所以数组中至
 * 少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的
 * 数组。例如,如果输入长度为8的数组{2, 3, 5, 4, 3, 2, 6, 7},那么对应的
 * 输出是重复的数字2或者3。
 */
public class Solution3 {

    /**
     * 创建一个新n+1的数组data,遍历原来的数组如 2 将2存到 data[2]中, 3存到data[3]中…. 等下下次一遍历到3 发现data[3]=3 说明重复了。
     * 算法时间复杂度为 O(n),空间复杂度O(n)
     * @param arr
     * @return
     */
    public int getDuplicate1(int[] arr) {
        if(null == arr || arr.length <= 0){
            System.out.println("数组输入无效!");
            return -1;
        }
        for(int num : arr){
            if (num < 1 || num > arr.length - 1){
                System.out.println("数组大小超出范围");
                return -1;
            }
        }
        int[] newArr = new int[arr.length];
        Map<Integer,Integer> map = new HashMap<>();
        for (int i = 0; i < newArr.length; i++) {
            newArr[i] = -1;
        }
        for (int i = 0; i < arr.length; i++) {
            // 即将该数组放到他在数组中和他角标相等的位置
            if (newArr[arr[i]] == arr[i]){
                map.put(arr[i],map.get(arr[i]) + 1);//重复的次数
            }else{
                newArr[arr[i]] = arr[i];
                map.put(arr[i],1);
            }
        }
        // 通用的Map迭代方式
        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            if (entry.getValue() > 1){
                System.out.println(entry.getKey()  +" : "+entry.getValue() );
                return entry.getKey();
            }
        }
        // JDK8的迭代方式
        /*map.forEach((key,value)->{
            if (value > 1){
                System.out.println(key +" : "+value);
            }
        });*/
        return -1;
    }
    /**
     * 找到数组中一个重复的数字
     * 返回-1代表无重复数字或者输入无效
     *
     * 采用二分思想。我们把1~n的数字从中间的数字middle分为两部分,
     * 统计输入数组中1~middle的数字的数目,如果这部分数目大于middle,则说明此部分中含有重复数字,否则就是另外的一部分含有重复数字。
     * 依此类推,将区间内的数目和区间大小比较,直到区间的头尾相等,就找到了重复数字。
     *
     *
     * 二分查找logn ,但是getCount每个数组遍历一变 n,时间复杂度为O(nlogn),空间复杂度为O(1)。但是这个方法有个问题不能找出所有的重复元素。
     */
    public int getDuplicate(int[] arr) {
        if(null == arr || arr.length <= 0){
            System.out.println("数组输入无效!");
            return -1;
        }
        for(int num : arr){
            if (num < 1 || num > arr.length - 1){
                System.out.println("数组大小超出范围");
                return -1;
            }
        }
        int low = 1;
        int high = arr.length - 1;// high即为题目的n
        int mid,count;
        while (low <= high){
            mid = ((high - low) >> 1) + low;//右移动n位,相当于除以2^n
            //统计每区间里数字的数目
            count = countRange(arr,low,mid);
            /*System.out.println("low: " + low);
            System.out.println("mid: " + mid);
            System.out.println("high: " + high);*/
            //直到区间的头尾相等,就找到了重复数字
            if (low == high){
                if (count > 1){
                    return low;
                }else {
                    break;// 必有重复,应该不会出现这种情况吧?
                }
            }
            if (count > mid - low + 1){
                high = mid;//说明这个区间有重复元素
            }else {
                low = mid + 1;
            }
        }
        return -1;
    }

    /**
     * 返回在[low,high]范围中数字的个数
     * 查找数组中值位于low和high之间的元素个数
     * 函数countRange()将被调用O(logn)次,每次需要O(n)的时间。
     * @return
     */
    private int countRange(int[] arr, int low, int high) {
        if (null == arr){
            return 0;
        }
        int count = 0;
        for(int num : arr){
            if (num >= low && num <= high){
                count++;
            }
        }
        return count;
    }
    //1.无效输入测试用例(空数组,数组数字越界等)
    @Test
    public void test1(){
        System.out.println("test1:");
        int[] arr = null;
        int dup = getDuplicate(arr);
        if (dup >= 0){
            System.out.println("重复数字为:" + dup);
        }
    }
    //2.数组中不包含重复的数字
    @Test
    public void test2(){
        System.out.println("test2:");
        int[] arr = {1,2,3,4};
        int dup = getDuplicate(arr);
        if (dup >= 0){
            System.out.println("重复数字为:" + dup);
        }
    }
    //3.数组中带一个或多个重复数字
    @Test
    public void test3() {
        System.out.print("test3:");
        int[] a = {2,3,5,4,3,2,6,7};
        int dup = getDuplicate1(a);
        if (dup >= 0)
            System.out.println("重复数字为:" + dup);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值