3568.清理教室的最少移动

目标

给你一个 m x n 的网格图 classroom,其中一个学生志愿者负责清理散布在教室里的垃圾。网格图中的每个单元格是以下字符之一:

  • 'S' :学生的起始位置
  • 'L' :必须收集的垃圾(收集后,该单元格变为空白)
  • 'R' :重置区域,可以将学生的能量恢复到最大值,无论学生当前的能量是多少(可以多次使用)
  • 'X' :学生无法通过的障碍物
  • '.' :空白空间

同时给你一个整数 energy,表示学生的最大能量容量。学生从起始位置 'S' 开始,带着 energy 的能量出发。

每次移动到相邻的单元格(上、下、左或右)会消耗 1 单位能量。如果能量为 0,学生此时只有处在 'R' 格子时可以继续移动,此区域会将能量恢复到 最大 能量值 energy。

返回收集所有垃圾所需的 最少 移动次数,如果无法完成,返回 -1。

示例 1:

输入: classroom = ["S.", "XL"], energy = 2
输出: 2
解释:
学生从单元格 (0, 0) 开始,带着 2 单位的能量。
由于单元格 (1, 0) 有一个障碍物 'X',学生无法直接向下移动。
收集所有垃圾的有效移动序列如下:
移动 1:从 (0, 0) → (0, 1),消耗 1 单位能量,剩余 1 单位。
移动 2:从 (0, 1) → (1, 1),收集垃圾 'L'。
学生通过 2 次移动收集了所有垃圾。因此,输出为 2。

示例 2:

输入: classroom = ["LS", "RL"], energy = 4
输出: 3
解释:
学生从单元格 (0, 1) 开始,带着 4 单位的能量。
收集所有垃圾的有效移动序列如下:
移动 1:从 (0, 1) → (0, 0),收集第一个垃圾 'L',消耗 1 单位能量,剩余 3 单位。
移动 2:从 (0, 0) → (1, 0),到达 'R' 重置区域,恢复能量为 4。
移动 3:从 (1, 0) → (1, 1),收集第二个垃圾 'L'。
学生通过 3 次移动收集了所有垃圾。因此,输出是 3。

示例 3:

输入: classroom = ["L.S", "RXL"], energy = 3
输出: -1
解释:
没有有效路径可以收集所有 'L'。

说明:

  • 1 <= m == classroom.length <= 20
  • 1 <= n == classroom[i].length <= 20
  • classroom[i][j] 是 'S'、'L'、'R'、'X' 或 '.' 之一
  • 1 <= energy <= 50
  • 网格图中恰好有 一个 'S'。
  • 网格图中 最多 有 10 个 'L' 单元格。

思路

学生从起点出发清理教室的垃圾,初始能量为 energy,向上下左右四个方向移动需要消耗 1 能量。教室里有障碍物,学生不能移动到障碍物的格子。能量重置点可以恢复能量到初始值。返回从起点出发清理所有垃圾所需的最少移动。

考虑使用 BFS,维护状态 (x, y, e, mask),使用四维数组维护是否处理过。

代码


/**
 * @date 2026-09-01 9:38
 */
public class MinMoves3568 {

    public int minMoves(String[] classroom, int energy) {
        int sx = -1, sy = -1;
        int m = classroom.length;
        int n = classroom[0].length();
        int[][] directions = new int[][]{{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
        int[][] grid = new int[m][n];
        int no = 1;
        int all = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                char c = classroom[i].charAt(j);
                if (c == 'S') {
                    sy = j;
                    sx = i;
                } else if (c == 'L') {
                    all |= 1 << no;
                    grid[i][j] = no++;
                } else if (c == 'X') {
                    grid[i][j] = -1;
                } else if (c == 'R') {
                    grid[i][j] = -2;
                }
            }
        }
        boolean[][][][] visited = new boolean[m][n][energy + 1][all + 1];
        Deque<int[]> q = new ArrayDeque<>();
        q.offer(new int[]{sx, sy, energy, 0});
        int res = 0;
        while (!q.isEmpty()) {
            int size = q.size();
            for (int i = 0; i < size; i++) {
                int[] p = q.poll();
                if (visited[p[0]][p[1]][p[2]][p[3]]) {
                    continue;
                }
                visited[p[0]][p[1]][p[2]][p[3]] = true;
                if (p[3] == all) {
                    return res;
                }
                if (p[2] == 0) {
                    continue;
                }
                for (int[] d : directions) {
                    int nx = p[0] + d[0];
                    int ny = p[1] + d[1];
                    int e = p[2];
                    int mask = p[3];
                    if (nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx][ny] != -1) {
                        if (grid[nx][ny] > 0) {
                            e--;
                            mask |= 1 << grid[nx][ny];
                        } else if (grid[nx][ny] == -2) {
                            e = energy;
                        } else {
                            e--;
                        }
                        if (!visited[nx][ny][e][mask]) {
                            q.offer(new int[]{nx, ny, e, mask});
                        }
                    }
                }
            }
            res++;
        }
        return -1;
    }

}

