力扣-442. 数组中重复的数据

1.题目介绍

题目地址(442. 数组中重复的数据 - 力扣(LeetCode))

https://leetcode.cn/problems/find-all-duplicates-in-an-array/

题目描述

给你一个长度为 n 的整数数组 nums ,其中 nums 的所有整数都在范围 [1, n] 内,且每个整数出现 一次两次 。请你找出所有出现 两次 的整数,并以数组形式返回。

你必须设计并实现一个时间复杂度为 O(n) 且仅使用常量额外空间的算法解决此问题。

 

示例 1:

输入:nums = [4,3,2,7,8,2,3,1]
输出:[2,3]

示例 2:

输入:nums = [1,1,2]
输出:[1]

示例 3:

输入:nums = [1]
输出:[]

 

提示:

  • n == nums.length
  • 1 <= n <= 105
  • 1 <= nums[i] <= n
  • nums 中的每个元素出现 一次两次

2.题解

2.1 哈希表

思路

哈希表记录出现次数,但是这种方法是错误的,并不满足使用常量级空间存储哈希表的要求!!!

代码

  • 语言支持:C++

C++ Code:


class Solution {
public:
    vector<int> findDuplicates(vector<int>& nums) {
        unordered_map<int, int> mp;
        int n = nums.size();
        for(int num: nums){
            if(mp.count(num)){
                mp[num]++;
            }else{
                mp.emplace(num, 1);
            }
        }

        vector<int> ans;
        for(int i = 1; i <= n; i++){
            if(mp[i] == 2){
                ans.push_back(i);
            }
        }
        return ans;
    }
};

复杂度分析

令 n 为数组长度。

  • 时间复杂度:\(O(n)\)
  • 空间复杂度:\(O(n)\)

2.2 交换位置

思路

我们知道应该将num[i] 这个值放到对应 nums[nums[i] - 1]的位置上, 所以我们考虑遍历数组, 进行 swap(num[i], nums[nums[i] - 1]), 这样就行了吗?
不行, 换出的值位置是对了, 换进来的却是不对的, 但是我们是按照从前向后的顺序执行,换进前面的数如果不对,后面就有可能一直不对下去,导致错误
[4,3,2,7,8,2,3,1]
0 [7,3,2,4,8,2,3,1]
1 [7,2,3,4,8,2,3,1]
2 [7,2,3,4,8,2,3,1]
3 [7,2,3,4,8,2,3,1]
4 [7,2,3,4,1,2,3,8]
5 [7,2,3,4,1,2,3,8]
6 [7,2,3,4,1,2,3,8]
7 [7,2,3,4,1,2,3,8]

我们思考后, 猜想是否应该保证遍历时每次的首项正确即可? 即最后 下标 == 值 - 1 ;(i == nums[i] - 1;)
像是加上 while (i != nums[i] - 1) 可以吗? 保证位置0对应1,位置1对应2.... 错误的, 这样判断如果对应位置的数不存在,比如像如果数组中不存在3,就会不断交换导致死循环!!!!

正确思路应该是: while (nums[i] != nums[nums[i] - 1]) , 想法和上面还是一样的 下标 == 值 - 1
while循环的停止条件 1. 重复元素-两个位置[i 和 nums[i] - 1]](像是下面例子中的两个3) 2.元素处于正确位置
这样就不是保证每个位置i对应的值都正确, 而是保证数组中所有存在的数作为位置下标nums[i], 值为 nums[nums[i] - 1]
执行的顺序不是按0-n, 而是按遍历数组的元素顺序来(每次确保数组的每一个元素位置正确), 最后停止时当前位置元素必定为重复元素/位于正确位置的数组元素, 前面不可能还有没有正确排位置的数组元素, 故继续向后遍历
[4,3,2,7,8,2,3,1]
0.1 [7,3,2,4,8,2,3,1]
0.2 [3,3,2,4,8,2,7,1]
0.3 [2,3,3,4,8,2,7,1]
0.4 [3,2,3,4,8,2,7,1]
1.1 [3,2,3,4,8,2,7,1]
2.1 [3,2,3,4,8,2,7,1]
3.1 [3,2,3,4,8,2,7,1]
4.1 [3,2,3,4,1,2,7,8]
4.2 [1,2,3,4,3,2,7,8]
5.1 [1,2,3,4,3,2,7,8]
6.1 [1,2,3,4,3,2,7,8]
7.1 [1,2,3,4,3,2,7,8]
经检查[5,6]不在数组中!

代码

class Solution {
public:
    vector<int> findDuplicates(vector<int>& nums) {
        vector<int> ans; 
        int n = nums.size();
        for(int i = 0; i < n; i++){
            while(nums[i] != nums[nums[i] - 1])
                swap(nums[i], nums[nums[i] - 1]); // 这里切不可只交换一次, 我们考虑一种极端情况,如果第一个数和第i个数交换后,第一个数到达正确位置,而第i个数到达错误位置
        }

        for(int i = 0; i < n; i++){
            if(nums[i] != i + 1){
                ans.push_back(nums[i]);
            }
        }
        return ans;
    }
};

2.3 索引-值

思路

同力扣-448. 找到所有数组中消失的数字
那里是出现零次和一次的数,这里是出现一次和两次的数,原理是一样的

代码

class Solution {
public:
    vector<int> findDuplicates(vector<int>& nums) {
        vector<int> ans; 
        int n = nums.size();
        for(int i = 0; i < n; i++){
            int x = (nums[i] - 1) % n;
            nums[x] += n;
        }

        for(int i = 0; i < n; i++){
            if(nums[i] > 2 * n){
                ans.push_back(i + 1);
            }
        }
        return ans;
    }
};

2.3 正负号标记

思路

思路同2.2, 我们需要一个能实时还原为下标的标记,然后就可以使用该标记判断哪些元素时重复出现了
这里使用正负号, 标记的时候使其变负即可; 而还原的时候简单的使用abs即可还原!

代码

class Solution {
public:
    vector<int> findDuplicates(vector<int>& nums) {
        vector<int> ans; 
        int n = nums.size();
        for(int i = 0; i < n; i++){
            int x = abs(nums[i]) - 1;  
            if(nums[x] > 0){
                nums[x] = -nums[x];
            } else{
                ans.push_back(x + 1);
            }         
        }
        return ans;
    }
};
posted @ 2024-04-25 17:09  DawnTraveler  阅读(15)  评论(0编辑  收藏  举报