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

思路

代码

性能

799.香槟塔

目标

我们把玻璃杯摆成金字塔的形状,其中 第一层 有 1 个玻璃杯, 第二层 有 2 个,依次类推到第 100 层,每个玻璃杯将盛有香槟。

从顶层的第一个玻璃杯开始倾倒一些香槟,当顶层的杯子满了,任何溢出的香槟都会立刻等流量的流向左右两侧的玻璃杯。当左右两边的杯子也满了,就会等流量的流向它们左右两边的杯子,依次类推。(当最底层的玻璃杯满了,香槟会流到地板上)

例如,在倾倒一杯香槟后,最顶层的玻璃杯满了。倾倒了两杯香槟后,第二层的两个玻璃杯各自盛放一半的香槟。在倒三杯香槟后,第二层的香槟满了 - 此时总共有三个满的玻璃杯。在倒第四杯后,第三层中间的玻璃杯盛放了一半的香槟,他两边的玻璃杯各自盛放了四分之一的香槟,如下图所示。

现在当倾倒了非负整数杯香槟后,返回第 i 行 j 个玻璃杯所盛放的香槟占玻璃杯容积的比例( i 和 j 都从0开始)。

示例 1:

输入: poured(倾倒香槟总杯数) = 1, query_glass(杯子的位置数) = 1, query_row(行数) = 1
输出: 0.00000
解释: 我们在顶层(下标是(0,0))倒了一杯香槟后,没有溢出,因此所有在顶层以下的玻璃杯都是空的。

示例 2:

输入: poured(倾倒香槟总杯数) = 2, query_glass(杯子的位置数) = 1, query_row(行数) = 1
输出: 0.50000
解释: 我们在顶层(下标是(0,0)倒了两杯香槟后,有一杯量的香槟将从顶层溢出,位于(1,0)的玻璃杯和(1,1)的玻璃杯平分了这一杯香槟,所以每个玻璃杯有一半的香槟。

示例 3:

输入: poured = 100000009, query_row = 33, query_glass = 17
输出: 1.00000

说明:

  • 0 <= poured <= 10^9
  • 0 <= query_glass <= query_row < 100

思路

将玻璃杯摆成金字塔,第一层 1 个杯子,第二层 2 个杯子,依次类推。持续向第一层第一杯倒入 poured 杯香槟,当杯子满了之后,会等流量的流向左右两个玻璃杯,返回 query_row 行(从 0 开始计数),第 query_glass 杯(从 0 开始)中的香槟与容积的比例。

模拟香槟左右的流量即可。第 query_row 行有 query_row + 1 个杯子,初始化流量数组 vol,比如,总共倾倒 poured 杯,如果 vol[0] > 1,多余的流量会从左右两侧流下,vol[1] = (vol[0] - 1)/2vol[0] = (vol[0] - 1)/2,依次类推,直到查询的杯子。

注意由于没有对流量数组分层,需要从后向前遍历才能保证数据更新的正确性。

代码


/**
 * @date 2026-02-25 15:06
 */
public class ChampagneTower799 {

    public double champagneTower(int poured, int query_row, int query_glass) {
        double[] vol = new double[query_row + 1];
        vol[0] = poured;
        for (int i = 0; i < query_row; i++) {
            for (int j = i; j >= 0; j--) {
                if (vol[j] > 1) {
                    vol[j]--;
                    double half = vol[j] / 2.0;
                    vol[j + 1] += half;
                    vol[j] = half;
                } else {
                    vol[j] = 0.0;
                }
            }
        }
        return Math.min(vol[query_glass], 1.0);
    }

}

性能

1161.最大层内元素和

目标

给你一个二叉树的根节点 root。设根节点位于二叉树的第 1 层,而根节点的子节点位于第 2 层,依此类推。

返回总和 最大 的那一层的层号 x。如果有多层的总和一样大,返回其中 最小 的层号 x。

示例 1:

输入:root = [1,7,0,7,-8,null,null]
输出:2
解释:
第 1 层各元素之和为 1,
第 2 层各元素之和为 7 + 0 = 7,
第 3 层各元素之和为 7 + -8 = -1,
所以我们返回第 2 层的层号,它的层内元素之和最大。

示例 2:

输入:root = [989,null,10250,98693,-89388,null,null,null,-32127]
输出:2

说明:

  • 树中的节点数在 [1, 10^4]范围内
  • -10^5 <= Node.val <= 10^5

思路

定义二叉树根节点为第 1 层,子节点层号依次累加,求和最大的层号,如果和相同,返回最小的层号。

BFS/DFS 都可以。

代码


/**
 * @date 2026-01-06 8:47
 */
public class MaxLevelSum1161 {

    public int maxLevelSum(TreeNode root) {
        Deque<TreeNode> q = new ArrayDeque<>();
        q.offer(root);
        int res = 1;
        int level = 1;
        int max = Integer.MIN_VALUE;
        while (!q.isEmpty()) {
            int size = q.size();
            int sum = 0;
            for (int i = 0; i < size; i++) {
                TreeNode node = q.poll();
                sum += node.val;
                if (node.left != null) {
                    q.offer(node.left);
                }
                if (node.right != null) {
                    q.offer(node.right);
                }
            }
            if (max < sum) {
                max = sum;
                res = level;
            }
            level++;
        }
        return res;
    }

}

性能