性能

3310.移除可疑的方法

目标

你正在维护一个项目,该项目有 n 个方法,编号从 0 到 n - 1。

给你两个整数 n 和 k,以及一个二维整数数组 invocations,其中 invocations[i] = [ai, bi] 表示方法 ai 调用了方法 bi。

已知如果方法 k 存在一个已知的 bug。那么方法 k 以及它直接或间接调用的任何方法都被视为 可疑方法 ,我们需要从项目中移除这些方法。

只有当一组方法没有被这组之外的任何方法调用时,这组方法才能被移除。

返回一个数组,包含移除所有 可疑方法 后剩下的所有方法。你可以以任意顺序返回答案。如果无法移除 所有 可疑方法,则 不 移除任何方法。

示例 1:

输入: n = 4, k = 1, invocations = [[1,2],[0,1],[3,2]]
输出: [0,1,2,3]
解释:
方法 2 和方法 1 是可疑方法,但它们分别直接被方法 3 和方法 0 调用。由于方法 3 和方法 0 不是可疑方法,我们无法移除任何方法,故返回所有方法。

示例 2:

输入: n = 5, k = 0, invocations = [[1,2],[0,2],[0,1],[3,4]]
输出: [3,4]
解释:
方法 0、方法 1 和方法 2 是可疑方法,且没有被任何其他方法直接调用。我们可以移除它们。

示例 3:

输入: n = 3, k = 2, invocations = [[1,2],[0,1],[2,0]]
输出: []
解释:
所有方法都是可疑方法。我们可以移除它们。

说明:

  • 1 <= n <= 10^5
  • 0 <= k <= n - 1
  • 0 <= invocations.length <= 2 * 10^5
  • invocations[i] == [ai, bi]
  • 0 <= ai, bi <= n - 1
  • ai != bi
  • invocations[i] != invocations[j]

思路

n 个方法,编号为 0 ~ n - 1invocations[i] = [ai, bi] 表示方法 ai 调用了方法 bi。已知方法 k 是可疑的,所有被 k 直接或间接调用的方法也都是可疑的。这些可疑方法如果没有被其它非可疑方法调用可以全部移除,返回剩余的方法。

先标记 k 直接或间接调用的方法,再从所有非可疑方法出发,判断是否会调用可疑方法。如果会调用,不可移除,否则直接返回所有非可疑方法。

代码


/**
 * @date 2026-08-05 9:17
 */
public class RemainingMethods3310 {

    public List<Integer> remainingMethods(int n, int k, int[][] invocations) {
        List<Integer>[] g = new ArrayList[n];
        Arrays.setAll(g, x -> new ArrayList<>());
        for (int[] i : invocations) {
            g[i[0]].add(i[1]);
        }
        boolean[] remove = new boolean[n];
        boolean[] visited = new boolean[n];
        dfs(k, g, remove);
        boolean canRemove = true;
        for (int i = 0; i < n; i++) {
            if (remove[i]) {
                continue;
            }
            if (dfs(i, g, visited, remove)) {
                canRemove = false;
            }
        }
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            if (!canRemove) {
                res.add(i);
            } else if (!remove[i]) {
                res.add(i);
            }
        }
        return res;
    }

    public void dfs(int m, List<Integer>[] g, boolean[] remove) {
        if (remove[m]) {
            return;
        }
        remove[m] = true;
        for (Integer next : g[m]) {
            dfs(next, g, remove);
        }
    }

    public boolean dfs(int m, List<Integer>[] g, boolean[] visited, boolean[] remove) {
        visited[m] = true;
        if (remove[m]) {
            return true;
        }
        boolean res = false;
        for (Integer next : g[m]) {
            if (!visited[next]) {
                res = res || dfs(next, g, visited, remove);
            }
        }
        return res;
    }

}

