貪婪策略是一種常見的算法思想,具體是指,在對問題求解時,老是作出在當前看來是最好的選擇。也就是說,不從總體最優上加以考慮,他所作出的是在某種意義上的局部最優解。貪心算法不是對全部問題都能獲得總體最優解,關鍵是貪心策略的選擇,選擇的貪心策略必須具有無後效性,即某個狀態之前的過程不會影響之後的狀態,只與當前狀態有關,這點和動態規劃同樣。前端
LeetCode 上對於貪婪策略有 73 道題目。咱們將其分紅幾個類型來說解,截止目前咱們暫時只提供覆蓋
問題,其餘的能夠期待個人新書或者以後的題解文章。python
咱們挑選三道來說解,這三道題除了使用貪婪法,你也能夠嘗試動態規劃來解決。算法
覆蓋問題的一大特徵,咱們能夠將其抽象爲給定數軸上的一個大區間 I 和 n 個小區間 i[0], i[1], ..., i[n - 1],問最少選擇多少個小區間,使得這些小區間的並集能夠覆蓋整個大區間。
segmentfault
咱們來看下這三道題吧。數組
給定一個非負整數數組,你最初位於數組的第一個位置。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
複雜度分析
你將會得到一系列視頻片斷,這些片斷來自於一項持續時長爲 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]從小到大進行排序。
如圖:
咱們當前的 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
複雜度分析
在 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
貪心策略,咱們儘可能找到可以覆蓋最遠(右邊)位置的水龍頭,並記錄它最右覆蓋的土地。
代碼支持: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
複雜度分析
歡迎關注個人公衆號《腦洞前端》獲取更多更新鮮的LeetCode題解