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

⭐算法OJ⭐跳跃游戏【BFS+滑动窗口】(C++实现)Jump Game 系列 III,VII

⭐算法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,返回 true
        if (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 的下标,返回 false
    return 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), and
  • s[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] 表示是否可以跳到下标 i
    dp[0] = true; // 起始位置是 0

    int 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;
            }
        }

        // 如果已经到达最后一个下标,直接返回 true
        if (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)

总结

通过动态规划和滑动窗口优化,我们可以高效地解决这个问题。动态规划的核心思想是通过状态转移逐步构建解,而滑动窗口优化则避免了重复计算,提高了算法的效率。这种方法不仅适用于本题,还可以推广到类似的跳跃问题中。

http://www.dtcms.com/a/54909.html

相关文章:

  • C++智能指针shared_ptr
  • 从0开始的操作系统手搓教程23:构建输入子系统——实现键盘驱动1——热身驱动
  • OpenGL ES -> GLSurfaceView纹理贴图VBO(Vertex Buffer Object)方法实现
  • 人工智能+乡村振兴+文旅+低空无人机产业链技术详解
  • MySQL5.7.44-winx64版本Windows Server下载安装教程图解
  • 江科大51单片机笔记【9】DS1302实时时钟(上)
  • 网络安全规划重安全性需求
  • sql注入的一般过程
  • 力扣hot100——贪心
  • 从0到1入门Linux
  • 使用 DeepSeek 配合 即梦AI 生成视频的详细教程
  • 第一章——计算机系统概论
  • MRI学习笔记-Meta分析之SDM-PSI
  • 从 Git 仓库流程到 C++ 类设计:一次巧妙的类比实现
  • 图论-腐烂的橘子
  • Kotlin D2
  • VMware Fusion虚拟机Mac版安装Ubuntu系统
  • mac安装nvm=>node=>nrm
  • 高并发内存池 · 基本认识
  • 数据保险箱:备份文件的关键价值与自动化实践
  • 知识周汇 | Python操作Excel全攻略系列(二):文件操作篇
  • leetcode700-二叉搜索树中的搜索
  • 【愚公系列】《Python网络爬虫从入门到精通》041-Matplotlib 图表的常用设置
  • 本地部署类似 ChatGPT 的大模型:基于 Ollama + Open-WebUI
  • ctf网络安全比赛有一张图片怎么查看
  • Qt之QGraphicsView实现思维导图
  • 爬虫逆向:脱壳工具 frida-dexdump 的使用详解
  • [总概]Vue2/3React Diff算法
  • centos8更换阿里云yum源
  • synchronized锁的原理