3296.移山所需的最少秒数

目标

给你一个整数 mountainHeight 表示山的高度。

同时给你一个整数数组 workerTimes,表示工人们的工作时间(单位:秒)。

工人们需要 同时 进行工作以 降低 山的高度。对于工人 i :

  • 山的高度降低 x,需要花费 workerTimes[i] + workerTimes[i] 2 + ... + workerTimes[i] x 秒。例如:
    • 山的高度降低 1,需要 workerTimes[i] 秒。
    • 山的高度降低 2,需要 workerTimes[i] + workerTimes[i] * 2 秒,依此类推。

返回一个整数,表示工人们使山的高度降低到 0 所需的 最少 秒数。

示例 1:

输入: mountainHeight = 4, workerTimes = [2,1,1]
输出: 3
解释:
将山的高度降低到 0 的一种方式是:
工人 0 将高度降低 1,花费 workerTimes[0] = 2 秒。
工人 1 将高度降低 2,花费 workerTimes[1] + workerTimes[1] * 2 = 3 秒。
工人 2 将高度降低 1,花费 workerTimes[2] = 1 秒。
因为工人同时工作,所需的最少时间为 max(2, 3, 1) = 3 秒。

示例 2:

输入: mountainHeight = 10, workerTimes = [3,2,2,4]
输出: 12
解释:
工人 0 将高度降低 2,花费 workerTimes[0] + workerTimes[0] * 2 = 9 秒。
工人 1 将高度降低 3,花费 workerTimes[1] + workerTimes[1] * 2 + workerTimes[1] * 3 = 12 秒。
工人 2 将高度降低 3,花费 workerTimes[2] + workerTimes[2] * 2 + workerTimes[2] * 3 = 12 秒。
工人 3 将高度降低 2,花费 workerTimes[3] + workerTimes[3] * 2 = 12 秒。
所需的最少时间为 max(9, 12, 12, 12) = 12 秒。

示例 3:

输入: mountainHeight = 5, workerTimes = [1]
输出: 15
解释:
这个示例中只有一个工人,所以答案是 workerTimes[0] + workerTimes[0] * 2 + workerTimes[0] * 3 + workerTimes[0] * 4 + workerTimes[0] * 5 = 15 秒。

说明:

  • 1 <= mountainHeight <= 10^5
  • 1 <= workerTimes.length <= 10^4
  • 1 <= workerTimes[i] <= 10^6

思路

整数 mountainHeight 表示山的高度,workerTimes[i] 表示工人 i 的时效,工人 i 将山的高度降低 x 所需的时间是 workerTimes[i] + workerTimes[i] * 2 + ... + workerTimes[i] * x,求工人同时工作,将山的高度降为 0 所需要的最少时间。

移山所需的时间是所有工人消耗时间的最大值,最小化这个最大值,可以考虑二分答案。

工人 i 将高度降低 x 所需时间为 workerTimes[i] * (1 + 2 + …… + x) = workerTimes[i] * (1 + x) * x / 2。如果最大时间是 target,解方程可得工人 i 最多将高度降低 (sqrt(1 + (8 * target) / workerTimes[i]) - 1) / 2,只需判断降低的高度是否超过 mountainHeight 即可。

代码


/**
 * @date 2026-03-13 9:42
 */
public class MinNumberOfSeconds3296 {

    public long minNumberOfSeconds(int mountainHeight, int[] workerTimes) {
        int max = 0;
        int n = workerTimes.length;
        for (int wt : workerTimes) {
            max = Math.max(max, wt);
        }
        long p = (mountainHeight - 1) / n + 1;
        long l = 0L, r = max * (1 + p) * p / 2;
        long m = l + (r - l) / 2;
        while (l <= r) {
            if (check(mountainHeight, workerTimes, m)) {
                r = m - 1;
            } else {
                l = m + 1;
            }
            m = l + (r - l) / 2;
        }
        return l;
    }

    public boolean check(int mountainHeight, int[] workerTimes, long target) {
        int total = 0;
        for (int w : workerTimes) {
            total += (int) (Math.sqrt(1 + (8 * target) / w) - 1) / 2;
            if (total >= mountainHeight) {
                return true;
            }
        }
        return false;
    }

}

性能

3600.升级后最大生成树稳定性

目标

给你一个整数 n,表示编号从 0 到 n - 1 的 n 个节点,以及一个 edges 列表,其中 edges[i] = [ui, vi, si, musti]:

  • ui 和 vi 表示节点 ui 和 vi 之间的一条无向边。
  • si 是该边的强度。
  • musti 是一个整数(0 或 1)。如果 musti == 1,则该边 必须 包含在生成树中,且 不能升级 。

