49.贪心算法之五:花园浇水问题

Posted 和孩子一起学Python

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了49.贪心算法之五:花园浇水问题相关的知识,希望对你有一定的参考价值。



    由浅入深,3种方法介绍贪心算法的经典问题:花园浇水问题。


    

    

    花园浇水问题

    有n个花盆排成一排,花盆2侧有一个喷水龙头,也即n+1个龙头。给定每个龙头的喷水范围,问最少开多少个龙头,可以浇灌到每盆花。


    比如下图中,有4盆花,5个水龙头。水龙头的喷水范围记录为 [2,3,0,0,1]。其中列表里的第1个元素2就表示第1个水龙头的喷水范围,它可以左边喷2格的距离,右边也是2格,当然,由于左边没有花盆,所以它实际上可以浇灌到它右边的2盆花。


49.贪心算法之五:花园浇水问题

       

    花园浇水问题的本质其实是一个经典的算法模型。即


    给定一个一维区间[0,n],以及n+1个小区间,问最少选择多少个小区间,就可以覆盖整个区间。


    如果扩展到2维或者3维平台,难度将会更大。    


    

回顾:会议室安排问题


    如果还记得《46.贪心算法之二:做个好秘书,会议安排问题》的话,会发现两个问题有相通之处。

    会议室安排问题的本质是

    给定一个一维区间[0,n],以及m个不超出[0,n]范围的小区间,问如何选择最多个不相交的小区间。


49.贪心算法之五:花园浇水问题


    会议室安排问题非常适用于贪心算法,每一步都选择

        结束时间最早,且上一个会议不冲突的会议。


 


方法1:最容易理解的方法

    

    假设给定的一维列表是[1,2,1,0,2,1,0,1],也即有8个水龙头。

    如下图,我们把每个水龙头的浇灌范围画出来。

    比如第1个水龙头,位置是0,值是1,浇灌范围是[0-1,0+1],也即[-1,1],因为负数没有意义,所以它有意义的浇灌范围是[0,1]。

    第2个水龙头,位置是1,值是2,浇灌范围是[1-2,1+2],也即[-1,3],也去掉负数部分,有意义的浇灌范围是[0,3]。

    第3个水龙头,位置是2,值是1,浇灌范围是[2-1,2+1],也即[1,3]。

    ……

49.贪心算法之五:花园浇水问题


 

    在选择水龙头时,第1步,按照思维习惯,我们肯定要选择可以浇灌到第1盆花的水龙头。也即包含0的区间。

    

    如上图,符合这个条件的子区间有[0,1]和[0,3],在这2个里,你会选择哪一个呢?根据贪心的思想,肯定选择[0,3]


    也即,第1步你会选择一个 起点小于或者等于0,且终点尽可能大的区间

    

    当第1步选择了[0,3]之后,第2步要如何选择?

    是不是选择 起点小于等于3,终点大于3,且尽可能大的区间

    

    为什么起点要小于等于3:因为第2个区间必须和第一个区间相加,比如第一个区间选择[0,3],第2个区间选择[4,8],中间就空了。

    为什么终点要大于3:因为小于等于3就没有意义了。比如第一个区间选择[0,3],第2个区间选择[1,3],这是没有意义的。


    有了上面的分析之后,程序就简单了。

    首先用一个新的列表lands,把每个龙头覆盖的区间保存起来。

    接下来就是2层循环。

    外层循环每执行一轮,都选出1个龙头。

    内层循环就是从所有的龙头里选出本轮符合条件的龙头。

    

def minTaps1(n, ranges) :
lands = []
for i in range(n+1):
lands.append([max(i-ranges[i],0),min(ranges[i]+i,n)])

minV = 0
maxV = 0
step = 0
for i in range(n+1):
temp = maxV
for j in range(n+1):
if lands[j][0] <= minV and lands[j][1] > maxV:
maxV = lands[j][1]
if maxV == n:
step += 1
break
elif
maxV == temp:
step = -1
break
else
:
step += 1
minV = maxV

return step



方法2:换种思维方式

    

    如下图中,每个水龙头上多了一个蓝色的数字。

    它表示,能浇灌到这个位置的水龙头,最远能够浇灌到的位置。


    这句话要怎么理解?

    以第1个位置0为例,可以浇灌到这个位置的水龙头有2个,它们的浇灌区间分别是

    [0,1],[0,3],

    所以,在所有包含0这个位置的区间,最远的终点是3。


    再比如1这个位置,可以浇灌到这个位置的水龙头有3个,它们的浇灌区间分别是

    [0,1],[0,3],[1,3]

    所以,在所有包含1这个位置的区间,最远的终点也是3。


    再比如2这个位置,可以浇灌到这个位置的水龙头有3个,它们的浇灌区间分别是

    [0,3],[1,3],[2,6]]

    所以,在所有包含2这个位置的区间,最远的终点是6。