性能

2685.统计完全连通分量的数量

目标

给你一个整数 n 。现有一个包含 n 个顶点的 无向 图,顶点按从 0 到 n - 1 编号。给你一个二维整数数组 edges 其中 edges[i] = [ai, bi] 表示顶点 ai 和 bi 之间存在一条 无向 边。

返回图中 完全连通分量 的数量。

如果在子图中任意两个顶点之间都存在路径,并且子图中没有任何一个顶点与子图外部的顶点共享边,则称其为 连通分量 。

如果连通分量中每对节点之间都存在一条边,则称其为 完全连通分量 。

示例 1:

输入:n = 6, edges = [[0,1],[0,2],[1,2],[3,4]]
输出:3
解释:如上图所示,可以看到此图所有分量都是完全连通分量。

示例 2:

输入:n = 6, edges = [[0,1],[0,2],[1,2],[3,4],[3,5]]
输出:1
解释:包含节点 0、1 和 2 的分量是完全连通分量,因为每对节点之间都存在一条边。
包含节点 3 、4 和 5 的分量不是完全连通分量,因为节点 4 和 5 之间不存在边。
因此,在图中完全连接分量的数量是 1 。

说明:

  • 1 <= n <= 50
  • 0 <= edges.length <= n * (n - 1) / 2
  • edges[i].length == 2
  • 0 <= ai, bi <= n - 1
  • ai != bi
  • 不存在重复的边

思路

求无向图中完全连通分量的个数。完全连通分量指连通分量中任意两个节点之间都有一条边。

暴力解法是使用并查集维护连通分量,找出同一连通分量内的节点,判断两两之间是否有边。

优化点:可以利用节点与边的关系来判断是否是完全连通分量,节点 v 与 边 e 的关系为:e = C(v, 2) = v * (v - 1) / 2

代码


/**
 * @date 2026-07-14 11:27
 */
public class CountCompleteComponents2685 {

    private class UnionFind {

        private final int[] fa;

        public UnionFind(int n) {
            fa = new int[n];
            Arrays.setAll(fa, i -> i);
        }

        public int find(int e) {
            if (e != fa[e]) {
                fa[e] = find(fa[e]);
            }
            return fa[e];
        }

        public void union(int a, int b) {
            int x = find(a);
            int y = find(b);
            if (x > y) {
                fa[x] = y;
            } else {
                fa[y] = x;
            }
        }

        public int getCompleteComponents(Set<Integer>[] g) {
            int n = fa.length;
            int res = 0;
            Set<Integer> visited = new HashSet<>();
            for (int i = 0; i < n; i++) {
                if (visited.contains(i)) {
                    continue;
                }
                visited.add(i);
                List<Integer> list = new ArrayList<>();
                for (int j = 0; j < n; j++) {
                    if (find(j) == find(i)) {
                        visited.add(j);
                        list.add(j);
                    }
                }
                int size = list.size();
                boolean flag = true;
                here:
                for (int p = 0; p < size; p++) {
                    for (int q = p + 1; q < size; q++) {
                        if (!g[list.get(p)].contains(list.get(q))) {
                            flag = false;
                            break here;
                        }
                    }
                }
                if (flag) {
                    res++;
                }
            }
            return res;
        }
    }

    public int countCompleteComponents(int n, int[][] edges) {
        UnionFind uf = new UnionFind(n);
        Set<Integer>[] g = new HashSet[n];
        Arrays.setAll(g, x -> new HashSet<>());
        for (int[] edge : edges) {
            int a = edge[0];
            int b = edge[1];
            uf.union(a, b);
            g[a].add(b);
            g[b].add(a);
        }
        return uf.getCompleteComponents(g);
    }

}

性能

2492.两个城市间路径的最小分数

目标

给你一个正整数 n ,表示总共有 n 个城市,城市从 1 到 n 编号。给你一个二维数组 roads ,其中 roads[i] = [ai, bi, distancei] 表示城市 ai 和 bi 之间有一条 双向 道路,道路距离为 distancei 。城市构成的图不一定是连通的。