你还有一个整数 k,表示你可以执行的最多 升级 次数。每次升级会使边的强度 翻倍 ,且每条可升级边(即 musti == 0)最多只能升级一次。

一个生成树的 稳定性 定义为其中所有边的 最小 强度。

返回任何有效生成树可能达到的 最大 稳定性。如果无法连接所有节点,返回 -1。

注意: 图的一个 生成树(spanning tree)是该图中边的一个子集,它满足以下条件:

  • 将所有节点连接在一起(即图是 连通的 )。
  • 不 形成任何环。
  • 包含 恰好 n - 1 条边,其中 n 是图中节点的数量。

示例 1:

输入: n = 3, edges = [[0,1,2,1],[1,2,3,0]], k = 1
输出: 2
解释:
边 [0,1] 强度为 2,必须包含在生成树中。
边 [1,2] 是可选的,可以使用一次升级将其强度从 3 提升到 6。
最终的生成树包含这两条边,强度分别为 2 和 6。
生成树中的最小强度是 2,即最大可能稳定性。

示例 2:

输入: n = 3, edges = [[0,1,4,0],[1,2,3,0],[0,2,1,0]], k = 2
输出: 6
解释:
所有边都是可选的,且最多可以进行 k = 2 次升级。
将边 [0,1] 从 4 升级到 8,将边 [1,2] 从 3 升级到 6。
生成树包含这两条边,强度分别为 8 和 6。
生成树中的最小强度是 6,即最大可能稳定性。

示例 3:

输入: n = 3, edges = [[0,1,1,1],[1,2,1,1],[2,0,1,1]], k = 0
输出: -1
解释:
所有边都是必选的,构成了一个环,这违反了生成树无环的性质。因此返回 -1。

说明:

  • 2 <= n <= 10^5
  • 1 <= edges.length <= 10^5
  • edges[i] = [ui, vi, si, musti]
  • 0 <= ui, vi < n
  • ui != vi
  • 1 <= si <= 10^5
  • musti 是 0 或 1。
  • 0 <= k <= n
  • 没有重复的边。

思路

// todo

代码

性能

3634.使数组平衡的最少移除数目

目标

给你一个整数数组 nums 和一个整数 k。

如果一个数组的 最大 元素的值 至多 是其 最小 元素的 k 倍,则该数组被称为是 平衡 的。

你可以从 nums 中移除 任意 数量的元素,但不能使其变为 空 数组。

返回为了使剩余数组平衡,需要移除的元素的 最小 数量。

注意:大小为 1 的数组被认为是平衡的,因为其最大值和最小值相等,且条件总是成立。

示例 1:

输入:nums = [2,1,5], k = 2
输出:1
解释:
移除 nums[2] = 5 得到 nums = [2, 1]。
现在 max = 2, min = 1,且 max <= min * k,因为 2 <= 1 * 2。因此,答案是 1。

示例 2:

输入:nums = [1,6,2,9], k = 3
输出:2
解释:
移除 nums[0] = 1 和 nums[3] = 9 得到 nums = [6, 2]。
现在 max = 6, min = 2,且 max <= min * k,因为 6 <= 2 * 3。因此,答案是 2。

示例 3:

输入:nums = [4,6], k = 2
输出:0
解释:
由于 nums 已经平衡,因为 6 <= 4 * 2,所以不需要移除任何元素。

说明:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^9
  • 1 <= k <= 10^5

思路

定义平衡数组是 元素最大值 <= k * 元素最小值 的数组。有一个数组 nums,每次操作可以移除一个元素,求使得数组变成平衡数组的最少操作次数。

直接二分查找大于 k * nums[i] 的最小下标 index,删掉的元素是前面 i 个元素(下标 i 其实是第 i + 1 个元素,前面有 i 个),加上 n - 1 - index + 1 个大于 k * nums[i] 的元素。

可以使用滑动窗口,删掉的元素就是总长度减去窗口内的元素,针对每一个 left 将其扩展到最右端,然后移出 left 继续判断。

代码


/**
 * @date 2026-02-06 8:54
 */
public class MinRemoval3634 {

    public int minRemoval(int[] nums, int k) {
        int n = nums.length;
        Arrays.sort(nums);
        int res = n - 1;
        int r = 0;
        for (int l = 0; l < n; l++) {
            while (r < n && (long) nums[l] * k >= nums[r]) {
                r++;
            }
            res = Math.min(res, n - (r - l));
        }
        return res;
    }

}

