# Leecode Dynamic Programming ###### tags: `Leetcode` ## [1129 Shortest Path with Alternating Colors](https://leetcode.com/problems/shortest-path-with-alternating-colors/) Breadth First Search ```python class Solution: def shortestAlternatingPaths(self, n: int, red_edges: List[List[int]], blue_edges: List[List[int]]) -> List[int]: original_path = [[[False, False] for j in range(n)] for i in range(n)] for edge in red_edges: original_path[edge[0]][edge[1]][0] = True for edge in blue_edges: original_path[edge[0]][edge[1]][1] = True upper_bound = 2 * n queue = [(0, 0), (0, 1)] path = [[upper_bound, upper_bound] for i in range(n)] visited = [[False, False] for i in range(n)] path[0][0], path[0][1] = 0, 0 visited[0][0], visited[0][1] = True, True while queue: node1, color = queue.pop(0) if not color and path[node1][0] < upper_bound: for i, node2 in enumerate(original_path[node1]): if node2[1] and not visited[i][1]: queue.append((i, 1)) path[i][1] = path[node1][0] + 1 visited[i][1] = True if color and path[node1][1] < upper_bound: for i, node2 in enumerate(original_path[node1]): if node2[0] and not visited[i][0]: queue.append((i, 0)) path[i][0] = path[node1][1] + 1 visited[i][0] = True return [min(i, j) if i < upper_bound or j < upper_bound else -1 for i, j in path] ``` ## [1130 Minimum Cost Tree From Leaf Values](https://leetcode.com/problems/minimum-cost-tree-from-leaf-values/) $DP[i,j]=DP[i,k]+DP[k+1,j]+max(A[i:k])\times max(A[k+1:j])$ ![](https://i.imgur.com/ez08VFl.jpg) ```python class Solution: def mctFromLeafValues(self, arr: List[int]) -> int: l = len(arr) DP = [[0 for j in range(l)] for i in range(l)] for n in range(l-1, -1, -1): for i in range(n+1): j = i + (l - 1 - n) temp = [] for k in range(i, j): temp.append(DP[i][k] + DP[k+1][j] + max(arr[i:k+1]) * max(arr[k+1:j+1])) if temp: DP[i][j] = min(temp) return DP[0][l-1] ``` ## [3 Longest Substring Without Repeating Characters](https://leetcode.com/problems/longest-substring-without-repeating-characters/) - 使用一個 hash table `current_pos`儲存每個字元上次出現的位置 - 使用一個與字串`s`相同長度的陣列`longest`儲存若包含當前字元的最長 substring 長度。 - 使用一個迴圈逐步更新陣列`longest` 要考慮的 case 有兩種: 1. 若當前的字元未曾出現過,則將其加入 hash table `current_pos`,長度為前一個字元的 substring 長度 +1 2. 若當前的字元已經出現過,則需要比較上一個字元的 substring 長度 +1 以及與上次出現當前字元的位置的距離,何者較小,並更新 hash table ```python class Solution: def lengthOfLongestSubstring(self, s: str) -> int: if not s: return 0 current_pos = {} longest = [0] * len(s) for i, c in enumerate(s): if c not in current_pos: current_pos[c] = i longest[i] = longest[i - 1] + 1 else: longest[i] = min(longest[i - 1] + 1, i - current_pos[c]) current_pos[c] = i return max(longest) ``` ## [5 Longest Palindromic Substring](https://leetcode.com/problems/longest-palindromic-substring/) 1. Brute Force Time Complexity: $O(n^3)$ Space Complexity: $O(1)$ ```python class Solution: def longestPalindrome(self, s: str) -> str: ans = "" for start in range(len(s)): for end in range(start, len(s)+1): new_s = s[start:end] if new_s[::-1] == new_s and len(new_s) > len(ans): ans = new_s return ans ``` 2. Dynamic Programming * Base case: 一個字元、兩個相同的字元 * Incremental case: * 若一個 string 已經是 palindrome,那麼在其左方及右方再各加上一個相同的字元,則結果依然會是一個 palindrome * 若一個 string 不是 palindrome,或是在一個是 palindrome 的 string 左右各加上不同的字元,結果都不會是 palindrome Time Complexity: $O(n^2)$ Space Complexity: $O(n^2)$ ```python class Solution: def longestPalindrome(self, s: str) -> str: n = len(s) dp = [[False for j in range(n)] for i in range(n)] for i in range(n): dp[i][i] = True start = 0 maxlen = 1 for i in range(n-1): if s[i] == s[i+1]: dp[i][i+1] = True start = i maxlen = 2 for k in range(3, n+1): for i in range(n-k+1): j = i+k-1 if dp[i+1][j-1] and s[i]==s[j]: dp[i][j] = True if k > maxlen: start = i maxlen = k return s[start:start+maxlen] ``` 較簡化的寫法: ```python class Solution: def longestPalindrome(self, s: str) -> str: n = len(s) dp = [[False for j in range(n)] for i in range(n)] start, end = 0, 0 for i in range(n-1,-1,-1): for j in range(i, n): if i == j: dp[i][j] = True elif j == i + 1 and s[i] == s[j]: dp[i][j] = True elif s[i] == s[j] and dp[i+1][j-1]: dp[i][j] = True if dp[i][j] and j - i + 1 > end - start + 1: start, end = i, j return s[start:end+1] ``` 簡化判斷式:$(a\wedge b)\lor(b\wedge c)\implies b\wedge(a\lor c)$ ```python class Solution: def longestPalindrome(self, s: str) -> str: n = len(s) dp = [[False for j in range(n)] for i in range(n)] start, end = 0, 0 for i in range(n-1,-1,-1): for j in range(i, n): if (i == j) or (s[i] == s[j]) and (j == i + 1 or dp[i+1][j-1]): dp[i][j] = True if dp[i][j] and j - i + 1 > end - start + 1: start, end = i, j return s[start:end+1] ``` ## 647 Palindromic Substrings ```python class Solution: def countSubstrings(self, s: str) -> int: n = len(s) DP = [[False for j in range(n)] for i in range(n)] cnt = 0 for i in range(n-1, -1, -1): for j in range(i, n): if i == j: DP[i][j] = True elif j == i + 1 and s[i] == s[j]: DP[i][j] = True elif s[i] == s[j] and DP[i+1][j-1]: DP[i][j] = True cnt += DP[i][j] return cnt ``` ## 516 Longest Palindromic Subsequence ```python class Solution: def longestPalindromeSubseq(self, s: str) -> int: n = len(s) DP = [[0 for j in range(n)] for i in range(n)] for i in range(n): DP[i][i] = 1 for i in range(n-2, -1, -1): for j in range(i+1, n): if s[i] == s[j]: DP[i][j] = DP[i+1][j-1] + 2 else: DP[i][j] = max(DP[i+1][j], DP[i][j-1]) return DP[0][-1] ``` ## 931 Minimum Falling Path Sum 來自三個方向的元素:左上、上、右上。 ```python class Solution: def minFallingPathSum(self, A: List[List[int]]) -> int: x, y = len(A), len(A[0]) DP = [[0 for j in range(y+2)] for i in range(x+1)] for i in range(1, x+1): DP[i][0] = DP[i][-1] = 101 for j in range(1, y+1): DP[i][j] = A[i-1][j-1] + min([DP[i-1][j-1], DP[i-1][j], DP[i-1][j+1]]) return min(DP[-1]) ``` in-place 的做法 ```python class Solution: def minFallingPathSum(self, A: List[List[int]]) -> int: n = len(A) for i in range(1, n): for j in range(0, n): A[i][j] += min([A[i-1][max(j-1, 0)], A[i-1][j], A[i-1][min(j+1, n-1)]]) return min(A[-1]) ``` ## 983 Minimum Cost For Tickets ```python class Solution: def mincostTickets(self, days: List[int], costs: List[int]) -> int: DP = [0] * 366 p = 0 for i in range(1, 366): if days[p] != i: DP[i] = DP[i-1] else: DP[i] = min(DP[i-1]+costs[0], DP[max(i-7,0)]+costs[1], DP[max(i-30,0)]+costs[2]) p += 1 p %= len(days) return DP[-1] ``` ## 413 Arithmetic Slices 原本想用二維的 DP 做,但是無奈 TLE,只能尋求 $O(n)$ 的解法了。 ```python class Solution: def numberOfArithmeticSlices(self, A: List[int]) -> int: n = len(A) diff = [A[i+1] - A[i] for i in range(n-1)] DP = [0] * n for i in range(2, n): if diff[i-1] == diff[i-2]: DP[i] = DP[i-1] + 1 return sum(DP) ``` ## 72 Edit Distance 多花時間理解 ```python class Solution: def minDistance(self, word1: str, word2: str) -> int: m, n = len(word1), len(word2) DP = [[0 for j in range(n+1)] for i in range(m+1)] for i in range(m+1): for j in range(n+1): if i == 0: DP[i][j] = j elif j == 0: DP[i][j] = i elif word1[i-1] == word2[j-1]: DP[i][j] = DP[i-1][j-1] else: DP[i][j] = 1 + min(DP[i-1][j], DP[i][j-1], DP[i-1][j-1]) return DP[m][n] ``` ## 1105 Filling Bookcase Shelves 多花時間理解 ```python class Solution: def minHeightShelves(self, books: List[List[int]], shelf_width: int) -> int: n = len(books) DP = [10000001] * (n+1) DP[0] = 0 for i in range(1, n+1): max_height, width_left = 0, shelf_width for j in range(i-1, -1, -1): width_left -= books[j][0] max_height = max(max_height, books[j][1]) if width_left >= 0: DP[i] = min(max_height + DP[j], DP[i]) return DP[-1] ``` ## 1143 Longest Common Subsequence ```python class Solution: def longestCommonSubsequence(self, text1: str, text2: str) -> int: n1, n2 = len(text1), len(text2) DP = [[0 for j in range(n2 + 1)] for i in range(n1 + 1)] for i in range(1, n1 + 1): for j in range(1, n2 + 1): if text1[i - 1] == text2[j - 1]: DP[i][j] = DP[i - 1][j - 1] + 1 else: DP[i][j] = max(DP[i - 1][j], DP[i][j - 1]) return DP[-1][-1] ``` ## 714 Best Time to Buy and Sell Stock with Transaction Fee ```python class Solution: def maxProfit(self, prices: List[int], fee: int) -> int: n = len(prices) DP = [[0 for j in range(n)] for i in range(n)] for k in range(n - 1, -1, -1): for i in range(k + 1): j = i + n - k - 1 DP[i][j] = max(prices[j] - prices[i] - fee, 0) for l in range(i, j): DP[i][j] = max(DP[i][j], DP[i][l] + DP[l + 1][j]) return DP[0][-1] ``` Time Complexity: $O(n^3)$ Space Complexity: $O(n^2)$ 很不幸的 TLE。 另個解法: 兩個 state,buy 與 sell: - `buy[i]`: 第`i`天選擇買入 or 不買入,看哪種方式可獲得最大的 prfit - 選擇買入: `buy[i]=sell[i-1]-prices[i]-fee` - 選擇不買入: `buy[i]=buy[i-1]` - `sell[i]`: 第`i`天選擇賣出 or 不賣出,看哪種方式可獲得最大的 prfit - 選擇賣出: `sell[i]=buy[i-1]+prices[i]` - 選擇不賣出: `sell[i]=sell[i-1]` 可以選擇在買入或賣出的時候扣除手續費,這邊選擇在買入的時候計算手續費。 ```python class Solution: def maxProfit(self, prices: List[int], fee: int) -> int: n = len(prices) buy, sell = [0] * n, [0] * n buy[0] = -prices[0] - fee for i in range(1, n): buy[i] = max(buy[i-1], sell[i-1] - prices[i] - fee) sell[i] = max(sell[i-1], buy[i-1] + prices[i]) return sell[-1] ``` Time Complexity: $O(n)$ Space Complexity: $O(n)$ ## 1027 Longest Arithmetic Sequence DP with hash table ```python class Solution: def longestArithSeqLength(self, A: List[int]) -> int: n = len(A) dist = [{} for _ in range(n)] max_len = 0 for i in range(n): for j in range(i): diff = A[i] - A[j] if diff in dist[j].keys(): dist[i][diff] = dist[j][diff] + 1 else: dist[j][diff] = 1 dist[i][diff] = 2 max_len = max(max_len, dist[i][diff]) return max_len ``` Time Complexity: $O(n^2)$ Space Complexity: $O(n^2)$ ## 62 Unique Paths DP - 2D array ```python class Solution: def uniquePaths(self, m: int, n: int) -> int: DP = [[0 for j in range(n)] for i in range(m)] for i in range(m): DP[i][0] = 1 for j in range(n): DP[0][j] = 1 for i in range(1, m): for j in range(1, n): DP[i][j] = DP[i-1][j] + DP[i][j-1] return DP[-1][-1] ``` Time Complexity: $O(n^2)$ Space Complexity: $O(n^2)$ --- DP - 1D array ```python class Solution: def uniquePaths(self, m: int, n: int) -> int: DP = [0 for _ in range(n+1)] DP[1] = 1 for i in range(m): for j in range(1, n+1): DP[j] = DP[j-1] + DP[j] return DP[-1] ``` Time Complexity: $O(n^2)$ Space Complexity: $O(n)$ --- Formula ```python class Solution: def uniquePaths(self, m: int, n: int) -> int: return self.f(m+n-2) // (self.f(m-1) * self.f(n-1)) def f(self, n): if n == 0: return 1 return n * self.f(n-1) ``` Time Complexity: $O(n)$ Space Complexity: $O(1)$ ## 64 Minimum Path Sum ```python class Solution: def minPathSum(self, grid: List[List[int]]) -> int: m, n = len(grid), len(grid[0]) for i in range(1, m): grid[i][0] += grid[i-1][0] for j in range(1, n): grid[0][j] += grid[0][j-1] for i in range(1, m): for j in range(1, n): grid[i][j] += min(grid[i-1][j], grid[i][j-1]) return grid[-1][-1] ``` Time Complexity: $O(n^2)$ Space Complexity: $O(1)$ ## 96 Unique Binary Search Trees $G(n)$: 1~n 可以組成幾種 BST $F(i,n)$: 當 i 在 root 時有幾種 BST 的組合 $G(n)=\sum_{i=1}^{n}F(i,n)$ $F(i,n)=G(i-1)\times G(n-i)$ ```python class Solution: def numTrees(self, n: int) -> int: G = [0] * (n + 1) G[0] = G[1] = 1 for i in range(2, n+1): for j in range(1, i+1): G[i] += G[j-1] * G[i-j] return G[n] ``` Time Complexity: $O(n^2)$ Space Complexity: $O(n)$ ## 650 2 Keys Keyboard ```python class Solution: def minSteps(self, n: int) -> int: DP = [0] * (n + 1) for i in range(2, n+1): DP[i] = i for j in range(i-1, 1, -1): if i % j == 0: DP[i] = DP[j] + (i // j) break return DP[n] ``` ## 486 Predict the Winner ```python class Solution: def PredictTheWinner(self, nums: List[int]) -> bool: return self.Score(nums, 0, len(nums) - 1) >= 0 def Score(self, nums, i, j): if i == j: return nums[i] return max(nums[i] - self.Score(nums, i+1, j), nums[j] - self.Score(nums, i, j-1)) ``` ```python class Solution: def PredictTheWinner(self, nums: List[int]) -> bool: n = len(nums) DP = [[0 for j in range(n)] for i in range(n)] for i in range(n): DP[i][i] = nums[i] for k in range(n-1, 0, -1): for i in range(k): j = i + n - k DP[i][j] = max(nums[i] - DP[i+1][j], nums[j] - DP[i][j-1]) return DP[0][n-1] >= 0 ```