# Leetcode 052721~ ## 052721 ### #454. 4Sum II #### 論壇解1 combined double hashmap: 不必考慮數值重複問題。符合資格的case,兩個hashmap的值相乘即可 ```python= class Solution: def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int: n1, n2, n3, n4 = len(nums1), len(nums2), len(nums3), len(nums4) tg_dict_a, tg_dict_b = {}, {} res = 0 for i in nums1: for j in nums2: if i + j not in tg_dict_a: tg_dict_a[i+j] = 1 else: tg_dict_a[i+j] += 1 for i in nums3: for j in nums4: if i + j not in tg_dict_b: tg_dict_b[i+j] = 1 else: tg_dict_b[i+j] += 1 for i in tg_dict_a: for j in tg_dict_b: if not i+j: res += (tg_dict_a[i] * tg_dict_b[j]) return res ``` ### #13. Roman to Integer #### first try: By rule, naively. ```python= class Solution: def romanToInt(self, s: str) -> int: res = 0 if 'CM' in s: s = s.replace('CM', '') res += 900 if 'CD' in s: s = s.replace('CD', '') res += 400 if 'XC' in s: s = s.replace('XC', '') res += 90 if 'XL' in s: s = s.replace('XL', '') res += 40 if 'IX' in s: s = s.replace('IX', '') res += 9 if 'IV' in s: s = s.replace('IV', 'we') res += 4 if 'D' in s: s = s.replace('D', '') res += 500 if 'L' in s: s = s.replace('L', '') res += 50 if 'V' in s: s = s.replace('V', '') res += 5 res += 10 * s.count('X') res += 100 * s.count('C') res += 1000 * s.count('M') res += s.count('I') return res ``` #### 網友解 直指問題本質的解法。 沒辦法,我就不是西方人。 ```python= class Solution: def romanToInt(self, s: str) -> int: s = s.lower() roman = {'i':1,'v':5,'x':10,'l':50,'c':100,'d':500,'m':1000} res, index = 0, 'i' for i in s[::-1]: res, index = res-roman[i] if roman[index] > roman[i] else res + roman[i], i return res ``` ## 053021 ### #19. Remove Nth Node From End of List #### first try: 題幹提示,希望one-pass。 naive solution的問題是,因為深度(長度)需要等traverse到尾端才知道,same for the location of removal. 需要先run一次確定深度,再run一次修改目標位置的指標。 所以我的方法就是建成一個list,list自然會包含長度資訊。接下來用list indexing就知道要抓的位置。 但這有一個問題是,list indexing本身是$O(1)$還是$O(N)$呢?如果是後者,那我的方法等同於naive solution的過程。 ```python= class Solution: def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode: lnlist = [] res = head while head.next: lnlist.append(head) head = head.next lnlist.append(head) if len(lnlist) - 1 >= n: lnlist[-n-1].next = lnlist[-n+1] if -n + 1 < 0 else None lnlist[-n] = None return res else: return res.next ``` #### 網友解 double pointer 等速雙指標,雖然他用fast, slow命名,但差距只有一開始的位置差(fast先前進n步),fast, slow之後一起移動。 等等,但他這樣就不算2 pass? 恩對,官方解答看來定調這樣叫做1 pass。 ```python= class Solution: def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode: fast = slow = head for _ in range(n): fast = fast.next if not fast: return head.next while fast.next: fast = fast.next slow = slow.next slow.next = slow.next.next return head ``` <br> <br> ### #26. Remove Duplicates from Sorted Array #### first try 明顯的浪費空間,不過Leetcode結果是看不出來的。 利用倒序刪除重複元素避免了index synchronization的問題。 ```python= class Solution: def removeDuplicates(self, nums: List[int]) -> int: loc = [0] * len(nums) for i in range(len(nums)): if i and nums[i] == nums[i-1]: loc[i] = 1 for i in range(len(loc)-1, -1, -1): if loc[i]: del nums[i] return len(nums) ``` #### 網友解: ```python= class Solution: def removeDuplicates(self, nums: List[int]) -> int: tail = 0 for i in range(1, len(nums)): if nums[i] == nums[tail]: continue tail += 1 nums[tail] = nums[i] return tail + 1 ``` <br> <br> ## 053121 ### #27. Remove Element #### first try: 跟昨天26題一樣的解法,但其實不需要,請見網友解1 ```python= class Solution: def removeElement(self, nums: List[int], val: int) -> int: for i in range(len(nums)-1, -1, -1): if nums[i] == val: del nums[i] return len(nums) ``` #### 網友解1: 用while就不會有iterable synchronization的問題 ```python= class Solution: def removeElement(self, nums: List[int], val: int) -> int: i = 0 while i < len(nums): if nums[i] == val: del nums[i] else: i += 1 return len(nums) ``` #### 網友解2: 最後是標準的double pointer,牛刀殺雞。 ```python= class Solution: def removeElement(self, nums: List[int], val: int) -> int: nums.sort() i, j = 0, 0 while j < len(nums): if nums[j] != val: nums[i] = nums[j] i, j = i + 1, j + 1 else: j += 1 return i ``` #### Analysis - Time: $O(N)$ for all - Space: $O(1)$ for all; all in-place ## 060121 ### #88. Merge Sorted Array 題目原意是排兩個sorted array,也就是merge sort的最後一步(或每個subroutine的最後一步)。 如下面網友解,用2 pointer 的方式比較原汁原味,但實際上簡單粗暴的方法卻比較好用: 手動pop nums1多餘的元素,再把nums2接上去。然後call sort(),這會做一個in place sorting。 重點在避免改到nums1的id。 #### first try ```python= class Solution: def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None: """ Do not return anything, modify nums1 in-place instead. """ for _ in range(n): nums1.pop() for i in range(n): nums1.append(nums2[i]) nums1.sort() ``` #### 網友解: 體現題目要的精神,可惜表現不如人意(56ms(PR5) vs 32ms(PR88), by first try) ```python= class Solution: def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None: """ Do not return anything, modify nums1 in-place instead. """ i, j = m-1, n-1 while j+1 and i+1: if nums1[i] > nums2[j]: nums1[i+j+1] = nums1[i] i -= 1 else: nums1[i+j+1] = nums2[j] j -= 1 while j+1: nums1[i+j+1] = nums2[j] j -= 1 ``` #### second try: 所以同first try的道理這樣也行。只是performance就真的比較差了。(60ms, PR5) ```python= class Solution: def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None: """ Do not return anything, modify nums1 in-place instead. """ nums3, i, j = [], 0, 0 while i < m and j < n: if nums1[i] < nums2[j]: nums3.append(nums1[i]) i += 1 else: nums3.append(nums2[j]) j += 1 if i < m: nums3 += nums1[i:m] elif j < n: nums3 += nums2[j:n] for i in range(len(nums3)): nums1[i] = nums3[i] ``` ### #75. Sort Colors ### first try 好這完全是來亂的。 但是performance最好。 ```python= class Solution: def sortColors(self, nums: List[int]) -> None: """ Do not return anything, modify nums in-place instead. """ nums.sort() ``` ### second try: 那就自己寫一個qsort。 performance在28ms(PR90)~56ms(PR 0)之間。leetcode的benchmark真的歹就補? ```python= class Solution: def sortColors(self, nums: List[int]) -> None: """ Do not return anything, modify nums in-place instead. """ def qsort(data, left, right): if left > right: return i, j = left, right while i != j: while j > i and data[j] > data[left]: j -= 1 while j > i and data[i] <= data[left]: i += 1 if j > i: data[i], data[j] = data[j], data[i] data[i], data[left] = data[left], data[i] qsort(data, left, i-1) qsort(data, i+1, right) qsort(nums, 0, len(nums)-1) ``` ### third try 既然都知道所有的選項了,那就用字典紀錄次數再重組就好了。 對於沒有內建hashmap的語言來說關鍵就是如何實作hash table了。 ```python= class Solution: def sortColors(self, nums: List[int]) -> None: """ Do not return anything, modify nums in-place instead. """ ndict = {0:0, 1:0, 2:0} for n in nums: ndict[n] += 1 for i in range(ndict[0]): nums[i] = 0 for i in range(ndict[0], ndict[0] + ndict[1]): nums[i] = 1 for i in range(ndict[0]+ndict[1], ndict[0]+ndict[1]+ndict[2]): nums[i] = 2 ``` ### 網友解: 原來這問題叫做Dutch national flag problem。簡單來說就是sorting的特例。 sorting的可能元素有限時,可以用$O(N)$解出而不是$O(NlogN)$。 只要拆成子問題即可。(如*) 但須考慮常數大小。常數太大,反而不如一般sorting。適用於總共數值不超過四五種的狀況。 (數值選項趨近len(data)時,其實就等同於$O(N^2)$了,超過的話甚至比bubble sorting還糟糕,所以真的是特例) ```python= class Solution: def sortColors(self, nums: List[int]) -> None: """ Do not return anything, modify nums in-place instead. """ w, r, b = 0, 0, len(nums)-1 # w == 0, r == 1, b == 2 while w <= b: if nums[w] == 1: w += 1 elif nums[w] == 0: nums[w], nums[r] = nums[r], nums[w] w, r = w + 1, r + 1 elif nums[w] == 2: nums[w], nums[b] = nums[b], nums[w] b -= 1 ``` <br> <br> \* Mauritius,模里西斯的國旗: ![](https://i.imgur.com/DjTv8dK.jpg =150x) 四色旗,和荷蘭旗問題一樣的概念。 同樣的概念理論上可以延伸到N種值。 ```python= mau = [1,3,2,2,3,0,1,2,3,0,1,2,3,1,1,1,2,2,0,3,3,3,2,2,3,1,2,3,2,2,1,0,1,0,0] print(mau) def fourcolor(data): i, j, k= 0, 0, len(data)-1 while i <= k: if data[i] < 2: data[i], data[j] = data[j], data[i] i, j = i + 1, j + 1 elif data[i] == 2: i += 1 elif data[i] == 3: data[i], data[k] = data[k], data[i] k -= 1 i, j = 0, j-1 while i <= j: if data[i] == 0: i += 1 else: data[i], data[j] = data[j], data[i] j -= 1 fourcolor(mau) print(mau) ``` ## 060221 ### #35. Search Insert Position 只是看了一下網友的解,修正一下reassign pointer的方式。 #### first try: ```python= class Solution: def searchInsert(self, nums: List[int], target: int) -> int: lo, hi = 0, len(nums) - 1 cur = (lo + hi) // 2 while lo <= hi: if nums[cur] == target: return cur if nums[cur] > target: hi = cur - 1 elif nums[cur] < target: lo = cur + 1 cur = (lo + hi) // 2 return lo ``` #### Analysis: 基本上就是sorted binary search 直接call bisect的bisect_left是一樣的意思。 - Time: $O(logN)$ - Space:$O(1)$ ## 060521 ### #4. Median of Two Sorted Arrays #### first try: redundant solution:比naive solution還差。做了多餘的排序,反而變成需要$O(NlogN)$。 naive solution是從兩邊的頭取較小的,取到median 為止,需要花費$O(N)$。 ```python= class Solution: def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float: index = len(nums1) + len(nums2) if not index % 2: index //= 2 return (sorted(nums1 + nums2)[index - 1] + sorted(nums1 + nums2)[index]) / 2 else: index //= 2 return sorted(nums1 + nums2)[index] ``` #### 網友解 最佳解,使用binary search。 因為限定是median number,位置固定的情況下,nums1取數後就知道nums2該取何者。所以就在兩者之中較短者上用binary search。 如何知道找到了? 只要檢視nums1[i-1], nums1[i], nums2[j-1], nums2[j]的關係就可以知道。因為median number必然是: ```python= max(nums1[i-1], nums2[j-1]) ``` 若len相加是偶數,則為: ```python= (min(nums1[i], nums2[j]) + max(nums1[i-1 + nums2[j-1]]) ) / 2 ``` 這樣就可以在$O(logN)$內得到解。 比較要注意的是邊界條件:例如i==0, j==0。這是令這題變得複雜的原因之一。 ## 060621 ### #1486. XOR Operation in an Array 乍看之下很簡單,照著題意做一次就得到$O(N)$的解,也會過。 但觀察後發現規則,只要照規則就可以$O(1)$判斷出來。 ```python= class Solution: def xorOperation(self, n: int, start: int) -> int: if n == 1: return start last = start + (n - 1) * 2 if start % 4 < 2: # 如果start % 4 < 2, 可以知道第2位一定是0(以下皆以LSB為第一位) # 所以如果n是4的倍數,XOR後一定為0 # 如果n % 4 == 2,因為只有第2位會變,所以一定是bx0010 # 那餘1和於3的情況就可以推導出來了 if n % 4 == 0: return 0 elif n % 4 == 1: return last elif n % 4 == 2: return 2 elif n % 4 == 3: return 2 ^ last else: # 如果start % 4 > 1: 其實就是上例往前推一步,也就是說start+2就會回到上面的例子。 # n % 4 == 3的情況,首項保留,中間4的倍數消去後,倒數第1、2位又如同上面start % 4 < 2的前兩位,所以XOR結果為2。 if n % 4 == 1: return start elif n % 4 == 2: return start ^ last elif n % 4 == 3: return start ^ 2 elif n % 4 == 0: return start ^ 2 ^ last ``` ## 060721 ### #53. Maximum Subarray 看起來是DP但其實不是,只不過有那個味道在。 #### 論壇解1 subarray最大和預設為首項值。一項項往後看,如果和小於當前最大值就直接丟棄,重設起點。 ```python= class Solution: def maxSubArray(self, nums: List[int]) -> int: self.max_sum = nums[0] def _max_sub(nums, f): if f == 0: self.max_sum = nums[0] return nums[0] local_max = max(nums[f], _max_sub(nums, f-1) + nums[f]) self.max_sum = max(local_max, self.max_sum) return local_max _max_sub(nums, len(nums) - 1) return self.max_sum if len(nums) > 1 else nums[0] ``` #### 論壇解2 概念一樣,只是不用遞迴,用iteration ```python= class Solution: def maxSubArray(self, nums: List[int]) -> int: res, sums = nums[0], nums[0] for i in range(1, len(nums)): sums = max(nums[i] + sums, nums[i]) if sums > res: res = sums return res ``` #### 論壇解3 用divide and conquer。 每個sub-procedure由中間往左右找該fragment最大值。 sub-procedure求最大值的過程看起來跟前面有點像,不同之處在於必須要跟最內側(nums[mid-1] for left part, nums[mid+1] for right part)連在一起,所以加過去一路取max就可以了。不過這作法不聰明,只是為DC而DC。 ```python= class Solution: def maxSubArray(self, nums: List[int]) -> int: def dvcqr(nums, l, r): if l > r: return -2**32 mid = (l + r) // 2 cur, rtbest, ltbest = 0, 0, 0 for i in range(mid + 1, r + 1): cur += nums[i] rtbest = max(rtbest, cur) cur = 0 for i in range(mid - 1, l - 1, -1): cur += nums[i] ltbest = max(ltbest, cur) ctbest = rtbest + ltbest + nums[mid] ltonlybest = dvcqr(nums, l, mid - 1) rtonlybest = dvcqr(nums, mid + 1, r) return max(ctbest, ltonlybest, rtonlybest) return dvcqr(nums, 0, len(nums) - 1) ``` #### 論壇解4 更聰明的DC。 我沒寫過,要找時間寫一遍。 ###### tags: TO DO ```python= class Solution: def maxSubArray(self, nums): """ :type nums: List[int] :rtype: int """ def divide_and_conquer(nums, i, j): if i == j-1: return nums[i],nums[i],nums[i],nums[i] # we will compute : # a which is max contiguous sum in nums[i:j] including the first value # m which is max contiguous sum in nums[i:j] anywhere # b which is max contiguous sum in nums[i:j] including the last value # s which is the sum of all values in nums[i:j] # compute middle index to divide array in two halves i_mid = i+(j-i)//2 # compute a, m, b, s for left half a1, m1, b1, s1 = divide_and_conquer(nums, i, i_mid) # compute a, m, b, s for right half a2, m2, b2, s2 = divide_and_conquer(nums, i_mid, j) # combine a, m, b, s values from left and right halves to form a, m, b, s for whole array (bottom up) a = max(a1, s1+a2) b = max(b2, s2+b1) m = max(m1,m2,b1+a2) s = s1+s2 return a,m,b,s _,m,_,_ = divide_and_conquer(nums, 0, len(nums)) return m ``` #### Analysis: - Time: $O(N)$, $O(N)$, $O(NlogN)$, $O(N)$ ### #7. Reverse Integer first javascript try ```javascript= /** * @param {number} x * @return {number} */ var reverse = function(x) { if (x > 0) { x = x.toString() let y = x.split('') y = y.reverse() y = y.join('') if (y <= 2 ** 31 - 1) return y else return 0 } else if (x < 0) { x = (-x).toString() let y = x.split('') y = y.reverse() y = y.join('') if (y <= 2 ** 31) return -y else return 0 } else return 0 }; ``` ## 060821 ### #121. Best Time to Buy and Sell Stock 上一題的變體。 解法觀念類似。這邊用上一題第一個解法的概念。 ```python= class Solution: def maxProfit(self, prices: List[int]) -> int: if len(prices) < 2: return 0 profit = - prices[0] lowest = highest = prices[0] for i in range(1, len(prices)): if prices[i] < lowest: lowest = prices[i] continue if prices[i] - lowest > profit: profit = prices[i] - lowest return profit if profit > 0 else 0 ``` ## 061621 ### #122. Best Time to Buy and Sell Stock II 題意想清楚就知道,這樣就可以了。比前一題還簡單。 最難的地方應該是了解題意的意涵。 #### first try ```python= class Solution: def maxProfit(self, prices: List[int]) -> int: if len(prices) == 1: return 0 res = 0 for i in range(1, len(prices)): if prices[i] > prices[i-1]: res += (prices[i] - prices[i-1]) return res ``` #### Analysis - Time:$O(N)$ ### #309. Best Time to Buy and Sell Stock with Cooldown #### HuaHua_1 DP, naive ver.: ```python= import sys class Solution: def maxProfit(self, prices: List[int]) -> int: if len(prices) == 1: return 0 prices = prices[0] lp = len(prices) hold, sold, rest = [0] * lp, [0] * lp, [0] * lp hold[0] = -sys.maxsize for i in range(1, lp): rest[i] = max(rest[i-1], sold[i-1]) sold[i] = hold[i-1] + prices[i] hold[i] = max(rest[i-1] - prices[i], hold[i-1]) return max(rest[-1], sold[-1]) ``` #### HuaHua_2 DP, dimension-reduction ver.: ```python= import sys class Solution: def maxProfit(self, prices: List[int]) -> int: if len(prices) == 1: return 0 sold, rest, hold = 0, 0, -sys.maxsize for p in prices: prev_sold = sold sold = hold + p hold = max(rest - p, hold) rest = max(rest, prev_sold) return max(sold, rest) ``` pic1. ![](https://i.imgur.com/SrGa8KS.png =250x) pic2. ![](https://i.imgur.com/qWKqsc1.png) 了解pic1的邏輯後,畫出來就可以簡單計算出答案。 因為每個狀態都只需要前一項,所以只要maintain每個狀態的前一項,不需要整個list,這就是第二個implementation做的簡化。 #### Analysis - Time: $O(N)$ - Space: $O(N)$ / $O(1)$ ## 061721 ### #123. Best Time to Buy and Sell Stock III 網友解: [小明](https://maxming0.github.io/2020/08/16/Best-Time-to-Buy-and-Sell-Stock-III/) 一樣用動態規劃。限制只能做兩次交易,那就是分成buy1, sell1; buy2, sell2兩對、四個數組來做DP。實務上,一樣,由於今日狀態只跟前一天有關,所以縮減成maintain四個variables就可以了。 需要給定限制,在第一次交易完成前不能進行第二次交易嗎?不需要。在我們的模型裡,第一次交易完成後(包含完成當下),第二次交易的結果會等同"當天立即買進賣出"。即假設現在為day 3,本日完成buy1賺2元;同日buy2和sell2的取值動作可以解讀為"這天立即買進並賣出",這跟沒做是一樣的,所以不違反題意。 所以最後也不用return max(s1, s2),return s2即可。 ```python= class Solution: def maxProfit(self, prices: List[int]) -> int: if len(prices) < 2: return 0 b1 = b2 = -float('inf') s1 = s2 = 0 for p in prices: b1 = max(b1, -p) s1 = max(b1 + p, s1) b2 = max(s1 - p, b2) s2 = max(b2 + p, s2) return s2 ``` #### Analysis - Time: $O(N)$ ### #188. Best Time to Buy and Sell Stock IV 呼呼,系列題最終回,也是最難的。 一樣使用DP with dimension diminishing 解析待補,以及greedy解法。待我沈澱一晚。 #### 網友解: ```python= class Solution: def maxProfit(self, k: int, prices: List[int]) -> int: n = len(prices) if n < 2: return 0 elif n <= k // 2: res = 0 for i, j in zip(prices[1:], prices[:-1]): if i > j: res += i - j return res res = [[0, 0] for _ in range(k + 1)] for i in range(k + 1): res[i][1] = -prices[0] for i in range(n): for j in range(1, k + 1): res[j - 1][1] = max(res[j - 1][1], res[j - 1][0] - prices[i]) res[j][0] = max(res[j][0], res[j - 1][1] + prices[i]) return res[k][0] ``` ### #67. Add Binary #### first try: 邊界條件注意一下。 ```python= class Solution: def addBinary(self, a: str, b: str) -> str: if len(a) < len(b): a, b = b, a b = b.zfill(len(a)) # 10011 -> 00010011 a, b = a[::-1] + '0', b[::-1] + '0' a, b = list(a), list(b) # 00010011, 10011100 => 110010000, 001110010 # one more 0 at tail for possible carrying number res = ['0'] * (len(a)) c, n1, n2 = 0, 0, 0 for i in range(len(a)): if not c: n1, n2 = int(a[i]), int(b[i]) if n1 + n2 == 2: c = 1 elif n1 + n2 < 2: res[i] = str(n1 + n2) elif c == 1: n1, n2 = int(a[i]), int(b[i]) if n1 + n2 == 2: res[i] = '1' elif n1 + n2 == 1: res[i] = '0' elif not (n1 + n2): res[i] = '1' c = 0 if not int(res[-1]): res = res[:-1] return "".join(res[::-1]) ``` #### 網友解 概念是一樣的。 不過更優良地利用了基本庫的函式,程式碼較簡潔。 ```python= class Solution: def addBinary(self, a: str, b: str) -> str: c, res = 0, '' a, b = list(a), list(b) while a or b or c: if a: c += int(a.pop()) if b: c += int(b.pop()) res += str(c%2) c //= 2 return res[::-1] ``` ## 070621 ### #1338. Reduce Array Size to The Half #### first try 題意就是要了解arr內一樣的數字分組,需要多少會過半。只要知道每個數字有多少個就好,所以就先用字典分組。然後排序,看加到多少會過半。 ```python= class Solution: def minSetSize(self, arr: List[int]) -> int: s = len(arr) number_times = {} for i in arr: if i not in number_times: number_times[i] = 1 else: number_times[i] += 1 number_times_rank = sorted(list(number_times.values()), reverse = True) if len(number_times_rank) == 1: return 1 res, aggre = 0, number_times_rank[0] while aggre < (len(arr) + 1) // 2: res += 1 aggre += number_times_rank[res] return res + 1 ``` #### 官方解1: bucket sort: 對一陣列arr: ```python= arr = [1,2,2,4,1,2,1,5,4,5,3,2,2,1] counts = collections.Counter(arr).values() # == [4,5,2,2,1] max_val = max(counts) # == 5 buckets = [0] * (max_val + 1) for c in counts: buckets[c] += 1 # buckets這個array非常重要。它的index等於'arr裡有index個的數字的個數' # buckets == [0,1,2,0,1,1] ``` 所以在這個情況下我們不需要跑任何n(logn)排序,因為只要由buckets的尾端往回取值,就可以知道我們已掌握了arr之中的多少個數字。取到超過一半時就是答案。當然最後一步需要一點處理: ```python= min(buckets[bucket], math.ceil(number_to_count / bucket)) ``` ```python= class Solution: def minSetSize(self, arr: List[int]) -> int: counts = collections.Counter(arr).values() # e.g. Counter({1: 4, 2: 5, 3: 1, 4: 2, 5: 2}) number_to_count = len(arr) // 2 # e.g. [4,5,1,2,2] max_val = max(counts) buckets = [0] * (max_val + 1) for c in counts: buckets[c] += 1 # buckets: [0,1,2,0,1,1] bucket = max_val num_set = 0 while number_to_count > 0: if number_to_count - buckets[bucket] * bucket >= 0: num_set += buckets[bucket] number_to_count -= buckets[bucket] * bucket bucket -= 1 continue num_set += min(buckets[bucket], math.ceil(number_to_count / bucket)) return num_set return num_set ``` #### Analysis: - Time: $O(NlogN)$ (mine) / $O(N)$ (official better solution) ### #94. Binary Tree Inorder Traversal ### first try: recursive method; is said to be trivial ```python= # Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def inorderTraversal(self, root: TreeNode) -> List[int]: if not root: return [] return self.inorderTraversal(root.left) + [root.val] + self.inorderTraversal(root.right) ``` ### 網友解: iterative method: stack method ```python= class Solution: def inorderTraversal(self, root: TreeNode) -> List[int]: res, stack = [], [] while True: while root: stack.append(root) root = root.left if not stack: return res node = stack.pop() res.append(node.val) root = node.right ``` ### 網友解2: Morris traversal 這是一種threaded binary tree。 其主要優點是可以在同樣$O(N)$時間複雜度的前提下,實現$O(1)$的空間複雜度,不需要使用stack等額外記錄路徑的data structure。 ```python= class Solution: def inorderTraversal(self, root: TreeNode) -> List[int]: res = [] while root: if not root.left: res.append(root.val) root = root.right else: pred = self.find_pred(root) if pred.right != root: pred.right = root root = root.left else: root.left = None return res def find_pred(self, root): cur = root.left while cur.right and cur.right != root: cur = cur.right return cur ``` [Morris Traversal](https://www.youtube.com/watch?v=wGXB9OWhPTg&ab_channel=TusharRoy-CodingMadeSimple) #### Analysis: - Time: $O(N)$ - Space: $O(N)$ $O(N)$ $O(1)$ ### #100. Same Tree ## 070821 ### #101. Symmetric Tree 和前面比較兩棵樹是否相等的作法一樣,稍微變化而已。 ```python= # Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def isSymmetric(self, root: TreeNode) -> bool: return self.is_symmetric_tree(root.left, root.right) def is_symmetric_tree(self, p, q): if not p and not q: return True if not p or not q: return False if p.val != q.val: return False return self.is_symmetric_tree(p.left, q.right) and self.is_symmetric_tree(p.right, q.left) ``` #### 網友解 補上iterative method ```python= from collections import deque class Solution: def isSymmetric(self, root: TreeNode) -> bool: p, q = root.left, root.right deq = deque([(p, q)]) while deq: p, q = deq.popleft() if not self.check(p, q): return False if p: deq.append((p.left, q.right)) deq.append((p.right, q.left)) return True def check(self, p, q): if p == None and q == None: return True if p == None or q == None: return False if p.val != q.val: return False return True ``` #### Analysis: - Time: $O(N)$ - Space: $O(N)$ ### #718. Maximum Length of Repeated Subarray DP #### 網友解 ```python= class Solution: def findLength(self, nums1: List[int], nums2: List[int]) -> int: if len(nums1) < len(nums2): nums1, nums2 = nums2, nums1 m, n = len(nums1), len(nums2) ans = 0 for a in range(-n + 1, m + n - 1): cnt = 0 for bptr in range(n): aptr = a + bptr if aptr < 0: continue if aptr >= m: break if nums1[aptr] == nums2[bptr]: cnt += 1 if cnt > ans: ans = cnt else: cnt = 0 return ans ``` ### #104. Maximum Depth of Binary Tree #### first try ```python= class Solution: max_depth, cur_depth = 0, 0 def maxDepth(self, root: TreeNode) -> int: if not root: return 0 self.cur_depth += 1 self.max_depth = max(self.max_depth, self.cur_depth) self.maxDepth(root.left) self.maxDepth(root.right) self.cur_depth -= 1 if self.cur_depth == 0: return self.max_depth ``` #### 網友解1 一行文 ```python= class Solution: def maxDepth(self, root: TreeNode) -> int: return 1 + max(self.maxDepth(root.right), self.maxDepth(root.left)) if root else 0 ``` ### #12. Integer to Roman 只是路過看到寫一下而已 ```python= class Solution: def intToRoman(self, num: int) -> str: digits = { 1000:'M', 900:'CM', 500:'D', 400:'CD', 100:'C', \ 90:'XC', 50:'L', 40:'XL', 10:'X', 9:'IX', 5:'V', 4:'IV', 1:'I' \ } digits_li = list(digits.keys()) res, ptr = '', 0 while num and ptr < len(digits_li): if num / digits_li[ptr] >= 1: num -= digits_li[ptr] res += digits[digits_li[ptr]] else: ptr += 1 return res ``` ### #110. Balanced Binary Tree #### first try 第12次提交。再不對,我可要哭了。 其實這樣就是DFS。 ```python= # Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def isBalanced(self, root: TreeNode) -> bool: self.balance = True if not root: return self.balance self.br_depth(root, 1) return self.balance def br_depth(self, root, cur_dep): lt_depth = rt_depth = cur_dep if root.left: lt_depth = self.br_depth(root.left, cur_dep + 1) if root.right: rt_depth = self.br_depth(root.right, cur_dep + 1) if abs(rt_depth - lt_depth) > 1: self.balance = False return max(lt_depth, rt_depth) ``` #### 網友解 DFS, 簡潔版 ```python= class Solution: def isBalanced(self, root: TreeNode) -> bool: self.balance = 1 def dfs(node): if not node: return 0 ld, rd = dfs(node.left), dfs(node.right) if abs(ld - rd) > 1: self.balance = 0 return max(ld, rd) + 1 dfs(root) return self.balance ``` #### Analysis: - Time:$O(N)$ - Space: $O(N)$ ## 081321 ### #94. Binary Tree Inorder Traversal (revisited) #### Morrison's traversal question: is space complexity of this really constant? since we make thread to root(or current) at every step and there should be $O(logN)$ number of this thread links, the space complexity should bt $O(logN)$ but if you allocate space for all node with their member variables no matter if they exist(i.e. if they are None or not), this traversal shall be $O(1)$ in space complexity. Since all we did is just use the already allocated space efficiently. 第一個判斷是現在的節點有沒有left subtree 如果沒有就直接印出現在的節點並移到右子樹,這邊很簡單。 第二個判斷是要找出thread,方法可以另外寫,但因為不複雜,直接寫在一起也可以。 thread是一個暫時的連結(産霊, musubi),其起點是cur的left subtree的最後一個節點(至於怎樣是最後一個節點要看traversal方法),終點是cur本身。 建立thread的方法是先移動到cur的left(因為我們才剛經歷過上一個條件判斷,這裡必定不會是None),然後一直往右移動(pred = pred.right)直到以下兩個情形之一發生為止: 1. pred.right == None 2. pred.right = cur 1的情況表示我們找到thread的正確起點了,這時就設定thread並且將cur = cur.left 2的情況表示這個的確是thread的起點,但這條路我們已經是第二次走了,並且對cur來說,他的left subtree應該已經都被瀏覽過了,所以直接切斷:cur.left = None,然後繼續。下一個while loop就會執行第一個判斷(cur.left==None)。 這樣應該夠清楚吧...... ```python= # Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]: res, cur = [], root while cur: if not cur.left: res.append(cur.val) cur = cur.right else: pred = cur.left while pred.right and pred.right != cur: pred = pred.right if not pred.right: pred.right = cur cur = cur.left else: # equal to "if pred.right == cur:" cur.left = None return res ``` ### #73. Set Matrix Zeroes #### first try naive method: ```python= class Solution: def setZeroes(self, matrix: List[List[int]]) -> None: """ Do not return anything, modify matrix in-place instead. """ i_set, j_set = set(), set() for i in range(len(matrix)): for j in range(len(matrix[i])): if matrix[i][j] == 0: i_set.add(i) j_set.add(j) for i in range(len(matrix)): if i in i_set: matrix[i] = [0] * len(matrix[i]) else: for j in range(len(matrix[i])): if j in j_set: matrix[i][j] = 0 ``` slightly changed: ```python= class Solution: def setZeroes(self, matrix: List[List[int]]) -> None: i_set, j_set = set(), set() for i in range(len(matrix)): for j in range(len(matrix[i])): if matrix[i][j] == 0: i_set.add(i) j_set.add(j) for i in i_set: matrix[i] = [0] * len(matrix[i]) for j in j_set: for i in range(len(matrix)): matrix[i][j] = 0 ``` ## 090221 ### ### #102. Binary Tree Level Order Traversal #### first try: 用字典存,key是level number。只要照著左->右的順序recursion,intralevel順序不會有問題,再來就是排序key,按照key順序填入答案。 不過這其實有點麻煩,因為照我目前的技術,還必須把levels分開寫,不然會有referenced before assignment的問題。 ```python= class Solution: def __init__(self): self.levels = {} def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]: if not root: return [] output = [] def _traverse(node, lv): if node.left: if lv not in self.levels: self.levels[lv] = [node.left.val] else: self.levels[lv].append(node.left.val) _traverse(node.left, lv + 1) if node.right: if lv not in self.levels: self.levels[lv] = [node.right.val] else: self.levels[lv].append(node.right.val) _traverse(node.right, lv + 1) self.levels[0] = [root.val] _traverse(root, 1) all_level = sorted(self.levels.keys()) for i in all_level: output.append(self.levels[i]) return output ``` #### #### second try: 採用BFS,好處是不需要遞迴呼叫。 ```python= class Solution: def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]: if not root: return [] res = [[root.val]] queue = [root] while queue: size = len(queue) layer = [] while size: size -= 1 s = queue[0] if s.left: queue.append(s.left) layer.append(s.left.val) if s.right: queue.append(s.right) layer.append(s.right.val) queue.pop(0) if layer: res.append(layer) return res ``` #### Analysis: - Time:$O(N)$ - Space:$O(N)$ N:node number ## 090621 ### #1629. Slowest Key 给你一个长度为 n 的字符串 keysPressed ,其中 keysPressed[i] 表示测试序列中第 i 个被按下的键。releaseTimes 是一个升序排列的列表,其中 releaseTimes[i] 表示松开第 i 个键的时间。字符串和数组的 下标都从 0 开始 。第 0 个键在时间为 0 时被按下,接下来每个键都 恰好 在前一个键松开时被按下。 测试人员想要找出按键 持续时间最长 的键。第 i 次按键的持续时间为 releaseTimes[i] - releaseTimes[i - 1] ,第 0 次按键的持续时间为 releaseTimes[0] 。 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/slowest-key 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 #### first try: ```python= class Solution: def slowestKey(self, releaseTimes: List[int], keysPressed: str) -> str: releaseTimes = [0] + releaseTimes longest, max_duration = ' ', 0 for k in range(len(keysPressed)): duration = releaseTimes[k + 1] - releaseTimes[k] if duration > max_duration: longest = keysPressed[k] max_duration = duration elif duration == max_duration and ord(keysPressed[k]) > ord(longest): longest = keysPressed[k] return longest ``` ### #562. Longest Line of Consecutive One in Matrix 33 #### 網友解1 善用python內建的data structure。 ```python= class Solution: def longestLine(self, mat: List[List[int]]) -> int: if not mat: return 0 def consecutive(line): return max(len(list(v)) if k else 0 for k, v in itertools.groupby(line)) group = collections.defaultdict(list) for n, row in enumerate(mat): for m, col in enumerate(row): group[0, n] += [col] group[1, m] += [col] group[2, n - m] += [col] group[3, n + m] += [col] return max( map(consecutive, group.values()) ) ``` # 網友解2 DFS ```python= class Solution: def longestLine(self, mat: List[List[int]]) -> int: if not mat or not mat[0]: return 0 seen = [set(), set(), set(), set()] cur_dir = ((1, 0), (0, 1), (1, 1), (1, -1)) row_len, col_len = len(mat), len(mat[0]) res = 0 for r in range(row_len): for c in range(col_len): if mat[r][c]: for i in range(4): if (r, c) not in seen[i]: _dir = cur_dir[i] res = max(res, self.dfs(r, c, _dir, i, seen, mat, row_len, col_len)) return res def dfs(self, r, c, _dir, i, seen, mat, row_len, col_len): cur_res = 1 seen[i].add((r, c)) new_r, new_c = r + _dir[0], c + _dir[1] if row_len > new_r >= 0 and col_len > new_c >= 0 and mat[new_r][new_c]: cur_res += self.dfs(new_r, new_c, _dir, i, seen, mat, row_len, col_len) return cur_res ``` #### Analysis: - Time: $O(M * N)$ - Space: $O(M * N)$ ## 090721 ### #206. Reverse Linked List 很好啊,這種題目一個小時寫不出來嘛~ #### 公式解1: iteration method ```python= # Definition for singly-linked list. # class ListNode: # def __init__(self, val=0, next=None): # self.val = val # self.next = next class Solution: def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: prev, cur = None, head while cur: next_temp = cur.next cur.next = prev prev = cur cur = next_temp return prev ``` #### 公式解2: recursive method ```python= class Solution: def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: # recursive method if not head or not head.next: return head p = self.reverseList(head.next) head.next.next = head head.next = None return p ``` #### Analysis: - Time: $O(N)$ both - Space: $O(1)$ / $O(N)$ ## 091021 ### #83. Remove Duplicates from Sorted List first try: ```python= class Solution: def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: if not head: return cur = head while cur.next: if cur.val == cur.next.val: while cur.val == cur.next.val: cur.next = cur.next.next if not cur.next: break if cur.next: cur = cur.next return head ``` #### Analysis: - Time: $O(N)$ - Space: $O(1)$ #### 網友解:recursion ```python= class Solution: def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: if not head or not head.next: return head head.next = self.deleteDuplicates(head.next) return head if head.val != head.next.val else head.next ``` #### Analysis: - Time: $O(N)$ - Space: $O(N)$ ### #82. Remove Duplicates from Sorted List II #### first try 解是解出來了,但是... ```python= class Solution: def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: pre, cur, _init = ListNode(float('-inf'), head), head, True if not head: return head while cur.next: if cur.val != cur.next.val: pre = cur cur = pre.next _init = False else: while cur.val == cur.next.val: cur.next = cur.next.next if not cur.next: break pre.next = cur.next cur = pre.next if _init: head = cur if not cur: break return head ``` #### 公式解 ```python= class Solution: def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: sentinel = ListNode(-100, head) pred = sentinel while head: if head.next and head.val == head.next.val: while head.next and head.val == head.next.val: head = head.next pred.next = head.next else: pred = pred.next head = head.next return sentinel.next ```