第15周Leetcode記錄
12.22 71. 兩地排程
公司計劃面試 2N 人。第 i 人飛往 A 市的費用為costs[i][0],飛往 B 市的費用為 costs[i][1]。
返回將每個人都飛到某座城市的最低費用,要求每個城市都有 N 人抵達。
輸入:[[10,20],[30,200],[400,50],[30,20]]
輸出:110
解釋:
第一個人去 A 市,費用為 10。
第二個人去 A 市,費用為 30。
第三個人去 B 市,費用為 50。
第四個人去 B 市,費用為 20。
最低總費用為 10 + 30 + 50 + 20 = 110,每個城市都有一半的人在面試。
最優解思路
公司首先將這 2N 個人全都安排飛往 BB 市,再選出 N 個人改變它們的行程,讓他們飛往 A 市。如果選擇改變一個人的行程,那麼公司將會額外付出 price_A - price_B 的費用,這個費用可正可負。因此最優的方案是,選出 price_A - price_B
A
市,其餘人飛往 B
市。
最優解
class Solution: def twoCitySchedCost(self, costs: List[List[int]]) -> int: # Sort by a gain which company has # by sending a person to city A and not to city B costs.sort(key = lambda x : x[0] - x[1]) total = 0 n = len(costs) // 2 # To optimize the company expenses, # send the first n persons to the city A # and the others to the city B for i in range(n): total += costs[i][0] + costs[i + n][1] return total
12.23 72. 最長單詞
給定一組單詞words,編寫一個程式,找出其中的最長單詞,且該單詞由這組單詞中的其他單詞組合而成。若有多個長度相同的結果,返回其中字典序最小的一項,若沒有符合要求的單詞則返回空字串。
輸入: ["cat","banana","dog","nana","walk","walker","dogwalker"]
輸出: "dogwalker"
解釋: "dogwalker"可由"dog"和"walker"組成。
最優解思路
先把字串陣列排序,字串長的在前面,相同長度的字典序小的在前面,排好序後加入到set裡判斷是否包含,從第一個字串開始判斷,看是否由其它字串組成,這裡可以用遞迴
遞迴出口: 如果字串長的長度為0,說明遍歷完了,之前的都滿足條件,返回true
遞迴操作: 遍歷字串的第0個位置開始,判斷set裡是否有,如果0到i的字串正好包含在set裡,下次從i+1的位置開始判斷,直到遍歷完了,字串長度為0,沒找到則返回false
最優解
class Solution {
public String longestWord(String[] words) {
Arrays.sort(words,(o1,o2)->{
if(o1.length() == o2.length())
return o1.compareTo(o2);
else{
return Integer.compare(o2.length(),o1.length());
}
});
Set<String> set = new HashSet<>(Arrays.asList(words));
for(String word : words){
set.remove(word);
if(find(set,word))
return word;
}
return "";
}
public boolean find(Set<String> set, String word){
if(word.length() == 0)
return true;
for(int i = 0; i < word.length(); i++){
if(set.contains(word.substring(0,i+1)) && find(set,word.substring(i+1)))
return true;
}
return false;
}
}
12.26 73. 二叉搜尋樹迭代器
實現一個二叉搜尋樹迭代器。你將使用二叉搜尋樹的根節點初始化迭代器。
呼叫 next()
將返回二叉搜尋樹中的下一個最小的數。
BSTIterator iterator = new BSTIterator(root);
iterator.next(); // 返回 3
iterator.next(); // 返回 7
iterator.hasNext(); // 返回 true
iterator.next(); // 返回 9
iterator.hasNext(); // 返回 true
iterator.next(); // 返回 15
iterator.hasNext(); // 返回 true
iterator.next(); // 返回 20
iterator.hasNext(); // 返回 false
思路
中序遍歷樹,各節點的值加入列表,pop。
最優解
class BSTIterator:
def __init__(self, root: TreeNode):
# Array containing all the nodes in the sorted order
self.nodes_sorted = []
# Pointer to the next smallest element in the BST
self.index = -1
# Call to flatten the input binary search tree
self._inorder(root)
def _inorder(self, root):
if not root:
return
self._inorder(root.left)
self.nodes_sorted.append(root.val)
self._inorder(root.right)
def next(self) -> int:
"""
@return the next smallest number
"""
self.index += 1
return self.nodes_sorted[self.index]
def hasNext(self) -> bool:
"""
@return whether we have a next smallest number
"""
return self.index + 1 < len(self.nodes_sorted)
12.26 74. 買賣股票的最佳時機
給定一個整數陣列 prices,其中第 i 個元素代表了第 i 天的股票價格 ;非負整數 fee 代表了交易股票的手續費用。
你可以無限次地完成交易,但是你每筆交易都需要付手續費。如果你已經購買了一個股票,在賣出它之前你就不能再繼續購買股票了。
返回獲得利潤的最大值。
注意:這裡的一筆交易指買入持有並賣出股票的整個過程,每筆交易你只需要為支付一次手續費。
輸入: prices = [1, 3, 2, 8, 4, 9], fee = 2
輸出: 8
解釋: 能夠達到的最大利潤:
在此處買入prices[0] = 1
在此處賣出 prices[3] = 8
在此處買入 prices[4] = 4
在此處賣出 prices[5] = 9
總利潤:((8 - 1) - 2) + ((9 - 4) - 2) = 8.
最優解思路
解一:動態規劃
考慮 dp[i][0]的轉移方程,如果這一天交易完後手裡沒有股票,那麼可能的轉移狀態為前一天已經沒有股票,即 dp[i-1][0],或者前一天結束的時候手裡持有一支股票,即 dp[i-1][1],這時候我們要將其賣出,並獲得prices[i] 的收益,但需要支付 fee 的手續費。因此為了收益最大化,我們列出如下的轉移方程:
dp[i][0]=max{dp[i−1][0],dp[i−1][1]+prices[i]−fee}
再來按照同樣的方式考慮 dp[i][1] 按狀態轉移,那麼可能的轉移狀態為前一天已經持有一支股票,即 dp[i-1]][1],或者前一天結束時還沒有股票,即dp[i-1]][0] ,這時候我們要將其買入,並減少prices[i] 的收益。可以列出如下的轉移方程:
dp[i][1]=max{dp[i−1][1],dp[i−1][0]−prices[i]}
對於初始狀態,根據狀態定義我們可以知道第 0 天交易結束的時候有dp[0][0]=0 以及dp[0][1]=-price[0].
class Solution:
def maxProfit(self, prices: List[int], fee: int) -> int:
n = len(prices)
dp = [[0, -prices[0]]] + [[0, 0] for _ in range(n - 1)]
for i in range(1, n):
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee)
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i])
return dp[n - 1][0]
解二:貪心演算法
即當我們賣出一支股票時,我們就立即獲得了以相同價格並且免除手續費買入一支股票的權利。
class Solution:
def maxProfit(self, prices: List[int], fee: int) -> int:
n = len(prices)
buy = prices[0] + fee
profit = 0
for i in range(1, n):
if prices[i] + fee < buy:
buy = prices[i] + fee
elif prices[i] > buy:
profit += prices[i] - buy
buy = prices[i]
return profit
12.28 75. LRU快取機制
運用你所掌握的資料結構,設計和實現一個 LRU (最近最少使用) 快取機制 。
實現 LRUCache 類:
LRUCache(int capacity) 以正整數作為容量 capacity 初始化 LRU 快取
int get(int key) 如果關鍵字 key 存在於快取中,則返回關鍵字的值,否則返回 -1 。
void put(int key, int value) 如果關鍵字已經存在,則變更其資料值;如果關鍵字不存在,則插入該組「關鍵字-值」。當快取容量達到上限時,它應該在寫入新資料之前刪除最久未使用的資料值,從而為新的資料值留出空間。
思路
LRU快取機制是通過hash表輔以雙向連結串列實現的。
- 雙向連結串列按照被使用的順序儲存了這些鍵值對,靠近頭部的鍵值對是最近使用的,而靠近尾部的鍵值對是最久未使用的。
- 雜湊表即為普通的雜湊對映(HashMap),通過快取資料的鍵對映到其在雙向連結串列中的位置。
最優解
class DLinkedNode:
def __init__(self, key=0, value=0):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity: int):
self.cache = dict()
# 使用偽頭部和偽尾部節點
self.head = DLinkedNode()
self.tail = DLinkedNode()
self.head.next = self.tail
self.tail.prev = self.head
self.capacity = capacity
self.size = 0
def get(self, key: int) -> int:
if key not in self.cache:
return -1
# 如果 key 存在,先通過雜湊表定位,再移到頭部
node = self.cache[key]
self.moveToHead(node)
return node.value
def put(self, key: int, value: int) -> None:
if key not in self.cache:
# 如果 key 不存在,建立一個新的節點
node = DLinkedNode(key, value)
# 新增進雜湊表
self.cache[key] = node
# 新增至雙向連結串列的頭部
self.addToHead(node)
self.size += 1
if self.size > self.capacity:
# 如果超出容量,刪除雙向連結串列的尾部節點
removed = self.removeTail()
# 刪除雜湊表中對應的項
self.cache.pop(removed.key)
self.size -= 1
else:
# 如果 key 存在,先通過雜湊表定位,再修改 value,並移到頭部
node = self.cache[key]
node.value = value
self.moveToHead(node)
def addToHead(self, node):
node.prev = self.head
node.next = self.head.next
self.head.next.prev = node
self.head.next = node
def removeNode(self, node):
node.prev.next = node.next
node.next.prev = node.prev
def moveToHead(self, node):
self.removeNode(node)
self.addToHead(node)
def removeTail(self):
node = self.tail.prev
self.removeNode(node)
return node
最優解總結
hash表key是存出鍵,value是連結串列的索引,連結串列的值時value。這樣查詢,插入複雜度都是o1
get操作,判斷是否存在,不存在返回-1,存在把對應的連結串列索引換到頭部。
put操作,若存在和get操作類似,不存在則建立一個新的連結串列節點,放到頭部再判斷長度,超過規定長度就會從尾部去除。