性能

744.寻找比目标字母大的最小字母

目标

给你一个字符数组 letters,该数组按非递减顺序排序,以及一个字符 target。letters 里至少有两个不同的字符。

返回 letters 中大于 target 的最小的字符。如果不存在这样的字符,则返回 letters 的第一个字符。

示例 1:

输入: letters = ['c', 'f', 'j'],target = 'a'
输出: 'c'
解释:letters 中字典上比 'a' 大的最小字符是 'c'。

示例 2:

输入: letters = ['c','f','j'], target = 'c'
输出: 'f'
解释:letters 中字典顺序上大于 'c' 的最小字符是 'f'。

示例 3:

输入: letters = ['x','x','y','y'], target = 'z'
输出: 'x'
解释:letters 中没有一个字符在字典上大于 'z',所以我们返回 letters[0]。

说明:

  • 2 <= letters.length <= 10^4
  • letters[i] 是一个小写字母
  • letters 按非递减顺序排序
  • letters 最少包含两个不同的字母
  • target 是一个小写字母

思路

有一个升序排列的字符数组 letters,返回大于 target 的最小字符,如不存在返回第一个字符。

使用二分,查找第一个大于 target 的字符。

代码


/**
 * @date 2026-02-02 9:42
 */
public class NextGreatestLetter744 {

    public char nextGreatestLetter(char[] letters, char target) {
        int n = letters.length;
        int r = n - 1;
        int l = 0;
        int m = l + (r - l) / 2;
        while (l <= r) {
            if (letters[m] <= target) {
                l = m + 1;
            } else {
                r = m - 1;
            }
            m = l + (r - l) / 2;
        }
        return l < n ? letters[l] : letters[0];
    }

}

性能

1292.元素和小于等于阈值的正方形的最大边长

目标

给你一个大小为 m x n 的矩阵 mat 和一个整数阈值 threshold。

请你返回元素总和小于或等于阈值的正方形区域的最大边长;如果没有这样的正方形区域,则返回 0 。

示例 1:

输入:mat = [[1,1,3,2,4,3,2],[1,1,3,2,4,3,2],[1,1,3,2,4,3,2]], threshold = 4
输出:2
解释:总和小于或等于 4 的正方形的最大边长为 2,如图所示。

示例 2:

输入:mat = [[2,2,2,2,2],[2,2,2,2,2],[2,2,2,2,2],[2,2,2,2,2],[2,2,2,2,2]], threshold = 1
输出:0

说明:

  • m == mat.length
  • n == mat[i].length
  • 1 <= m, n <= 300
  • 0 <= mat[i][j] <= 10^4
  • 0 <= threshold <= 10^5

思路

有一个 m x n 矩阵,返回其中元素和不超过 threshold 的正方形的最大边长。

使用二维前缀和计算面积,枚举右下顶点与边长,边长从已知的最大值开始枚举,超出 threshold 就退出循环,时间复杂度为 O(mn + min(m, n))

代码


/**
 * @date 2026-01-19 9:46
 */
public class MaxSideLength1292 {

    public int maxSideLength_v1(int[][] mat, int threshold) {
        int m = mat.length;
        int n = mat[0].length;
        int[][] prefix = new int[m + 1][n + 1];
        int res = 0;
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                prefix[i][j] = prefix[i][j - 1] + mat[i - 1][j - 1] + prefix[i - 1][j] - prefix[i - 1][j - 1];
                int l = Math.min(i, j);
                for (int k = res + 1; k <= l; k++) {
                    int area = prefix[i][j] - prefix[i - k][j] - prefix[i][j - k] + prefix[i - k][j - k];
                    if (area <= threshold) {
                        res = Math.max(res, k);
                    } else {
                        break;
                    }
                }
            }
        }
        return res;
    }

}

性能

3453.分割正方形I

目标

给你一个二维整数数组 squares ,其中 squares[i] = [xi, yi, li] 表示一个与 x 轴平行的正方形的左下角坐标和正方形的边长。

找到一个最小的 y 坐标,它对应一条水平线,该线需要满足它以上正方形的总面积 等于 该线以下正方形的总面积。

答案如果与实际答案的误差在 10^-5 以内,将视为正确答案。

注意:正方形 可能会 重叠。重叠区域应该被 多次计数 。

示例 1:

输入: squares = [[0,0,1],[2,2,1]]
输出: 1.00000
解释:
任何在 y = 1 和 y = 2 之间的水平线都会有 1 平方单位的面积在其上方,1 平方单位的面积在其下方。最小的 y 坐标是 1。

示例 2:

输入: squares = [[0,0,2],[1,1,1]]
输出: 1.16667
解释:
面积如下:
线下的面积:7/6 * 2 (红色) + 1/6 (蓝色) = 15/6 = 2.5。
线上的面积:5/6 * 2 (红色) + 5/6 (蓝色) = 15/6 = 2.5。
由于线以上和线以下的面积相等,输出为 7/6 = 1.16667。

说明:

  • 1 <= squares.length <= 5 * 10^4
  • squares[i] = [xi, yi, li]
  • squares[i].length == 3
  • 0 <= xi, yi <= 10^9
  • 1 <= li <= 10^9
  • 所有正方形的总面积不超过 10^12。

思路

二维平面内有一些正方形,squares[i] = [xi, yi, li] 表示正方形坐下顶点坐标为 (xi, yi),边长为 li。找到最小的 y 水平线,使得这些正方形在水平线上方的面积等于下方的面积,重叠部分的面积应被重复计算。

二分答案,计算上方与下方的面积。

代码


/**
 * @date 2026-01-13 9:08
 */
public class SeparateSquares3453 {

    public double separateSquares(int[][] squares) {
        double l = 0.0, r = 1000000000.0;
        double m = l + (r - l) / 2;
        while (l <= r) {
            if (check(squares, m)) {
                r = m - 0.00001;
            } else {
                l = m + 0.00001;
            }
            m = l + (r - l) / 2;
        }
        return l;
    }

    private boolean check(int[][] squares, double m) {
        double upperSum = 0.0, lowerSum = 0.0;
        for (int[] square : squares) {
            int y = square[1], l = square[2];
            if (y + l <= m) {
                lowerSum += (double) l * l;
            } else if (y >= m) {
                upperSum += (double) l * l;
            } else {
                upperSum += (double) l * (y + l - m);
                lowerSum += (double) l * (m - y);
            }
        }
        return upperSum <= lowerSum;
    }

}

性能

1970.你能穿过矩阵的最后一天

目标

给你一个下标从 1 开始的二进制矩阵,其中 0 表示陆地,1 表示水域。同时给你 row 和 col 分别表示矩阵中行和列的数目。

一开始在第 0 天,整个 矩阵都是 陆地 。但每一天都会有一块新陆地被 水 淹没变成水域。给你一个下标从 1 开始的二维数组 cells ,其中 cells[i] = [ri, ci] 表示在第 i 天,第 ri 行 ci 列(下标都是从 1 开始)的陆地会变成 水域 (也就是 0 变成 1 )。

你想知道从矩阵最 上面 一行走到最 下面 一行,且只经过陆地格子的 最后一天 是哪一天。你可以从最上面一行的 任意 格子出发,到达最下面一行的 任意 格子。你只能沿着 四个 基本方向移动(也就是上下左右)。

请返回只经过陆地格子能从最 上面 一行走到最 下面 一行的 最后一天 。

示例 1:

输入:row = 2, col = 2, cells = [[1,1],[2,1],[1,2],[2,2]]
输出:2
解释:上图描述了矩阵从第 0 天开始是如何变化的。
可以从最上面一行到最下面一行的最后一天是第 2 天。

示例 2:

输入:row = 2, col = 2, cells = [[1,1],[1,2],[2,1],[2,2]]
输出:1
解释:上图描述了矩阵从第 0 天开始是如何变化的。
可以从最上面一行到最下面一行的最后一天是第 1 天。

示例 3:

输入:row = 3, col = 3, cells = [[1,2],[2,1],[3,3],[2,2],[1,1],[1,3],[2,3],[3,2],[3,1]]
输出:3
解释:上图描述了矩阵从第 0 天开始是如何变化的。
可以从最上面一行到最下面一行的最后一天是第 3 天。

说明:

  • 2 <= row, col <= 2 * 10^4
  • 4 <= row col <= 2 10^4
  • cells.length == row * col
  • 1 <= ri <= row
  • 1 <= ci <= col
  • cells 中的所有格子坐标都是 唯一 的。

思路

有一个二进制矩阵,0 代表陆地,1 代表水域。第 0 天,整个矩阵都是陆地,之后的每一天都会有一块陆地变为水域,cells[i] = [rowi, coli] 表示第 i + 1 天,矩阵的 rowi 行,coli 列会变为水域,注意行列的下标从 1 开始。返回能从第一行走到最后一行的最后一天是哪天。

