这一小节和大家分享的是「力扣」第 448 题:找到所有数组中消失的数字 的做法。
给定一个范围在 ( 为数组的大小) 的整型数组,数组中的元素有一些出现了两次,另一些只出现一次。
题目要求我们:
1、找到所有在 范围之间没有出现在数组中的数字。
2、您能在不使用额外空间且时间复杂度为 的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。
题目给出了一个示例:输入数组是:[4, 3, 2, 7, 8, 2, 3, 1]
它的长度为
,根据题目意思,数组里的数值的范围就在 [1, 8]
。我们从
开始到
一个一个地看,发现数字
和
出现了
次, 数字
和
分别出现了
次。因此输出:[5, 6]
。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-all-numbers-disappeared-in-an-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路分析:
1、有了第 41 题的经验,题目告诉我们数字的范围是 [1, n]
,因此依然可以将数组本身当成哈希表,遍历到一个数的时候,我们就把它放到它应该放置的位置上。根据这个思路解题;
这个思路是符合线性时间复杂度 要求的。
2、题目还要求我们不使用额外空间。我们知道在交换两个数组元素的的过程中,需要使用到一个额外的空间,那么怎么突破这个限制呢。这需要一些经验和位运算的知识。我说需要经验的意思就是这其实并不容易想到,如果一下子没有想到这个办法其实也没有关系。
- 使用异或运算的性质交换两个变量的值。
这个异或运算的性质其实就是异或运算定义本身。由于异或运算是在二进制下不进位的加法运算,因此:
(1)如果 a ^ b = c
,那么 a ^ c = b
与 b ^ c = a
同时成立,利用这一条,可以用于交换两个变量的值;
(2)一个数 num
异或上同一个数(a
)
次,它的值不变:nums ^ a ^ a = num
。
于是交换两个变量的值,例如 a
和 b
,不使用第三个变量,可以这样做:
a = a ^ b
b = a ^ b
a = a ^ b
它的道理是这样的:
第 1 个数 | 第 2 个数 |
---|---|
a |
b |
ab |
b |
ab |
a |
b |
a |
但是这种做法有一个坑,我们等将完这道题再说。
我们使用一个具体的例子分析:
- 我们的目的是让
nums[i]
位于下标为nums[i] - 1
的位置上。
最好的样子就是:
数值:[1, 2, 3, 4]
;
下标:[0, 1, 2, 3]
。
说明:让作为数值的 4
放在下标为 3
的位置上,这里数值和索引位置有一个长度为 1
的偏移。
假设当前遍历看到的 nums[i]
是 4
,我们需要看一下索引位置 3
上的元素是不是 4
。
(1)如果数组下标 3
的位置上不是数字 4
,就需要把当前看到的 4
交换到数组下标 3
的位置上;
(2)如果索引位置 3
上已经是 4
了,当前看到的 4
不用管它,它是多余的,此时什么都不用做,继续看下一个数。
nums[i]
是数值,而 nums[i] - 1
表示索引位置。
(可以使用额外空间的写法)
Java 代码:
import java.util.ArrayList;
import java.util.List;
public class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
int len = nums.length;
List<Integer> res = new ArrayList<>(2);
for (int i = 0; i < len; i++) {
// 数字 4 应该在下标 3 的位置上
while (nums[i] != nums[nums[i] - 1]) {
swap(nums, i, nums[i] - 1);
}
}
for (int i = 0; i < len; i++) {
if (nums[i] != i + 1) {
res.add(i + 1);
}
}
return res;
}
private void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
}
(不使用额外空间的写法)
Java 代码:
import java.util.ArrayList;
import java.util.List;
public class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
List<Integer> res = new ArrayList<>(2);
for (int i = 0; i < nums.length; i++) {
// 数字 4 应该在下标 3 的位置上
while (nums[i] != nums[nums[i] - 1]) {
swap(nums, i, nums[i] - 1);
}
}
for (int i = 0; i < nums.length; i++) {
if (nums[i] != i + 1) {
res.add(i + 1);
}
}
return res;
}
private void swap(int[] nums, int index1, int index2) {
if (index1 == index2) {
return;
}
nums[index1] = nums[index1] ^ nums[index2];
nums[index2] = nums[index1] ^ nums[index2];
nums[index1] = nums[index1] ^ nums[index2];
}
public static void main(String[] args) {
Solution7 solution7 = new Solution7();
int[] nums = {4, 3, 2, 7, 8, 2, 3, 1};
List<Integer> res = solution7.findDisappearedNumbers(nums);
System.out.println(res);
}
}