LeetCode 2105给植物浇水II

LeetCode 算法题解析:两人浇灌植物的最少补水次数

一、题目详细解读

在这个有趣的算法问题中,我们面临这样一个场景:Alice 和 Bob 要给花园里排成一行的 n 株植物浇水。这些植物从左到右依次编号为 0 到 n - 1,并且每一株植物都有其特定的需水量,第 i 株植物的需水量由数组 plants[i] 表示。Alice 和 Bob 每人都有一个初始状态为满水的水罐,水罐的容量分别为 capacityA 和 capacityB

他们的浇水规则如下:

  1. 浇水方向与同步性:Alice 从最左边的植物(编号为 0)开始,按照从左到右的顺序依次给植物浇水;Bob 则从最右边的植物(编号为 n - 1)开始,按照从右到左的顺序同时给植物浇水。这意味着他们的动作是同时进行的,而且给每株植物浇水所需的时间是相同的,不受植物需水量的影响。

  2. 水量与补水机制:当面对一株植物时,如果 Alice 或 Bob 水罐中的水量足够满足该植物的需水量,那么他们必须使用水罐中的水来给这株植物浇水;如果水罐中的水量不足以满足植物的需水量,那么他们需要先立即重新灌满水罐,然后再给植物浇水。

  3. 相遇时的浇水决策:如果在浇水过程中,Alice 和 Bob 同时到达了同一株植物,那么需要比较他们水罐中的水量。水罐中剩余水量较多的一方将负责给这株植物浇水;如果两人水罐中的剩余水量相同,按照规则由 Alice 给这株植物浇水。

我们的任务就是精确计算出在浇灌完所有植物的过程中,两人总共需要重新灌满水罐的次数。

示例 1:

  • 输入
plants = [2, 1, 1]
capacityA = 2
capacityB = 2
  • 输出
0
  • 详细解释: 初始时,Alice 的水罐中有 2 单位的水,Bob 的水罐中也有 2 单位的水。 Alice 开始给 plants[0] 浇水,这株植物需要 2 单位的水,Alice 的水罐水量刚好足够,浇完后水罐中剩余 0 单位的水。 Bob 开始给 plants[2] 浇水,这株植物需要 1 单位的水,Bob 浇完后水罐中剩余 1 单位的水。 接着两人相遇在 plants[1],此时 Alice 水罐中剩余 0 单位的水,Bob 水罐中剩余 1 单位的水。由于 Bob 水罐中的水量更多,所以 Bob 直接用剩余的 1 单位水给 plants[1] 浇水,在整个浇水过程中,两人都不需要重新灌满水罐,因此重新灌满水罐的次数为 0

示例 2:

  • 输入
plants = [2, 2, 3]
capacityA = 2
capacityB = 3
  • 输出
2
  • 详细解释: 初始状态下,Alice 的水罐有 2 单位的水,Bob 的水罐有 3 单位的水。 Alice 面对 plants[0],这株植物需要 2 单位的水,Alice 的水罐水量刚好足够,浇完后水罐中剩余 0 单位的水。 Bob 面对 plants[2],这株植物需要 3 单位的水,Bob 的水罐水量也刚好足够,浇完后水罐中剩余 0 单位的水。 当 Alice 处理 plants[1] 时,她的水罐中剩余 0 单位的水,而这株植物需要 2 单位的水,所以 Alice 必须重新灌满水罐,然后给这株植物浇水,此时重新灌满水罐的次数增加 1。 同时,Bob 在处理 plants[1] 时,他的水罐同样剩余 0 单位的水,也需要重新灌满水罐后才能给这株植物浇水,重新灌满水罐的次数再增加 1。最终,两人重新灌满水罐的总次数为 2

二、解题思路深度剖析

