目录
哈希
2024..4.15
1 两数之和
简单但是有很多点也需要注意!
49 字母异位词分组
List<List<String>>
是一个接口。ArrayList<List<String>>
是 List
接口的一个具体实现。
哈希表的key为一组字母异位词的标志(内部字母排序),哈希表的value为一组字母异位词列表。
128 最长连续序列
HashSet
是一个用于存储唯一元素的集合类。Set<Integer> num_set=new HashSet<Integer>();添加元素add();检查元素是否存在contains();删除元素remove();获取集合大小size();清空集合clear()
为了减少复杂度,因为如果从x开始往后是连续的,那么如果存在x-1,从x开始的连续长度就没有从x-1开始的连续长度大,所以就不考虑x了,也就是说对于每个元素判断是否存在x-1,存在的话就跳过;外层只遍历连续序列开始的第一个元素。x进入内层以后,如果set中有x+1,就进入循环,将数+1,将以x开始的序列长度也+1;直到没有跳出循环,并且与最大长度进行比较,最终时间复杂度为O(n)
双指针
2024.4.16
283 移动零
快慢指针
11 盛水最多的容器
用两个指针一个在头一个在尾,每次按最低的计算水量并更新最大值,并将低的那个向后或向前移动进行下一次循环,结束条件为两个指针相遇。
15 三数之和
输出的是和为0的三个数组成的三元组,与位置无关,因此要去重
再次写,还是有很多容易出错的地方!!!
List<List<Integer>> list=new ArrayList<>();
Arrays.sort(nums); //从小到大排序
for(int i=0;i<nums.length;i++){
if(nums[i]>0){ //因为排过序,如果当前元素大于0,那么肯定不存在
return list;
}
if(i>0 && nums[i]==nums[i-1]){ // i>0,因为第一次肯定不重复
continue;
}
int left=i+1,right=nums.length-1;
while(left<right){
if(nums[i]+nums[left]+nums[right]>0){ // 判断条件,移动指针
right--;
}
else if(nums[i]+nums[left]+nums[right]<0){
left++;
}
else{
list.add(Arrays.asList(nums[i],nums[left],nums[right]));
while(left<right && nums[left]==nums[left+1]){ //先存入一个,再去重
left++;
}
while(left<right && nums[right]==nums[right-1]){
right--;
}
left++;
right--;
}
}
}
return list;
※42 接雨水
自己想法也差不多,但是没有想到怎么更新区间,想到了找后面第一个大于当前区间左边的值;
使用单调栈,left和i代表区间的左右,每次计算left到i中top的雨水量;其中while是一直进行的,如果当前的i是右区间,就会一直计算在区间里的每个位置的雨水量,并移除。
滑动窗口
2024.4.17
3 无重复字符的最长子串
又忘记怎么做了!用滑动窗口和HashMap;每次循环如果map中出现了这个元素,更新区间的left值,将元素加入map中,计算最大长度。
if(s.length()==0){
return 0;
}
HashMap<Character,Integer> map=new HashMap<>();
int max=0,left=0;
for(int i=0;i<s.length();i++){
if(map.containsKey(s.charAt(i))){
left=Math.max(left,map.get(s.charAt(i))+1);
}
map.put(s.charAt(i),i);
max=Math.max(max,i-left+1);
}
return max;
※ 438 找到字符串中所有字母异位词
自己先做但是不对,处理太复杂了;滑动窗口的位置是固定的,用两个长度为26的数组来存储字母出现的次数,对于每个滑动窗口移动的时候,将出去的字母和进来的字母对应的值分别-1和+1,如果两个数组一样就将滑动窗口的起始位置加入列表。
子串
2024.4.18
※560 和为k的子数组
- 遍历数组nums求得所有前缀和,将其存放在 prefix 中;
- 然后遍历数组prefix,每次遍历的时候,先判断本身这个前缀和等于k,再 查询 Map 中是否存在需要的前缀和,如果有将其 value 加到结果中,将该值加入map中(前缀和可能会出现一样的,要判断,让map中key-前缀和是唯一的)
前缀和,出现的次数可能会很大,用Long
239 滑动窗口最大值
用队列,从头开始遍历数组,保证队列头中一直是当前滑动窗口的最大值;队列不为空的时候才能进行操作;i>=k 窗口开始移动,i>=k-1此时可以开始陆续记录下每个滑动窗口的最大值;nums[i]是每次窗口移动要加入的元素
2024.4.22
※76 最小覆盖子串
滑动窗口+哈希(记录字符串t中元素出现的个数以及滑动窗口中的出现字符串t中的元素的个数)
先扩right 全都包括之后,再移动left找到最小长度
普通数组
2024.4.23
53 最大子数组和![](https://img-blog.csdnimg.cn/direct/75e0fbfe619e45e09a828e87bd535188.png)
※ 动态规划 !!!
(是由前一个状态推导出来的)
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
我们用 f(i)代表以第 i个数结尾的「连续子数组的最大和」,动态规划转移方程:
2024.4.24
56 合并区间
贪心算法!!!(选择每一阶段的局部最优,从而达到全局最优)
用一个新的列表进行记录,里面的每个元素是一维数组
排序:Arrays.sort(intervals,(x,y) -> Integer.compare(x[0], y[0]));
给列表中加数组元素:res.add(new int[]{start,right});
列表转化为二维数组:return res.toArray(new int[res.size()][]);
2024.4.28
189 轮转数组
1. 新建一个数组进行存储,空间复杂度为O(N)
+k之后对长度取模就实现了右移;System.arraycopy(arr, 0, nums, 0, len);是对数组进行复制(从arr复制到nums),分别从0开始,复制长度为len
2. 先整体翻转,再以k分隔,两部分分别翻转
2024.4.29
238 除自身以外数组的乘积
先算出前面的乘积,再将后面的乘积乘进数组;每次不需要从头计算,在前一个的基础上×就可
41 缺失的第一个正数
要满足时间复杂度O(N),空间复杂度O(1)
将数组设置成哈希表
如果数组长度是N,里面的数从1到N都有,那么没有出现的最小正数就是N+1,否则是[1,N]里面的数
我们对数组进行遍历,对于遍历到的数 x,如果它在 [1,N] 的范围内,那么就将数组中的第 x−1 个位置(注意:数组下标从 0开始)打上「标记」。在遍历结束之后,如果所有的位置都被打上了标记,那么答案是 N+1,否则答案是最小的没有打上标记的位置加 1。
- 我们将数组中所有小于等于0的数修改为 N+1;此时数组中元素全为正数
- 我们遍历数组中的每一个数 x,它可能已经被打了标记,因此原本对应的数为∣x∣,其中∣∣为绝对值符号。如果∣x∣∈[1,N],那么我们给数组中的第∣x∣−1 个位置的数添加一个负号。注意如果它已经有负号,不需要重复添加;(第i-1个位置是负数,说明i出现过)
- 在遍历完成之后,如果数组中的每一个数都是负数,那么答案是 N+1,否则答案是第一个正数的位置加 1。
矩阵
73 矩阵置零
在原来矩阵上改动,将第一行第一列作为标志,设置两个标记记录原来第一行第一列是否有0
2024.4.30
54 螺旋矩阵
使用上下左右四个标志进行层层遍历即可,每加入一层 四个标志进行++或者--;
2024.5.7
48 旋转图像
不需要另外的矩阵,在原来的矩阵上使用一个temp,旋转四次后到达原位置;遍历行列的结束条件分别为 i<n/2 和 j<(n+1)/2
2024.5.8
240 搜索二维矩阵Ⅱ
每次比较消去一行或者一列来更新矩阵,标志数为新的左下角的元素
- 时间复杂度 O(M+N):其中,N 和 M 分别为矩阵行数和列数,此算法最多循环 M+N 次。
- 空间复杂度 O(1) : i, j 指针使用常数大小额外空间