BFS 可用于判断路径是否存在,二分答案判断即可。

代码


/**
 * @date 2025-12-31 9:56
 */
public class LatestDayToCross1970 {

    public int latestDayToCross(int row, int col, int[][] cells) {
        int l = 0, r = cells.length - 1;
        int m = l + (r - l) / 2;
        while (l <= r) {
            if (check(row, col, m, cells)) {
                l = m + 1;
            } else {
                r = m - 1;
            }
            m = l + (r - l) / 2;
        }
        return r;
    }

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

    public boolean check(int row, int col, int m, int[][] cells) {
        int[][] grid = new int[row + 1][col + 1];
        for (int i = 0; i < m; i++) {
            grid[cells[i][0]][cells[i][1]] = 1;
        }
        Deque<int[]> q = new ArrayDeque<>();
        for (int i = 1; i <= col; i++) {
            if (grid[1][i] == 0) {
                grid[1][i] = 1;
                q.offer(new int[]{1, i});
            }
        }
        while (!q.isEmpty()) {
            int size = q.size();
            for (int i = 0; i < size; i++) {
                int[] cur = q.poll();
                for (int[] direction : directions) {
                    int x = cur[0] + direction[0];
                    int y = cur[1] + direction[1];
                    if (x >= 1 && x <= row && y >= 1 && y <= col && grid[x][y] == 0) {
                        if (x == row) {
                            return true;
                        }
                        grid[x][y] = 1;
                        q.offer(new int[]{x, y});
                    }
                }
            }
        }
        return false;
    }

}

性能

1351.统计有序矩阵中的负数

目标

给你一个 m * n 的矩阵 grid,矩阵中的元素无论是按行还是按列,都以非严格递减顺序排列。 请你统计并返回 grid 中 负数 的数目。

示例 1:

输入:grid = [[4,3,2,-1],[3,2,1,-1],[1,1,-1,-2],[-1,-1,-2,-3]]
输出:8
解释:矩阵中共有 8 个负数。

示例 2:

输入:grid = [[3,2],[1,0]]
输出:0

说明:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 100
  • -100 <= grid[i][j] <= 100

进阶:你可以设计一个时间复杂度为 O(n + m) 的解决方案吗?

思路

有一个 m x n 矩阵 grid,按行按列非严格递减,统计其中的负数个数,要求时间复杂度为 O(m + n)

暴力做法的复杂度为 O(m x n)。根据矩阵有序的性质,如果 grid[i][j] < 0,那么 grid[i + k][j] < 0,对于 i + k 行,只需继续向前判断,累加当前行的负数个数即可。

代码


/**
 * @date 2025-12-30 11:26
 */
public class CountNegatives1351 {

    public int countNegatives(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        int i = 0, j = n - 1;
        int res = 0;
        while (i < m) {
            while (j >= 0 && grid[i][j] < 0) {
                j--;
            }
            res += n - j - 1;
            i++;
        }
        return res;
    }

}

性能

2054.两个最好的不重叠活动

目标

给你一个下标从 0 开始的二维整数数组 events ,其中 events[i] = [startTimei, endTimei, valuei] 。第 i 个活动开始于 startTimei ,结束于 endTimei ,如果你参加这个活动,那么你可以得到价值 valuei 。你 最多 可以参加 两个时间不重叠 活动,使得它们的价值之和 最大 。

请你返回价值之和的 最大值 。

注意,活动的开始时间和结束时间是 包括 在活动时间内的,也就是说,你不能参加两个活动且它们之一的开始时间等于另一个活动的结束时间。更具体的,如果你参加一个活动,且结束时间为 t ,那么下一个活动必须在 t + 1 或之后的时间开始。

示例 1:

输入:events = [[1,3,2],[4,5,2],[2,4,3]]
输出:4
解释:选择绿色的活动 0 和 1 ,价值之和为 2 + 2 = 4 。

示例 2:

输入:events = [[1,3,2],[4,5,2],[1,5,5]]
输出:5
解释:选择活动 2 ,价值和为 5 。

示例 3:

输入:events = [[1,5,3],[1,5,1],[6,6,5]]
输出:8
解释:选择活动 0 和 2 ,价值之和为 3 + 5 = 8 。

说明:

  • 2 <= events.length <= 10^5
  • events[i].length == 3
  • 1 <= startTimei <= endTimei <= 10^9
  • 1 <= valuei <= 10^6

