帮我总结下算法题常用的思路和模板(先写个伪代码,然后用java实现)
생성일: 2026년 1월 21일
생성일: 2026년 1월 21일
帮我总结下算法题常用的思路和模板(先写个伪代码,然后用java实现)
下面给你一份「算法题高频思路 + 常用模板」速查,按套路 → 伪代码 → Java 模板来写。你刷题时基本就是在这些框架里换“状态/转移/判定”。
思路清单:
n, m, 值域 → 决定复杂度目标(O(n), O(n log n), O(n^2))适用: 子数组/子串,要求“最长/最短/计数”,且窗口性质可维护(通常非负、或可用计数表维护)。
textl = 0 for r in [0..n-1]: add(a[r]) while window invalid: remove(a[l]); l++ ans = max(ans, r-l+1)
javaint l = 0, ans = 0; for (int r = 0; r < n; r++) { // add a[r] // update freq / sum / distinct... while (/* invalid */) { // remove a[l] l++; } ans = Math.max(ans, r - l + 1); }
适用: “最大化最小值/最小化最大值/最小满足条件”,答案单调。
textlo, hi = possible range while lo < hi: mid = (lo + hi) // 2 if check(mid) true: hi = mid else: lo = mid + 1 return lo
javalong lo = L, hi = R; while (lo < hi) { long mid = lo + (hi - lo) / 2; if (check(mid)) hi = mid; else lo = mid + 1; } return lo;
若是“找最大可行”,常用
mid = lo + (hi - lo + 1)/2,然后可行则lo = mid否则hi = mid - 1。
适用: 区间和、区间频繁查询;差分适合区间加减更新。
textpre[0]=0 for i in 1..n: pre[i]=pre[i-1]+a[i-1] sum(l,r)=pre[r+1]-pre[l]
javalong[] pre = new long[n + 1]; for (int i = 0; i < n; i++) pre[i + 1] = pre[i] + a[i]; long rangeSum(int l, int r) { // inclusive return pre[r + 1] - pre[l]; }
textdiff[l]+=v diff[r+1]-=v 最后做前缀还原
适用: 下一个更大/更小元素,柱状图最大矩形,子数组最小值之和等。
textstack empty (存下标) for i in 0..n-1: while stack not empty and a[stack.top] < a[i]: j = stack.pop nextGreater[j] = i stack.push(i)
javaint[] next = new int[n]; Arrays.fill(next, -1); Deque<Integer> st = new ArrayDeque<>(); for (int i = 0; i < n; i++) { while (!st.isEmpty() && a[st.peek()] < a[i]) { next[st.pop()] = i; } st.push(i); }
适用: 动态取最小/最大,TopK,合并K路。
textheap empty for x in arr: push x if heap size > K: pop 答案在堆中
javaPriorityQueue<Integer> pq = new PriorityQueue<>(); // min-heap for (int x : arr) { pq.offer(x); if (pq.size() > K) pq.poll(); }
适用: 边权全1,求最短步数;层序遍历。
textqueue push(start), dist[start]=0 while queue not empty: u = pop for v in neighbors(u): if dist[v] unset: dist[v]=dist[u]+1 push(v)
javaArrayDeque<int[]> q = new ArrayDeque<>(); q.offer(new int[]{sx, sy}); dist[sx][sy] = 0; while (!q.isEmpty()) { int[] cur = q.poll(); int x = cur[0], y = cur[1]; for (int[] d : dirs) { int nx = x + d[0], ny = y + d[1]; if (/* in bounds and not visited */) { dist[nx][ny] = dist[x][y] + 1; q.offer(new int[]{nx, ny}); } } }
适用: 枚举方案、排列组合、搜索路径、约束满足。
textdfs(pos, path): if满足终止: record for choice in choices from pos: choose dfs(next, path) undo
javavoid dfs(int idx) { if (/* done */) { res.add(new ArrayList<>(path)); return; } for (int i = idx; i < n; i++) { path.add(a[i]); dfs(i + 1); path.remove(path.size() - 1); } }
核心: 状态定义 + 转移 + 初始化 + 遍历顺序 + 答案位置。
textdp[0]=base for i=1..n-1: dp[i]=min/max over transitions return dp[n-1] or best(dp)
javalong[] dp = new long[n]; Arrays.fill(dp, INF); dp[0] = base; for (int i = 1; i < n; i++) { // dp[i] from dp[i-1], dp[i-2]... }
textdp[0]=0 for each item (w,v): for j from W down to w: dp[j]=max(dp[j], dp[j-w]+v)
javaint[] dp = new int[W + 1]; for (Item it : items) { for (int j = W; j >= it.w; j--) { dp[j] = Math.max(dp[j], dp[j - it.w] + it.v); } }
适用: 动态连通、成环判断、最小生成树 Kruskal。
textinit parent[i]=i, size[i]=1 find(x): path compression union(a,b): by size/rank
javastatic class DSU { int[] p, sz; DSU(int n){ p=new int[n]; sz=new int[n]; for(int i=0;i<n;i++){p[i]=i; sz[i]=1;} } int find(int x){ return p[x]==x? x : (p[x]=find(p[x])); } boolean union(int a,int b){ a=find(a); b=find(b); if(a==b) return false; if(sz[a]<sz[b]){ int t=a; a=b; b=t; } p[b]=a; sz[a]+=sz[b]; return true; } }
适用: 边权非负。
textdist[start]=0 pq push (0,start) while pq not empty: (d,u)=pop if d!=dist[u]: continue for (v,w) in edges[u]: if dist[v] > d+w: dist[v]=d+w push(dist[v], v)
javalong[] dist = new long[n]; Arrays.fill(dist, (long)4e18); dist[s] = 0; PriorityQueue<long[]> pq = new PriorityQueue<>(Comparator.comparingLong(a -> a[0])); pq.offer(new long[]{0, s}); while (!pq.isEmpty()) { long[] cur = pq.poll(); long d = cur[0]; int u = (int)cur[1]; if (d != dist[u]) continue; for (int[] e : g[u]) { // e={v,w} int v = e[0], w = e[1]; if (dist[v] > d + w) { dist[v] = d + w; pq.offer(new long[]{dist[v], v}); } } }
适用: 子串查找 O(n+m)。
textnext[0]=0 for i=1..m-1: j=next[i-1] while j>0 and p[i]!=p[j]: j=next[j-1] if p[i]==p[j]: j++ next[i]=j
javaint[] buildNext(char[] p){ int m=p.length; int[] ne=new int[m]; for(int i=1,j=0;i<m;i++){ while(j>0 && p[i]!=p[j]) j=ne[j-1]; if(p[i]==p[j]) j++; ne[i]=j; } return ne; } int kmp(String s, String pat){ char[] a=s.toCharArray(), p=pat.toCharArray(); int[] ne=buildNext(p); for(int i=0,j=0;i<a.length;i++){ while(j>0 && a[i]!=p[j]) j=ne[j-1]; if(a[i]==p[j]) j++; if(j==p.length) return i - p.length + 1; // first index } return -1; }
javastatic class FastScanner { private final byte[] buffer = new byte[1 << 16]; private int ptr = 0, len = 0; private final InputStream in; FastScanner(InputStream is){ in = is; } private int read() throws IOException { if (ptr >= len) { len = in.read(buffer); ptr = 0; if (len <= 0) return -1; } return buffer[ptr++]; } long nextLong() throws IOException { int c; do { c = read(); } while (c <= ' ' && c != -1); long sign = 1; if (c == '-') { sign = -1; c = read(); } long val = 0; while (c > ' ') { val = val * 10 + (c - '0'); c = read(); } return val * sign; } int nextInt() throws IOException { return (int) nextLong(); } String next() throws IOException { int c; do { c = read(); } while (c <= ' ' && c != -1); StringBuilder sb = new StringBuilder(); while (c > ' ') { sb.append((char)c); c = read(); } return sb.toString(); } }
如果你愿意,我还可以把这些模板整理成一份可直接复制的 Java “刷题母板(Main + 常用工具类 + 典型函数)”,以及按题型给你一个“看到题目关键词就选模板”的快速索引。你平时主要刷 LeetCode 还是牛客/洛谷/Codeforces?(不回答也行,我也可以按通用竞赛风格直接给母板。)
动态规划 DP这个详细点
好,我们把 动态规划 DP 单独、系统、从“怎么想 → 怎么写伪代码 → 怎么落 Java” 讲透。你以后看到 DP 题,基本就按这个流程机械套。
题目同时满足下面 2~3 个特征,80% 是 DP:
最值 / 方案数 / 可行性
有“阶段性决策”
子问题会被重复用到
👉 一句话判断法:
“当前选择只和之前的状态有关,而与更早的细节无关”
90% 的 DP 题,差别只在第 1、2 步
一句话描述一个 dp 状态:
dp[i][j] 表示:在「前 i 个 / 到位置 i / 用了 i 步」的前提下,状态为 j 时的最优值 / 方案数
📌 常见状态含义模板:
| 状态 | 含义 |
|---|---|
| dp[i] | 到第 i 个位置的最优解 |
| dp[i][j] | 前 i 个元素,某种限制为 j |
| dp[i][j][k] | 多加一个维度(次数/状态) |
想最后一步在干嘛
👉 一般形式:
dp[当前状态] = min / max / sum ( dp[之前状态] + 当前代价 )
题意:一次走 1 或 2 步,走到 n 有多少种方式?
dp[i] = 到第 i 阶的方法数
dp[i] = dp[i-1] + dp[i-2]
textdp[0] = 1 dp[1] = 1
javaint[] dp = new int[n + 1]; dp[0] = dp[1] = 1; for (int i = 2; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n];
👉 空间优化
javaint a = 1, b = 1; for (int i = 2; i <= n; i++) { int c = a + b; a = b; b = c; } return b;
dp[i] = 抢到第 i 家为止的最大金额
dp[i] = max(dp[i-1], dp[i-2] + nums[i])
javaint n = nums.length; if (n == 0) return 0; if (n == 1) return nums[0]; int[] dp = new int[n]; dp[0] = nums[0]; dp[1] = Math.max(nums[0], nums[1]); for (int i = 2; i < n; i++) { dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]); } return dp[n - 1];
dp[i][j] = 用前 i 件物品,容量为 j 的最大价值
text不选:dp[i][j] = dp[i-1][j] 选: dp[i][j] = dp[i-1][j-w[i]] + v[i]
textfor i in 1..n: for j in 0..W: dp[i][j] = dp[i-1][j] if j >= w[i]: dp[i][j] = max(dp[i][j], dp[i-1][j-w[i]] + v[i])
javaint[][] dp = new int[n + 1][W + 1]; for (int i = 1; i <= n; i++) { for (int j = 0; j <= W; j++) { dp[i][j] = dp[i - 1][j]; if (j >= w[i - 1]) { dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - w[i - 1]] + v[i - 1]); } } }
j 必须倒序
javaint[] dp = new int[W + 1]; for (int i = 0; i < n; i++) { for (int j = W; j >= w[i]; j--) { dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]); } }
dp[i] = 以 i 结尾的 LIS 长度
dp[i] = max(dp[j] + 1) where j < i and a[j] < a[i]
javaint n = a.length; int[] dp = new int[n]; Arrays.fill(dp, 1); int ans = 1; for (int i = 0; i < n; i++) { for (int j = 0; j < i; j++) { if (a[j] < a[i]) { dp[i] = Math.max(dp[i], dp[j] + 1); } } ans = Math.max(ans, dp[i]); } return ans;
特征:
[l, r] 做决策dp[l][r] = 合并区间 [l,r] 的最小代价
dp[l][r] = min(dp[l][k] + dp[k+1][r] + cost(l,r))
textfor len = 2..n: for l = 0..n-len: r = l+len-1 dp[l][r] = INF for k = l..r-1: dp[l][r] = min(...)
❌ 状态定义不清
👉 一定要能用一句人话解释 dp[i][j]
❌ 转移方向写反
👉 看“当前状态依赖谁”,谁在前就先算谁
❌ 忘了初始化不合法状态
👉 最小值问题用 INF,最大值用 -INF
❌ 背包 j 顺序写错
👉
java// 1. 定义 dp 数组 // dp[i][j] 含义 // 2. 初始化 Arrays.fill(dp, INF); dp[0][0] = base; // 3. 状态转移 for (...) { for (...) { dp[...] = ... } } // 4. 返回答案 return dp[...];
如果你愿意,下一步我可以帮你做 3 件事之一(都很实用):
1️⃣ 把 DP 题型分类成「看到关键词就会选状态」的速查表
2️⃣ 用 一道 LeetCode 中等题,从 0 带你完整推 DP
3️⃣ 给你一套 “DP 模板 + 常见错误对照表”(适合考前速看)
你选哪个?
经典用例加上详细的解释
好,那我直接给你一套 「DP 经典用例大全」,每一道都做到:
题目直觉 → 为什么是 DP → 状态如何定义 → 转移怎么想 → 伪代码 → Java 实现 → 常见坑
你把这几道吃透,80% 的 DP 题都会“眼熟”。
一次可以走 1 或 2 步,走到第 n 阶有多少种方法?
👉 典型线性 DP
dp[i] = 走到第 i 阶的方法数
最后一步:
dp[i] = dp[i-1] + dp[i-2]
textdp[0] = 1 // 什么都不做也是一种方式 dp[1] = 1
⚠️ dp[0] = 1 很关键,否则 dp[2] 算不对
textdp[0]=1, dp[1]=1 for i=2..n: dp[i]=dp[i-1]+dp[i-2] return dp[n]
javaint[] dp = new int[n + 1]; dp[0] = dp[1] = 1; for (int i = 2; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n];
❌ dp[0] 忘了初始化
❌ 用递归导致超时(重复子问题)
一排房子,不能抢相邻的,求最大金额。
dp[i] = 抢到第 i 家为止的最大金额
最后一家(第 i 家):
dp[i] = dp[i-1]
dp[i] = dp[i-2] + nums[i]
取最大:
dp[i] = max(dp[i-1], dp[i-2] + nums[i])
textdp[0] = nums[0] dp[1] = max(nums[0], nums[1])
javaint n = nums.length; if (n == 0) return 0; if (n == 1) return nums[0]; int[] dp = new int[n]; dp[0] = nums[0]; dp[1] = Math.max(nums[0], nums[1]); for (int i = 2; i < n; i++) { dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]); } return dp[n - 1];
选当前 ⇒ 跳过上一个
n 件物品,每件有重量 w 和价值 v,背包容量 W,求最大价值。
dp[i][j] = 用前 i 件物品,容量为 j 的最大价值
第 i 件物品:
dp[i][j] = dp[i-1][j]
dp[i][j] = dp[i-1][j-w[i]] + v[i]
取最大
textfor i=1..n: for j=0..W: dp[i][j]=dp[i-1][j] if j>=w[i]: dp[i][j]=max(dp[i][j], dp[i-1][j-w[i]]+v[i])
javaint[][] dp = new int[n + 1][W + 1]; for (int i = 1; i <= n; i++) { for (int j = 0; j <= W; j++) { dp[i][j] = dp[i - 1][j]; if (j >= w[i - 1]) { dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - w[i - 1]] + v[i - 1]); } } }
javaint[] dp = new int[W + 1]; for (int i = 0; i < n; i++) { for (int j = W; j >= w[i]; j--) { dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]); } }
⚠️ 必须倒序,否则会重复选物品
求最长严格递增子序列长度。
dp[i] = 以 i 结尾的 LIS 长度
枚举前一个位置 j:
textif a[j] < a[i]: dp[i] = max(dp[i], dp[j] + 1)
javaint n = a.length; int[] dp = new int[n]; Arrays.fill(dp, 1); int ans = 1; for (int i = 0; i < n; i++) { for (int j = 0; j < i; j++) { if (a[j] < a[i]) { dp[i] = Math.max(dp[i], dp[j] + 1); } } ans = Math.max(ans, dp[i]); } return ans;
“我做结尾,前面谁能接我?”
dp[i][j] = s1 前 i 个字符 与 s2 前 j 个字符 的 LCS 长度
dp[i][j] = dp[i-1][j-1] + 1
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
javaint n = s1.length(), m = s2.length(); int[][] dp = new int[n + 1][m + 1]; for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s1.charAt(i - 1) == s2.charAt(j - 1)) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); } } } return dp[n][m];
DP = 明确状态 + 想最后一步 + 找依赖 + 控制顺序
1️⃣ 我带你 手推一整道 DP 中等题(含画表)
2️⃣ 给你一张 「DP 题型 → 状态定义」速查表
3️⃣ 专门讲 DP 面试常问 & 易错点合集
你选一个,我继续给你“打通任督二脉”。