《剑指offer》03--数组中重复的数字[C++]

1 [0, ..., n-1]中第一个重复的数字
数组中重复的数字_牛客题霸_牛客网【牛客题霸】收集各企业高频校招笔面试题目,配有官方题解,在线进行百度阿里腾讯网易等互联网名企笔试面试模拟考试练习,和牛人一起讨论经典试题,全面提升你的技术能力https://www.nowcoder.com/practice/623a5ac0ea5b4e5f95552655361ae0a8?tpId=13&tqId=11203&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

题目描述

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

解题思路

【C++解法】

哈希法

  1. 由于所有元素值是有范围的,因此可以用一个长度为n的数组,下标表示序列中的每一个值,下标对应的值表示该下标出现的次数。
  2. 只需扫描一次原序列,就统计出所有元素出现的次数;
  3. 再扫描一次哈希数组,找到一个出现次数大于1的值即可。

1、利用原数组:直接改变数据做标记

时间复杂度为O(n),空间复杂度为O(1)。但会修改原数组。

class Solution {
public:
    // Parameters:
    //        numbers:     an array of integers
    //        length:      the length of array numbers
    //        duplication: (Output) the duplicated number in the array number
    // Return value:       true if the input is valid, and there are some duplications in the array number
    //                     otherwise false
    bool duplicate(int numbers[], int length, int* duplication) {
        if(numbers==NULL || length<=0) return false;
        for(int i=0;i!=length;++i){
            int index = numbers[i]%length;
            if(numbers[index] >= length){
                *duplication = index;
                return 1;
            }
            numbers[index]+=length;
        }
    return 0;
    }
};

2、构建一个hash数组

时间复杂度为O(n),空间复杂度为O(n)。

class Solution {
public:
    bool duplicate(int numbers[], int length, int* duplication) {
        if(numbers==NULL || length<=0) return false;
        vector<bool> k(length, 0);
        for (int i=0; i<length; i++) {
            if(numbers[i]<0 || numbers[i]>length-1) return false;
            if (k[numbers[i]]) {
                *duplication = numbers[i];
                return true;
            } else k[numbers[i]] = 1;
        }
        return false;
    }
};

3、构建一个map

时间复杂度为O(n),空间复杂度为O(n)。

class Solution {
public:
    bool duplicate(int numbers[], int length, int* duplication) {
        if(numbers==NULL || length<=0) return false; // 判断数组是否合法(每个数都在0~n-1之间)
        unordered_map<int,int> hash;
        for(int i=0; i<length; i++){
            if(numbers[i]<0 || numbers[i]>length-1) return false;
            if (hash[numbers[i]]) {
                *duplication = numbers[i];
                return true;
            } else hash[numbers[i]]++;
        }
    return false;
    }
};

4、n很小时,扑克牌思想: 用位作哈希表

class Solution {
public:
    bool duplicate(int numbers[], int length, int* duplication) {
        if(numbers==NULL || length<=0) return false;
        int flag=0;
        for(int i=0;i<length;i++){
            int temp=numbers[i];
            if(((flag>>temp)&1)==1){//判断flag的第temp位是否为1
                duplication[0]=temp;
                return true;
            }
            flag=flag|(1<<temp);//将flag的第temp位置为1
        }
        return false;
    }
};

如果要求时间复杂度 O(N),空间复杂度 O(1),则不能使用排序的方法,也不能使用额外的标记数组。

如果数据量较大,则也不能用第一种方法和上一种方法。

【Java解法】

import java.util.*;
public class Solution {
    public boolean duplicate(int numbers[],int length,int [] duplication) {
        if (numbers == null || length == 0) {return false;}
        HashSet<Integer> set = new HashSet<>();
        for (int i = 0; i < length; i++) {
            if (set.contains(numbers[i])) {
                duplication[0] = numbers[i];
                return true;
            } else {
                set.add(numbers[i]);
            }
        }
        return false;
    }
}

2 [0, ..., n-1]中任一重复的数字

题目描述

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

解题思路

1、修改原数组:交换顺序

对于这种数组元素在 [0, n-1] 范围内的问题,可以将值为 i 的元素调整到第 i 个位置上进行求解。

