【算法专题】数组中重复(缺失)的数字

数组中重复(缺失)的数字

剑指 Offer 03 数组中重复的数字

题目描述:剑指 Offer 03 数组中重复的数字

在这里插入图片描述

分析

解法一

  • 本题的考点:数学,哈希表

  • 本题中最常规的做法可以使用哈希表统计每个数据出现的次数,如果存在某个数据出现次数超过两次,返回即可。

  • 这里采用另一种做法,因为所有数据范围都在[0~n-1]之间,每次遍历到一个数据x=nums[i],如果nums[x]位置的数据为正数,则将nums[x]上的数据去相反数;如果nums[x]位置的数据为负数,则说明x第二次出现,返回x即可。

  • 如果遍历结束都没返回值,则说明[1~n-1]最多出现了一次,则说明0出现了多次,返回0即可。

解法二

  • 我们让下标i放置的数据为i,类似于桶排序。

  • 依次考察每个元素nums[i],循环让nums[i]放置到下标为nums[i]的位置,在交换后如果还有nums[i]!=i,说明下标为nums[i]的位置已经放置了nums[i],因此nums[i]重复,返回nums[i]即可。

代码

  • C++
// 解法一
class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        for (int i = 0; i < nums.size(); i++) {
            int x = abs(nums[i]);
            if (nums[x] < 0) return x;
            nums[x] *= -1;
        }
        return 0;
    }
};
// 解法二
class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {

        for (int i = 0; i < nums.size(); i++) {
            while (nums[nums[i]] != nums[i]) swap(nums[nums[i]], nums[i]);
            if (nums[i] != i) return nums[i];
        }
        return 0;
    }
};

时空复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)n为数组长度。

  • 空间复杂度: O ( 1 ) O(1) O(1)

AcWing 14. 不修改数组找出重复的数字

问题描述

在这里插入图片描述

分析

解法一

  • 因为不能修改原始数组,因此剑指 Offer 03 数组中重复的数字的方法,可以使用哈希表统计每个元素出现次数,但这样空间复杂度是 O ( n ) O(n) O(n)的。

  • 因为要求空间是 O ( 1 ) O(1) O(1)的,本题需要使用二分解决,相当于用时间换空间。

  • 答案在区间[1,n]中,因此初始二分区间是[1, n],取中点mid=(l + r)/2,统计在[l, mid]中数据的个数,记为s,如果s>mid-l+1,根据抽屉原理在[l, mid]中必然存在重复的数,否则在[mid+1, r]中必然存在重复的数。

解法二

  • 可以使用快慢指针解决,类似于题目Leetcode 0142 环形链表 II

  • 我们首先考虑如何建图,首先图中有0~n一共n+1个点,对于数组中的元素nums[i],则我们在图中连一条i->nums[i]的边,因为数组nums长度为n+1,因此有n+1条边,根据抽屉原理,则必有环。并且0不再环中,因为没有边会指向0。例如题目中的第一个样例:

在这里插入图片描述

  • 对于这个环的入口,一定会有两条边指向它,这说明存在两个索引,对应的位置存储的是同一个数,因此环的入口就是我们要找到数。可以转换为LC142。

代码

  • C++
class Solution {
public:
    int duplicateInArray(vector<int>& nums) {
        
        int l = 1, r = nums.size() - 1;  // [l, r] 初始为 [1, n]
        while (l < r) {
            int mid = l + r >> 1;
            int s = 0;
            for (auto x : nums) s += x >= l && x <= mid;
            if (s > mid - l + 1) r = mid;  // 说明[l, mid]中的数目多于坑数,根据抽屉原理,其中必有重复元素
            else l = mid + 1;
        }
        return r;
    }
};
class Solution {
public:
    int duplicateInArray(vector<int>& nums) {
        
        int a = 0, b = 0;
        while (true) {
            a = nums[a], b = nums[nums[b]];
            if (a == b) {
                a = 0;
                while (a != b) a = nums[a], b = nums[b];
                return a;
            }
        }
    }
};

Leetcode 0041 缺失的第一个正数

题目描述:Leetcode 0041 缺失的第一个正数

在这里插入图片描述

