2960.统计已测试设备

目标

给你一个长度为 n 、下标从 0 开始的整数数组 batteryPercentages ,表示 n 个设备的电池百分比。

你的任务是按照顺序测试每个设备 i,执行以下测试操作:

  • 如果 batteryPercentages[i] 大于 0:
    • 增加 已测试设备的计数。
    • 将下标在 [i + 1, n - 1] 的所有设备的电池百分比减少 1,确保它们的电池百分比 不会低于 0 ,即 batteryPercentages[j] = max(0, batteryPercentages[j] - 1)。
    • 移动到下一个设备。
  • 否则,移动到下一个设备而不执行任何测试。

返回一个整数,表示按顺序执行测试操作后 已测试设备 的数量。

示例 1:

输入:batteryPercentages = [1,1,2,1,3]
输出:3
解释:按顺序从设备 0 开始执行测试操作:
在设备 0 上,batteryPercentages[0] > 0 ,现在有 1 个已测试设备,batteryPercentages 变为 [1,0,1,0,2] 。
在设备 1 上,batteryPercentages[1] == 0 ,移动到下一个设备而不进行测试。
在设备 2 上,batteryPercentages[2] > 0 ,现在有 2 个已测试设备,batteryPercentages 变为 [1,0,1,0,1] 。
在设备 3 上,batteryPercentages[3] == 0 ,移动到下一个设备而不进行测试。
在设备 4 上,batteryPercentages[4] > 0 ,现在有 3 个已测试设备,batteryPercentages 保持不变。
因此,答案是 3 。

示例 2:

输入:batteryPercentages = [0,1,2]
输出:2
解释:按顺序从设备 0 开始执行测试操作:
在设备 0 上,batteryPercentages[0] == 0 ,移动到下一个设备而不进行测试。
在设备 1 上,batteryPercentages[1] > 0 ,现在有 1 个已测试设备,batteryPercentages 变为 [0,1,1] 。
在设备 2 上,batteryPercentages[2] > 0 ,现在有 2 个已测试设备,batteryPercentages 保持不变。
因此,答案是 2 。

说明:

  • 1 <= n == batteryPercentages.length <= 100
  • 0 <= batteryPercentages[i] <= 100

思路

如果设备的剩余电量大于0,已测试设备加1且后续设备的所有电量减1。

简而言之,后续设备需要减去的电量为已检测的设备数。

题解上说这是差分的思想。我们没必要检测一个设备就向后循环并将电量减1,当前设备需要减去的电量等于上一个设备需要减去的电量+1。

代码

/**
 * @date 2024-05-10 8:43
 */
public class CountTestedDevices2960 {
    public int countTestedDevices(int[] batteryPercentages) {
        int res = 0;
        for (int i = 0; i < batteryPercentages.length; i++) {
            if (batteryPercentages[i] > res) {
                res++;
            }
        }
        return res;
    }
}

性能

2105.给植物浇水II

目标

Alice 和 Bob 打算给花园里的 n 株植物浇水。植物排成一行,从左到右进行标记,编号从 0 到 n - 1 。其中,第 i 株植物的位置是 x = i 。

每一株植物都需要浇特定量的水。Alice 和 Bob 每人有一个水罐,最初是满的 。他们按下面描述的方式完成浇水:

  • Alice 按 从左到右 的顺序给植物浇水,从植物 0 开始。Bob 按 从右到左 的顺序给植物浇水,从植物 n - 1 开始。他们 同时 给植物浇水。
  • 如果没有足够的水 完全 浇灌下一株植物,他 / 她会立即重新灌满浇水罐。
  • 不管植物需要多少水,浇水所耗费的时间都是一样的。
  • 不能 提前重新灌满水罐。
  • 每株植物都可以由 Alice 或者 Bob 来浇水。
  • 如果 Alice 和 Bob 到达同一株植物,那么当前水罐中水更多的人会给这株植物浇水。如果他俩水量相同,那么 Alice 会给这株植物浇水。