两个城市之间一条路径的 分数 定义为这条路径中道路的 最小 距离。

返回城市 1 和城市 n 之间的所有路径的 最小 分数。

注意:

  • 一条路径指的是两个城市之间的道路序列。
  • 一条路径可以 多次 包含同一条道路,你也可以沿着路径多次到达城市 1 和城市 n 。
  • 测试数据保证城市 1 和城市n 之间 至少 有一条路径。

示例 1:

输入:n = 4, roads = [[1,2,9],[2,3,6],[2,4,5],[1,4,7]]
输出:5
解释:城市 1 到城市 4 的路径中,分数最小的一条为:1 -> 2 -> 4 。这条路径的分数是 min(9,5) = 5 。
不存在分数更小的路径。

示例 2:

输入:n = 4, roads = [[1,2,2],[1,3,4],[3,4,7]]
输出:2
解释:城市 1 到城市 4 分数最小的路径是:1 -> 2 -> 1 -> 3 -> 4 。这条路径的分数是 min(2,2,4,7) = 2 。

说明:

  • 2 <= n <= 10^5
  • 1 <= roads.length <= 10^5
  • roads[i].length == 3
  • 1 <= ai, bi <= n
  • ai != bi
  • 1 <= distancei <= 10^4
  • 不会有重复的边。
  • 城市 1 和城市 n 之间至少有一条路径。

思路

单源最短路径,可以使用 Dijkstra 算法。但是需要注意,这里求的不是路径和的最小值,而是从城市 1 出发,每段路径的最小值。

从城市 1 开始 dfs 访问所能到达的城市,记录路径中的最小值。需要注意,虽然城市可能已访问过,但是边仍可能是未处理的,比如 1 -> 2 -> 3 -> 13 -> 1 这条路径也应该参与最终结果的计算,因此需要先比较再判断是否访问过。

代码


/**
 * @date 2026-07-06 11:35
 */
public class MinScore2492 {

    public int minScore(int n, int[][] roads) {
        List<int[]>[] g = new List[n + 1];
        Arrays.setAll(g, x -> new ArrayList<>());
        for (int[] road : roads) {
            g[road[0]].add(new int[]{road[1], road[2]});
            g[road[1]].add(new int[]{road[0], road[2]});
        }
        boolean[] visited = new boolean[n + 1];
        return dfs(1, g, visited);
    }

    public int dfs(int cur, List<int[]>[] g, boolean[] visited) {
        int res = Integer.MAX_VALUE;
        visited[cur] = true;
        for (int[] next : g[cur]) {
            res = Math.min(res, next[1]);
            if (visited[next[0]]) {
                continue;
            }
            res = Math.min(dfs(next[0], g, visited), res);
        }
        return res;
    }

}

性能

3286.穿越网格图的安全路径

目标

给你一个 m x n 的二进制矩形 grid 和一个整数 health 表示你的健康值。

你开始于矩形的左上角 (0, 0) ,你的目标是矩形的右下角 (m - 1, n - 1) 。

你可以在矩形中往上下左右相邻格子移动,但前提是你的健康值始终是 正数 。

对于格子 (i, j) ,如果 grid[i][j] = 1 ,那么这个格子视为 不安全 的,会使你的健康值减少 1 。

如果你可以到达最终的格子,请你返回 true ,否则返回 false 。

注意 ,当你在最终格子的时候,你的健康值也必须为 正数 。

示例 1:

输入:grid = [[0,1,0,0,0],[0,1,0,1,0],[0,0,0,1,0]], health = 1
输出:true
解释:
沿着图中灰色格子走,可以安全到达最终的格子。

示例 2:

输入:grid = [[0,1,1,0,0,0],[1,0,1,0,0,0],[0,1,1,1,0,1],[0,0,1,0,1,0]], health = 3
输出:false
解释:
健康值最少为 4 才能安全到达最后的格子。

示例 3:

输入:grid = [[1,1,1],[1,0,1],[1,1,1]], health = 5
输出:true
解释:
沿着下图中灰色格子走,可以安全到达最终的格子。
任何不经过格子 (1, 1) 的路径都是不安全的,因为你的健康值到达最终格子时,都会小于等于 0 。

