当前位置: 首页 > news >正文

LeetCode 1871. 跳跃游戏 VII(中等)

题目描述

给你一个下标从 开始的二进制字符串 s 和两个整数 minJump 和 maxJump 。一开始,你在下标 0 处,且该位置的值一定为 '0' 。当同时满足如下条件时,你可以从下标 i 移动到下标 j 处:

  • i + minJump <= j <= min(i + maxJump, s.length - 1) 且
  • s[j] == '0'.

如果你可以到达 s 的下标 s.length - 1 处,请你返回 true ,否则返回 false 。

示例 1:

输入:s = "011010", minJump = 2, maxJump = 3
输出:true
解释:
第一步,从下标 0 移动到下标 3 。
第二步,从下标 3 移动到下标 5 。

示例 2:

输入:s = "01101110", minJump = 2, maxJump = 3
输出:false

提示:

  • 2 <= s.length <= 10^5
  • s[i] 要么是 '0' ,要么是 '1'
  • s[0] == '0'
  • 1 <= minJump <= maxJump < s.length

思路分析

这道题可以用多种方法解决,我们来分析几种主要的解法:

方法一:动态规划 + 前缀和优化

核心思想:

  • 定义 dp[i] 表示是否能从起点到达位置 i
  • 对于位置 i,如果 s[i] == '0',那么需要检查是否存在某个位置 j,使得:
  • dp[j] == true(位置 j 可达)
  • j + minJump <= i <= j + maxJump(从 j 可以跳到 i)

优化关键:

使用前缀和来快速判断区间内是否存在可达的位置,避免重复遍历。

方法二:BFS(广度优先搜索)

核心思想:

  • 将问题抽象为图的连通性问题
  • 每个值为 '0' 的位置是图中的节点
  • 如果位置 i 可以跳到位置 j,则在它们之间连边
  • 使用 BFS 判断起点和终点是否连通

算法过程

通过示例 s = "011010", minJump = 2, maxJump = 3 来详细解释 动态规划 + 前缀和优化 这个算法。

第一步:理解问题转换

原问题: 从位置 i 能跳到哪些位置?

  • 从位置 i 可以跳到 [i + minJump, i + maxJump] 范围内值为 '0' 的位置

转换后: 哪些位置能跳到位置 j?

  • 能跳到位置 j 的位置范围是 [j - maxJump, j - minJump]
原始字符串: s = "011010"
索引:        0 1 2 3 4 5
minJump = 2, maxJump = 3

第二步:初始化状态

dp[i] 表示是否能到达位置 i
prefixSum[i] 表示 dp[0] 到 dp[i] 中 true 的个数初始状态:
位置:     0  1  2  3  4  5
字符:     0  1  1  0  1  0
dp:      [T, F, F, F, F, F]  (只有起点可达)
前缀和:   [1, 1, 1, 1, 1, 1]  (初始时只有 dp[0] = true)

第三步:逐步计算每个位置

计算位置 1 (i = 1)
s[1] = '1' → 无法到达,跳过
dp[1] = false
prefixSum[1] = prefixSum[0] + 0 = 1
计算位置 2 (i = 2)
s[2] = '1' → 无法到达,跳过
dp[2] = false
prefixSum[2] = prefixSum[1] + 0 = 1
计算位置 3 (i = 3) - 关键步骤
s[3] = '0' → 可能到达,需要检查能跳到位置 3 的范围计算:
left = max(0, 3 - 3) = max(0, 0) = 0
right = max(0, 3 - 2) = max(0, 1) = 1检查区间 [0, 1] 内是否有可达位置:
sum = prefixSum[1] - prefixSum[-1] = 1 - 0 = 1 > 0
所以 dp[3] = trueprefixSum[3] = prefixSum[2] + 1 = 1 + 1 = 2
位置 3 的计算过程:
位置:     0  1  2  3  4  5
字符:     0  1  1  0  1  0
dp:      [T, F, F, ?, F, F]↑     ↑|     |检查范围 [0,1]从位置 0 能跳到位置 3 吗?
0 + minJump = 0 + 2 = 2 ≤ 3 ≤ 0 + 3 = 3 ✓
且 s[3] = '0' ✓
所以位置 3 可达!结果: dp[3] = true
计算位置 4 (i = 4)
s[4] = '1' → 无法到达,跳过
dp[4] = false
prefixSum[4] = prefixSum[3] + 0 = 2
计算位置 5 (i = 5) - 最终目标
s[5] = '0' → 可能到达,需要检查能跳到位置 5 的范围计算:
left = max(0, 5 - 3) = max(0, 2) = 2
right = max(0, 5 - 2) = max(0, 3) = 3检查区间 [2, 3] 内是否有可达位置:
sum = prefixSum[3] - prefixSum[1] = 2 - 1 = 1 > 0
所以 dp[5] = trueprefixSum[5] = prefixSum[4] + 1 = 2 + 1 = 3
位置 5 的计算过程:
位置:     0  1  2  3  4  5
字符:     0  1  1  0  1  0
dp:      [T, F, F, T, F, ?]↑  ↑|  |检查范围 [2,3]从位置 3 能跳到位置 5 吗?
3 + minJump = 3 + 2 = 5 ≤ 5 ≤ 3 + 3 = 6 ✓
且 s[5] = '0' ✓
所以位置 5 可达!结果: dp[5] = true

