【算法提升班】貪婪策略

貪婪策略是一種常見的算法思想,具體是指,在對問題求解時,老是作出在當前看來是最好的選擇。也就是說,不從總體最優上加以考慮,他所作出的是在某種意義上的局部最優解。貪心算法不是對全部問題都能獲得總體最優解,關鍵是貪心策略的選擇,選擇的貪心策略必須具有無後效性,即某個狀態之前的過程不會影響之後的狀態,只與當前狀態有關,這點和動態規劃同樣。前端

LeetCode 上對於貪婪策略有 73 道題目。咱們將其分紅幾個類型來說解,截止目前咱們暫時只提供覆蓋問題,其餘的能夠期待個人新書或者以後的題解文章。python

覆蓋

咱們挑選三道來說解,這三道題除了使用貪婪法,你也能夠嘗試動態規劃來解決。算法

覆蓋問題的一大特徵,咱們能夠將其抽象爲給定數軸上的一個大區間 I 和 n 個小區間 i[0], i[1], ..., i[n - 1],問最少選擇多少個小區間,使得這些小區間的並集能夠覆蓋整個大區間。segmentfault

咱們來看下這三道題吧。數組

45. 跳躍遊戲 II

題目描述

給定一個非負整數數組,你最初位於數組的第一個位置。ide

數組中的每一個元素表明你在該位置能夠跳躍的最大長度。spa

你的目標是使用最少的跳躍次數到達數組的最後一個位置。3d

示例:code

輸入: [2,3,1,1,4]
輸出: 2
解釋: 跳到最後一個位置的最小跳躍數是 2。
  從下標爲 0 跳到下標爲 1 的位置,跳  1  步,而後跳  3  步到達數組的最後一個位置。
說明:視頻

假設你老是能夠到達數組的最後一個位置。

思路

貪婪策略,即咱們每次在可跳範圍內選擇可使得跳的更遠的位置,因爲題目保證了你老是能夠到達數組的最後一個位置,所以這種算法是完備的。

以下圖,開始的位置是 2,可跳的範圍是橙色的。而後由於 3 能夠跳的更遠,因此跳到 3 的位置。

以下圖,而後如今的位置就是 3 了,能跳的範圍是橙色的,而後由於 4 能夠跳的更遠,因此下次跳到 4 的位置。

寫代碼的話,咱們用 end 表示當前能跳的邊界,對於上邊第一個圖的橙色 1,第二個圖中就是橙色的 4,遍歷數組的時候,到了邊界,咱們就從新更新新的邊界。

圖來自 https://leetcode-cn.com/u/win...

代碼

代碼支持:Python3

Python3 Code:

class Solution:
    def jump(self, nums: List[int]) -> int:
        n, cnt, furthest, end = len(nums), 0, 0, 0
        for i in range(n - 1):
            furthest = max(furthest, nums[i] + i)
            if i == end:
                cnt += 1
                end = furthest

        return cnt

複雜度分析

  • 時間複雜度:$O(N)$。
  • 空間複雜度:$O(1)$。

1024. 視頻拼接

題目描述

你將會得到一系列視頻片斷,這些片斷來自於一項持續時長爲  T  秒的體育賽事。這些片斷可能有所重疊,也可能長度不一。

視頻片斷  clips[i]  都用區間進行表示:開始於  clipsi  並於  clipsi  結束。咱們甚至能夠對這些片斷自由地再剪輯,例如片斷  [0, 7]  能夠剪切成  [0, 1] + [1, 3] + [3, 7]  三部分。

咱們須要將這些片斷進行再剪輯,並將剪輯後的內容拼接成覆蓋整個運動過程的片斷([0, T])。返回所需片斷的最小數目,若是沒法完成該任務,則返回  -1 。

示例 1:

輸入:clips = [[0,2],[4,6],[8,10],[1,9],[1,5],[5,9]], T = 10
輸出:3
解釋:
咱們選中 [0,2], [8,10], [1,9] 這三個片斷。
而後,按下面的方案重製比賽片斷:
將 [1,9] 再剪輯爲 [1,2] + [2,8] + [8,9] 。
如今咱們手上有 [0,2] + [2,8] + [8,10],而這些涵蓋了整場比賽 [0, 10]。
示例 2:

輸入:clips = [[0,1],[1,2]], T = 5
輸出:-1
解釋:
咱們沒法只用 [0,1] 和 [0,2] 覆蓋 [0,5] 的整個過程。
示例 3:

輸入:clips = [[0,1],[6,8],[0,2],[5,6],[0,4],[0,3],[6,7],[1,3],[4,7],[1,4],[2,5],[2,6],[3,4],[4,5],[5,7],[6,9]], T = 9
輸出:3
解釋:
咱們選取片斷 [0,4], [4,7] 和 [6,9] 。
示例 4:

輸入:clips = [[0,4],[2,8]], T = 5
輸出:2
解釋:
注意,你可能錄製超過比賽結束時間的視頻。

提示:

1 <= clips.length <= 100
0 <= clipsi, clipsi <= 100
0 <= T <= 100

思路