分析

  • 本题的考点:桶排序

  • 分为两步:

    (1)使用桶排序,对原数组进行操作,使得1出现在nums[0]的位置上,2出现在nums[1]的位置上,…,n出现在nums[n-1]的位置。因此,如果 0 < n u m s [ i ] ≤ n 0<nums[i] \le n 0<nums[i]n,则nums[i]应该出现在nums[nums[i] - 1]的位置上。

    (2)上述过程结束后,找到第一个不在应在位置上的1到n的数,即当nums[i] != i+1时返回i+1,如果全部满足nums[i] == i+1,则说明整个数组中的元素是1~n,返回n+1即可。

  • 参考网址:网址

代码

  • C++
class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {

        int n = nums.size();

        // (1) 让所有在1~n中的数据t放置到nums[t - 1]中
        for (int i = 0; i < n; i++) {
            int &t = nums[i];  // t应该放到nums[t - 1]的位置
            while (t > 0 && t <= n && nums[t - 1] != t)
                swap(t, nums[t - 1]);  // 此时下标为t-1的位置正确放置了t, 但是nums[i]换过来的数据不一定是i+1, 因此是while
        }

        // (2) 找出不满足nums[i] == i+1的位置,返回i+1; 如果都满足,返回n+1
        for (int i = 0; i < n; i++)
            if (nums[i] != i + 1)
                return i + 1;
        return n + 1;
    }
};
  • Java
class Solution {
    public int firstMissingPositive(int[] nums) {
        
        int n = nums.length;

        // (1) 让所有在1~n中的数据nums[i]放置到nums[nums[i] - 1]中
        for (int i = 0; i < n; i++) {
            // nums[i]>0且nums[i]<=n时, nums[i]应该放到nums[nums[i] - 1]的位置
            while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i])
                swap(nums, i, nums[i] - 1);
        }

        // (2) 找出不满足nums[i] == i+1的位置,返回i+1; 如果都满足,返回n+1
        for (int i = 0; i < n; i++)
            if (nums[i] != i + 1)
                return i + 1;
        return n + 1;
    }

    private void swap(int[] nums, int i, int j) {
        int t = nums[i]; nums[i] = nums[j]; nums[j] = t;
    }
}
  • Python
class Solution:
    def firstMissingPositive(self, nums: List[int]) -> int:
        n = len(nums)
        for i in range(0, n):
            while 0 < nums[i] <= n and nums[i] != nums[nums[i] - 1]:
                nums[nums[i] - 1], nums[i] = nums[i], nums[nums[i] - 1]
        for i in range(0, n):
            if nums[i] != i + 1:
                return i + 1
        return n + 1

时空复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)。因为每次交换,会将一个数放在正确的位置上,所以总共最多执行n次,所以总时间复杂度 O ( n ) O(n) O(n)

  • 空间复杂度: O ( 1 ) O(1) O(1)。使用原数组。

Leetcode 0287 寻找重复数

题目描述:Leetcode 0287 寻找重复数

在这里插入图片描述

分析

  • 本题的考点:快慢指针

  • 本题本质上就是Leetcode 0142 环形链表 II

  • 我们首先考虑如何建图,首先图中有0~n一共n+1个点,对于数组中的元素nums[i],则我们在图中连一条i->nums[i]的边,因为数组nums长度为n+1,因此有n+1条边,根据抽屉原理,则必有环。并且0不再环中,因为没有边会指向0。例如题目中的第一个样例:

在这里插入图片描述

  • 对于这个环的入口,一定会有两条边指向它,这说明存在两个索引,对应的位置存储的是同一个数,因此环的入口就是我们要找到数。可以转换为LC142。

代码

  • C++
class Solution {
public:
    int findDuplicate(vector<int> &nums) {

        int a = 0, b = 0;  // a为慢指针,b为快指针
        while (true) {
            a = nums[a], b = nums[nums[b]];
            if (a == b) {
                a = 0;
                while (a != b) a = nums[a], b = nums[b];
                return a;
            }
        }
    }
};
  • Java
class Solution {
    public int findDuplicate(int[] nums) {

        int a = 0, b = 0;
        while (true) {
            a = nums[a];
            b = nums[nums[b]];
            if (a == b) {
                a = 0;
                while (a != b) {
                    a = nums[a];
                    b = nums[b];
                }
                return a;
            }
        }
    }
}

时空复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)n为数组长度。

  • 空间复杂度: O ( 1 ) O(1) O(1)

Leetcode 0442 数组中重复的数据

题目描述:Leetcode 0442 数组中重复的数据

在这里插入图片描述

