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

LeetCode 1345. 跳跃游戏 IV(困难)

题目描述

给你一个整数数组 arr ,你一开始在数组的第一个元素处(下标为 0)。

每一步,你可以从下标 i 跳到下标 i + 1 、i - 1 或者 j :

  • i + 1 需满足:i + 1 < arr.length
  • i - 1 需满足:i - 1 >= 0
  • j 需满足:arr[i] == arr[j] 且 i != j

请你返回到达数组最后一个元素的下标处所需的 最少操作次数 。

注意:任何时候你都不能跳到数组外面。

示例 1:

输入:arr = [100,-23,-23,404,100,23,23,23,3,404]
输出:3
解释:那你需要跳跃 3 次,下标依次为 0 --> 4 --> 3 --> 9 。下标 9 为数组的最后一个元素的下标。

示例 2:

输入:arr = [7]
输出:0
解释:一开始就在最后一个元素处,所以你不需要跳跃。

示例 3:

输入:arr = [7,6,9,6,9,6,9,7]
输出:1
解释:你可以直接从下标 0 处跳到下标 7 处,也就是数组的最后一个元素处。

提示:

  • 1 <= arr.length <= 5 * 10^4
  • -10^8 <= arr[i] <= 10^8

问题分析

这道题是跳跃游戏系列的第四题,较前三题有了更多的变化和难度提升。主要特点是:

  • 三种跳跃方式:
    • 向前跳一步(i + 1)        
    • 向后跳一步(i - 1)
    • 跳到任何一个与当前位置值相同的位置(arr[i] == arr[j])
  • 目标:找到从起点(下标0)到终点(下标arr.length - 1)的最少操作次数。
  • 可能无解:在某些情况下,可能无法到达最后一个位置,需要返回-1。

由于我们需要找到最少操作次数,这是一个典型的最短路径问题,最适合使用广度优先搜索(BFS)来解决。


解题思路

广度优先搜索(BFS)

BFS的核心思想是"层级遍历",每一层代表走的步数,先访问的节点一定是距离起点最近的节点。对于这道题:

  • 使用队列保存待访问的节点(下标)
  • 使用集合记录已访问的节点,避免重复访问
  • 对于每个节点,尝试三种跳跃方式:
    • 向右跳一步(i + 1)
    • 向左跳一步(i - 1)
    • 跳到所有与当前位置值相同的其他位置
  • 为了优化性能,我们可以使用哈希表预处理数组,将相同值的下标分组存储,这样可以快速找到所有与当前位置值相同的位置。
  • 当我们第一次访问到终点时,当前的步数就是最少操作次数。

优化点

一个关键的优化是:对于值相同的位置,一旦我们访问过其中一个位置后,可以将所有相同值的位置都标记为已访问。这是因为如果我们能从A跳到B,也能从B跳到A,所以没必要重复访问。


算法图解

让我们以示例1为例详细解释BFS的执行过程:

arr = [100,-23,-23,404,100,23,23,23,3,404]
  • 预处理:构建值到下标的映射
    •    100 -> [0, 4]-23 -> [1, 2]404 -> [3, 9]23 -> [5, 6, 7]3 -> [8]

  • 初始化:
    • 队列:[0](起点)
    • 已访问:[true, false, false, ..., false]
    • 步数:0
  • 第1步:处理下标0
    • 当前值:arr[0] = 100
    • 相同值的其他下标:4
    • 向左跳:不可行(已是最左侧)
    • 向右跳:下标1,加入队列
    • 跳到相同值:下标4,加入队列
    • 队列:[1, 4]
    • 已访问:[true, true, false, false, true, false, ...]
    • 清空100对应的下标列表
    • 步数:1
  • 第2步:处理下标1和4
    • 处理下标1:
      • 当前值:arr[1] = -23
      • 相同值的其他下标:2
      • 向左跳:下标0,已访问,跳过
      • 向右跳:下标2,加入队列
      • 跳到相同值:下标2,加入队列(重复,实际只加一次)
    • 处理下标4:
      • 当前值:arr[4] = 100
      • 相同值的其他下标:无(已清空)
      • 向左跳:下标3,加入队列
      • 向右跳:下标5,加入队列
    • 队列:[2, 3, 5]
    • 已访问:[true, true, true, true, true, true, false, ...]
    • 步数:2
  • 第3步:处理下标2、3和5
    • 处理下标2:
      • 当前值:arr[2] = -23
      • 相同值的其他下标:无(已清空)
      • 向左跳:下标1,已访问,跳过
      • 向右跳:下标3,已访问,跳过
    • 处理下标3:
      • 当前值:arr[3] = 404
      • 相同值的其他下标:9(终点)
      • 向左跳:下标2,已访问,跳过
      • 向右跳:下标4,已访问,跳过
      • 跳到相同值:下标9(终点),找到目标!
      • 返回步数:3

到此,我们已经找到了从起点到终点的最短路径,步数为3。路径是:0 -> 4 -> 3 -> 9。


详细代码实现

Java 实现

