《剑指offer》刷题——【数组】面试题3:数组中重复的数字

这篇博客介绍了如何在数组中找出重复的数字。方法包括:排序后扫描、使用哈希表以及数组重排。对于不修改数组的情况,采用二分查找策略也能找到重复数字,但无法保证找出所有重复项。每种方法的时间复杂度和空间复杂度也进行了分析。
摘要由CSDN通过智能技术生成

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

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

方法一:先排序,再扫描

  1. 先把输入的数组排序 O(nlogn)
  2. 然后从头到尾扫描

方法二:哈希表

  1. 从头到尾按顺序扫描数组的每个数组;
  2. 每扫描到一个数字的时候,都可以用O(1)判断哈希表里是否已经包含了该数字;
  3. 如果没有这个数字,就把他加入到哈希表中
  4. 如果哈希表已经存在该数字,就找到一个重复的数字
  5. 时间复杂度:O(n)
  6. 空间复杂度:O(n)
import java.util.Hashtable;
public class Solution {
    // Parameters:
    //    numbers:     an array of integers
    //    length:      the length of array numbers
    //    duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
    //                  Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
    //    这里要特别注意~返回任意重复的一个,赋值duplication[0]
    // Return value:       true if the input is valid, and there are some duplications in the array number
    //                     otherwise false

    public boolean duplicate(int numbers[],int length,int [] duplication) {
        /**
        * 借助Hashtable,遍历数组,若此值在hashtable中存在,则将此数组放入到结果集中
        */
        if(numbers==null || length<=0)
            return false;
        Hashtable<Integer,Integer> table = new Hashtable<Integer, Integer>();
        for(int i=0; i<length; i++){
            if(table.containsKey(numbers[i])){
                duplication[0] = numbers[i];
                return true;
            }else{
                table.put(numbers[i],1);
            }
        }
        return false;
    }
}

方法三:数组重排

  1. 数组中的数字都在【0~n-】范围内,如果这个数组中没有重复的数字,那么当数组排序之后数字 i 将出现在下标为 i 的位置。由于数组中有重复的数字,那么有些位置可能存在多个数字,有些位置可能没有数字
  2. 利用上述规律,进行数组重排
  3. 遍历数组中每一个数字,当遍历到第 i 个数字时,比较第 i 个数字(表示为 x)是否等于 i
  4. 如果是,则遍历下一个数字;
  5. 如果不是,将第 i 个数字与第 x 数字比较,若与第 x 数字相等,就找到一个重复的数字(因为 i x 位置都出现了);
  6. 如果与第 x 个数字不相等,就把第 i 个数字和第m个数字交换,把m放到属于它的位置
  7. 重复比较,交换过程,直至找到一个重复的数字位置
  8. 时间复杂度O(n)
  9. 空间复杂度O(1)
    注:一维数组在内存中占据连续的空间,因此可以根据下标定位对应的元素。
import java.util.Hashtable;
public class Solution {
    // Parameters:
    //    numbers:     an array of integers
    //    length:      the length of array numbers
    //    duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
    //                  Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
    //    这里要特别注意~返回任意重复的一个,赋值duplication[0]
    // Return value:       true if the input is valid, and there are some duplications in the array number
    //                     otherwise false

    public boolean duplicate(int numbers[],int length,int [] duplication) {
        //1. 判断是否分配内存 是否长度不为空
        if(numbers==null || length<=0)
            return false;
        //2. 检查所有数字是否在【0~n-1】之间
        for(int i=0; i<length; i++){
            if(numbers[i]<0 || numbers[i]>length-1){
                return false;
            }
        }
        //3. 遍历每个数字
        for(int i=0; i<length-1; i++){
            //是否第i个数字等于i,是则进行下一数字,不是则需要进一步判断第i个数字m是否和第m个数字相等
            while(numbers[i] != i){
                //第i个数字是否等于第m个数字
                if(numbers[i] == numbers[numbers[i]]){
                    duplication[0]=numbers[i];
                    return true;
                }
                //第i个数字和第m个数字交换
                int temp = numbers[i];
                numbers[i] = numbers[temp];
                numbers[temp] = temp;
            }
        }
        return false;
    }
}

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

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

方法一:和上一题方法三同

  1. 创建一个长度为n+1的辅助数组;
  2. 然后将原数组中的每个数字复制到辅助数组中
  3. 然后重复方法三的过程
  4. 此方案需要额外的辅助空间O(n)

方法二:二分查找

  1. 假设没有重复的数字,那么在1~n的范围内只有n个数字。然而数组中包含超过n个数字,所以一定包含了重复的数字
  2. 利用上述规律,结合二分查找,解决此问题
  3. 把1~n的数字从中间的数字m分为两部分,前面一半为 1~m ,后面一半为m+1~ n
  4. 如果1~m的数字在整个数组中出现过的次数超过m,那么这一半的区间里一定包含重复的数字;否则另一半中一定包含重复的数字
  5. 继续包含重复数字的区间一分为二,直到找到一个重复的数字
  6. 时间复杂度:O(nlogn)
  7. 空间复杂度:O(1)
    注意:这种算法不能保证找出所有重复的数字
package array_question;
/**
 * @author hw
 * @version 0.1
 * @description 不修改数组找出重复的数字
 * @date 2019/4/15
 */
public class TestFindDuplication {
    //找出重复的数字
    public int getDuplication(int[] arr){
        //数组输入无效
        if (arr==null || arr.length<=0){
            System.out.println("数组输入无效");
            return -1;
        }
        //数组数字超出范围
        for (int a  :arr ) {
            if (a <1 || a > arr.length-1){
                System.out.println("数组数字超出范围");
                return -1;
            }
        }

        int start = 1;
        int end = arr.length-1;
        while (end>=start){
            int mid = ((end-start)>>1)+start;
            int count = countRange(arr,start,mid);
            //两个元素,1-1范围
            if(end==start){
                if (count > 1)
                    return start;
                else
                    break;
            }
            if (count>(mid-start+1))
                end = mid;
            else
                start = mid+1;
        }
        return -1;
    }
    public int countRange(int[] arr, int start, int end){
        int count = 0;
        if (arr == null)
            return 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) {
        TestFindDuplication t = new TestFindDuplication();
        int[] a = null;
        int[] b = {1,2,3,4};
        int[] c = { 1, 2, 3, 2, 4 };
        int dup1 = t.getDuplication(a);
        int dup2 = t.getDuplication(b);
        int dup3 = t.getDuplication(c);
        if (dup1 >= 0)
            System.out.println("重复数字为:" + dup1);
        if (dup2 >= 0)
            System.out.println("重复数字为:" + dup2);
        if (dup3 >= 0)
            System.out.println("重复数字为:" + dup3);
    }

}

运行结果:
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值