分析

  • 本题的考点:数学

  • 因为所有数据都是在1~n之间的,对于每个数据x,我们将下标为x-1的位置每次取相反数,如果这个对应位置的数据变为正数,说明出现了两次。

代码

  • C++
class Solution {
public:
    vector<int> findDuplicates(vector<int>& nums) {
        vector<int> res;
        for (auto x : nums) {
            int p = abs(x) - 1;
            nums[p] *= -1;
            if (nums[p] > 0) res.push_back(abs(x));
        }
        return res;
    }
};
  • Java
class Solution {
    public List<Integer> findDuplicates(int[] nums) {
        List<Integer> res = new ArrayList<>();
        for (int x : nums) {
            int p = Math.abs(x) - 1;
            nums[p] *= -1;
            if (nums[p] > 0) res.add(Math.abs(x));
        }
        return res;
    }
}

时空复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)n为数组长度。

  • 空间复杂度: O ( 1 ) O(1) O(1)

Leetcode 0448 找到所有数组中消失的数字

题目描述:Leetcode 0448 找到所有数组中消失的数字

在这里插入图片描述

分析

  • 本题的考点:数学

  • 两次遍历数组:第一次,对于遍历到的数据x,将nums[x-1]位置的数据变为负数;第二次使用i1遍历到n,如果nums[i-1]为正数,说明i没在数组中出现过,记到答案中。最终返回答案即可。

代码

  • C++
class Solution {
public:
    vector<int> findDisappearedNumbers(vector<int>& nums) {
        for (auto x : nums) {
            x = abs(x);
            if (nums[x - 1] > 0) nums[x - 1] *= -1;
        }
        vector<int> res;
        for (int i = 1; i <= nums.size(); i++)
            if (nums[i - 1] > 0)
                res.push_back(i);
        return res;
    }
};
  • Java
class Solution {
    public List<Integer> findDisappearedNumbers(int[] nums) {
        for (int x : nums) {
            x = Math.abs(x);
            if (nums[x - 1] > 0) nums[x - 1] *= -1;
        }
        List<Integer> res = new ArrayList<>();
        for (int i = 1; i <= nums.length; i++)
            if (nums[i - 1] > 0)
                res.add(i);
        return res;
    }
}

时空复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)n为数组长度。

  • 空间复杂度: O ( 1 ) O(1) O(1)

Leetcode 0645 错误的集合

题目描述:Leetcode 0645 错误的集合

在这里插入图片描述

分析

  • 本题的考点:数学

  • 因为所有数据都是在1~n之间的,对于每个数据x,我们将下标为x-1的位置每次取相反数,如果这个对应位置的数据最终是正数,说明出现了两次或者没有出现。

  • 如何区分这两个数呢(一个没有出现,一个出现了两次)?可以采取两次遍历,第一次求出现两次的数,第二次遍历数组,如果对应位置的数大于0且不等于出现两次的数,说明是缺失的数。

代码

  • C++
class Solution {
public:
    vector<int> findErrorNums(vector<int>& nums) {
        vector<int> res(2);
        for (auto x : nums) {
            int k = abs(x);
            if (nums[k - 1] < 0) res[0] = k;
            nums[k - 1] *= -1;
        }
        for (int i = 0; i < nums.size(); i++) 
            if (nums[i] > 0 && i + 1 != res[0]) {
                res[1] = i + 1;
                break;
            }
        return res;
    }
};
  • Java
class Solution {
    public int[] findErrorNums(int[] nums) {

        int[] res = new int[2];
        for (int x : nums) {  // x 大小在[1...n]之间
            int k = Math.abs(x);
            if (nums[k - 1] < 0) res[0] = k;
            nums[k - 1] *= -1;
        }
        for (int i = 1; i <= nums.length; i++)
            if (nums[i - 1] > 0 && i != res[0]) {
                res[1] = i;
                break;
            }

        return res;
    }
}
  • Python
class Solution:
    def findErrorNums(self, nums: List[int]) -> List[int]:
        res = [0] * 2
        for x in nums:
            k = abs(x)
            if nums[k - 1] < 0:
                res[0] = k
            nums[k - 1] *= -1
        for i in range(len(nums)):
            if nums[i] > 0 and i + 1 != res[0]:
                res[1] = i + 1
                break
        return res

时空复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)n为数组长度。

  • 空间复杂度: O ( 1 ) O(1) O(1)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值