1. 128. 最长连续序列
1.1 思路
- 题目要设计时间复杂度为O(n)的算法解决此问题。考虑枚举数组中的每个数 x x x,考虑其为起点,不断尝试匹配 x + 1 , x + 2 , . . . x+1,x+2,... x+1,x+2,...,是否存在,假设最长匹配到了 x + y x+y x+y,那么其长度为 y + 1 y+1 y+1。
- 匹配的过程,暴力的方法是 O ( n ) O(n) O(n)遍历数组去看它后面连接的数是否存在,更高效的方法是用一个哈希表存储数组中的数,这样查看一个数是否存在即能优化至 O ( 1 ) O(1) O(1)的时间复杂度。但是这样算法的时间复杂度最坏情况还是会达到 O ( n 2 ) O(n^2) O(n2)(外层需要枚举 O ( n ) O(n) O(n)个数,内层暴力匹配也需要 O ( n ) O(n) O(n)次)。无法满足题目的要求,如果已知一个 x , x + 1 , x + 2 , . . . , x + y x,x+1,x+2,..., x +y x,x+1,x+2,...,x+y的连续序列,我们就不需要从 x + 1 , x + 2 , x + y x+1,x+2,x+y x+1,x+2,x+y处尝试开始匹配,因为这个结果一定不会优于从 x x x开始的连续序列,因此,在外层循环碰到这种情况直接跳过即可。
- 如何判断是否为这种情况?
枚举的数
x x x一定是在数组中不存在前驱数
x − 1 x-1 x−1的,否则我们可以从 x − 1 x-1 x−1开始匹配,没必要从 x x x开始匹配。因此,每次在哈希表中检查是否存在 x − 1 x-1 x−1,即能判断是否需要跳过。
1.2 代码实现
class Solution {
public int longestConsecutive(int[] nums) {
Set<Integer> set = new HashSet<>();
for (int num : nums) {
set.add(num);
}
int len = 0;
int maxLen = 0;
int numCur = 0;
for (int num : set) {
if (!set.contains(num - 1)) {
numCur = num;
while(set.contains(numCur + 1)) {
numCur++;
}
len = numCur - num + 1;
maxLen = Math.max(len, maxLen);
}
}
return maxLen;
}
}
1.3 思考
- 在题目要求考虑时间复杂度时,可以考虑
哈希表
这种用空间换时间的思想。
2. 448. 找到所有数组中消失的数字
2.1 思考
- 方法一:首先想到的就是使用哈希表,此题可以直接使用数组存储,nums数组中出现的数字 i i i就令numbers数组中下标为 i − 1 i-1 i−1的数组元素为1,证明在nums数组中有元素 i i i。
- 方法二:题目要求不要使用额外的空间,且假定返回的数组不算在额外空间内,所以要想到
原地修改
,nums的长度也是n,让nums充当哈希表即可。遍历数组nums,每遇到一个数 x x x,就让 n u m s [ x − 1 ] nums[x-1] nums[x−1]增加 n n n,由于nums中所有的数均在 [ 1 , n ] [1,n] [1,n]中,增加以后,这些数必然大于 n n n。最后再遍历nums,如果 n u m s [ i ] nums[i] nums[i]未大于 n n n,就说明没有遇到过数 i + 1 i + 1 i+1。这样就能找到缺失的数。
2.2 代码实现
- 方法一代码实现
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
int n = nums.length;
int[] numbers = new int[n];
for (int num : nums) {
numbers[num - 1] = 1;
}
List<Integer> list = new ArrayList<>();
for (int i = 0; i < n; i++) {
if (numbers[i] == 0) {
list.add(i + 1);
}
}
return list;
}
}
- 方法二代码实现
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
List<Integer> res = new ArrayList<>();
int n = nums.length;
for (int num : nums) {
int x = (num - 1) % n;
nums[x] += n;
}
for (int i = 0; i < n; i++) {
if (nums[i] <= n) {
res.add(i + 1);
}
}
return res;
}
}
说明:
- 为什么要减一然后对n取余,首先说减一是因为 n u m s nums nums数组中的元素取值范围是 1 ∼ n 1 \sim n 1∼n,而 n u m s nums nums数组的下标是 0 ∼ n − 1 0 \sim n-1 0∼n−1,所以要保持一一对应就要让元素值减一。而对 n n n取余是因为每次给元素值加 n n n可能导致元素的值大于 n − 1 n - 1 n−1,要超出数组下标,每次加 n n n,要想不超出对 n n n取余就行。
- 在第二个for循环中, n u m s [ i ] < = n nums[i]<=n nums[i]<=n而不是 n u m s [ i ] < n nums[i] < n nums[i]<n是因为当元素值为n的时候说明根本没加,因为原本的值都大于0,存在该元素的话加上 n n n,一定要大于 n n n。所以,小于等于 n n n就代表 i + 1 i +1 i+1在原来的数组中不存在。
2.3 思考
- 要空间复杂度为
O
(
1
)
O(1)
O(1)就要考虑
原地修改
。