Java 中打开水龙头给花园浇水的最小数量问题

2025年03月17日 | 阅读 9 分钟

问题陈述

问题描述:一位园丁想通过打开最少数量的水龙头来浇灌一个花园(一维的)。任务是找出应该打开多少个水龙头才能浇灌整个花园,如果花园无法浇灌,则返回 -1。

问题解决方案

为简化起见,假设花园沿长度 L 的轴(x 轴或 y 轴)是一维的,即花园从点 0 开始到点 L 结束。在花园中的 T + 1 个水龙头位于点 posArr = [0, 1, 2, …, T]。水龙头的范围定义为第 j 个水龙头的范围是 (j - rangeArr[j], j + rangeArr[j])。rangeArr[] 是一个数组,其中定义了水龙头的范围。请注意,如果无法浇灌整个花园,则必须在控制台上显示适当的消息。

让我们看一个例子以更好地理解。

示例

输入: L = 5, rangeArr = [1, 2, 3, 0, 4, 2]

输出 1

解释: 让我们看看每个水龙头的范围。

对于第一个位置的水龙头,rangeArr[0] 是 1。因此,范围是 [0 - 1, 0 + 1] = [-1, 1]。

对于第二个位置的水龙头,rangeArr[1] 是 2。因此,范围是 [1 - 2, 1 + 2] = [-1, 3]。

对于第三个位置的水龙头,rangeArr[2] 是 3。因此,范围是 [2 - 3, 2 + 3] = [-1, 5]。

对于第四个位置的水龙头,rangeArr[3] 是 0。因此,范围是 [3 - 0, 3 + 0] = [3, 3]。

对于第五个位置的水龙头,rangeArr[4] 是 4。因此,范围是 [4 - 4, 4 + 4] = [0, 8]。

对于第六个位置的水龙头,rangeArr[5] 是 2。因此,范围是 [5 - 2, 5 + 2] = [3, 7]。

下图显示了相同的情况。

Minimum Number of Taps to Open to Water a Garden in Java

从上图可以看出,我们可以打开位于第 3 个位置 rangeArr[2] 的水龙头,或者打开位于第 5 个位置 rangeArr[4] 的水龙头。这是因为这些水龙头可以覆盖花园的全部长度,范围是从 0 到 5。因此,答案是 1。

算法

步骤 1: 解决问题的概念是将数组 rangeArr[] 转换为区间列表并进行排序。排序应根据给定水龙头的范围的起始点进行。将区间排序保留在数组 intervals[] 中。

步骤 2: 从数组 intervals[] 中选择一个元素(从左到右选择),并检查范围的起始点和终点(使用循环从区间中选择元素)。在范围的终点内,必须有另一个水龙头的范围,如果不存在,则无法浇灌整个花园,因此我们可以停止在此显示适当的消息。如果存在范围,则选择可以覆盖该范围的水龙头,并将所需水龙头的计数加一。

步骤 3: 重复步骤二,直到找到一个可以单独覆盖整个花园的水龙头,或者我们已经遍历了数组 intervals[] 的所有元素。返回所需水龙头的计数。

实施

以下是上述算法的实现。

文件名: FillGardenWithWater.java

输出

The minimum number of taps required to water the whole park is: 1
The taps cannot water the whole park.
The minimum number of taps required to water the whole park is: 3

复杂度分析

在上述程序中,我们使用了循环和排序。循环需要 O(n) 时间,排序需要 O(n * log(n)) 时间。因此,程序的总时间复杂度为 O(n + n * log(n)),其中 n 是水龙头的总数。由于还使用了一个数组来存储范围,因此程序的空间复杂度为 O(n)。

我们可以通过避免排序来优化上述方法。以下方法说明了这一点。

优化方法

步骤 1: 在此方法中,创建一个大小为 L + 1 的 jumpArr[] 数组,该数组用于跟踪从给定索引(当前索引)可以达到的最大范围。为此,请将数组 jumpArr[] 的所有元素都赋值为 -1。任何索引的 -1 都表示无法到达该索引(坐标)。

步骤 2: 使用一个变量 tapCount 并将其初始化为 1。

步骤 3: 遍历数组 rangesArr[] 并为每个水龙头找到范围。范围可以计算为

start = maximum of (0, i - rangesArr[i])

end = minimum of (L, i + rangesArr[i])

步骤 4: 更新数组 jumpArr[],将其设置为 jumpArr[start] = maximum of (jumpArr[start], end)。如果 jumpArr[0] = -1,则表示无法浇灌整个花园。

步骤 5: 使用一个变量 current,并将其赋值为 jumpArr[0]。另外,使用两个变量 nxt 和 j。将 0 赋值给它们。

当 current < L 时,表示花园尚未完全浇灌到最后一个索引 L。

如果 nxt 与 current 相同,则表示无法前进。因此,无法浇灌整个地面。

如果 nxt 与 current 不同,则使用 nxt 更新 current,并将 tapCount 加 1。

实施

让我们在 Java 程序中实现上述方法。

文件名: FillGardenWithWater1.java

输出

The minimum number of taps required to water the whole park is: 1
The taps cannot water the whole park.
The minimum number of taps required to water the whole park is: 3

复杂度分析

在上述程序中,我们使用了嵌套的 while 循环。然而,数组 jumpArr[] 的元素仍然只被遍历了一次。这是因为两个 while 循环都朝同一方向工作,使得程序的 T O(N)。此外,我们使用了一个辅助数组来跟踪最大范围,这使得程序的空间复杂度为 O(N),其中 N 是花园中水龙头的总数。