以 (2, 3, 1, 0, 2, 5) 为例,遍历到位置 4 时,该位置上的数为 2,但是第 2 个位置上已经有一个 2 的值了,因此可以知道 2 重复。此大法利用了哈希的特性,但不需要额外的存储空间。注意,该方法仅限于找到任一个数字重复的情况,无法保证找到第一个重复的数字。(放牛客上无法通过所有用例)

  • 把当前序列当成是一个下标和下标对应值是相同的数组;
  • 遍历数组,判断当前位的值和下标是否相等: 2.1. 若相等,则遍历下一位; 2.2. 若不等,则将当前位置i上的元素和a[i]位置上的元素比较:若它们相等,则成功!若不等,则将它们两交换。换完之后a[i]位置上的值和它的下标是对应的,但i位置上的元素和下标并不一定对应;重复2.2的操作,直到当前位置i的值也为i,将i向后移一位,再重复2.
class Solution { 
public:
    bool duplicate(int numbers[], int length, int* duplication) {
        if(length<=0 || numbers==NULL) return false;//判断每一个元素是否非法
        for(int i=0; i<length; ++i) {
            if(numbers[i]<=0 || numbers[i]>length-1) return false;
            while(numbers[i]!=i) {
                if(numbers[i]==numbers[numbers[i]]) {
                    *duplication = numbers[i];
                    return true;
                }
                int temp = numbers[i];//交换numbers[i]和numbers[numbers[i]]
                numbers[i] = numbers[temp];
                numbers[temp] = temp;
            }
        }
        return false;
    }
};

3 [1, ..., n]有一个数字缺失而另一个重复

LeetCode-645. Set Mismatch [C++][Java]_贫道绝缘子的博客-CSDN博客You have a set of integerss, which originally contains all the numbers from1ton. Unfortunately, due to some error, one of the numbers insgot duplicated to another number in the set, which results inrepetition of onenumber andloss of anothernumberhttps://blog.csdn.net/qq_15711195/article/details/122647744

4 [1, ..., n]中任意一重复的数字

题目描述

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

解题思路

1、哈希法

由于不能修改输入数组,我们可以创建一个长度为 n+1 的辅助数组,逐一把原数组的数字复制到辅助数组。如果原数组中的数字是 m,则把它复制到数组中下标为 m 的位置。持续执行则会发现重复数字,但这个需要 O(n) 的辅助空间。

2、分治法

接下来我们避免使用 O(n) 的辅助空间。假设没有重复数字,那 1~n 的范围内最多只有 n 个数字,而题目却有 n+1 个数字,所以一定存在重复数字。所以某范围内数字的个数对解决这个问题很重要。

原数组的数字范围为 1~n,我们用中间大小 m 把原数组按大小范围分为两部分,前一半的大小范围为 1~m,后一半的范围为 m+1~n。如果范围 1~m 的数字个数超过 m 个,则当中一定存在重复数字,否则后一半存在重复数字。我们继续把包含重复数字的范围一分为二,直到找到一个重复的数字。
复杂度为O(nlog(n))+O(1)

int getDuplication(const int* numbers, int length) {// 数组长度 n+1, 数字范围 1~n
    if (numbers==nullptr || length<=0) return -1;
    int start = 1, end = length - 1;
    while (end >= start) {
        int middle = ((end - start) >> 1) + start; // 不易越界,(end + start)/2 容易越界
        int count = countRange(numbers, length, start, middle);
        if (end == start) {// 当只剩一个数字
            if (count > 1) return start;//且这个数字在数组中出现的次数大于1,返回此重复的数字
            else break; // 否则跳出循环,数组内无重复数字
        }
        if (count > (middle-start+1)) end = middle; // 如果前半部分范围内数字出现的次数大于范围,说明此部分有重复数字,接着在前半范围搜索
        else start = middle+1; // 否则后半部分有重复数字,接着在后半范围搜索
    }
    return -1;
}
// 统计给定数组给定范围内数字的个数
int countRange(const int* numbers, int length, int start, int end) {
    if (numbers == nullptr) return 0;
    int count = 0;
    for (int i = 0; i < length; i++) if (numbers[i] >= start && numbers[i] <= end) ++count;
    return count;
}

5 [1, ..., n]中所有重复的数字

LeetCode-442. Find All Duplicates in an Array_贫道绝缘子的博客-CSDN博客Given an integer arraynumsof lengthnwhere all the integers ofnumsare in the range[1, n]and each integer appearsonceortwice, returnan array of all the integers that appearstwice.https://blog.csdn.net/qq_15711195/article/details/122656513

6 [0, ..., n]中缺失一个数字

LeetCode-268. Missing Number [C++][Java]_贫道绝缘子的博客-CSDN博客Given an arraynumscontainingndistinct numbers in the range[0, n], returnthe only number in the range that is missing from the array.https://blog.csdn.net/qq_15711195/article/details/122657967

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贫道绝缘子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值