貪心
455分發餅干
假設你是一位很棒的家長,想要給你的孩子們一些小餅干。但是,每個孩子最多只能給一塊餅干。對每個孩子 i ,都有一個胃口值 gi ,這是能讓孩子們滿足胃口的餅干的最小尺寸;並且每塊餅干 j ,都有一個尺寸 sj 。如果 sj >= gi ,我們可以將這個餅干 j 分配給孩子 i ,這個孩子會得到滿足。你的目標是盡可能滿足越多數量的孩子,並輸出這個最大數值。
注意:
你可以假設胃口值為正。
一個小朋友最多只能擁有一塊餅干。
示例 1:
輸入: [1,2,3], [1,1]
輸出: 1
解釋:
你有三個孩子和兩塊小餅干,3個孩子的胃口值分別是:1,2,3。
雖然你有兩塊小餅干,由於他們的尺寸都是1,你只能讓胃口值是1的孩子滿足。
所以你應該輸出1。
示例 2:
輸入: [1,2], [1,2,3]
輸出: 2
解釋:
你有兩個孩子和三塊小餅干,2個孩子的胃口值分別是1,2。
你擁有的餅干數量和尺寸都足以讓所有孩子滿足。
所以你應該輸出2.
題解
為了滿足最多的孩子,應該盡量讓胃口小的吃小餅干, 把胃口小的先滿足。將孩子胃口,和餅干都升序排列,這里還用了兩個指針。
class Solution:
def findContentChildren(self, g: List[int], s: List[int]) -> int:
g.sort()
s.sort()
i,j = 0,0
count = 0
while True:
if i>=len(g) or j>=len(s):
break
if g[i]<=s[j]:
count += 1
i += 1
j += 1
return count
435無重疊區間
給定一個區間的集合,找到需要移除區間的最小數量,使剩余區間互不重疊。
注意:
可以認為區間的終點總是大於它的起點。
區間 [1,2] 和 [2,3] 的邊界相互“接觸”,但沒有相互重疊。
示例 1:
輸入: [ [1,2], [2,3], [3,4], [1,3] ]
輸出: 1
解釋: 移除 [1,3] 后,剩下的區間沒有重疊。
示例 2:
輸入: [ [1,2], [1,2], [1,2] ]
輸出: 2
解釋: 你需要移除兩個 [1,2] 來使剩下的區間沒有重疊。
示例 3:
輸入: [ [1,2], [2,3] ]
輸出: 0
解釋: 你不需要移除任何區間,因為它們已經是無重疊的了。
題解
在每次選擇中,區間的結尾最為重要,選擇的區間結尾越小,盡可能留給后面的區間的空間越大,那么后面能夠選擇的區間個數也就越大。
按區間的結尾進行排序,每次選擇結尾最小,並且和前一個區間不重疊的區間。
class Solution:
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
if not intervals:
return 0
intervals.sort(key=lambda x: x[1]) # 注意這里是按照第二個元素來排序
count = 0
end = intervals[0][1] # 初始化end
for i in range(1,len(intervals)):
if intervals[i][0] < end:
count += 1
else:
end = intervals[i][1] # 更新end
return count
402移掉K位數字
給定一個以字符串表示的非負整數 num,移除這個數中的 k 位數字,使得剩下的數字最小。
注意:
num 的長度小於 10002 且 ≥ k。
num 不會包含任何前導零。
示例 1 :
輸入: num = "1432219", k = 3
輸出: "1219"
解釋: 移除掉三個數字 4, 3, 和 2 形成一個新的最小的數字 1219。
示例 2 :
輸入: num = "10200", k = 1
輸出: "200"
解釋: 移掉首位的 1 剩下的數字為 200. 注意輸出不能有任何前導零。
示例 3 :
輸入: num = "10", k = 2
輸出: "0"
解釋: 從原數字移除所有的數字,剩余為空就是0。
這道題主要思路:當指針指向的數比左邊的數小,那么舍棄左邊的數,在刪除數字時應該從左向右迭代,這樣的數整體最小。這里用到棧的思想,也就是一個列表。
class Solution:
def removeKdigits(self, num: str, k: int) -> str:
numStack = []
for i in num:
while k and numStack and numStack[-1]>i: #這里循環是為了將i與棧內的所有數進行比較,保證整體最小。
numStack.pop()
k -= 1
numStack.append(i)
numStack = numStack[:-k] if k else numStack
return ''.join(numStack).lstrip('0') or '0'
452用最少數量的箭引爆氣球
在二維空間中有許多球形的氣球。對於每個氣球,提供的輸入是水平方向上,氣球直徑的開始和結束坐標。由於它是水平的,所以y坐標並不重要,因此只要知道開始和結束的x坐標就足夠了。開始坐標總是小於結束坐標。平面內最多存在104個氣球。
一支弓箭可以沿着x軸從不同點完全垂直地射出。在坐標x處射出一支箭,若有一個氣球的直徑的開始和結束坐標為 xstart,xend, 且滿足 xstart ≤ x ≤ xend,則該氣球會被引爆。可以射出的弓箭的數量沒有限制。 弓箭一旦被射出之后,可以無限地前進。我們想找到使得所有氣球全部被引爆,所需的弓箭的最小數量。
Example:
輸入:
[[10,16], [2,8], [1,6], [7,12]]
輸出:
2
解釋:
對於該樣例,我們可以在x = 6(射爆[2,8],[1,6]兩個氣球)和 x = 11(射爆另外兩個氣球)。
這道題也是計算區間重疊的個數,和上面那道差不多。按每個區間end升序排列。
class Solution:
def findMinArrowShots(self, points: List[List[int]]) -> int:
if len(points)==0:
return 0
points.sort(key=lambda x:x[1])
end = points[0][1]
count = 1
for i in range(1,len(points)):
if points[i][0]<=end:
continue
else:
end = points[i][1]
count+=1
return count
406. 根據身高重建隊列
假設有打亂順序的一群人站成一個隊列。 每個人由一個整數對(h, k)表示,其中h是這個人的身高,k是排在這個人前面且身高大於或等於h的人數。 編寫一個算法來重建這個隊列。
注意:
總人數少於1100人。
示例
輸入:
[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
輸出:
[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]
讓我們從最簡單的情況下思考,當隊列中所有人的 (h,k) 都是相同的高度 h,只有 k 不同時,解決方案很簡單:每個人在隊列的索引 index = k。
即使不是所有人都是同一高度,這個策略也是可行的。因為個子矮的人相對於個子高的人是 “看不見” 的,所以可以先安排個子高的人。
上圖中我們先安排身高為 7 的人,將它放置在與 k 值相等的索引上;再安排身高為 6 的人,同樣的將它放置在與 k 值相等的索引上。
該策略可以遞歸進行:
- 將最高的人按照 k 值升序排序,然后將它們放置到輸出隊列中與 k 值相等的索引位置上。
- 按降序取下一個高度,同樣按 k 值對該身高的人升序排序,然后逐個插入到輸出隊列中與 k 值相等的索引位置上。
- 直到完成為止
在python里直接用list.insert(index, i)在Index處插入。最高的人中,肯定有一個人排他前面人數是0,所以我們這里插入第一個肯定是[最高的身高,0]這樣
class Solution:
def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:
if len(people)<2:
return people
result = []
people.sort(key=lambda x:(-x[0],x[1]))
for i in people:
result.insert(i[1], i)
return result
121. 買賣股票的最佳時機
121
給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。
如果你最多只允許完成一筆交易(即買入和賣出一支股票),設計一個算法來計算你所能獲取的最大利潤。
注意你不能在買入股票前賣出股票。
示例 1:
輸入: [7,1,5,3,6,4]
輸出: 5
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 5 天(股票價格 = 6)的時候賣出,最大利潤 = 6-1 = 5 。
注意利潤不能是 7-1 = 6, 因為賣出價格需要大於買入價格。
示例 2:
輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤為 0。
只要記錄前面的最小價格,將這個最小價格作為買入價格,然后將當前的價格作為售出價格,查看當前收益是不是最大收益。
查看評論后,把它看作DP問題理解起來簡單點。
假設當前在第 i 天,令 minPrice 表示前 i-1 天的最低價格;令 maxProfit 表示前 i-1 天的最大收益。那么考慮第 i 天的收益時,存在兩種情況:
- 在第 i 天賣出。很顯然,想要獲得最大收益,應該在前 i-1 天中價格最低的時候買入,即此時的收益為:prices[i] - minPrice。(可能會出現負數,但是沒關系)
- 不在第 i 天賣出。那么第 i 天的最大收益就等於前 i -1 天中的最大收益
狀態轉移方程為:第 i 天最大收益 = max( 在第 i 天賣出的所得收益 , 前 i-1 天的最大收益)
題解
class Solution:
def maxProfit(self, prices: List[int]) -> int:
if len(prices)==0:
return 0
Min = prices[0]
Max = 0
for i in range(1,len(prices)):
temp = prices[i] - Min
Max = max(Max, temp)
Min = min(Min, prices[i])
return Max
貪心做法
只允許一筆交易,在從左往右掃描數組的過程中,保存當前的最小值,結果就是ans = max(ans, price[i]-Min)
,保證ans時刻保存了當前最大收益。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
if len(prices)==0:
return 0
Min, ans = prices[0], 0
for i in range(len(prices)):
if Min>=prices[i]:
Min = prices[i]
continue
else:
ans = max(ans, prices[i] - Min)
return ans
188. 買賣股票的最佳時機 IV(這類題通解)
參考LeetCode
763. 划分字母區間
字符串 S 由小寫字母組成。我們要把這個字符串划分為盡可能多的片段,同一個字母只會出現在其中的一個片段。返回一個表示每個字符串片段的長度的列表。
示例 1:
輸入: S = "ababcbacadefegdehijhklij"
輸出: [9,7,8]
解釋:
划分結果為 "ababcbaca", "defegde", "hijhklij"。
每個字母最多出現在一個片段中。
像 "ababcbacadefegde", "hijhklij" 的划分是錯誤的,因為划分的片段數較少。
對於遇到的每一個字母,去找這個字母最后一次出現的位置,用來更新當前的最小區間。參考
定義數組 last[char] 來表示字符 char 最后一次出現的下標。定義 anchor 和 j 來表示當前區間的首尾。如果遇到的字符最后一次出現的位置下標大於 j, 就讓 j=last[c] 來拓展當前的區間。當遍歷到了當前區間的末尾時(即 i==j ),把當前區間加入答案,同時將 start 設為 i+1 去找下一個區間。
class Solution(object):
def partitionLabels(self, S):
last = {c: i for i, c in enumerate(S)} # 構建一個字典來保存每個字符最后一次出現位置,很巧妙。
j = anchor = 0
ans = []
for i, c in enumerate(S):
j = max(j, last[c])
if i == j:
ans.append(i - anchor + 1)
anchor = i + 1
return ans