import java.util.*;class Solution {public int minJumps(int[] arr) {int n = arr.length;if (n == 1) return 0;  // 只有一个元素,已在终点// 构建值到下标的映射(相同值的所有下标)Map<Integer, List<Integer>> valueToIndices = new HashMap<>();for (int i = 0; i < n; i++) {valueToIndices.computeIfAbsent(arr[i], k -> new ArrayList<>()).add(i);}// BFS需要的数据结构Queue<Integer> queue = new LinkedList<>();boolean[] visited = new boolean[n];// 初始化:将起点加入队列queue.offer(0);visited[0] = true;// BFSint steps = 0;while (!queue.isEmpty()) {int size = queue.size();for (int i = 0; i < size; i++) {int current = queue.poll();// 到达终点if (current == n - 1) {return steps;}// 获取当前值对应的所有下标List<Integer> sameValueIndices = valueToIndices.get(arr[current]);// 尝试向左跳if (current - 1 >= 0 && !visited[current - 1]) {queue.offer(current - 1);visited[current - 1] = true;}// 尝试向右跳if (current + 1 < n && !visited[current + 1]) {queue.offer(current + 1);visited[current + 1] = true;}// 尝试跳到相同值的位置for (int idx : sameValueIndices) {if (idx != current && !visited[idx]) {queue.offer(idx);visited[idx] = true;}}// 清空该值对应的下标列表,避免重复访问sameValueIndices.clear();}steps++;}return -1;  // 无法到达终点}
}

C# 实现

using System;
using System.Collections.Generic;public class Solution {public int MinJumps(int[] arr) {int n = arr.Length;if (n == 1) return 0;  // 只有一个元素,已在终点// 构建值到下标的映射(相同值的所有下标)Dictionary<int, List<int>> valueToIndices = new Dictionary<int, List<int>>();for (int i = 0; i < n; i++) {if (!valueToIndices.ContainsKey(arr[i])) {valueToIndices[arr[i]] = new List<int>();}valueToIndices[arr[i]].Add(i);}// BFS需要的数据结构Queue<int> queue = new Queue<int>();bool[] visited = new bool[n];// 初始化:将起点加入队列queue.Enqueue(0);visited[0] = true;// BFSint steps = 0;while (queue.Count > 0) {int size = queue.Count;for (int i = 0; i < size; i++) {int current = queue.Dequeue();// 到达终点if (current == n - 1) {return steps;}// 获取当前值对应的所有下标List<int> sameValueIndices = valueToIndices[arr[current]];// 尝试向左跳if (current - 1 >= 0 && !visited[current - 1]) {queue.Enqueue(current - 1);visited[current - 1] = true;}// 尝试向右跳if (current + 1 < n && !visited[current + 1]) {queue.Enqueue(current + 1);visited[current + 1] = true;}// 尝试跳到相同值的位置foreach (int idx in sameValueIndices) {if (idx != current && !visited[idx]) {queue.Enqueue(idx);visited[idx] = true;}}// 清空该值对应的下标列表,避免重复访问sameValueIndices.Clear();}steps++;}return -1;  // 无法到达终点}
}

复杂度分析

  • 时间复杂度:O(n),其中n是数组的长度。在最坏情况下,我们需要访问每个位置一次,每个位置的操作(包括向左/右跳和跳到相同值的位置)的时间复杂度是O(1)。虽然构建映射表需要O(n),但总体时间复杂度仍然是O(n)。
  • 空间复杂度:O(n),主要用于存储队列、已访问标记和值到下标的映射。在最坏情况下,队列可能包含所有位置,映射表也需要存储所有位置。

优化与技巧

  1. 预处理相同值的下标:通过哈希表提前建立值到下标的映射,可以在O(1)时间内找到所有相同值的位置。
  2. 清空已访问的值列表:一旦我们访问了某个值,就可以将该值对应的所有下标从映射表中移除,避免重复访问。这个优化非常关键,可以将复杂度从O(n²)降低到O(n)。
  3. 提前终止:一旦发现终点,立即返回当前步数,无需继续BFS。
  4. 边界检查:确保向左/右跳不会越界。
  5. 特殊情况处理:如果数组只有一个元素,可以直接返回0,因为已经在终点了。

相关文章:

  • ZooKeeper 原理解析及优劣比较
  • Gartner《AI Infrastructure WithKubernetes参考架构》学习心得
  • LabVIEW下AI开发
  • 杰里7006d日志分析
  • 前端混色实现半透明效果
  • conda 设置env后,环境还是安装在c盘的解决方式:
  • CSS专题之常见布局
  • 虚拟环境中VSCode运行jupyter文件
  • Spring Boot中的分布式缓存方案
  • LSTM语言模型验证代码
  • 零售智能执行大模型架构设计:从空间建模到上下文推理,再到智能Agent
  • 小程序涉及提供提供文本深度合成技术,请补充选择:深度合成-AI问答类目
  • 【Redisson】快速实现分布式锁
  • 打卡第二十三天
  • 车道线检测:自动驾驶的“眼睛”
  • 通义灵码助力Neo4J开发:快速上手与智能编码技巧
  • css使用clip-path属性切割显示可见内容
  • 【深度学习】Transformer 的应用
  • Python的collections模块:数据结构的百宝箱
  • OSA实战笔记二
  • 经济日报:人工智能开启太空经济新格局
  • 人民网三评“网络烂梗”:莫让低级趣味围猎青少年
  • 知名猎头公司创始人兼首席执行官庄华因突发疾病逝世,享年62岁
  • “75万买299元路由器”事件进展:重庆市纪委等三部门联合介入调查
  • 从这些电影与影像,看到包容开放的上海
  • 吉林:消纳绿电,“氢”装上阵