我们采用双指针法来解决这个问题,这种方法能够有效地模拟 Alice 和 Bob 的浇水过程,并且清晰地处理各种情况。

  1. 指针和变量初始化

    • 定义左指针 left,并将其初始化为 0,它代表 Alice 浇水的起始位置,也就是从最左边的植物开始浇水。
    • 定义右指针 right,将其初始化为 plants.length - 1,它代表 Bob 浇水的起始位置,即从最右边的植物开始浇水。
    • 设立变量 refill 用于记录两人在整个浇水过程中重新灌满水罐的总次数,初始值设为 0
    • 分别用 waterA 和 waterB 来跟踪 Alice 和 Bob 水罐中的实时水量,初始时,waterA = capacityAwaterB = capacityB,即水罐都处于满水状态。
  2. 指针同步移动与浇水操作

    • 在 left < right 的条件下,进入循环,模拟两人同时浇水的过程。
    • 对于 Alice 负责的一侧(左指针 left 指向的植物):
      • 首先检查 waterA 是否小于 plants[left],如果小于,这意味着 Alice 水罐中的水不足以浇灌当前的植物。在这种情况下,需要重新灌满水罐,于是 refill 的值增加 1,同时将 waterA 重置为 capacityA,即水罐重新装满。
      • 然后,无论是否进行了补水操作,都要更新 Alice 水罐中的剩余水量,即 waterA -= plants[left],表示用掉了浇灌当前植物所需的水量。之后,将左指针 left 向右移动一位,即 left++,准备处理下一株植物。
    • 对于 Bob 负责的一侧(右指针 right 指向的植物):
      • 类似地,检查 waterB 是否小于 plants[right],如果小于,说明 Bob 水罐中的水不够浇灌当前的植物。此时 refill 的值增加 1,同时将 waterB 重置为 capacityB,即重新灌满水罐。
      • 接着更新 Bob 水罐中的剩余水量,waterB -= plants[right],并将右指针 right 向左移动一位,即 right--,准备处理下一株植物。
  3. 相遇情况的特殊处理

    • 当 left == right 时,表明 Alice 和 Bob 相遇在同一株植物处。
    • 此时有以下几种情况需要分别考虑:
      • 若 waterA < plants[left] 且 waterB < plants[right],说明两人水罐中的水都不足以浇灌这株植物。由于在水量相同的情况下是 Alice 浇水,所以这里优先让 Alice 重新灌满水罐,refill 的值增加 1
      • 若 waterA < plants[left] 但 waterB >= plants[right],意味着 Alice 水罐中的水不够,而 Bob 水罐中的水足够,此时由 Bob 浇水,不需要重新灌满水罐,refill 的值保持不变。
      • 若 waterA >= plants[left],即 Alice 水罐中的水足够,那么由 Alice 浇水,同样不需要重新灌满水罐,refill 的值也保持不变。

三、代码实现(Java)

class Solution {
    public int minimumRefill(int[] plants, int capacityA, int capacityB) {
        int left = 0;
        int right = plants.length - 1;
        int refill = 0;
        int waterA = capacityA;
        int waterB = capacityB;

        while (left < right) {
            if (waterA < plants[left]) {
                refill++;
                waterA = capacityA;
            }
            waterA -= plants[left++];

            if (waterB < plants[right]) {
                refill++;
                waterB = capacityB;
            }
            waterB -= plants[right--];
        }

        if (left == right) {
            if (waterA < plants[left] && waterB < plants[right]) {
                refill++;
            } else if (waterA < plants[left]) {
                // 若 Alice 不够但 Bob 够,用 Bob 的水,不补水
            }
        }

        return refill;
    }
}