说明:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 50
  • 2 <= m * n
  • 1 <= health <= m + n
  • grid[i][j] 要么是 0 ,要么是 1 。

思路

有一个 m x n 的二进制矩阵,从左上角 (0, 0) 出发,可以沿上下左右四个方向移动到相邻的格子。如果经过的格子值为 1 视为不安全,健康度 health1。判断是否存在路径使得到达 (m - 1, n - 1) 时,health > 0

判断路径和是否严格小于 health,可以使用 Dijkstra 求出最短路。

代码


/**
 * @date 2026-07-02 9:09
 */
public class FindSafeWalk3286 {

    private final int[][] directions = new int[][]{{0, 1}, {1, 0}, {0, -1}, {-1, 0}};

    public boolean findSafeWalk(List<List<Integer>> grid, int health) {
        int m = grid.size();
        int n = grid.get(0).size();
        int[][] dis = new int[m][n];
        for (int[] row : dis) {
            Arrays.fill(row, Integer.MAX_VALUE);
        }
        // 出错点:不要忘了初始化
        dis[0][0] = grid.get(0).get(0);
        Deque<int[]> q = new ArrayDeque<>();
        q.offer(new int[]{0, 0});
        while (!q.isEmpty()) {
            int[] p = q.poll();
            int r = p[0], c = p[1];
            for (int[] d : directions) {
                int nr = r + d[0];
                int nc = c + d[1];
                if (nr >= 0 && nr < m && nc >= 0 && nc < n && dis[r][c] + grid.get(nr).get(nc) < dis[nr][nc]) {
                    Integer cost = grid.get(nr).get(nc);
                    dis[nr][nc] = dis[r][c] + cost;
                    if (cost == 0) {
                        q.offerFirst(new int[]{nr, nc});
                    } else {
                        q.offer(new int[]{nr, nc});
                    }
                }
            }
        }
        return dis[m - 1][n - 1] < health;
    }

}

性能

1871.跳跃游戏VII

目标

给你一个下标从 0 开始的二进制字符串 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

思路

有一个长度为 n 的二进制字符串 s,开始在位置 0 且该位置的值为 0,从该位置出发每次可以跳跃到 [i + minJump, min(i + maxJump, n - 1)] 中元素值为 0 的位置 j,判断能否到达 n - 1

定义 dp[i] 表示能否到达下标 i,如果 dp[i] = true,标记 [Math.max(j, i + minJump), Math.min(n - 1, i + maxJump)] 中元素值为 '0' 的下标,其中 j 表示之前可覆盖的最大下标 + 1。

代码


/**
 * @date 2026-05-25 8:50
 */
public class CanReach1871 {

    public boolean canReach(String s, int minJump, int maxJump) {
        int n = s.length();
        char[] chars = s.toCharArray();
        if (chars[n - 1] != '0') {
            return false;
        }
        boolean[] dp = new boolean[n];
        dp[0] = true;
        int j = 1;
        for (int i = 0; i < n; i++) {
            if (dp[i]) {
                for (j = Math.max(j, i + minJump); j <= Math.min(n - 1, i + maxJump); j++) {
                    dp[j] = chars[j] == '0';
                }
            }
        }
        return dp[n - 1];
    }

}

性能

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

思路

有一个数组 nums,从下标 0 开始移动,目标是到达下标 n - 1。每次移动可以移动到相邻的位置,或者满足 nums[j] == nums[i] 的位置 j。返回最少移动次数。

3629.通过质数传送到达终点的最少跳跃次数 类似,那个题目质数列表的处理比这个相同元素列表稍微复杂一些。

思路都是 BFS,从起点出发将下一个可以到达的节点加入队列,直到到达终点。关键点是处理完一次相同元素下标列表之后要清空,避免重复访问。

小优化:只取相同元素的首尾下标。

代码


/**
 * @date 2026-05-18 8:59
 */
public class MinJumps1345 {