第四步:完整的状态转移过程

步骤 | 位置 | 字符  | 检查范围  | 前缀和查询  |     dp值      | 前缀和数组
-----|------|------|----------|------------|---------------|------------
初始 |  -   |  -   |    -     |     -      | [T,F,F,F,F,F] | [1,1,1,1,1,1]1   |  1   |  1   |    -     |     -      | [T,F,F,F,F,F] | [1,1,1,1,1,1]2   |  2   |  1   |    -     |     -      | [T,F,F,F,F,F] | [1,1,1,1,1,1]3   |  3   |  0   |  [0,1]   |   1-0=1    | [T,F,F,T,F,F] | [1,1,1,2,2,2]4   |  4   |  1   |    -     |     -      | [T,F,F,T,F,F] | [1,1,1,2,2,2]5   |  5   |  0   |  [2,3]   |   2-1=1    | [T,F,F,T,F,T] | [1,1,1,2,2,3]

第五步:前缀和优化的关键

为什么需要前缀和?

如果不用前缀和,我们需要这样检查:

// 朴素方法 - O(n²) 时间复杂度
boolean canReach = false;
for (int j = left; j <= right; j++) {if (dp[j]) {canReach = true;break;}
}

使用前缀和优化后:

// 优化方法 - O(1) 时间复杂度
int sum = prefixSum[right] - (left > 0 ? prefixSum[left-1] : 0);
boolean canReach = sum > 0;

Java 解法

解法一:动态规划 + 前缀和

