初始化:使用两个指针slow和fast,都指向链表的头节点。 寻找环:让slow每次移动一步,fas...
बनाया गया: 28 जनवरी 2026
बनाया गया: 28 जनवरी 2026
初始化:使用两个指针slow和fast,都指向链表的头节点。
寻找环:让slow每次移动一步,fast每次移动两步。如果链表中存在环,那么这两个指针最终会在环内相遇。
判断无环:如果fast或fast.next为null,说明链表没有环,返回null。
确定环的起始节点:相遇后,将slow重新指向链表头,然后slow和fast每次都移动一步,再次相遇的节点就是环的起始节点。
返回结果:返回相遇节点即为环的起始节点。 第四点为什么
好问题,这一步正是 Floyd 判圈算法里最“神奇”的地方 👍
我用直观推导 + 图形化理解给你解释清楚「第四点为什么成立」。
假设:
abc示意一下(→ 表示走的方向):
head --(a)--> 环入口 --(b)--> 相遇点 --(c-b)--> 环入口
走过的距离 = a + b
走过的距离 = 2(a + b)
fast 比 slow 多走了一些整圈,因为它在环里绕圈了:
2(a + b) = a + b + n * c
👉 n 是 fast 在环里多跑的圈数(n ≥ 1)
化简一下:
a + b = n * c
再整理:
a = n * c - b
a = (n-1)*c + (c - b)
解释成一句人话:
从“相遇点”继续往前走
c - b步,再绕(n-1)圈,就正好回到环入口
而 c - b 是什么?
👉 相遇点到环入口的距离
相遇后:
- slow 放回头节点
- fast 留在相遇点
- 两个指针都每次走 1 步
a 步a 步🎯 所以它们一定会在环入口再次相遇
因为在第一次相遇时,slow 到环入口的距离
刚好等于 fast 从相遇点沿环走到入口的距离,
所以两指针同步前进时,必然在环入口相遇。
第一次相遇在环内
第二次相遇在入口
如果你愿意,我也可以:
本地缓存和redis不都是存在内存里吗,他们有啥区别
对,这俩都在内存里,但差别非常大。
一句话先给你个直觉:
本地缓存 = 你兜里的小本子
Redis = 全公司共用的公告板
下面拆开讲,保证不绕。
| 对比点 | 本地缓存 | Redis |
|---|---|---|
| 存在哪里 | 应用进程内存 | 独立服务器内存 |
| 是否共享 | ❌ 不共享 | ✅ 多机器共享 |
| 进程重启 | ❌ 数据全没 | ⚠️ 可持久化 |
| 访问速度 | 🚀 极快 | ⚡ 快,但有网络 |
| 分布式支持 | ❌ 不支持 | ✅ 天生支持 |
| 数据一致性 | ❌ 很难保证 | ✅ 统一数据源 |
| 内存容量 | 小 | 大(可集群) |
javaMap<Long, User> cache = new ConcurrentHashMap<>();
或者:
“我自己用,谁也别动”
❌ 多实例数据不一致
举个真实场景:
text你有 3 台服务器 A、B、C
👉 分布式系统里非常危险
text服务 A ─┐ 服务 B ─┼──> Redis 服务 C ─┘
不仅是 Map:
👉 能干的事非常多:
服务器重启,数据不一定丢
Redis 已经很快了(微秒级),但:
text本地缓存 ≈ 50ns Redis ≈ 0.2~1ms(网络)
👉 差一个数量级
所以真实项目里经常这样玩 👇
text请求 ↓ 本地缓存(L1) ↓ miss Redis(L2) ↓ miss 数据库
不是“是不是内存”,而是“谁能看到这块内存”
如果你愿意,我可以再给你讲:
你更想听哪一个?
class Solution {
static int count;
textpublic int numOfSubarrays(int[] arr, int k, int threshold) { dfs(arr, k, threshold, 0, 1, 0); return count; } public void dfs(int[] arr, int k, int threshold, int sum, int num, int start) { if (num == k) { if (sum >= threshold * k) { count += 1; return; } } for (int i = start; i < arr.length; i++) { sum += arr[i]; num +=1; dfs(arr, k, threshold, sum, num, start + 1); sum -= arr[i]; num -=1; } }
} 这个回溯算法有什么问题
这段回溯/dfs有好几个致命问题,导致:结果不对、效率极差、甚至多测例会串结果。
count 用 static 且不重置:多次调用会累加串数据count 是 static,你在 numOfSubarrays 里没把它置 0。
所以同一个 JVM 里多次调用 numOfSubarrays(比如 LeetCode 多个用例、或你本地多次跑)会把上次的结果继续加上去。
✅ 修:在方法开头 count = 0;
start 传错了:你一直传 start + 1,而不是 i + 1这里:
javadfs(arr, k, threshold, sum, num, start + 1);
应该是沿着你当前选择的下标往后走,所以要用 i + 1。
否则会出现:
i,但下一层却从 start+1 开始,跟 i 没关系✅ 应该改成:
javadfs(arr, k, threshold, sum, num, i + 1);
numOfSubarrays(LeetCode 1343)要求的是:
统计 所有长度为 k 的连续子数组,平均值 ≥ threshold 的个数
但你的 DFS 在做的是:从数组里选 k 个数(还不是严格正确的选法),属于组合问题,不是连续子数组。
比如 arr=[1,2,3,4], k=2
连续子数组只有:[1,2],[2,3],[3,4]
而组合会算:[1,3],[1,4],[2,4] 这些都不该出现。
所以即使你把第2点修了,题意也不匹配。
num == k 后不“停止继续搜索”你这里:
javaif (num == k) { if (sum >= threshold * k) { count += 1; return; } }
如果 num == k 但 sum 不够,你没有 return,会继续进入 for 往下选,变成选 k+1, k+2... 个元素,完全跑偏。
✅ 正确写法至少应是:
javaif (num == k) { if (sum >= threshold * k) count++; return; }
dfs(..., 0, 1, 0) 从一开始就“选了1个数”num 表示已选元素数量的话,你一开始就传 1,但 sum 却是 0,语义对不上。
通常应从 num=0 开始。
连续子数组长度固定为 k,用滑动窗口一次遍历就行;回溯会超时。
(给你一份直接可用的 Java)
javaclass Solution { public int numOfSubarrays(int[] arr, int k, int threshold) { int target = threshold * k; int sum = 0, count = 0; for (int i = 0; i < arr.length; i++) { sum += arr[i]; if (i >= k) sum -= arr[i - k]; if (i >= k - 1 && sum >= target) count++; } return count; } }
如果你想“就回溯代码本身”改到不崩(虽然不适合本题),我也可以按组合枚举的正确写法帮你修一版;但这题面试/刷题标准解就是滑窗。
假如就是组合枚举,把这个回溯改成正确的
行,那就把它当成“从数组里选 k 个元素的组合,统计满足 sum >= threshold*k 的组合数”。
你这版回溯主要需要改 4 件事:
count 不能用 static(或至少每次调用重置)num==k 必须直接 return(不然会继续选成 k+1…)start 必须用 i+1(不是 start+1)num 应该从 0 开始下面给你一份正确的组合枚举回溯(还加了一个剪枝:剩余元素不够就不走了):
javaclass Solution { private int count; public int numOfSubarrays(int[] arr, int k, int threshold) { count = 0; // 每次调用重置 int target = threshold * k; dfs(arr, k, target, 0, 0, 0); return count; } // start: 下一次可选的起始下标 // chosen: 已选元素个数 // sum: 已选元素和 private void dfs(int[] arr, int k, int target, int start, int chosen, int sum) { // 选够 k 个,统计并返回 if (chosen == k) { if (sum >= target) count++; return; } // 剪枝:剩余元素不够选满 k 个,直接返回 if (arr.length - start < k - chosen) return; for (int i = start; i < arr.length; i++) { dfs(arr, k, target, i + 1, chosen + 1, sum + arr[i]); // 注意 i+1 } } }
dfs(... start + 1) ➜ 必须是 dfs(... i + 1)num==k 且不满足条件时没 return ➜ 无论满足与否都 returnnum=1 ➜ 改为 0static count ➜ 改成成员变量,并在入口重置如果你还想要“组合枚举但要求必须连续”的版本,那就不是组合了,会退化回滑动窗口/固定长度子数组那套。
为什么不需要,“选择,执行dfs,然后撤销”
这个问题问得非常到位,已经到「真正理解回溯」这一步了 👍
结论先给你:
因为你现在的写法是“参数传值 + 不共享状态”,所以不需要显式撤销。
下面我一步一步拆给你看。
你脑子里的应该是这种 👇
javapath.add(arr[i]); // 选择 dfs(...); path.removeLast(); // 撤销
这种必须撤销,是因为:
👉 path 是一个 共享的可变对象(List / 数组)
如果不撤销:
你现在的 dfs 是这样的:
javadfs(arr, k, target, i + 1, chosen + 1, sum + arr[i]);
注意这三点 👀:
sum 是 intchosen 是 intint 是基本类型,Java 是值传递
也就是说:
javasum + arr[i]
会产生一个新的值,只属于这一层递归。
假设数组 [1,2,3],k=2
递归过程像这样:
textdfs(start=0, chosen=0, sum=0) ├─ dfs(start=1, chosen=1, sum=1) │ ├─ dfs(start=2, chosen=2, sum=3) │ └─ dfs(start=3, chosen=2, sum=4) └─ dfs(start=2, chosen=1, sum=2) └─ dfs(start=3, chosen=2, sum=5)
注意:
dfs(...)sum、chosen 不会被子调用修改当子函数返回时:
javadfs(...); // 结束
父函数里的 sum、chosen 自动恢复
👉 因为它们从来没被改过
你原来写的是:
javasum += arr[i]; num += 1; dfs(...); sum -= arr[i]; num -= 1;
这是另一种写法,对应的是:
👉 在当前栈帧里修改变量
所以必须手动撤销。
而现在我们把它改成了:
javadfs(..., chosen + 1, sum + arr[i]);
👉 不在当前层改变量 👉 而是把新状态“传下去”
这就是为什么不需要撤销
List / 数组 / StringBuilderjavapath.add(x); dfs(); path.remove(path.size() - 1);
javadfs(sum + x, chosen + 1);
javasum += arr[i]; dfs(); sum -= arr[i];
javadfs(sum + arr[i]);
👉 本质一样 👉 第二种更不容易出 bug
只要递归状态不共享、不在当前层修改,就不需要“撤销”;
撤销是为了解决“共享可变状态”的副作用。
如果你愿意,我可以下一步帮你总结一张
👉「看到代码就知道要不要撤销」的判断清单
👉 或对比 Java / C++ / Python 的差异
你现在这个问题,已经是中高级水平了 👌
class Solution {
public int maxScore(int[] cardPoints, int k) {
int max = Integer.MIN_VALUE;
if (cardPoints.length < k) {
return 0;
}
textint leftSum = 0;//左边和 int rightSum = 0;//右边和 for (int i = 0; i < k; i++) { for (int j = 0; j <= i; j++) { leftSum += cardPoints[j]; } for (int j = cardPoints.length - 1; j > cardPoints.length - k + i; j--) { rightSum += cardPoints[j]; } max = Math.max(leftSum + rightSum, max); leftSum = 0; rightSum = 0; } return max; }
} 哪里有问题
主要问题是:你枚举“左边取 i 张、右边取 k-i 张”的边界写错了,再加上实现是 O(k²),k 大时会超时。
你右边这段:
javafor (int j = cardPoints.length - 1; j > cardPoints.length - k + i; j--) { rightSum += cardPoints[j]; }
你想在第 i 次时取 右边 k-i 张。
但你用了 j > (n - k + i),这会导致:
j > n-1,一次都进不去,导致右边取 0 张。举个最小反例:
cardPoints = [1,2,3], k=1j=2, 条件 j > 3-1+0 =2 ⇒ 2>2 false ⇒ 右边和=0✅ 修正方式:把 > 改成 >=(这样边界才包含那一张)
javafor (int j = cardPoints.length - 1; j >= cardPoints.length - k + i; j--) { rightSum += cardPoints[j]; }
你外层是 for (int i = 0; i < k; i++),这里 i 表示“左边取 i+1 张”(因为你左边内层 j<=i)。
因此你永远不会考虑:
即使你把右边边界修好了,也仍然少一种组合。
✅ 正确枚举应该是:左边取 0..k 张(共 k+1 种)
你每次 i 都重新把左边、右边用内层循环算一遍:
LeetCode 这题(1423)k 可能很大,O(k²) 通常会 TLE。
先算“右边取 k 张”的和,然后每次从右边少取一张、左边多取一张:
javaclass Solution { public int maxScore(int[] cardPoints, int k) { int n = cardPoints.length; if (k > n) return 0; int rightSum = 0; for (int i = n - k; i < n; i++) rightSum += cardPoints[i]; int max = rightSum; int leftSum = 0; for (int i = 0; i < k; i++) { leftSum += cardPoints[i]; // 左边多拿一张 rightSum -= cardPoints[n - k + i]; // 右边少拿一张 max = Math.max(max, leftSum + rightSum); } return max; } }
> 少算了一张如果你想,我也可以在你原结构上“最小改动”修成正确版(但仍然慢),或者帮你把边界推导画成图,保证你以后不再踩这个 off-by-one。
class Solution {
public int maxScore(int[] cardPoints, int k) {
int max = Integer.MIN_VALUE;
if (cardPoints.length < k) {
return 0;
}
textint leftSum = 0;//左边和 int rightSum = 0;//右边和 for (int i = 0; i < k; i++) { for (int j = 0; j < i; j++) { leftSum += cardPoints[j]; } for (int j = cardPoints.length-1; j>=cardPoints.length-k+i; j--) { rightSum += cardPoints[j]; } max = Math.max(Math.max(leftSum, rightSum), max); leftSum = 0; rightSum = 0; } return max; }
}
这版依然有几个明显问题(其中有的是逻辑错,不是边界小错)。
题目是“从两端一共取 k 张”,所以每一种选择都应该比较:
但你写成了:
javamax = Math.max(Math.max(leftSum, rightSum), max);
这只是在比较“只取左边”或“只取右边”的最大值,完全不对。
✅ 应该是:
javamax = Math.max(max, leftSum + rightSum);
你左边循环:
javafor (int j = 0; j < i; j++) leftSum += cardPoints[j];
这表示左边取 i 张(当 i=0 时取0张)。
而右边循环表示右边取 k - i 张(这是对的)。
所以枚举 i=0..k 才能覆盖所有情况,但你外层是:
javafor (int i = 0; i < k; i++)
这只覆盖 i=0..k-1,缺少 i=k(左边取 k 张,右边取 0 张) 这种情况。
✅ 外层应为:
javafor (int i = 0; i <= k; i++)
你每次 i 都从头把 leftSum / rightSum 重算一遍,复杂度是 O(k²)。k 大会 TLE。
如果你就想按你这个结构改到正确:
javaclass Solution { public int maxScore(int[] cardPoints, int k) { int n = cardPoints.length; if (n < k) return 0; int max = Integer.MIN_VALUE; for (int i = 0; i <= k; i++) { // i: 左边取 i 张 int leftSum = 0, rightSum = 0; for (int j = 0; j < i; j++) leftSum += cardPoints[j]; for (int j = n - 1; j >= n - (k - i); j--) rightSum += cardPoints[j]; max = Math.max(max, leftSum + rightSum); // 必须相加 } return max; } }
如果你想 AC + 性能好(推荐用这个)我再贴一遍 O(k) 的写法:
javaclass Solution { public int maxScore(int[] cardPoints, int k) { int n = cardPoints.length; int rightSum = 0; for (int i = n - k; i < n; i++) rightSum += cardPoints[i]; int max = rightSum, leftSum = 0; for (int i = 0; i < k; i++) { leftSum += cardPoints[i]; rightSum -= cardPoints[n - k + i]; max = Math.max(max, leftSum + rightSum); } return max; } }
如果你把你理解的 “i 的含义”(左取 i 张还是左取 i+1 张)说一下,我也可以按你想的枚举方式帮你把边界公式写成一眼不出错的版本。
class Solution {
public int maxScore(int[] cardPoints, int k) {
int max = Integer.MIN_VALUE;
if (cardPoints.length < k) {
return 0;
}
textint leftSum = 0;//左边和 int rightSum = 0;//右边和 for (int i = 0; i < k; i++) { for (int j = 0; j <= i; j++) { leftSum += cardPoints[j]; } for (int j = cardPoints.length - 1; j >= cardPoints.length - k + i; j--) { rightSum += cardPoints[j]; } max = Math.max(leftSum + rightSum, max); leftSum = 0; rightSum = 0; } return max; }
}
这版主要有 两个逻辑问题(边界/枚举含义错),导致会把“取的牌数”算错、并且漏掉一种情况。
你左边:
javafor (int j = 0; j <= i; j++) leftSum += cardPoints[j];
这表示左边取的是 i+1 张。
你右边:
javafor (int j = n - 1; j >= n - k + i; j--) rightSum += cardPoints[j];
这个实际上是在取右边 k - i 张(因为从 n-1 取到 n-k+i 一共 k-i 个)。
所以你每次总共取到的是:
(i+1) + (k-i) = k+1 张 ❌
题目要求总共只能取 k 张。
举个最小例子:k=1
i < k 会漏掉“全从右边取 k 张”的情况(按你当前左边 i+1 的定义)你 i 从 0 到 k-1,对应左边取 1..k 张。 但 左边取 0 张、右边取 k 张 这种情况没覆盖到。
k-(i+1) 张,并且 i 只能到 k-1,但要额外覆盖左取0的情况(比较绕,不推荐)javaclass Solution { public int maxScore(int[] cardPoints, int k) { int n = cardPoints.length; int max = Integer.MIN_VALUE; for (int i = 0; i <= k; i++) { // i: 左边取 i 张,右边取 k-i 张 int leftSum = 0, rightSum = 0; for (int j = 0; j < i; j++) leftSum += cardPoints[j]; for (int j = n - 1; j >= n - (k - i); j--) rightSum += cardPoints[j]; max = Math.max(max, leftSum + rightSum); } return max; } }
这版是“组合枚举两端拿牌”的正确逻辑,但复杂度是 O(k²)。想要高效再用滑动 O(k)(你之前那题标准解)。
如果你想继续沿用你当前写法(j<=i 这种),我也能帮你把右边边界推到完全正确,但写起来更容易再踩 off-by-one。
class Solution {
public int maxScore(int[] cardPoints, int k) {
int n = cardPoints.length;
int rightSum = 0;
for (int i = n - k; i < n; i++) rightSum += cardPoints[i];
textint max = rightSum, leftSum = 0; for (int i = 0; i < k; i++) { leftSum += cardPoints[i]; rightSum -= cardPoints[n - k + i]; max = Math.max(max, leftSum + rightSum); } return max; }
} 这个的算法思路
这段代码的思路其实很“巧”:把“两端取 k 张”转换成“左边取 i 张 + 右边取 k-i 张”,然后用一个滑动方式把所有 i 从 0 到 k 都快速算一遍,复杂度 O(k)。
从两端取 k 张,等价于:
i 张k - i 张i 可以是 0..k所以我们只要把这 k+1 种情况都比较一遍取最大即可。
javaint rightSum = 0; for (int i = n - k; i < n; i++) rightSum += cardPoints[i]; int max = rightSum;
此时:
leftSum = 0rightSum = 最右边 k 张的和循环 i = 0..k-1:
javaleftSum += cardPoints[i]; // 左边多拿一张(第 i 张) rightSum -= cardPoints[n - k + i]; // 右边少拿一张(原右边窗口的最左那张) max = Math.max(max, leftSum + rightSum);
这一步很关键:
leftSum 每次增加一张:从左端逐渐拿更多rightSum 每次减少一张:右端拿的张数相应减少对应关系:
所以它把所有可能都扫了一遍。
n - k + i?一开始右边取的是区间:
[n-k, ..., n-1] 共 k 张当左边取了 i+1 张时,右边应该只取 k-(i+1) 张
也就是把原来右边那 k 张里最左侧的那一张丢掉一张、再丢一张……
丢掉的顺序正好是:
n-k(第一次)n-k+1(第二次)n-k+2(第三次)所以用 cardPoints[n - k + i] 来减。
[1,2,3,4,5,6,1], k=3
[6,1]? 不对,右3张是 [6,1]少了。完整是 [6,1]前面是 ?=5,所以 rightSum=5+6+1=12(i=0)先拿右边 k 张当作基准,然后每次“左边加一张、右边减一张”,维护两个和,就能在 O(k) 内枚举所有两端取牌的情况。
如果你想,我可以把这段代码配一张“窗口移动示意图”画出来,你会更直观地看到 rightSum 是怎么“滑”的。
class Solution {
static int minPath = Integer.MAX_VALUE;
public int minPathSum(int[][] grid) {
dfs(grid, grid[0][0], 0, 0);
return minPath;
}
textvoid dfs(int[][]grid, int sum, int i, int j){ int r = grid.length; int c = grid[0].length; if(i==r-1 && j == c-1){ if(sum<minPath){ minPath = sum; } return; } if(i>r-1 || j>c-1){ return; } dfs(grid,sum+grid[i][j],i+1,j); dfs(grid,sum+grid[i][j],i,j+1); }
} 有什么问题 力扣64
这段 DFS 用来做力扣 64(最小路径和)有几个关键问题,会导致结果偏大/重复加值、越界风险、多用例串值,并且会超时。
你一开始调用:
javadfs(grid, grid[0][0], 0, 0);
sum 已经包含了 grid[0][0]。
但在 dfs 里你又做了:
javadfs(grid, sum + grid[i][j], i+1, j); dfs(grid, sum + grid[i][j], i, j+1);
这会把当前格子 grid[i][j] 再加一次。
举例:起点 (0,0) 的值会被加两次:一次在入口,另一次在从 (0,0) 出发递归时。
✅ 正确做法二选一:
方式 A:入口传 0,递归时加当前格子
javadfs(grid, 0, 0, 0); // dfs 内:sum2 = sum + grid[i][j]
方式 B:入口传 grid[0][0],递归时加“下一格”
javadfs(grid, grid[0][0], 0, 0); // dfs 内往下走时加 grid[i+1][j] / grid[i][j+1]
你在判断越界之前就访问了 grid[i][j](加到 sum 里):
javadfs(grid,sum+grid[i][j],i+1,j);
如果 i 或 j 已经越界,访问 grid[i][j] 会直接崩。
而你现在的越界判断在后面:
javaif(i>r-1 || j>c-1) return;
✅ 必须把越界判断放在任何 grid[i][j] 访问之前,并且用 >= 更直观:
javaif (i >= r || j >= c) return;
minPath 用 static 且不重置,会串用例static int minPath = Integer.MAX_VALUE;
LeetCode 会在同一个 JVM 里跑多个测试,你这个值如果不重置,可能受上一次调用影响。
✅ 在 minPathSum 里重置:
javaminPath = Integer.MAX_VALUE;
或者别用 static,改成成员变量/局部变量。
从 (0,0) 到 (m-1,n-1) 只能右/下,但路径数是组合数,DFS 暴力枚举是 指数级,网格稍大就 TLE。
力扣 64 标准解:DP / 原地 DP,时间 O(mn)。
加上剪枝(当前 sum 已经 ≥ minPath 就不用走了),并修复加值/越界/static 问题:
javaclass Solution { private int minPath; public int minPathSum(int[][] grid) { minPath = Integer.MAX_VALUE; dfs(grid, 0, 0, 0); return minPath; } void dfs(int[][] grid, int sum, int i, int j) { int r = grid.length, c = grid[0].length; if (i >= r || j >= c) return; sum += grid[i][j]; if (sum >= minPath) return; // 剪枝 if (i == r - 1 && j == c - 1) { minPath = Math.min(minPath, sum); return; } dfs(grid, sum, i + 1, j); dfs(grid, sum, i, j + 1); } }
javaclass Solution { public int minPathSum(int[][] grid) { int r = grid.length, c = grid[0].length; for (int i = 0; i < r; i++) { for (int j = 0; j < c; j++) { if (i == 0 && j == 0) continue; int up = i > 0 ? grid[i - 1][j] : Integer.MAX_VALUE; int left = j > 0 ? grid[i][j - 1] : Integer.MAX_VALUE; grid[i][j] += Math.min(up, left); } } return grid[r - 1][c - 1]; } }
你现在更想要:
1)把 DFS 彻底剪枝到能 AC(带记忆化),还是 2)把 DP 思路推一遍(为什么这样转移)?
class Solution {
int minPath = Integer.MAX_VALUE;
textpublic int minPathSum(int[][] grid) { dfs(grid, grid[0][0], 0, 0); return minPath; } void dfs(int[][] grid, int sum, int i, int j) { int r = grid.length; int c = grid[0].length; if (i == r - 1 && j == c - 1) { minPath = Math.min(minPath, sum); return; } if (i > r - 1 || j > c - 1) { return; } sum += grid[i][j]; dfs(grid, sum, i + 1, j); dfs(grid, sum, i, j + 1); }
} 为什么我结果是错的
你这版结果错,核心原因就一个:起点格子被加了两次(而且还会影响所有路径)。
入口你传的是:
javadfs(grid, grid[0][0], 0, 0);
也就是 sum 一开始已经包含了 (0,0) 的值。
但在 dfs 里你又做了:
javasum += grid[i][j];
当第一次进入 dfs(0,0) 时,又把 grid[0][0] 加了一次。
所以每条路径的总和都会 多加一次起点值,导致答案偏大,当然就错了。
你先判断到终点、再判断越界,然后才 sum += grid[i][j]。
虽然你越界判断在加值之前(这一点还好),但你的终点判断用的是传入的 sum,而你当前的 sum 是否已经包含当前格子,取决于你怎么传参,很容易造成“终点到底加没加”的错乱(你这版正好也因为起点重复加导致全局错)。
javaclass Solution { int minPath = Integer.MAX_VALUE; public int minPathSum(int[][] grid) { minPath = Integer.MAX_VALUE; dfs(grid, 0, 0, 0); return minPath; } void dfs(int[][] grid, int sum, int i, int j) { int r = grid.length, c = grid[0].length; if (i >= r || j >= c) return; sum += grid[i][j]; if (i == r - 1 && j == c - 1) { minPath = Math.min(minPath, sum); return; } dfs(grid, sum, i + 1, j); dfs(grid, sum, i, j + 1); } }
javaclass Solution { int minPath = Integer.MAX_VALUE; public int minPathSum(int[][] grid) { minPath = Integer.MAX_VALUE; dfs(grid, grid[0][0], 0, 0); return minPath; } void dfs(int[][] grid, int sum, int i, int j) { int r = grid.length, c = grid[0].length; if (i == r - 1 && j == c - 1) { minPath = Math.min(minPath, sum); return; } if (i + 1 < r) dfs(grid, sum + grid[i + 1][j], i + 1, j); if (j + 1 < c) dfs(grid, sum + grid[i][j + 1], i, j + 1); } }
顺带一句:就算修对了,纯 DFS 还是很容易 TLE(指数级),力扣 64 标准做法是 DP / 记忆化。
如果你把你跑错的具体用例和输出贴出来,我还能帮你对照看“多加了哪一格”。
class Solution {
int minPath = Integer.MAX_VALUE;
textpublic int minPathSum(int[][] grid) { dfs(grid, 0, 0, 0); return minPath; } void dfs(int[][] grid, int sum, int i, int j) { int r = grid.length; int c = grid[0].length; if (i == r - 1 && j == c - 1) { minPath = Math.min(minPath, sum); return; } if (i > r - 1 || j > c - 1) { return; } sum += grid[i][j]; dfs(grid, sum, i + 1, j); dfs(grid, sum, i, j + 1); }
} 为什么还是错的
还是错的原因:你到达终点时,终点那一格没有被加进 sum(并且越界判断位置也不对)。
你现在的顺序是:
(i,j) 是不是终点sum += grid[i][j]所以当递归走到终点 (r-1,c-1) 时,sum 里面只包含了终点之前的路径和,没有包含 grid[r-1][c-1],自然就偏小/错误。
grid = [[5]]
调用 dfs(0,0,0,sum=0)
你先判断终点成立 → minPath = min(∞, 0) → 返回
但正确答案应该是 5。
javaclass Solution { int minPath = Integer.MAX_VALUE; public int minPathSum(int[][] grid) { minPath = Integer.MAX_VALUE; dfs(grid, 0, 0, 0); return minPath; } void dfs(int[][] grid, int sum, int i, int j) { int r = grid.length, c = grid[0].length; if (i >= r || j >= c) return; // 先判越界 sum += grid[i][j]; // 先把当前格子加进来 if (i == r - 1 && j == c - 1) { // 再判终点 minPath = Math.min(minPath, sum); return; } dfs(grid, sum, i + 1, j); dfs(grid, sum, i, j + 1); } }
注意我把越界条件改成
>=,更标准也更不容易写错。
另外提醒:即使这样改对了,纯 DFS 在大 grid 上会 TLE(路径数指数级)。要想稳 AC,需要 DP 或 DFS+记忆化。