给你一个下标从 0 开始的整数数组 plants ,数组由 n 个整数组成。其中,plants[i] 为第 i 株植物需要的水量。另有两个整数 capacityA 和 capacityB 分别表示 Alice 和 Bob 水罐的容量。返回两人浇灌所有植物过程中重新灌满水罐的 次数 。

示例 1:

输入:plants = [2,2,3,3], capacityA = 5, capacityB = 5
输出:1
解释:
- 最初,Alice 和 Bob 的水罐中各有 5 单元水。
- Alice 给植物 0 浇水,Bob 给植物 3 浇水。
- Alice 和 Bob 现在分别剩下 3 单元和 2 单元水。
- Alice 有足够的水给植物 1 ,所以她直接浇水。Bob 的水不够给植物 2 ,所以他先重新装满水,再浇水。
所以,两人浇灌所有植物过程中重新灌满水罐的次数 = 0 + 0 + 1 + 0 = 1 。

示例 2:

输入:plants = [2,2,3,3], capacityA = 3, capacityB = 4
输出:2
解释:
- 最初,Alice 的水罐中有 3 单元水,Bob 的水罐中有 4 单元水。
- Alice 给植物 0 浇水,Bob 给植物 3 浇水。
- Alice 和 Bob 现在都只有 1 单元水,并分别需要给植物 1 和植物 2 浇水。
- 由于他们的水量均不足以浇水,所以他们重新灌满水罐再进行浇水。
所以,两人浇灌所有植物过程中重新灌满水罐的次数 = 0 + 1 + 1 + 0 = 2 。

示例 3:

输入:plants = [5], capacityA = 10, capacityB = 8
输出:0
解释:
- 只有一株植物
- Alice 的水罐有 10 单元水,Bob 的水罐有 8 单元水。因此 Alice 的水罐中水更多,她会给这株植物浇水。
所以,两人浇灌所有植物过程中重新灌满水罐的次数 = 0 。

说明:

  • n == plants.length
  • 1 <= n <= 10^5
  • 1 <= plants[i] <= 10^6
  • max(plants[i]) <= capacityA, capacityB <= 10^9

思路

两个人同时从左右两边开始浇水,如果水不够可以立即装满,浇水耗时都相同,如果同时到达相同的植物则剩余水多的浇,问总重新灌水的次数。

由于不计算装水时间,浇水耗时又相同,两人同时从左右开始浇水,同时到达同一植物只有在总植物数量为奇数时才会发生。

先计算二人各自需要重新灌水的次数然后再考虑是否存在同时到达以及是否需要重新灌水即可。

需要注意,无论n的奇偶性, for (int i = 0; i < (n >> 1); i++) {} 与 for (int i = n - 1; i > (n - 1 >> 1); i--) {} 都不会访问到中间节点。

代码

/**
 * @date 2024-05-09 8:40
 */
public class MinimumRefill2105 {
    public int minimumRefill(int[] plants, int capacityA, int capacityB) {
        int n = plants.length;
        int remainderA = capacityA;
        int remainderB = capacityB;
        int res = 0;
        for (int i = 0; i < (n >> 1); i++) {
            if (remainderA < plants[i]) {
                remainderA = capacityA - plants[i];
                res++;
            } else {
                remainderA -= plants[i];
            }
        }
        for (int i = n - 1; i > (n - 1 >> 1); i--) {
            if (remainderB < plants[i]) {
                remainderB = capacityB - plants[i];
                res++;
            } else {
                remainderB -= plants[i];
            }
        }
        if (n % 2 == 1) {
            int remainder = Math.max(remainderA, remainderB);
            if (remainder < plants[n >> 1]) {
                res++;
            }
        }

        return res;
    }
}

性能

2079.给植物浇水

目标