public class Solution {public boolean canReach(String s, int minJump, int maxJump) {int n = s.length();// 如果终点是 '1',直接返回 falseif (s.charAt(n - 1) == '1') {return false;}// dp[i] 表示是否能到达位置 iboolean[] dp = new boolean[n];dp[0] = true;// 前缀和数组,prefixSum[i] 表示 dp[0] 到 dp[i] 中 true 的个数int[] prefixSum = new int[n];prefixSum[0] = 1; // dp[0] = true,所以前缀和为 1for (int i = 1; i < n; i++) {// 只有当前位置是 '0' 时才可能到达if (s.charAt(i) == '0') {// 计算能跳到位置 i 的范围 [left, right]int left = Math.max(0, i - maxJump);int right = i - minJump;// 确保范围有效if (right >= 0 && left <= right) {// 使用前缀和快速查询范围内可达位置的数量int count = prefixSum[right] - (left > 0 ? prefixSum[left - 1] : 0);dp[i] = count > 0;}}// 更新前缀和prefixSum[i] = prefixSum[i - 1] + (dp[i] ? 1 : 0);}return dp[n - 1];}
}

解法二:BFS 

import java.util.*;public class Solution {public boolean canReach(String s, int minJump, int maxJump) {int n = s.length();// 如果终点是 '1',直接返回 falseif (s.charAt(n - 1) == '1') {return false;}Queue<Integer> queue = new LinkedList<>();boolean[] visited = new boolean[n];queue.offer(0);visited[0] = true;// 记录上次检查的最远位置,避免重复检查int farthest = 0;while (!queue.isEmpty()) {int curr = queue.poll();// 如果到达终点,返回 trueif (curr == n - 1) {return true;}// 计算可跳跃的范围int start = Math.max(farthest + 1, curr + minJump);int end = Math.min(n - 1, curr + maxJump);// 遍历可跳跃的位置for (int next = start; next <= end; next++) {if (s.charAt(next) == '0' && !visited[next]) {visited[next] = true;queue.offer(next);}}// 更新最远检查位置farthest = Math.max(farthest, end);}return false;}
}

C# 解法

解法一:动态规划 + 前缀和

public class Solution {public bool CanReach(string s, int minJump, int maxJump) {int n = s.Length;// 如果终点是 '1',直接返回 falseif (s[n - 1] == '1') {return false;}// dp[i] 表示是否能到达位置 ibool[] dp = new bool[n];dp[0] = true;// 前缀和数组,prefixSum[i] 表示 dp[0] 到 dp[i] 中 true 的个数int[] prefixSum = new int[n];prefixSum[0] = 1; // dp[0] = true,所以前缀和为 1for (int i = 1; i < n; i++) {// 只有当前位置是 '0' 时才可能到达if (s[i] == '0') {// 计算能跳到位置 i 的范围 [left, right]int left = Math.Max(0, i - maxJump);int right = i - minJump;// 确保范围有效if (right >= 0 && left <= right) {// 使用前缀和快速查询范围内可达位置的数量int count = prefixSum[right] - (left > 0 ? prefixSum[left - 1] : 0);dp[i] = count > 0;}}// 更新前缀和prefixSum[i] = prefixSum[i - 1] + (dp[i] ? 1 : 0);}return dp[n - 1];}
}

解法二:BFS

using System;
using System.Collections.Generic;public class Solution {public bool CanReach(string s, int minJump, int maxJump) {int n = s.Length;// 如果终点是 '1',直接返回 falseif (s[n - 1] == '1') {return false;}Queue<int> queue = new Queue<int>();bool[] visited = new bool[n];queue.Enqueue(0);visited[0] = true;// 记录上次检查的最远位置,避免重复检查int farthest = 0;while (queue.Count > 0) {int curr = queue.Dequeue();// 如果到达终点,返回 trueif (curr == n - 1) {return true;}// 计算可跳跃的范围int start = Math.Max(farthest + 1, curr + minJump);int end = Math.Min(n - 1, curr + maxJump);// 遍历可跳跃的位置for (int next = start; next <= end; next++) {if (s[next] == '0' && !visited[next]) {visited[next] = true;queue.Enqueue(next);}}// 更新最远检查位置farthest = Math.Max(farthest, end);}return false;}
}

复杂度分析

动态规划 + 前缀和方法:

  • 时间复杂度: O(n),其中 n 是字符串长度。每个位置只访问一次。
  • 空间复杂度: O(n),需要 dp 数组和前缀和数组。

BFS 方法:

  • 时间复杂度: O(n),通过 farthest 指针避免重复访问,每个位置最多访问一次。
  • 空间复杂度: O(n),需要队列和访问标记数组。

相关文章:

  • 面试题——计算机网络:HTTP和HTTPS的区别?
  • C++异步通信-future学习
  • nt!MmMapViewInSystemCache函数分析PointerPte的填充
  • 使用Vue + Element Plus实现可多行编辑的分页表格
  • APL Photonics封面成果:KAUST用五边形激光腔刷新物理随机数生成极限——800Gb/s!
  • Ovito建模并正交化方法
  • webstrorm 提示(This file does not belong to the project)此文件不属于该项目
  • MVCC原理解析
  • 扩展摩尔投票法:找出出现次数超过 n/3 的元素
  • DAY 36神经网络加速器easy
  • 网络协议之办公室网络是怎样的?
  • 实验设计与分析(第6版,Montgomery)第3章单因子实验:方差分析3.11思考题3.7 R语言解题
  • 卸载 Office PLUS
  • ZYNQ-PS与PL端BRAM数据交互
  • PortSwigger-03-点击劫持
  • 链路追踪神器zipkin安装详细教程教程
  • Redis击穿,穿透和雪崩详解以及解决方案
  • Polar编译码(SCL译码)和LDPC编译码(BP译码)的matlab性能仿真,并对比香浓限
  • BEVDepth- Acquisition of Reliable Depth for Multi-view 3D Object Detection
  • 数据库管理与高可用-MySQL数据库操作
  • 南昌做网站和微信小程序的公司/电商培训内容
  • 网站建设的案例教程视频教程/seo排名赚下载
  • 一流设计网站/百度竞价推广价格
  • 酒店网站建设设计/上海seo网站排名优化公司
  • 怎么在欧美做网站推广/福州seo网站管理
  • 网站建设合同黑客攻击/网页设计与制作考试试题及答案