算法-貪心算法詳解

目錄python

概述算法

引入數組

鈔票支付問題ide

解答性能

最優子結構spa

區間問題code

leetcode 435 無重疊區間排序

解答遊戲

問題轉換ci

leetcode 55 跳躍遊戲

解答

題目


概述

顧名思義,貪心算法或貪心思想採用貪心的策略,保證每次操做都是局部最優的,從而使最後獲得的結果是全局最優的。

引入

鈔票支付問題

有1元、5元、10元、20元、100元的鈔票無窮多張。現使用這些鈔票支付X元,最少須要多少張?
例如,X=628

解答

按照常識,咱們先使用大額的進行支付。

from typing import List


def getMinNum(moneys: List[int], X: int) -> int:
    moneys.sort(reverse=True)
    i, ans = 0, 0
    while X != 0:
        number = X // moneys[i]
        X -= moneys[i] * number
        print(moneys[i], number)
        ans += number
        i += 1
    return ans


if __name__ == '__main__':
    moneys = [1, 2, 5, 10, 20, 100]
    X = 628
    print(getMinNum(moneys, X))

100 6
20 1
10 0
5 1
2 1
1 1
10

雖然結果是對的,可是咱們並無給出證實。這裏也不用數學公式證實了,簡單一句話就是大的鈔票能夠換成小的鈔票,若使用小的鈔票,那麼會更多,不符合題目要求。其次,最優解包含子問題的最優解,628的最優解去除1就是627的最優解。

思考一個問題,若是鈔票面值添加7呢?

最優解
X 面值*數量 總數量 
7 7*1 1
8 7*1+1*1 2
9 7*1+2*1 2
10 10*1 1
11 10*1 + 1*1 2
12 10*1 + 2*1 2
13 10*1 +2*1+1*1 3
14 10*1 + 2 * 2 3

相信讀者看到了,若仍是貪心的話,14的結果就不對了,由於14的最優解爲7*2,2張便可。

最優子結構

最優子結構指的是,問題的最優解包含子問題的最優解。反過來講就是,咱們能夠經過子問題的最優解,推導出問題的最優解。

X=14時,選7+7比選10+2+2要好。或者說,X=14時的第一個選擇7,不是子問題X=13時的第一個選擇10。這樣先選最大的這種貪心策略就不對了。

以上,就說明了只有經過子問題的最優解能推導出問題的最優解的狀況下才能使用貪心算法。

接下來看幾個典型問題。

區間問題

leetcode 435 無重疊區間

給定一個區間的集合,找到須要移除區間的最小數量,使剩餘區間互不重疊。

注意:

    能夠認爲區間的終點老是大於它的起點。
    區間 [1,2] 和 [2,3] 的邊界相互「接觸」,但沒有相互重疊。

示例 1:

輸入: [ [1,2], [2,3], [3,4], [1,3] ]

輸出: 1

解釋: 移除 [1,3] 後,剩下的區間沒有重疊。

解答

若是已經肯定了一個區間,如何肯定另外一附近區間呢?因爲題目要要移除區間的的最小數量,咱們應該選擇延展最少的(貪心),這樣覆蓋的少。例如,咱們肯定了從小到大,選區了第一個區間,而有一區間的右端點比較大,若選擇它,那麼不少右端點小的就會和這一區間重複,致使去除了不少重複區間。所以,這裏按照區間右端點從小到大排序,選擇第一個區間開始,依次向後判斷是否重疊,重疊則去除。

class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
        intervals.sort(key=lambda x: x[1])
        print(intervals)

        # 返回是否重疊
        def isOver(a, b):
            if b[0] < a[1]:
                return True
            return False

        start, ans = intervals[0], 0
        for cur in intervals[1:]:
            if isOver(start, cur):
                ans += 1
            else:
                start = cur
        return ans

固然,你若是選擇最右邊的開始也是同樣的。

問題轉換

leetcode 55 跳躍遊戲

給定一個非負整數數組 nums ,你最初位於數組的 第一個下標 。

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

判斷你是否可以到達最後一個下標。

示例 1:

輸入:nums = [2,3,1,1,4]
輸出:true
解釋:能夠先跳 1 步,從下標 0 到達下標 1, 而後再從下標 1 跳 3 步到達最後一個下標。

示例 2:

輸入:nums = [3,2,1,0,4]
輸出:false
解釋:不管怎樣,總會到達下標爲 3 的位置。但該下標的最大跳躍長度是 0 , 因此永遠不可能到達最後一個下標。

解答

第一時間想到的多是每次選擇最遠的跳,這樣能夠跳的「更遠」,從而達到最後。其實這樣是不對的,可能你跳的「更遠」的下一跳反而比其餘選擇的下一跳近,因此咱們並非選擇跳的最遠的,而是選擇在可跳的範圍內最終能跳的最遠的。

這就是對局部最優的理解,你沒有沿着你的選擇跳到最後,你並不知道是否是局部最優。所以,咱們維護一個當前能跳的最遠位置,將全部選擇的下一跳計算出來並持續更新最遠位置。

跳躍過程
pos max_pos
2 2
3 4
1 4
1 4
4

class Solution:
    def canJump(self,nums):
        n = len(nums)
        max_pos = 0                               # 當前最遠位置
        for pos, jump in enumerate(nums):         # poi爲當前位置,jump是當前位置的跳數
            if max_pos>=pos and pos+jump>max_pos: # 若是當前位置能到達,而且 當前位置+跳數>最遠位置,
                max_pos = pos+jump                # 更新最遠能到達位置
            # 提早結束
            if max_pos >= n - 1:
                return True
            if max_pos < pos:
                return False
        return True

固然,咱們能夠提早結束,讓咱們的程序性能更好。

你們能夠練練相關題目,鞏固一下。

題目

leetcode 435 無重疊區間

leetcode 455 分發糖果

leetcode 376 擺動序列

leetcode 402 移掉K位數字

leetcode 55 跳躍遊戲

相關文章
相關標籤/搜索