    public int minJumps_v1(int[] arr) {
        int n = arr.length;
        Map<Integer, List<Integer>> map = new HashMap<>();
        int i = 0;
        while (i < n) {
            int start = i;
            while (i < n && arr[i] == arr[start]) {
                i++;
            }
            map.computeIfAbsent(arr[start], x -> new ArrayList<>()).add(start);
            if (start != i - 1) {
                map.get(arr[start]).add(i - 1);
            }
        }
        List<Integer> q = new ArrayList<>();
        q.add(0);
        boolean[] visited = new boolean[n];
        int res = 0;
        while (!q.isEmpty()) {
            List<Integer> tmp = new ArrayList<>();
            for (Integer cur : q) {
                if (cur == n - 1) {
                    return res;
                }
                if (cur > 0 && !visited[cur - 1]) {
                    visited[cur - 1] = true;
                    tmp.add(cur - 1);
                }
                if (!visited[cur + 1]){
                    visited[cur + 1] = true;
                    tmp.add(cur + 1);
                }
                if (map.get(arr[cur]) != null) {
                    for (Integer index : map.get(arr[cur])) {
                        if (!visited[index]){
                            visited[index] = true;
                            tmp.add(index);
                        }
                    }
                    map.remove(arr[cur]);
                }
            }
            q = tmp;
            res++;
        }
        return res;
    }

}

性能

1306.跳跃游戏III

目标

给定一个非负整数数组 arr,你最开始位于该数组的起始下标 start 处。当你位于下标 i 处时,你可以跳到 i + arr[i] 或者 i - arr[i]。

请你判断自己是否能够跳到对应元素值为 0 的 任一 下标处。

注意,不管是什么情况下,你都无法跳到数组之外。

示例 1:

输入:arr = [4,2,3,0,3,1,2], start = 5
输出:true
解释:
到达值为 0 的下标 3 有以下可能方案: 
下标 5 -> 下标 4 -> 下标 1 -> 下标 3 
下标 5 -> 下标 6 -> 下标 4 -> 下标 1 -> 下标 3 

示例 2:

输入:arr = [4,2,3,0,3,1,2], start = 0
输出:true 
解释:
到达值为 0 的下标 3 有以下可能方案: 
下标 0 -> 下标 4 -> 下标 1 -> 下标 3

示例 3:

输入:arr = [3,0,2,1,2], start = 2
输出:false
解释:无法到达值为 0 的下标 1 处。 

说明:

  • 1 <= arr.length <= 5 * 10^4
  • 0 <= arr[i] < arr.length
  • 0 <= start < arr.length

思路

有一个数组 arr,起始位于 start,每次跳跃可以到达 i + arr[i] 或者 i - arr[i] 的下标,判断能否跳到任一一个元素值为 0 的下标。

start 开始 dfs,记录访问过的下标,并判断元素值是否为 0

代码


/**
 * @date 2026-05-18 10:53
 */
public class CanReach1306 {

    public boolean canReach(int[] arr, int start) {
        boolean[] visited = new boolean[arr.length];
        return dfs(arr, start, visited);
    }

    public boolean dfs(int[] arr, int cur, boolean[] visited) {
        if (cur < 0 || cur >= arr.length || visited[cur]) {
            return false;
        }
        if (arr[cur] == 0) {
            return true;
        }
        visited[cur] = true;
        return dfs(arr, cur + arr[cur], visited) || dfs(arr, cur - arr[cur], visited);
    }
}

性能

3629.通过质数传送到达终点的最少跳跃次数

目标

给你一个长度为 n 的整数数组 nums。

你从下标 0 开始,目标是到达下标 n - 1。

在任何下标 i 处,你可以执行以下操作之一:

  • 移动到相邻格子:跳到下标 i + 1 或 i - 1,如果该下标在边界内。
  • 质数传送:如果 nums[i] 是一个质数 p,你可以立即跳到任何满足 nums[j] % p == 0 的下标 j 处,且下标 j != i 。

返回到达下标 n - 1 所需的 最少 跳跃次数。

质数 是一个大于 1 的自然数,只有两个因子,1 和它本身。

示例 1:

输入: nums = [1,2,4,6]
输出: 2
解释:
一个最优的跳跃序列是:
从下标 i = 0 开始。向相邻下标 1 跳一步。
在下标 i = 1,nums[1] = 2 是一个质数。因此,我们传送到索引 i = 3,因为 nums[3] = 6 可以被 2 整除。
因此,答案是 2。

示例 2:

输入: nums = [2,3,4,7,9]
输出: 2
解释:
一个最优的跳跃序列是:
从下标 i = 0 开始。向相邻下标 i = 1 跳一步。
在下标 i = 1,nums[1] = 3 是一个质数。因此,我们传送到下标 i = 4,因为 nums[4] = 9 可以被 3 整除。
因此,答案是 2。

示例 3:

输入: nums = [4,6,5,8]
输出: 3
解释:
由于无法进行传送,我们通过 0 → 1 → 2 → 3 移动。因此,答案是 3。

说明:

  • 1 <= n == nums.length <= 10^5
  • 1 <= nums[i] <= 10^6

思路

有一个数组 nums,从下标 0 开始移动,目标是到达下标 n - 1。每次移动可以移动到相邻的位置,如果 nums[i] 是质数,可以直接移动到满足 nums[j] % nums[i] == 0 的位置。返回最少移动次数。

枚举 2 ~ MAX,为每一个数记录质因子,得到哈希表 (num,primeFactorList),枚举数组中的元素,获取 primeFactorList,为其中的每一个质数建立一个跳转列表 jumpIndexList,将当前下标加进去。从 0 开始 bfs,记录跳转次数,直到到达下标 n - 1

代码


/**
 * @date 2026-05-08 16:55
 */
public class MinJumps3629 {

    public static final int MAX = 1000000;
    public static Map<Integer, List<Integer>> primeFactorMap = new HashMap<>();

    static {
        primeFactorMap.computeIfAbsent(1, k -> new ArrayList<>());
        for (int i = 2; i <= MAX; i++) {
            if (primeFactorMap.get(i) == null) {
                for (int j = i; j <= MAX; j += i) {
                    primeFactorMap.computeIfAbsent(j, k -> new ArrayList<>()).add(i);
                }
            }
        }
    }

    public int minJumps(int[] nums) {
        int n = nums.length;
        Map<Integer, List<Integer>> jumpIndexList = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            List<Integer> primeFactors = primeFactorMap.get(nums[i]);
            for (Integer prime : primeFactors) {
                jumpIndexList.computeIfAbsent(prime, k -> new ArrayList<>()).add(i);
            }
        }
        boolean[] visited = new boolean[n];
        Deque<Integer> q = new ArrayDeque<>();
        q.offer(0);
        int res = 0;
        here:
        while (!q.isEmpty()) {
            int size = q.size();
            for (int i = 0; i < size; i++) {
                Integer index = q.poll();
                if (visited[index]) {
                    continue;
                }
                visited[index] = true;
                if (index == n - 1) {
                    break here;
                }
                int next = index + 1;
                int prev = index - 1;
                if (next < n) {
                    q.offer(next);
                }
                if (prev >= 0) {
                    q.offer(prev);
                }
                if (jumpIndexList.get(nums[index]) != null) {
                    q.addAll(jumpIndexList.get(nums[index]));
                    jumpIndexList.get(nums[index]).clear();
                }
            }
            res++;
        }
        return res;
    }

}

性能

3666.使二进制字符串全为1的最少操作次数

目标

给你一个二进制字符串 s 和一个整数 k。

在一次操作中,你必须选择 恰好 k 个 不同的 下标,并将每个 '0' 翻转 为 '1',每个 '1' 翻转为 '0'。

返回使字符串中所有字符都等于 '1' 所需的 最少 操作次数。如果不可能,则返回 -1。

示例 1:

输入: s = "110", k = 1
输出: 1
解释:
s 中有一个 '0'。
由于 k = 1,我们可以直接在一次操作中翻转它。

示例 2:

输入: s = "0101", k = 3
输出: 2
解释:
每次操作选择 k = 3 个下标的一种最优操作方案是:
操作 1:翻转下标 [0, 1, 3]。s 从 "0101" 变为 "1000"。
操作 2:翻转下标 [1, 2, 3]。s 从 "1000" 变为 "1111"。
因此,最少操作次数为 2。

示例 3:

输入: s = "101", k = 2
输出: -1
解释:
由于 k = 2 且 s 中只有一个 '0',因此不可能通过翻转恰好 k 个位来使所有字符变为 '1'。因此,答案是 -1。

说明:

  • 1 <= s.length <= 10^5
  • s[i] 的值为 '0' 或 '1'。
  • 1 <= k <= s.length

思路

代码

性能