你打算用一个水罐给花园里的 n 株植物浇水。植物排成一行,从左到右进行标记,编号从 0 到 n - 1 。其中,第 i 株植物的位置是 x = i 。x = -1 处有一条河,你可以在那里重新灌满你的水罐。

每一株植物都需要浇特定量的水。你将会按下面描述的方式完成浇水:

  • 按从左到右的顺序给植物浇水。
  • 在给当前植物浇完水之后,如果你没有足够的水 完全 浇灌下一株植物,那么你就需要返回河边重新装满水罐。
  • 你 不能 提前重新灌满水罐。

最初,你在河边(也就是,x = -1),在 x 轴上每移动 一个单位 都需要 一步 。

给你一个下标从 0 开始的整数数组 plants ,数组由 n 个整数组成。其中,plants[i] 为第 i 株植物需要的水量。另有一个整数 capacity 表示水罐的容量,返回浇灌所有植物需要的 步数 。

示例 1:

输入:plants = [2,2,3,3], capacity = 5
输出:14
解释:从河边开始,此时水罐是装满的:
- 走到植物 0 (1 步) ,浇水。水罐中还有 3 单位的水。
- 走到植物 1 (1 步) ,浇水。水罐中还有 1 单位的水。
- 由于不能完全浇灌植物 2 ,回到河边取水 (2 步)。
- 走到植物 2 (3 步) ,浇水。水罐中还有 2 单位的水。
- 由于不能完全浇灌植物 3 ,回到河边取水 (3 步)。
- 走到植物 3 (4 步) ,浇水。
需要的步数是 = 1 + 1 + 2 + 3 + 3 + 4 = 14 。

示例 2:

输入:plants = [1,1,1,4,2,3], capacity = 4
输出:30
解释:从河边开始,此时水罐是装满的:
- 走到植物 0,1,2 (3 步) ,浇水。回到河边取水 (3 步)。
- 走到植物 3 (4 步) ,浇水。回到河边取水 (4 步)。
- 走到植物 4 (5 步) ,浇水。回到河边取水 (5 步)。
- 走到植物 5 (6 步) ,浇水。
需要的步数是 = 3 + 3 + 4 + 4 + 5 + 5 + 6 = 30 。

示例 3:

输入:plants = [7,7,7,7,7,7,7], capacity = 8
输出:49
解释:每次浇水都需要重新灌满水罐。
需要的步数是 = 1 + 1 + 2 + 2 + 3 + 3 + 4 + 4 + 5 + 5 + 6 + 6 + 7 = 49 。

说明:

  • n == plants.length
  • 1 <= n <= 1000
  • 1 <= plants[i] <= 10^6
  • max(plants[i]) <= capacity <= 10^9

思路

简单的动态规划。

代码

/**
 * @date 2024-05-08 0:01
 */
public class WateringPlants2079 {
    public int wateringPlants(int[] plants, int capacity) {
        int n = plants.length;
        int[] dp = new int[n];
        int remainder = capacity - plants[0];
        dp[0] = 1;
        for (int i = 1; i < plants.length; i++) {
            if (remainder >= plants[i]) {
                remainder -= plants[i];
                dp[i] = dp[i - 1] + 1;
            } else {
                remainder = capacity - plants[i];
                dp[i] = dp[i - 1] + (i << 1) + 1;
            }
        }
        return dp[n - 1];
    }
}

性能

857.雇佣K名工人的最低成本

目标

有 n 名工人。 给定两个数组 quality 和 wage ,其中,quality[i] 表示第 i 名工人的工作质量,其最低期望工资为 wage[i] 。

现在我们想雇佣 k 名工人组成一个工资组。在雇佣 一组 k 名工人时,我们必须按照下述规则向他们支付工资:

  1. 对工资组中的每名工人,应当按其工作质量与同组其他工人的工作质量的比例来支付工资。
  2. 工资组中的每名工人至少应当得到他们的最低期望工资。

