[Leetcode]1326.灌溉花园的最少水龙头数目
这道题是45.跳跃游戏的进阶版,和跳跃游戏的不同在于,这道题的背景稍微复杂,一个点可以往左右两边浇水,我们需要把这个转换成跳跃游戏。如何转换呢?
可以这样想,把每个点可以浇的水的范围[l, r]
,就当成是一个人在 l
点最远可以跳到r
点,或者看成一个人在r
点最远可以跳到l
点。因为跳跃游戏规定了是向后跳,所以跳跃游戏只需要单向考虑就行。
这道题的话,不管是从左边按照跳跃游戏的方式遍历到右边,还是从右边遍历到左边都可以。
这道题还有一个注意点,就是一开始需要预处理出跳跃游戏的那个数组,官方解答给的是对于一个右端点,预处理prev
数组,其实就是从后往前跳的跳跃游戏。这道题不容易理解的就是,跳跃游戏每个点,假如不是0的话,都可以是一段区间的最左边界,属于稠密区间,而本题可能会不能全部浇完,属于稀疏区间,不是每个prev[i]
都是有意义的,如果prev[i] == i
的话,说明是有区间的缺失,等于跳跃游戏中,nums[i] == 0
,这里稍微难理解一些。
- 贪心法
public int minTaps(int n, int[] ranges) {
int[] prev = new int[n + 1];
for(int i = 0; i < n + 1; i++){
prev[i] = i;
}
for (int i = 0; i <= n; ++i) {
int l = Math.max(i - ranges[i], 0);
int r = Math.min(i + ranges[i], n);
prev[r] = Math.min(prev[r], l);
}
//上面其实做的就是初始化跳跃游戏的nums数组
int step = 0;
int min = n;
int onestep_maxrange = n;
for(int i = n; i > 0; i--){
min = Math.min(min, prev[i]);
if(i == onestep_maxrange){
if(min >= onestep_maxrange){
return -1;
}
onesteprange = min;
step++;
}
}
return step;
}
- 动态规划
public int minTaps(int n, int[] ranges) {
int[] dp = new int[n + 1];
Arrays.fill(dp, (int)2e9);
dp[n] = 0;
int[] prev = new int[n + 1];
for(int i = 0; i < n + 1; i++){
prev[i] = i;
}
for(int i = 0; i <= n; i++){
int l = Math.max(0, i - ranges[i]);
int r = Math.min(n, i + ranges[i]);
prev[r] = Math.min(l, prev[r]);
}
// System.out.println(Arrays.toString(prev));
for(int i = n; i >= 0; i--){
for(int j = prev[i]; j < i; j++){
dp[j] = Math.min(dp[j], dp[i] + 1);
}
}
return dp[0] == (int)2e9 ? -1 : dp[0];
}