长度最小的子数组
给定一个含有 n个正整数的数组和一个正整数 target。
找出该数组中满足其总和大于等于target的长度最小的 子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度**。** 如果不存在符合条件的子数组,返回 0 。
要点: 同向的滑动窗口,先找到right,再left缩小尝试
java
class Solution {
public int minSubArrayLen(int target, int[] nums) {
//滑动窗口
int left = 0;
int right = 0;
int sum = 0;
int ans = nums.length + 1;
while(right < nums.length){
sum += nums[right];
while(sum-nums[left] >= target ){
sum = sum - nums[left];
left++;
}
if(sum >= target){
ans = Math.min(ans, right - left +1);
}
right++;
}
return ans == nums.length +1 ? 0 : ans;
}
}
乘积小于 K 的子数组
要点,和上面差不多,ans += right - left +1;
java
class Solution {
public int numSubarrayProductLessThanK(int[] nums, int k) {
//同向双指针
int left = 0;
int right = 0;
int n = nums.length;
int sum = 1;
int ans = 0;
for(int i = 0; i < n; i++){
sum *= nums[i];
while(left <= i && sum >= k){
sum = sum/nums[left];
left++;
}
ans += i -left +1;
}
return ans;
}
}
无重复字符的最长子串
要点:set去重,也是找到right,然后移除left
java
class Solution {
public int lengthOfLongestSubstring(String s) {
//set和滑动窗口
Set<Character> temp = new HashSet<>();
int left = 0;
int right = 0;
int n = s.length();
int ans = 0;
while(right < n){
char c = s.charAt(right);
if(!temp.contains(c)){
temp.add(c);
right++;
}else{
char remove = s.charAt(left);
temp.remove(remove);
left++;
}
ans = Math.max(ans, temp.size());
}
return ans;
}
}
最小覆盖子串
要点:need数组,required数组,valid,start
一句话总结:用双指针滑动窗口,右指针扩展至包含 t 的所有字符(按需计数),再收缩左指针以缩小窗口,同时记录最短合法子串。
java
class Solution {
public String minWindow(String s, String t) {
if(s == null || t == null || s.length() < t.length()){
return "";
}
//统计t中每个字符的需求次数
int[] need = new int[128];
for(char c : t.toCharArray()){
need[c]++;
}
//没顺序要求,就是包含就行
int required = 0;
for(int count : need){
if(count > 0){
required++;
}
}
int[] window = new int[128];
int left = 0;
int right = 0;
int valid = 0;
int min = Integer.MAX_VALUE;
int start = 0;
for(int i = 0; i < s.length(); i++){
char c = s.charAt(i);
//更新window 和 valid
if(need[c] > 0){
window[c]++;
if(window[c] == need[c]){
valid++;
}
}
//达到要求则开始收缩
while(valid == required){
if(i - left < min ){
min = i - left +1;
start = left;
}
char d = s.charAt(left);
left++;
if(need[d] > 0){
if(window[d] == need[d]){
valid --;
}
window[d] --;
}
}
}
return min == Integer.MAX_VALUE ? "" : s.substring(start, start+min);
}
}
随机知识
双指针(Two Pointers)完整总结
一、什么是双指针
使用两个指针(下标/迭代器)遍历数据结构,通过指针的移动策略高效解决问题。时间复杂度通常 O(n) 或 O(n log n)(含排序)。
二、四种经典模式
| 模式 | 指针移动方向 | 典型特征 | 代表题目 |
|---|---|---|---|
| 同向双指针(滑动窗口) | 都从左向右,一快一慢 | 维护连续窗口,满足某种条件 | 无重复最长子串、最小覆盖子串 |
| 对向双指针(左右夹逼) | 一左→右,一右→左 | 有序数组或两端逼近求极值 | 两数之和II、三数之和、盛水容器 |
| 快慢指针 | 速度不同,同一序列 | 检测环、找中点、找重复数 | 环形链表、寻找重复数 |
| 分离双指针 | 各自在不同序列上 | 比较/归并两个有序数组/字符串 | 判断子序列、合并有序数组 |
注意:滑动窗口只是同向双指针的一种,不要把对向双指针也叫做滑动窗口。
三、各模式模板与例题
1. 对向双指针(左右夹逼)
适用:有序数组求两数和/三数和、最大面积、接雨水等。
模板(两数之和 II):
python
left, right = 0, len(arr) - 1
while left < right:
s = arr[left] + arr[right]
if s == target:
return [left+1, right+1]
elif s < target:
left += 1
else:
right -= 1
模板(三数之和):
python
arr.sort()
res = []
for i in range(n-2):
if i > 0 and arr[i] == arr[i-1]: continue
left, right = i+1, n-1
while left < right:
s = arr[i] + arr[left] + arr[right]
if s == 0:
res.append([arr[i], arr[left], arr[right]])
while left < right and arr[left] == arr[left+1]: left += 1
while left < right and arr[right] == arr[right-1]: right -= 1
left += 1; right -= 1
elif s < 0:
left += 1
else:
right -= 1
典型题目:
-
167.两数之和II(有序数组)
-
15.三数之和
-
11.盛最多水的容器(移动高度较小的指针)
-
42.接雨水(双指针法:维护左右最大高度)
2. 同向双指针(滑动窗口)
详细见独立笔记,此处只给快速识别:要求连续子串/子数组 + 条件 + 最优值。
模板骨架:
python
left = 0
for right in range(n):
加入nums[right]
while 不满足条件:
移除nums[left]
left += 1
更新答案(此时窗口满足条件)
3. 快慢指针
适用:链表/数组判环、找环入口、找中点、找重复数。
模板(链表判环):
python
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
4. 分离双指针(双序列)
适用:判断子序列、合并两个有序数组。
模板(判断子序列):
python
i = j = 0
while i < len(s) and j < len(t):
if s[i] == t[j]:
i += 1
j += 1
return i == len(s)
四、快速判断表(遇到题目对照)
| 题目关键词 | 双指针类型 |
|---|---|
| 连续子数组/子串 + 条件 + 最长/最短 | 同向(滑动窗口) |
| 有序数组 + 两数和/三数和 | 对向 |
| 最大面积(垂线) | 对向 |
| 接雨水 | 对向 或 单调栈 |
| 判断子序列 | 分离 |
| 链表有环/环入口 | 快慢 |
| 数组原地去重/移除元素 | 同向(快慢变种) |
五、常见误区
-
接雨水:对向双指针(不是滑动窗口)
-
盛最多水的容器:对向双指针
-
判断子序列:分离双指针
-
两数之和II:对向双指针
-
三数之和:对向双指针(需先排序)
六、刷题顺序建议
-
对向:167 → 11 → 15 → 42
-
同向(滑动窗口):3 → 209 → 76
-
分离:392 → 88
-
快慢:141 → 142 → 287
167.两数之和2
要点:双指针
class Solution {
public int[] twoSum(int[] numbers, int target) {
//有序 - 找两个数
int left = 0;
int right = numbers.length -1;
while(left < right){
int sum = numbers[left] + numbers[right];
if(sum == target){
break;
}else if(sum < target){
left++;
}else{
right--;
}
}
return new int[]{left+1, right+1};
}
}
三数之和
要点:双指针+去重,i里面直接continue跳过
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
//a + b+ c = 0
List<List<Integer>> ans = new ArrayList<>();
Arrays.sort(nums);
int n = nums.length;
for(int i = 0; i < n-2; i++){
if(i != 0 && nums[i-1] ==nums[i]){
//不能修改i
continue;
}
int a = nums[i];
int left = i+1;
int right = n -1;
while(left < right){
int sum = a + nums[left] +nums[right];
if(sum == 0){
ans.add(new ArrayList<>(Arrays.asList(a,nums[left], nums[right])));
//ans.add(new ArrayList<>(Arrays.asList(a, left, right)));
left++;
right--;
while(left < right && nums[left] == nums[left -1]){
left++;
}
while(left < right && nums[right] == nums[right+1]){
right--;
}
}else if(sum < 0){
left++;
}else{
right--;
}
}
}
return ans;
}
}
滑动窗口
维护一个窗口,
left。right
滑动窗口(Sliding Window)总结
一、什么时候用滑动窗口?
触发条件(同时满足):
-
处理线性结构:数组、字符串
-
要求连续子序列/子数组/子串
-
求最优值:最长、最短、恰好、包含、覆盖、最大和等
快速判断口诀:连续区间问最优,条件变化可滑走
二、怎么用?三步固定框架
python
left = 0
for right in range(n):
# 1. 移入右指针元素,更新窗口状态
add(nums[right])
# 2. 当窗口不满足条件时,移动左指针直到重新满足
while 窗口不满足题目条件:
remove(nums[left])
left += 1
# 3. 此时窗口满足条件,更新答案(最长/最短/计数等)
更新答案
关键定义:
-
窗口满足条件:如"窗口内无重复字符"、"窗口内元素和 ≥ target"
-
窗口不满足条件:需要移出左边元素直到再次满足
三、两大常见分支速查表
| 类型 | 典型题目 | 窗口调整核心 |
|---|---|---|
| 固定窗口大小 | 大小为k的子数组最大和 | 先构建k窗口,之后每次右移一步,左边同步移出 |
| 可变窗口(求最长) | 无重复字符的最长子串 | 遇到违反条件 → 缩小左边界直到满足 → 更新最长 |
| 可变窗口(求最短) | 和≥target的最短子数组 | 满足条件时 → 尝试缩小左边界找更短 → 更新最短 |
| 计数类(含模式) | 最小覆盖子串 | 用need/have计数器,满足全部种类后收缩左边界 |
记忆提示:
求最长:右移后更新
求最短:左移后(收缩时)更新
四、经典例题演练(无重复字符的最长子串)
输入 :s = "abcabcbb" 输出:3
过程模拟(理解核心):
-
[a]→ 无重复 → 最长=1 -
[ab]→ 无重复 → 最长=2 -
[abc]→ 无重复 → 最长=3 -
加
a→ 出现重复 → 左移直到无重复 →[bca]→ 最长仍为3 -
继续扫描,最终最长=3
时间复杂度:O(n)(每个元素最多进出窗口各一次)
五、从"不会"到"会"的训练方法
遇到新题问自己:
-
是连续子串/子数组吗? 不是 → 换方法(DP、二分等)
-
答案与长度/和/覆盖有关吗? 是 → 滑动窗口候选
-
窗口变化有明确条件吗? 有 → 滑动窗口
刻意练习步骤:
-
先写框架(while结构),不纠结细节
-
口述:为什么用滑动窗口?什么时候右移?什么时候左移?
-
改题目条件,推理窗口变化(如"最长无重复" → "最多重复一次")
六、推荐必做题目(按难度顺序)
-
LeetCode 3:无重复字符的最长子串
-
LeetCode 209:长度最小的子数组
-
LeetCode 76:最小覆盖子串(较难,但完全符合框架)
碎碎念:后续会更新每天学习的八股和算法 题,开始准备秋招的第17天。努力连续更新100天!以后每天就按,秋招项目【java+agent】,科研,必做项目,算法,八股,锻炼身体来总结。
总结:不要放弃呀,
1.算法要系统过一遍【灵神】3/27【早上加晚上】
2.秋招项目,【java】开始实际看业务,2.5/10;【agent】还在学,决定把helloagent看一遍,3/16
3.科研要跑一下,无
4.检测项目也得总结文档,无,
5.训练项目看看先选择好,4小时,但是还没搞清楚+测试视频1小时【下午】
6.背八股,无
7.锻炼身体,无
不要再熬夜了!!
【保持心态,持续努力】