LeetCode 每日一题笔记
0. 前言
- 日期:2026.05.24
- 题目:1340. 跳跃游戏 V
- 难度:困难
- 标签:数组、动态规划、记忆化搜索、单调栈
1. 题目理解
问题描述 :
给定一个整数数组 arr 和整数 d,从下标 i 出发,每次可以向左或向右跳 1~d 步。跳跃规则为:只能跳到比当前位置更低的位置,且路径中不能出现比当前位置更高的元素。你可以从任意下标开始,返回最多可以访问的下标数量。
示例:
输入:
arr = [6,4,14,6,8,13,9,7,10,6,12], d = 2输出:
4解释:以
arr[10]=12为起点,路径为12→10→9→7,共访问 4 个下标。
2. 解题思路
核心观察
- 从
i能跳到的位置,只能是i左右两侧[i-d, i+d]范围内、且路径中无更高元素的更低位置。 - 这是一个典型的"树形依赖"问题:每个位置的最长跳跃路径,依赖于它能跳到的位置的最长路径,因此可以用记忆化搜索(递归+DP)解决。
- 为了优化时间复杂度,可先用单调栈预处理每个位置左右最近的更高元素,直接找到能跳的终点,避免遍历中间所有元素。
算法步骤
基础版(记忆化搜索):
- 定义
dp[i]表示从i出发能访问的最大下标数。 - 递归遍历每个位置
i,分别向左、向右检查1~d步的所有位置,遇到更高元素则停止。 - 用
dp[i] = max(dp[j]) + 1转移,其中j是i能跳到的位置。 - 用
dp数组缓存结果,避免重复计算。
优化版(单调栈+记忆化):
- 用单调栈预处理每个位置
i左右两侧、距离不超过d的最近更高元素left[i]和right[i]。 - 从
i只能跳到left[i]和right[i]之间的更低位置,因此递归时直接跳转,无需遍历中间元素。 - 用
memo[i]缓存结果,时间复杂度从 O(n·d) 优化到 O(n)。
3. 代码实现
java
package lc1340;
import java.util.Arrays;
public class Solution {
int jump(int[] arr, int[] dp, int i, int d) {
if (dp[i] != -1) return dp[i];
int res = 1;
int right = Math.min(arr.length - 1, i + d);
int left = Math.max(0, i - d);
// 向左跳
for (int j = i - 1; j >= left; j--) {
if (arr[j] >= arr[i]) break;
res = Math.max(res, jump(arr, dp, j, d) + 1);
}
// 向右跳
for (int j = i + 1; j <= right; j++) {
if (arr[j] >= arr[i]) break;
res = Math.max(res, jump(arr, dp, j, d) + 1);
}
dp[i] = res;
return res;
}
public int maxJumps(int[] arr, int d) {
int dp[] = new int[arr.length];
Arrays.fill(dp, -1);
int res = 1;
for (int i = 0; i < arr.length; i++) {
res = Math.max(res, jump(arr, dp, i, d));
}
return res;
}
}
4. 代码优化说明
java
class Solution {
public int maxJumps(int[] arr, int d) {
int n = arr.length;
// 计算 arr[i] 左边最近的更高元素 arr[left[i]]
int[] left = new int[n];
int[] st = new int[n];
int top = -1; // 栈顶下标
for (int i = 0; i < n; i++) {
int x = arr[i];
while (top >= 0 && arr[st[top]] <= x) {
top--; // 出栈
}
// 如果左边没有更高的数,或者跳跃距离超过 d,都标记为 -1
left[i] = top < 0 || i - st[top] > d ? -1 : st[top];
st[++top] = i; // 入栈
}
// 计算 arr[i] 右边最近的更高元素 arr[right[i]]
int[] right = new int[n];
top = -1;
for (int i = n - 1; i >= 0; i--) {
int x = arr[i];
while (top >= 0 && arr[st[top]] <= x) {
top--;
}
// 如果右边没有更高的数,或者跳跃距离超过 d,都标记为 -1
right[i] = top < 0 || st[top] - i > d ? -1 : st[top];
st[++top] = i;
}
int[] memo = new int[n];
// 枚举终点,倒着跳
int ans = 0;
for (int i = 0; i < n; i++) {
ans = Math.max(ans, dfs(i, left, right, memo));
}
return ans;
}
private int dfs(int i, int[] left, int[] right, int[] memo) {
if (memo[i] == 0) { // 没有计算过
// 往左跳 vs 往右跳
int l = left[i] == -1 ? 0 : dfs(left[i], left, right, memo);
int r = right[i] == -1 ? 0 : dfs(right[i], left, right, memo);
memo[i] = Math.max(l, r) + 1;
}
return memo[i];
}
}
优化说明
- 用单调栈预处理
left和right数组,避免递归中遍历1~d步的所有元素,直接找到能跳的终点。 - 简化
dfs中的if分支判断,用三元运算符处理left[i]和right[i]为-1的情况。 - 时间复杂度从 O(n·d) 优化到 O(n),空间复杂度为 O(n),避免了大规模用例的超时问题。
5. 复杂度分析
- 基础版 :
- 时间复杂度:O(n·d),每个位置最多遍历
d步,且每个位置仅计算一次。 - 空间复杂度:O(n),递归栈深度和
dp数组均为 O(n)。
- 时间复杂度:O(n·d),每个位置最多遍历
- 优化版 :
- 时间复杂度:O(n),单调栈预处理和记忆化搜索均为线性时间。
- 空间复杂度:O(n),
left、right、memo数组和单调栈均为 O(n)。
6. 总结
- 本题核心是利用记忆化搜索 解决树形依赖问题,基础版实现简单但易超时,优化版通过单调栈预处理大幅降低时间复杂度。
- 解题关键在于理解"路径中无更高元素"的规则,通过预处理找到能跳的终点,避免不必要的遍历。
- 对于大规模用例,优先使用优化版;基础版适合快速实现和理解问题。