给定整数 k ,返回 组成满足上述条件的付费群体所需的最小金额 。在实际答案的 10^-5 以内的答案将被接受。

示例 1:

输入: quality = [10,20,5], wage = [70,50,30], k = 2
输出: 105.00000
解释: 我们向 0 号工人支付 70,向 2 号工人支付 35。

示例 2:

输入: quality = [3,1,10,10,1], wage = [4,8,2,2,7], k = 3
输出: 30.66667
解释: 我们向 0 号工人支付 4,向 2 号和 3 号分别支付 13.33333。

说明:

  • n == quality.length == wage.length
  • 1 <= k <= n <= 10^4
  • 1 <= quality[i], wage[i] <= 10^4

思路

从quality中选k个,按照工作质量比例支付工资,并且每人的工资不能低于wage中的最低期望工资,问雇佣这k个人的最低成本是多少。

// todo

代码

/**
 * @date 2024-05-02 20:44
 */
public class MincostToHireWorkers857 {
    public double mincostToHireWorkers(int[] quality, int[] wage, int k) {
        int n = wage.length;
        PriorityQueue<double[]> wqq = new PriorityQueue<>(Comparator.comparingDouble(x -> x[0]));
        double[][] wq = new double[n][2];
        for (int i = 0; i < wage.length; i++) {
            wq[i][0] = (double) wage[i] / quality[i];
            wq[i][1] = i;
            wqq.offer(wq[i]);
        }
        PriorityQueue<Integer> q = new PriorityQueue<>((x, y) -> y - x);
        int sum = 0;
        int ri = 0;
        for (int i = 0; i < k; i++) {
            double[] choose = wqq.poll();
            int index = (int) choose[1];
            sum += quality[index];
            q.offer(quality[index]);
            ri = (int) choose[1];
        }
        double res = (double) sum * wage[ri] / quality[ri];
        while (!wqq.isEmpty()) {
            double[] choose = wqq.poll();
            int index = (int) choose[1];
            if (q.peek() > quality[index]) {
                sum = sum - q.peek() + quality[index];
                double resTmp = (double)sum * wage[index] / quality[index];
                q.poll();
                q.offer(quality[index]);
                if (res > resTmp) {
                    res = resTmp;
                }
            }
        }
        return res;
    }
}

性能

2462.雇佣K位工人的总代价

目标

给你一个下标从 0 开始的整数数组 costs ,其中 costs[i] 是雇佣第 i 位工人的代价。

同时给你两个整数 k 和 candidates 。我们想根据以下规则恰好雇佣 k 位工人:

  • 总共进行 k 轮雇佣,且每一轮恰好雇佣一位工人。
  • 在每一轮雇佣中,从最前面 candidates 和最后面 candidates 人中选出代价最小的一位工人,如果有多位代价相同且最小的工人,选择下标更小的一位工人。
    • 比方说,costs = [3,2,7,7,1,2] 且 candidates = 2 ,第一轮雇佣中,我们选择第 4 位工人,因为他的代价最小 [3,2,7,7,1,2] 。
    • 第二轮雇佣,我们选择第 1 位工人,因为他们的代价与第 4 位工人一样都是最小代价,而且下标更小,[3,2,7,7,2] 。注意每一轮雇佣后,剩余工人的下标可能会发生变化。
  • 如果剩余员工数目不足 candidates 人,那么下一轮雇佣他们中代价最小的一人,如果有多位代价相同且最小的工人,选择下标更小的一位工人。
  • 一位工人只能被选择一次。

返回雇佣恰好 k 位工人的总代价。

示例 1:

输入:costs = [17,12,10,2,7,2,11,20,8], k = 3, candidates = 4
输出:11
解释:我们总共雇佣 3 位工人。总代价一开始为 0 。
- 第一轮雇佣,我们从 [17,12,10,2,7,2,11,20,8] 中选择。最小代价是 2 ,有两位工人,我们选择下标更小的一位工人,即第 3 位工人。总代价是 0 + 2 = 2 。
- 第二轮雇佣,我们从 [17,12,10,7,2,11,20,8] 中选择。最小代价是 2 ,下标为 4 ,总代价是 2 + 2 = 4 。
- 第三轮雇佣,我们从 [17,12,10,7,11,20,8] 中选择,最小代价是 7 ,下标为 3 ,总代价是 4 + 7 = 11 。注意下标为 3 的工人同时在最前面和最后面 4 位工人中。
总雇佣代价是 11 。

示例 2:

输入:costs = [1,2,4,1], k = 3, candidates = 3
输出:4
解释:我们总共雇佣 3 位工人。总代价一开始为 0 。
- 第一轮雇佣,我们从 [1,2,4,1] 中选择。最小代价为 1 ,有两位工人,我们选择下标更小的一位工人,即第 0 位工人,总代价是 0 + 1 = 1 。注意,下标为 1 和 2 的工人同时在最前面和最后面 3 位工人中。
- 第二轮雇佣,我们从 [2,4,1] 中选择。最小代价为 1 ,下标为 2 ,总代价是 1 + 1 = 2 。
- 第三轮雇佣,少于 3 位工人,我们从剩余工人 [2,4] 中选择。最小代价是 2 ,下标为 0 。总代价为 2 + 2 = 4 。
总雇佣代价是 4 。

说明:

  • 1 <= costs.length <= 10^5
  • 1 <= costs[i] <= 10^5
  • 1 <= k, candidates <= costs.length

思路

给我们一个数组 costs 和一个整数 candidates,每次从数组的前candidates与后candidates中选取值最小的元素,如果最小值相等则取下标最小的,costs中的每个值只能被选一次,求选出的k个元素和。

很直接的想法是使用优先队列保存这些元素,考虑到相同值需要取下标最小的,于是还要保存下标信息。循环从队列取出头元素累加到res,每取出一个元素需要从前/后补一个元素到队列中。

注意题目的数据范围,需要返回long类型。还有就是放队列以及补元素的停止条件以免重复。

代码

/**
 * @date 2024-05-01 17:03
 */
public class TotalCost2462 {
    public long totalCost(int[] costs, int k, int candidates) {
        PriorityQueue<Integer[]> q = new PriorityQueue<>((x, y) -> {
            int compare = x[0] - y[0];
            return compare == 0 ? x[1] - y[1] : compare;
        });
        int n = costs.length;
        int leftPos;
        int rightPos = n - 1;
        for (leftPos = 0; leftPos < candidates; leftPos++) {
            if (leftPos < rightPos) {
                // rightPos 与 leftPos指向的都是需要添加的下一个位置,
                q.offer(new Integer[]{costs[leftPos], leftPos});
                q.offer(new Integer[]{costs[rightPos], rightPos});
                rightPos--;
            }
            else if (leftPos == rightPos) {
                // 这里已经覆盖了数组的所有元素,循环结束后leftPos > rightPos
                q.offer(new Integer[]{costs[leftPos], leftPos});
            }
            else {
                break;
            }
        }
        // 不使用long会溢出
        long res = 0;
        // 选到最小之后,补够candidates
        for (int i = 0; i < k && !q.isEmpty(); i++) {
            Integer[] c = q.poll();
            res += c[0];
            // 如果已经覆盖了所有元素就无需再添加了
            if (leftPos > rightPos) {
                continue;
            }
            if (c[1] < leftPos) {
                q.offer(new Integer[]{costs[leftPos], leftPos});
                leftPos++;
            } else if (c[1] > rightPos) {
                q.offer(new Integer[]{costs[rightPos], rightPos});
                rightPos--;
            }
        }

        return res;
    }
}

性能

勉强通过,官网也是这个思路,耗时82ms。有网友的解法是分别使用了两个优先队列,只需要22ms。