⭐算法OJ⭐跳跃游戏【贪心算法】(C++实现)Jump Game 系列 I,II
这篇文章介绍 跳跃游戏 的第三题和第七题,两道题目有异曲同工之妙,都运用了BFS广度优先搜索算法实现,难度相比于前两题较高,而且不同于更常见的在二维矩阵或者图中的BFS,一维的BFS更加抽象,同时也更能揭示算法的本质。我们一起来看看吧!
1306. Jump Game III
Given an array of non-negative integers arr
, you are initially positioned at start
index of the array. When you are at index i
, you can jump to i + arr[i]
or i - arr[i]
, check if you can reach any index with value 0.
Notice that you can not jump outside of the array at any time.
Example 1:
Input: arr = [4,2,3,0,3,1,2], start = 5
Output: true
Explanation:
All possible ways to reach at index 3 with value 0 are:
index 5 -> index 4 -> index 1 -> index 3
index 5 -> index 6 -> index 4 -> index 1 -> index 3
Example 2:
Input: arr = [4,2,3,0,3,1,2], start = 0
Output: true
Explanation:
One possible way to reach at index 3 with value 0 is:
index 0 -> index 4 -> index 1 -> index 3
Example 3:
Input: arr = [3,0,2,1,2], start = 2
Output: false
Explanation: There is no way to reach at index 1 with value 0.
问题描述
给定一个非负整数数组 arr
,你最初位于数组的起始下标 start
。当你位于下标 i
时,你可以跳到 i + arr[i]
或 i - arr[i]
。请检查你是否能够到达数组中任意一个值为 0 的下标。
解题思路
这个问题可以通过广度优先搜索(BFS) 来解决。我们需要从起始下标开始,尝试向左右两个方向跳跃,并检查是否能够到达值为 0 的下标。
步骤
- 使用一个队列来存储待访问的下标。
- 使用一个数组
visited
来记录已经访问过的下标,避免重复访问。 - 从起始下标开始,尝试向左右两个方向跳跃:
- 如果跳跃后的下标合法且未被访问过,则将其加入队列。
- 如果跳跃后的下标值为 0,则返回
true
。
- 如果队列为空且未找到值为 0 的下标,则返回
false
。
bool canReach(vector<int>& arr, int start) {int n = arr.size();vector<bool> visited(n, false); // 记录是否访问过queue<int> q; // BFS 队列q.push(start); // 将起始下标加入队列visited[start] = true; // 标记为已访问while (!q.empty()) {int current = q.front(); // 取出当前下标q.pop();// 如果当前下标值为 0,返回 trueif (arr[current] == 0) {return true;}// 向左跳跃int left = current - arr[current];if (left >= 0 && !visited[left]) {visited[left] = true;q.push(left);}// 向右跳跃int right = current + arr[current];if (right < n && !visited[right]) {visited[right] = true;q.push(right);}}// 如果队列为空且未找到值为 0 的下标,返回 falsereturn false;
}
复杂度分析
- 时间复杂度:每个下标最多被访问一次,时间复杂度为 O ( n ) O(n) O(n),其中 n n n 是数组的长度。
- 空间复杂度:使用了队列和
visited
数组,空间复杂度为 O ( n ) O(n) O(n)。
总结
通过 BFS,我们可以高效地解决这个问题。BFS 的核心思想是从起始点出发,逐层扩展搜索范围,直到找到目标或遍历完所有可能的下标。这种方法不仅适用于本题,还可以推广到类似的图搜索问题中
1871. Jump Game VII
You are given a 0-indexed binary string s
and two integers minJump
and maxJump
. In the beginning, you are standing at index 0
, which is equal to '0'
. You can move from index i
to index j
if the following conditions are fulfilled:
i + minJump <= j <= min(i + maxJump, s.length - 1)
, ands[j] == '0'
.
Return true
if you can reach index s.length - 1
in s
, or false
otherwise.
Example 1:
Input: s = "011010", minJump = 2, maxJump = 3
Output: true
Explanation:
In the first step, move from index 0 to index 3.
In the second step, move from index 3 to index 5.
Example 2:
Input: s = "01101110", minJump = 2, maxJump = 3
Output: false
解题思路
这个问题可以通过 广度优先搜索(BFS) 或 动态规划 来解决。我们需要从起始下标 0 开始,尝试跳跃到满足条件的位置,并检查是否能够到达最后一个下标。
动态规划思路
- 定义状态:
dp[i]
表示是否可以从起始位置跳到下标i
。 - 初始化:
dp[0] = true
,因为起始位置是 0。 - 状态转移:
对于每个下标i
,如果dp[i] == true
,则尝试跳跃到[i + minJump, i + maxJump]
范围内的所有j
,如果s[j] == '0'
,则设置dp[j] = true
。 - 结果:
返回dp[n - 1]
,其中n
是字符串的长度。
优化
使用滑动窗口优化跳跃范围,避免重复计算。
bool canReach(string s, int minJump, int maxJump) {int n = s.length();if (s[n - 1] != '0') {return false; // 如果最后一个字符不是 '0',直接返回 false}vector<bool> dp(n, false); // dp[i] 表示是否可以跳到下标 idp[0] = true; // 起始位置是 0int prev = 0; // 记录上一个可以跳跃的范围for (int i = 0; i < n; ++i) {if (!dp[i]) {continue; // 如果当前下标不可达,跳过}// 计算当前跳跃范围int left = i + minJump;int right = min(i + maxJump, n - 1);// 滑动窗口优化:跳过已经处理过的范围if (left > prev) {prev = left;} else {left = prev + 1;}// 遍历跳跃范围for (int j = left; j <= right; ++j) {if (s[j] == '0') {dp[j] = true;}}// 如果已经到达最后一个下标,直接返回 trueif (dp[n - 1]) {return true;}}return dp[n - 1];
}
复杂度分析
- 时间复杂度:每个下标最多被访问一次,时间复杂度为 O ( n ) O(n) O(n),其中 n n n 是字符串的长度。
- 空间复杂度:使用了
dp
数组,空间复杂度为 O ( n ) O(n) O(n)。
总结
通过动态规划和滑动窗口优化,我们可以高效地解决这个问题。动态规划的核心思想是通过状态转移逐步构建解,而滑动窗口优化则避免了重复计算,提高了算法的效率。这种方法不仅适用于本题,还可以推广到类似的跳跃问题中。