import java.util.*;
public class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> window = new HashMap<>();
int left = 0;
int answer = 0;
for (int right = 0; right < s.length(); right++) {
char c = s.charAt(right);
window.merge(c, 1, Integer::sum); // 右侧字符进窗口
while (window.get(c) > 1) { // 窗口不合法:有重复
char d = s.charAt(left);
window.merge(d, -1, Integer::sum);
window.remove(d, 0);
left++; // 左侧收缩,直到重新合法
}
answer = Math.max(answer, right - left + 1);
}
return answer;
}
}
public int minSubArrayLen(int target, int[] nums) {
int left = 0, sum = 0, answer = Integer.MAX_VALUE;
for (int right = 0; right < nums.length; right++) {
sum += nums[right];
while (sum >= target) { // 条件换成「和达标」
answer = Math.min(answer, right - left + 1);
sum -= nums[left++];
}
}
return answer == Integer.MAX_VALUE ? 0 : answer;
}
int left = 0;
Map<Character, Integer> win = new HashMap<>();
for (int right = 0; right < s.length(); right++) {
win.merge(s.charAt(right), 1, Integer::sum);
while (/* 窗口不合法 */) {
win.merge(s.charAt(left), -1, Integer::sum);
win.remove(s.charAt(left), 0);
left++;
}
// 在此更新答案
}
2. 双指针 Two Pointers
识别特征:有序(或排序后处理)数组;两数配对、三数之和、去重、原地交换、盛水
代表题:167 两数之和 II、15 三数之和、11 盛最多水
int left = 0, right = nums.length - 1;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == target) { /* 记录答案 */ }
else if (sum < target) left++;
else right--;
}
3. 快慢指针 Fast & Slow Pointers
识别特征:链表判环、环的入口、找中点、倒数第 K 个;或数字不断变换会不会循环(快乐数)
代表题:141 环形链表、142 环的入口、876 链表中点
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) return true; // 相遇说明有环
}
return false;
List<List<Integer>> res = new ArrayList<>();
res.add(new ArrayList<>());
for (int num : nums) { // 每读一个新数
int n = res.size();
for (int i = 0; i < n; i++) { // 复制现有子集并追加
List<Integer> next = new ArrayList<>(res.get(i));
next.add(num);
res.add(next);
}
}
11. 二分查找 Binary Search
识别特征:数组有序;或答案本身单调(最小可行值、最大不可行值,即「答案二分」);旋转数组找分段点
代表题:704 二分查找、33 搜索旋转排序数组、875 爱吃香蕉的珂珂
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // 防止 (l+r) 溢出
if (nums[mid] == target) return mid;
if (nums[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
// 答案二分:while(left < right),可行时 right = mid
12. Top K 元素
识别特征:第 K 大/小、前 K 个高频元素、最接近的 K 个点;数据是流式的无法一次全排序。用大小为 K 的堆,复杂度 O(n log k)
代表题:215 第 K 大元素、347 前 K 高频、973 最接近原点 K 个点
Queue<Integer> heap = new PriorityQueue<>(); // 留最大的 K 个 → 小顶堆
for (int n : nums) {
heap.offer(n);
if (heap.size() > k) heap.poll();
}
13. K 路归并 K-way Merge
识别特征:K 个已经有序的数组或链表要合并;每一步只需从 K 个队首中取最小;小顶堆把比较从 O(K) 降到 O(log K)
代表题:23 合并 K 个升序链表、378 有序矩阵第 K 小、632 最小区间
Queue<ListNode> heap = new PriorityQueue<>(Comparator.comparingInt(a -> a.val));
for (ListNode node : lists) if (node != null) heap.offer(node);
ListNode dummy = new ListNode(), tail = dummy;
while (!heap.isEmpty()) {
ListNode node = heap.poll();
tail.next = node;
tail = node;
if (node.next != null) heap.offer(node.next);
}
14. 动态规划 Dynamic Programming
识别特征:求最值(最大、最小、最长)、计数(多少种走法)、可行性(能不能);当前选择影响后续,且子问题反复出现。先定义 dp[i] 的含义,再写转移方程,最后给 base case
代表题:70 爬楼梯、198 打家劫舍、322 零钱兑换、300 最长递增子序列
int[] dp = new int[n + 1];
dp[0] = 1; // base case
for (int i = 1; i <= n; i++) {
dp[i] = dp[i - 1] /* + 其它前置状态 */;
}
// 只依赖前 1~2 个状态时,用滚动变量把空间降到 O(1)