数组中的重复数字问题
1 概述
本文将介绍 LeetCode 常见的一些数组问题,这些数组的特定都是长度为 n ,数组元素在 [1,n] 之间。本文将总结一些针对这些特点数组的套路!
2 实战
2.1 只出现一次的数字
题目地址:136. 只出现一次的数字
2.2 只出现一次的数字 II
题目地址:137. 只出现一次的数字 II
2.3 只出现一次的数字 III
题目地址:260. 只出现一次的数字 III
2.4 丢失的数字
2.5 寻找重复数
2.6 找不同
2.7 数组中重复的数据
题目地址:442.数组中重复的数据
给你一个长度为 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 中的每个元素出现 一次 或 两次
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-all-duplicates-in-an-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
- 思路:由于题目限制我们时间复杂度为 O(N) 且使用常量级别的额外空间,那我们就不考虑利用 Map 统计计数相关的算法了。而且排序相关的算法也达不到题目要求的时空复杂度,所以也排除。题目还有一个有用的条件我们没用到:n 个数字,且数字范围也是 [1,n] 。假如我们如果将数字 x 放置到 x - 1 的索引位置,如果数字 x 是重复数字,当第二个 x 想要被放置到 x - 1 的位置上时,发现坑已经被占了,那它只能待在不属于它的坑里。我们最后再整体遍历一次,将那些在不属于自己坑里的数字统计出来,即为所求。
- 代码
class Solution { public List<Integer> findDuplicates(int[] nums) { // 思路:将数字 x 放置到下标为 x - 1 的位置,再次遍历数组时如果发现数字与下标不匹配且对应下标已经有符合的数字占位,则该数字为出现两次的数字 if (nums == null || nums.length == 0) { throw new IllegalArgumentException(); } int n = nums.length; List<Integer> ans = new ArrayList<>(); // 将元素 x 放置到 x - 1 下标处 for (int i = 0; i < n; i++) { while (nums[nums[i] - 1] != nums[i]) { swap(nums, nums[i] - 1, i); } } // 如果元素不在自己应该在的下标处,就添加到 ans for (int i = 0; i < n; i++) { if (nums[i] - 1 != i) { ans.add(nums[i]); } } return ans; } private void swap(int[] nums, int p, int q) { int tmp = nums[p]; nums[p] = nums[q]; nums[q] = tmp; } }
- 复杂度
- 时间复杂度:O(N),每个元素“归位” O(N),寻找答案O(N),总的时间复杂度 O(N)
- 空间复杂度:O(1),注意我们说的空间复杂度一般是指额外空间复杂度,ans 由于是要求的结果,不计算在内
- 本题还有一种解法:每一次遍历到数字 x ,将 x - 1 处的元素值为其相反数;若某个位置的值已经为负值,说明时第二次访问,记录该 x。空间复杂度为 O(N),空间复杂度为:O(1)。具体代码如下:
class Solution { public List<Integer> findDuplicates(int[] nums) { // 思路:每一次遍历到数字 x ,将 x - 1 处的元素值为其相反数;若某个位置的值已经为负值,说明时第二次访问,记录该值 if (nums == null || nums.length == 0) { throw new IllegalArgumentException(); } int n = nums.length; List<Integer> ans = new ArrayList<>(); for (int i = 0; i < n; i++) { // 由于 nums[i] 可能已经为负值,所以需要求绝对值 int idx = Math.abs(nums[i]) - 1; if (nums[idx] < 0) { ans.add(idx + 1); } // 每次都将 x - 1 位置的元素取相反数 nums[idx] = -nums[idx]; } return ans; } }
2.8 找到所有数组中消失的数字
题目地址:448. 找到所有数组中消失的数字
给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。
示例 1:
输入:nums = [4,3,2,7,8,2,3,1]
输出:[5,6]
示例 2:
输入:nums = [1,1]
输出:[2]
提示:
n == nums.length
1 <= n <= 105
1 <= nums[i] <= n
进阶:你能在不使用额外空间且时间复杂度为 O(n) 的情况下解决这个问题吗? 你可以假定返回的数组不算在额外空间内。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-all-numbers-disappeared-in-an-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
- 思路:与2.7中的题目基本类似,只不过我们这次找的是缺失的元素——将每个元素 x 置换到 x - 1 下标处,再次遍历时,如果下标与元素不对应,则记录下标 + 1(下标 + 1 即缺失的元素)
- 代码
class Solution { public List<Integer> findDisappearedNumbers(int[] nums) { // 思路:将每个元素 x 置换到 x - 1 下标处,再次遍历时,如果下标与元素不对应,则记录下标 + 1(下标 + 1 即缺失的元素) if (nums == null || nums.length == 0) { throw new IllegalArgumentException(); } int n = nums.length; List<Integer> ans = new ArrayList<>(); for (int i = 0; i < n; i++) { while (nums[nums[i] - 1] != nums[i]) { swap(nums, nums[i] - 1, i); } } for (int i = 0; i < n; i++) { if (nums[i] != i + 1) { ans.add(i + 1); } } return ans; } private void swap(int[] nums, int p, int q) { int tmp = nums[p]; nums[p] = nums[q]; nums[q] = tmp; } }
- 复杂度
- 时间复杂度:O(N)
- 空间复杂度:O(1)
-
同样,也可以使用2.7题目中第4部分的标记算法,代码如下:
class Solution { public List<Integer> findDisappearedNumbers(int[] nums) { // 思路:每次遍历到数字 x 的时候,如果 x - 1 位置的数字为正,则将其反转。再遍历一次数组,如果某个位置的值为正的,说明其下标对应的值不存在(值 = 下标 + 1) if (nums == null || nums.length == 0) { throw new IllegalArgumentException(); } int n = nums.length; List<Integer> ans = new ArrayList<>(); for (int i = 0; i < n; i++) { int idx = Math.abs(nums[i]) - 1; if (nums[idx] > 0) { nums[idx] = -nums[idx]; } } for (int i = 0; i < n; i++) { if (nums[i] > 0) { ans.add(i + 1); } } return ans; } }