思路

有一个二维数组 eventsevents[i] 表示事件 i 的 (开始时间,结束时间,价值) 三元组,至多参加两个活动,这两个活动不能重叠 (结束时间与开始时间也不能重叠),求参加活动的最大价值。

根据开始时间排序,二分查找第一个大于结束时间的下标,维护后缀最大值。

代码


/**
 * @date 2025-12-23 8:53
 */
public class MaxTwoEvents2054 {

    public int maxTwoEvents(int[][] events) {
        Arrays.sort(events, (a, b) -> a[0] - b[0]);
        int res = 0;
        int n = events.length;
        int[] suffix = new int[n + 1];
        for (int i = n - 1; i >= 0; i--) {
            suffix[i] = Math.max(suffix[i + 1], events[i][2]);
        }
        for (int[] event : events) {
            int index = bs(events, event[1]);
            res = Math.max(res, event[2] + suffix[index]);
        }
        return res;
    }

    public int bs(int[][] events, int target) {
        int l = 0;
        int r = events.length - 1;
        int m = l + (r - l) / 2;
        while (l <= r) {
            if (events[m][0] <= target) {
                l = m + 1;
            } else {
                r = m - 1;
            }
            m = l + (r - l) / 2;
        }
        return l;
    }

}

性能

2141.同时运行N台电脑的最长时间

目标

你有 n 台电脑。给你整数 n 和一个下标从 0 开始的整数数组 batteries ,其中第 i 个电池可以让一台电脑 运行 batteries[i] 分钟。你想使用这些电池让 全部 n 台电脑 同时 运行。

一开始,你可以给每台电脑连接 至多一个电池 。然后在任意整数时刻,你都可以将一台电脑与它的电池断开连接,并连接另一个电池,你可以进行这个操作 任意次 。新连接的电池可以是一个全新的电池,也可以是别的电脑用过的电池。断开连接和连接新的电池不会花费任何时间。

注意,你不能给电池充电。

请你返回你可以让 n 台电脑同时运行的 最长 分钟数。

示例 1:

输入:n = 2, batteries = [3,3,3]
输出:4
解释:
一开始,将第一台电脑与电池 0 连接,第二台电脑与电池 1 连接。
2 分钟后,将第二台电脑与电池 1 断开连接,并连接电池 2 。注意,电池 0 还可以供电 1 分钟。
在第 3 分钟结尾,你需要将第一台电脑与电池 0 断开连接,然后连接电池 1 。
在第 4 分钟结尾,电池 1 也被耗尽,第一台电脑无法继续运行。
我们最多能同时让两台电脑同时运行 4 分钟,所以我们返回 4 。

示例 2:

输入:n = 2, batteries = [1,1,1,1]
输出:2
解释:
一开始,将第一台电脑与电池 0 连接,第二台电脑与电池 2 连接。
一分钟后,电池 0 和电池 2 同时耗尽,所以你需要将它们断开连接,并将电池 1 和第一台电脑连接,电池 3 和第二台电脑连接。
1 分钟后,电池 1 和电池 3 也耗尽了,所以两台电脑都无法继续运行。
我们最多能让两台电脑同时运行 2 分钟,所以我们返回 2 。

说明:

  • 1 <= n <= batteries.length <= 10^5
  • 1 <= batteries[i] <= 10^9

思路

有 m 个电池,batteries[i] 表示第 i 个电池的电量,将电池分成 n 组,要求每组电池电量和的最小值最大。

贪心的做法是找到上界 x = sum / n,从大到小遍历电池容量:

  • 如果大于 x,超过的部分也不能再使用了,因为一个电池在同一时间只能为一台电池供电,问题规模缩小,sum -= batteries[i]n--
  • 如果小于等于 x,可以用完了再接上下一个电池,不会出现一个电池供两台电脑的情况,因为如果出现这种情况,总是可以将其放到一台的末尾与一台的开头将它们错开。

代码


/**
 * @date 2025-12-01 8:57
 */
public class MaxRunTime2141 {

    public long maxRunTime(int n, int[] batteries) {
        long sum = 0L;
        for (int battery : batteries) {
            sum += battery;
        }
        Arrays.sort(batteries);
        int m = batteries.length;
        for (int i = m - 1; i >= 0; i--) {
            long x = sum / n;
            if (batteries[i] <= x) {
                return x;
            }
            sum -= batteries[i];
            n--;
        }
        return -1;
    }
}

性能