49.贪心算法之五:花园浇水问题

        

    知道这点有什么好处呢?

    第1步,按照思维习惯,我们肯定要选择可以浇灌到第1盆花的水龙头。也即包含0的区间。且终点尽可能大的区间

    而假设我们已经知道了包含每个位置的所有区间中,最远可以到达的位置,比如所有包含0这个位置的区间里,最远可以达到的位置是3。

    所以,第1步,我们选择的区间最远可以达到3.


    第2步,我们达到3这个位置,包含3的区间,最远可以达到6。所以第2步,我们选择的区间,最远可以达到6。

    

    第3步,我们达到6这个位置,包含6的区间,最远可以达到7。所以第3步,我们选择的区间,最远可以达到7。也即一共选择3个区间,可以覆盖整个区间。


    在整个过程中,要理解的是

    前1个位置能达到的最远位置,后一个位置肯定也能达到。

    比如0这个位置,因为有2个区间[0,1],[0,3]包含它,所以最远能达到的位置是3。

    那么1这个位置,肯定至少也可以达到3。


    程序如下,相对难理解的地方是,如何确定能浇灌到该位置的所有区间,最远可以达到的位置。

    其实就是把所有的区间都循环一次,每次将这个区间包含的所有位置的最远位置都更新一次。

       

def minTaps2(n,ranges):
land = [0] * n
for i in range(n+1):
l = max(i - ranges[i],0)
r = min(i + ranges[i],n)
for j in range(l,r):
land[j] = max(land[j],r)

count = 0
cur = 0
while cur < n :
if land[cur] == 0 :
return -1
else:
cur = land[cur]
count += 1
return count


    

方法3:降低时间复杂度的方法

    

    虽然第2种方法的时间复杂度要比第1种方法更优,但还是O(n*n)

    而接下来要介绍的这种方法,时间复杂度是O(n)

    

    如下图,每个水龙头的上面也多了一个数字。

    怎么理解这个数字呢?

    在下图中,用不同颜色的小柱子表示了不同的区间,我们可以想象成,这根小柱子是挂在它最右边的水龙头上的。

    比如最下面那根绿色的柱子,可以想象它是挂在7那个水龙头上。

    而6那个水龙头,则挂了3根小柱子,只不过其中一根的长度是0。


    下图中每个水龙头上的数字,表示的是

    挂在这个水龙头上的所有柱子,最左可以达到什么位置。


    如果我们已经有了这些数字,然后从右往左找。

    第1步:7位置上的水龙头,挂的所有柱子,最左只能达到6,所以它只能跳1步,达到6的位置。

    第2步:6位置上的水龙头,挂的所有柱子,最左能到底2。    

               是不是从6直接跳到2呢?

               不是!

               我们比较(6,2]里的每一个水龙头,也即要比较假设分别以5、4、3、2                   为“垫脚石”,再下一步谁跳得更远。

               在下图中,6-->3-->0,而6-->2-->2,也即从6跳到3,再跳一步可                     以跳到0,而从6跳到2,再跳一步还是只能到2。                  

49.贪心算法之五:花园浇水问题


    至于怎么  得到挂在这个水龙头上的所有柱子,最左可以达到什么位置 ,只要从左到右变量一遍就可以了。  

    程序如下:

    

def minTaps3(n, ranges) :
prev = [x for x in range(n + 1)]
for i in range(n + 1):
l = max(i - ranges[i], 0)
r = min(i + ranges[i], n)
prev[r] = min(prev[r], l)

breakpoint, furthest = n, 2 ** 30
ans = 0
for i in range(n, 0, -1):
furthest = min(furthest, prev[i])
if i == breakpoint:
if furthest >= i:
return -1
breakpoint = furthest
ans += 1

return ans


最短路径问题

    

    假设有如下地图,一共有5个地点

    带箭头的线段表示可行的道路,注意,道路是单向的,如下图中可以从1去到2,但是不能从2去到1。

    线段上的数字表示路程的长度。


    问题是,从地点1去往其它地点的最短路径分别是多少?

    

49.贪心算法之五:花园浇水问题


    

    往期文章 https://dwz.cn/gBg1UPmk
    或关注公众号,点“原创文章”






扫描二维码

获取更多精彩

少儿编程


以上是关于49.贪心算法之五:花园浇水问题的主要内容,如果未能解决你的问题,请参考以下文章

青年贪心算法:一举两得

给植物浇水

LeetCode 2105. 给植物浇水 II

LeetCode 2105.给植物浇水 II

暑期集训第九天(6-30)题解及总结

贪心算法:划分字母区间