代码详细说明

  1. 变量初始化部分

    • int left = 0;:定义左指针 left,从数组的最左边开始,即代表 Alice 的起始位置。
    • int right = plants.length - 1;:定义右指针 right,从数组的最右边开始,即代表 Bob 的起始位置。
    • int refill = 0;:用于记录重新灌满水罐的总次数,初始值为 0
    • int waterA = capacityA;:用 waterA 记录 Alice 水罐中的水量,初始值为其水罐的容量 capacityA
    • int waterB = capacityB;:用 waterB 记录 Bob 水罐中的水量,初始值为其水罐的容量 capacityB
  2. 循环模拟浇水部分

    • while (left < right):只要左指针 left 小于右指针 right,就持续循环,模拟两人未相遇时的浇水过程。
    • if (waterA < plants[left]) {... }:检查 Alice 水罐中的水量是否足够浇灌当前植物。若不足,执行 refill++;,增加重新灌满水罐的次数,然后 waterA = capacityA;,将 Alice 的水罐重新灌满。接着 waterA -= plants[left++];,更新 Alice 水罐中的剩余水量,并将左指针 left 向右移动一位。
    • if (waterB < plants[right]) {... }:检查 Bob 水罐中的水量是否足够浇灌当前植物。若不足,同样执行 refill++;,增加重新灌满水罐的次数,然后 waterB = capacityB;,将 Bob 的水罐重新灌满。最后 waterB -= plants[right--];,更新 Bob 水罐中的剩余水量,并将右指针 right 向左移动一位。
  3. 相遇情况处理部分

    • if (left == right):当两人相遇时,进入该条件判断。
    • if (waterA < plants[left] && waterB < plants[right]) {... }:若两人水罐中的水都不足以浇灌当前植物,执行 refill++;,让 Alice 重新灌满水罐(因为水量相同是 Alice 浇)。
    • else if (waterA < plants[left]) {... }:若只有 Alice 水罐中的水不够,而 Bob 的水够,此时不需要重新灌满水罐,这里只是做条件判断,不执行具体操作。
  4. 最终结果返回

    • return refill;:返回整个过程中重新灌满水罐的总次数 refill

四、复杂度分析

  1. 时间复杂度: 在整个算法过程中,我们使用了一个 while 循环来遍历数组。循环的次数取决于数组的长度,每次循环内部的操作都是常数时间复杂度,即 O(1)。无论是检查水罐中的水量、决定是否需要补水,还是更新指针和水罐中的水量,这些操作的时间消耗都不依赖于数组的规模。因为我们只需要遍历数组一次,所以时间复杂度为 O(n),其中 n 为数组 plants 的长度。这意味着随着植物数量的增加,算法的运行时间会线性增长。

  2. 空间复杂度: 从空间使用的角度来看,我们在算法中只使用了几个固定的变量,如 leftrightrefillwaterA 和 waterB。这些变量的数量不会随着输入数据规模(即植物的数量 n)的变化而变化,始终保持为常数个。因此,该算法的空间复杂度为O(1),即常数级别的空间复杂度。这表明无论有多少株植物需要浇水,算法所占用的额外内存空间都是固定不变的,不会因为输入规模的扩大而显著增加。

五、总结与拓展

这道 LeetCode 算法题通过设置 Alice 和 Bob 浇水的有趣场景,考查了我们对双指针法的运用以及对复杂逻辑的处理能力。双指针法在解决这类涉及数组遍历和条件判断的问题时,具有简洁高效的特点。通过精确地模拟两人的浇水过程,特别是对相遇时各种情况的细致处理,我们能够准确地计算出重新灌满水罐的次数。

在实际编程应用中,这类问题的解题思路和方法可以有很多拓展。例如,在物流配送场景中,两辆车从不同的地点出发向中间运输货物,每到一个站点需要消耗一定量的燃油,我们可以通过类似的方法来计算两辆车在运输过程中总的加油次数;在数据处理中,从数组的两端同时查找满足特定条件的数据元素,也可以借鉴这种双指针的思路。

此外,我们还可以进一步思考这道题目的扩展情况。如果浇水的人数增加到三人或更多,他们从不同的方向同时浇水,水罐的容量和植物的需水量规则保持类似,那么我们需要如何调整算法来适应这种变化呢?或者,如果植物的需水量不是固定的值,而是随着时间动态变化的,我们又该如何设计一个更加灵活的算法来应对呢?这些拓展思考有助于我们进一步提升算法思维和编程能力,更好地应对各种复杂的实际问题。希望通过对这道题目的详细剖析,能为大家在算法学习和编程实践中提供有益的帮助和启发。

你可能感兴趣的:(leetcode,算法,职场和发展,java,开发语言)