貪婪策略,咱們選擇知足條件的最大值。和上面的不一樣,此次咱們須要手動進行一次排序,實際上貪婪策略常常伴隨着排序,咱們按照 clip[0]從小到大進行排序。

如圖:

  • 1 不能夠,所以存在斷層
  • 2 能夠
  • 3 不行,由於不到 T

咱們當前的 clip 開始結束時間分別爲 s,e。 上一段 clip 的結束時間是 t1,上上一段 clip 結束時間是 t2。

那麼這種狀況下 t1 其實是不須要的,由於 t2 徹底能夠覆蓋它:

那什麼樣 t1 纔是須要的呢?如圖:

用代碼來講的話就是s > t2 and t2 <= t1

代碼

代碼支持:Python3

Python3 Code:

class Solution:
    def videoStitching(self, clips: List[List[int]], T: int) -> int:
        #  t1 表示選取的上一個clip的結束時間
        #  t2 表示選取的上上一個clip的結束時間
        t2, t1, cnt = -1, 0, 0
        clips.sort(key=lambda a: a[0])
        for s, e in clips:
            # s > t1 已經肯定不能夠了, t1 >= T 已經能夠了
            if s > t1 or t1 >= T:
                break
            if s > t2 and t2 <= t1:
                cnt += 1
                t2 = t1
            t1 = max(t1,e)
        return cnt if t1 >= T else - 1

複雜度分析

  • 時間複雜度:因爲使用了排序(假設是基於比較的排序),所以時間複雜度爲 $O(NlogN)$。
  • 空間複雜度:$O(1)$。

1326. 灌溉花園的最少水龍頭數目

題目描述

在 x 軸上有一個一維的花園。花園長度爲  n,從點  0  開始,到點  n  結束。

花園裏總共有  n + 1 個水龍頭,分別位於  [0, 1, ..., n] 。

給你一個整數  n  和一個長度爲  n + 1 的整數數組  ranges ,其中  ranges[i] (下標從 0 開始)表示:若是打開點  i  處的水龍頭,能夠灌溉的區域爲  [i -  ranges[i], i + ranges[i]] 。

請你返回能夠灌溉整個花園的   最少水龍頭數目  。若是花園始終存在沒法灌溉到的地方,請你返回  -1 。

示例 1:

輸入:n = 5, ranges = [3,4,1,1,0,0]
輸出:1
解釋:
點 0 處的水龍頭能夠灌溉區間 [-3,3]
點 1 處的水龍頭能夠灌溉區間 [-3,5]
點 2 處的水龍頭能夠灌溉區間 [1,3]
點 3 處的水龍頭能夠灌溉區間 [2,4]
點 4 處的水龍頭能夠灌溉區間 [4,4]
點 5 處的水龍頭能夠灌溉區間 [5,5]
只須要打開點 1 處的水龍頭便可灌溉整個花園 [0,5] 。
示例 2:

輸入:n = 3, ranges = [0,0,0,0]
輸出:-1
解釋:即便打開全部水龍頭,你也沒法灌溉整個花園。
示例 3:

輸入:n = 7, ranges = [1,2,1,0,2,1,0,1]
輸出:3
示例 4:

輸入:n = 8, ranges = [4,0,0,0,0,0,0,0,4]
輸出:2
示例 5:

輸入:n = 8, ranges = [4,0,0,0,4,0,0,0,4]
輸出:1

提示:

1 <= n <= 10^4
ranges.length == n + 1
0 <= ranges[i] <= 100

思路

貪心策略,咱們儘可能找到可以覆蓋最遠(右邊)位置的水龍頭,並記錄它最右覆蓋的土地。

  • 咱們使用 furthest[i] 來記錄通過每個水龍頭 i 可以覆蓋的最右側土地。
  • 一共有 n+1 個水龍頭,咱們遍歷 n + 1 次。
  • 對於每次咱們計算水龍頭的左右邊界,[i - ranges[i], i + ranges[i]]
  • 咱們更新左右邊界範圍內的水龍頭的 furthest
  • 最後從土地 0 開始,一直到土地 n ,記錄水龍頭數目

代碼

代碼支持:Python3

Python3 Code:

class Solution:
    def minTaps(self, n: int, ranges: List[int]) -> int:
        furthest, cnt, cur = [0] * n, 0, 0

        for i in range(n + 1):
            l = max(0, i - ranges[i])
            r = min(n, i + ranges[i])
            for j in range(l, r):
                furthest[j] = max(furthest[j], r)
        while cur < n:
            if furthest[cur] == 0: return -1
            cur = furthest[cur]
            cnt += 1
        return cnt

複雜度分析

  • 時間複雜度:時間複雜度取決 l 和 r,也就是說取決於 ranges 數組的值,假設 ranges 的平均大小爲 Size 的話,那麼時間複雜度爲 $O(N \* Size)$。
  • 空間複雜度:咱們使用了 furthest 數組, 所以空間複雜度爲 $O(N)$。

歡迎關注個人公衆號《腦洞前端》獲取更多更新鮮的LeetCode題解

相關文章
相關標籤/搜索