--- title: Leetcode Top In 150 1/2 autoHeadingNumber: true --- [TOC] <!-- 按 Ctrl+Shift+P → Markdown: Create Table of Contents--> # Leetcode Top Interview 150 1/2 ## Array / String ### 88. Merge Sorted Array - Given two sorted integer arrays `nums1` of length `m`and `nums2` of length `n`. - merge `nums2` into `nums1` so that `nums1` becomes a single sorted array. - Solution:將兩個list除了0之外的地方合併,題目給的list剛好0都會在最後,利用提供的整數數量m、n寫迴圈將整數都加入到list b,最後再全部複製到nums1 ```python class Solution(object): def merge(self, nums1, m, nums2, n): b = [] for i in range(m): b.append(nums1[i]) for j in range(n): b.append(nums2[j]) b.sort() nums1[:] = b return nums1 ''' # 快速解法 nums1[:] = nums1[:m] + nums2[:n] nums1.sort() # 排序 ''' ``` ### 27. Remove Element - 回傳`nums`裡面不是`val`的數量`k` - `nums`不用回傳,但會檢查`nums`裡面的前`k`項不能有`val` - 強調使用`in-place`的技巧 ```python class Solution(object): def removeElement(self, nums, val): k = 0 for i in range(len(nums)): if nums[i] != val: nums[k] = nums[i] # 將「不等於 val」的值移動到 nums[k] k += 1 return k ``` ### 26. Remove Duplicates from Sorted Array - 給一個遞增排序的`nums` - 把一樣`value`的項目刪掉 - 輸出不重複的數字數量`k`,且`nums`的前`k`項不能重複 - Solution:不能連續兩個一樣 = 跟前面第一個(k-1)不一樣即可 ```python class Solution(object): def removeDuplicates(self, nums): """ :type nums: List[int] :rtype: int """ if not nums: return 0 k = 1 # 下一個不重複數字要放的位置 for i in range(1, len(nums)): if nums[i] != nums[i - 1]: nums[k] = nums[i] # in-place 覆寫 k += 1 return k ``` ```cpp class Solution { public: int removeDuplicates(vector<int>& nums) { if (nums.empty()) return 0; int k = 1; // 下個不重複數字要放的位置 for (int i = 1; i < nums.size(); i++) { if (nums[i] != nums[i - 1]) { nums[k] = nums[i]; // in-place 覆寫 k++; } } return k; } }; ``` ### 80. Remove Duplicates from Sorted Array II - 給一個遞增排序的`nums` - 把連續三個以上一樣`value`的項目刪掉 - 輸出不超過連續三個以上一樣的數字數量`k`,且`nums`的前`k`項不能重複三個以上 - Solution:不能連續三個一樣 = 跟前面第二個(k-2)不一樣即可 ```python class Solution(object): def removeDuplicates(self, nums): if len(nums) < 2: return len(nums) k = 2 # 下一個能放的位置 for i in range(2, len(nums)): if nums[i] != nums[k - 2]: nums[k] = nums[i] k += 1 return k ``` ### 169. Majority Element - 給一個長度為`n`的`nums` - 輸出超過`n/2`數量的元素element ```python class Solution(object): def majorityElement(self, nums): nums.sort() count = 0 max_count = 0 max_number = nums[0] for i in range(len(nums)-1): if nums[i]==nums[i+1]: count += 1 if count > max_count: max_number = nums[i] max_count = max(max_count,count) else: count = 0 return max_number ``` ### 189. Rotate Array - Input: nums = [1,2,3,4,5,6,7], k = 3 - Output: [5,6,7,1,2,3,4] - Explanation: - rotate 1 steps to the right: [7,1,2,3,4,5,6] - rotate 2 steps to the right: [6,7,1,2,3,4,5] - rotate 3 steps to the right: [5,6,7,1,2,3,4] ```python class Solution(object): def rotate(self, nums, k): n = len(nums) k %= n # 處理 k > n 的情況 nums.reverse() nums[:k] = reversed(nums[:k]) # 0~k nums[k:] = reversed(nums[k:]) # k~最後 ``` ```c void reverse(int* nums, int left, int right) { while (left < right) { int tmp = nums[left]; nums[left] = nums[right]; nums[right] = tmp; left++; right--; } } void rotate(int* nums, int numsSize, int k) { if (numsSize <= 1 || k == numsSize) return; k = k % numsSize; // 處理 k > n 的情況 reverse(nums, 0, numsSize - 1); reverse(nums, 0, k - 1); reverse(nums, k, numsSize - 1); } ``` ### 121. Best Time to Buy and Sell Stock - You are given an array `prices` where `prices[i]` is the price of a given stock on the ith day. - 哪天買哪天賣可以賺最多錢,return 可以賺的最高金額 ```python class Solution(object): def maxProfit(self, prices): """ :type prices: List[int] :rtype: int """ ''' # 這個方法會超時 max_profit = 0 for i in range(len(prices) - 1, 0, -1): for j in range(i - 1, -1, -1): now_profit = prices[i] - prices[j] max_profit = max(max_profit, now_profit) ''' # O(n),邏輯是有更低的價格再更新就好,否則一路往下更新max_profit min_price = prices[0] max_profit = 0 for price in prices: if price < min_price: min_price = price else: max_profit = max(max_profit, price - min_price) return max_profit ``` ### 122. Best Time to Buy and Sell Stock II - You are given an array `prices` where `prices[i]` is the price of a given stock on the ith day. - 買賣不限一次,return 可以賺的最高金額 - Solution:只要明天比今天高,那就今天買明天賣 - 我原本想從右邊到左邊,有更大值才更新基準值,但這樣測資`[1,81,1,101]`只有賺100元,其實可以賺180元 ```python class Solution(object): def maxProfit(self, prices): """ :type prices: List[int] :rtype: int """ # 只要明天的價格比今天高,就今天買明天賣 # GPT說這是Greedy max_profit = 0 for i in range(0, len(prices) - 1): if prices[i]<prices[i+1]: max_profit = max_profit+(prices[i+1]-prices[i]) return max_profit print(Solution.maxProfit(Solution,[7,1,5,3,6,4])) ``` ```cpp class Solution { public: int maxProfit(vector<int>& prices) { int max_profit = 0; for (int i=0; i < prices.size()-1;i++){ if (prices[i]<prices[i+1]){ max_profit = max_profit+(prices[i+1]-prices[i]); } } return max_profit; } }; ``` ### 55. Jump Game - You are given an integer array `nums`. You are initially positioned at the array's first index, - and each element in the array represents your maximum jump length at that position. - Return `true` if you can reach the last index, or `false` otherwise. ```python class Solution(object): def canJump(self, nums): """ :type nums: List[int] :rtype: bool """ # 假如第一個數字是0就不用跳了 # 同理nums[n]如果n-1項是0且n-1前面其他項都跳不過來,那n就是死路 # GPT說這是Greedy max_range = nums[0] for i in range(len(nums)): if i<=max_range: max_range = max(max_range, i+nums[i]) else: return False return True ``` ```cpp class Solution { public: bool canJump(vector<int>& nums) { int max_range = nums[0]; for (int i = 0; i <nums.size(); i++){ if (i<=max_range){ max_range = max(max_range, i+nums[i]); } else{ return false; } } return true; } }; ``` ### 45. Jump Game II - Input: nums = [2,3,1,1,4] - Output: 2 - Explanation: Jump 1 step from index 0 to 1, then 3 steps to the last index. - Solution: - for-if-if結構,我覺得超難 - 不能無腦擴張領土就+1,因為array的值都至少有1 - 要有一個邊界max_line,那個邊界內的都是step多少以內能到的 - nums[0]是step0 - 其中step0最遠能到的區域都是step1 - 假如step0最遠能到nums[3],那nums[1:3] = step1 - 再看step1最遠能到多少 - 假如step1最遠到nums[10],那nums[4:10] = step2 ```python class Solution(object): def jump(self, nums): max_range = 0 step = 0 last_step_line = 0 max_line = len(nums) - 1 for i in range(len(nums)): if last_step_line >= max_line: return step max_range = max(max_range, i + nums[i]) if i == last_step_line: # 當i = last_step_line = 0時,進入step1 step += 1 last_step_line = max_range # step1的範圍是1~0+nums[0] ``` ```cpp class Solution { public: int jump(vector<int>& nums) { int max_range = 0; int step = 0; int last_step_line = 0; int max_line = nums.size()-1; for (int i = 0; i < nums.size();i++){ if (last_step_line >= max_line){ return step; // return沒有寫在函式最後,隨便return一個0就好 } max_range = max(max_range, i + nums[i]); if (i==last_step_line){ step++; last_step_line = max_range; } } return 0; } }; ``` ### 274. H-Index - `citations[i]` is the number of citations a researcher received for their ith paper, return h-index. - h-index is defined as the maximum value of h such that the given researcher has published at least h papers that have each been cited at least h times. - 最多有H篇論文至少被引用H次 - Input:citations = [3, 0, 6, 1, 5], n = 5 - Output:h = 3 - Solution: - (最多有H篇論文)>=(至少)被引用H次 - 論文被引用的次數統整在`count[]`裡面,這是一種bucket(分類) - index: 0 1 2 3 4 5 - count: [1, 1, 0, 1, 0, 2] - 最多n篇論文往下找,當n-k>=被引用的次數總和時,輸出那次的次數 ```python class Solution(object): def hIndex(self, citations): """ :type citations: List[int] :rtype: int """ # 最多有H篇論文至少被引用H次 h_score = 0 for i in range(1, len(citations) + 1): # 窮舉法,從h=1~n逐步檢查 count = 0 for j in citations: if j >= i: count += 1 if count >= i: h_score = max(h_score, i) return h_score # print(Solution.hIndex(Solution,[1])) def hIndex2(self, citations): # 建立count[]後從高往下推,直到>=為止 n = len(citations) count = [0] * (n + 1) for c in citations: if c >= n: count[n] += 1 else: count[c] += 1 total = 0 for h in range(n, -1, -1): total += count[h] if total >= h: return h ``` ```c int hIndex(int* citations, int citationsSize) { int n = citationsSize; int count[n + 1]; for (int i = 0; i <= n; i++) { count[i] = 0; } for (int i = 0; i < n; i++) { // bucket分類 int c = citations[i]; if (c >= n) { count[n]++; } else { count[c]++; } } int total = 0; for (int h = n; h >= 0; h--) { total += count[h]; // 被引用 >= h 的論文數 if (total >= h) { return h; } } return 0; } ``` ### 380. Insert Delete GetRandom O(1) - 要求三個操作都要平均 O(1): insert(val) → 插入 O(1), remove(val) → 刪除 O(1), getRandom() → 隨機取值 O(1) - 如果只有 list: 插入 O(1), 刪除 O(n)(因為要找元素在哪個 index), getRandom O(1) - 如果只有 dict: 插入 O(1), 刪除 O(1), getRandom 沒辦法做到,因為 dict 沒有隨機取「第幾個」的功能 - 所以才要 list + dict 搭配: list 用來隨機抽取, dict 用來快速找到元素的位置 - Solutionn: - insert / remove → 主要靠 dict,加上 list 做同步更新 - getRandom → 只用 list - list 與 dict 雙結構同時維護,各自負責不同需求 ```python import random class RandomizedSet(object): def __init__(self): self.nums = [] self.nums_dict = {} def insert(self, val): """ :type val: int :rtype: bool """ if val in self.nums_dict: return False else: self.nums.append(val) # list self.nums_dict[val] = len(self.nums) - 1 # dict return True def remove(self, val): """ :type val: int :rtype: bool """ if val not in self.nums_dict: return False else: exchange_place = self.nums_dict[val] # dict self.nums_dict[self.nums[-1]] = exchange_place # dict self.nums[exchange_place], self.nums[exchange_place] = self.nums[len(self.nums) - 1], self.nums[ len(self.nums) - 1] # list self.nums.pop() # list del self.nums_dict[val] # dict return True def getRandom(self): """ :rtype: int """ if len(self.nums) == 0: return False else: return random.choice(self.nums) # ===================== 測資 ===================== inputs = ["RandomizedSet", "insert", "remove", "insert", "getRandom", "remove", "insert", "getRandom"] params = [[], [1], [2], [2], [], [1], [2], []] obj = None output = [] for cmd, param in zip(inputs, params): if cmd == "RandomizedSet": obj = RandomizedSet() output.append(None) # 對應 LeetCode 的 null elif cmd == "insert": output.append(obj.insert(param[0])) elif cmd == "remove": output.append(obj.remove(param[0])) elif cmd == "getRandom": output.append(obj.getRandom()) print("Input:") print(inputs) print(params) print("Output:") print(output) # Output = [null, true, false, true, 2, true, false, 2] ''' # dict{x, y}:x對y可以一對一、多對一,不能一對多 # 舉例刪除掉3 # list = [1, 2, 3, 4, 5] # dict = {1:0, 2:1, 3:2, 4:3, 5:4} # step1: exchange_place = self.nums_dict[val] self.nums_dict[self.nums[-1]] = exchange_place # -> dict = {1:0, 2:1, 3:2, 4:3, 5:2} # step2: self.nums[exchange_place], self.nums[exchange_place] = self.nums[len(self.nums) - 1], self.nums[len(self.nums) - 1] # -> list = [1, 2, 5, 4, 3] # step3: self.nums.pop() # -> list = [1, 2, 5, 4] del self.nums_dict[val] # -> dict = {1:0, 2:1, 4:3, 5:2} 所以刪除後會引響list的順序,dict的value也會同步更新位子 # 比對時間複雜度 leetcode_380_dict的學號對應list的位子 if val in dict: O(1) if val in list: O(n) ''' ``` ### 238. Product of Array Except Self - 給一個`nums`,全部相乘是某個整數,輸出`answer`,`answer[i]`是該整數中,因數不包含`nums[i]`的積 - Input: nums = [1,2,3,4] - Output: [24,12,8,6] - Solution:`answer[i]`其實就是左邊跟右邊所有元素相乘 - prefix - suffix ```python class Solution(object): def productExceptSelf(self, nums): """ :type nums: List[int] :rtype: List[int] """ n = len(nums) answer = [1] * n left = 1 for i in range(0, n - 1, 1): left *= nums[i] # 因為你要累乘 answer[i + 1] *= left right = 1 for i in range(n - 1, 0, -1): right *= nums[i] answer[i - 1] *= right return answer ``` ```c int* productExceptSelf(int* nums, int n, int* returnSize){ *returnSize = n; int* answer = (int*)malloc(sizeof(int) * n); for (int i = 0; i < n; i++) { answer[i] = 1; } int left = 1; for (int i = 0; i < n - 1; i++) { left *= nums[i]; answer[i + 1] *= left; } int right = 1; for (int i = n - 1; i > 0; i--) { right *= nums[i]; answer[i - 1] *= right; } return answer; } ``` ```cpp class Solution { public: vector<int> productExceptSelf(vector<int>& nums) { // vector不用指標 int n = nums.size(); vector<int> answer(n, 1); int left = 1; for (int i = 0; i < n - 1; ++i) { left *= nums[i]; answer[i + 1] *= left; } int right = 1; for (int i = n - 1; i > 0; --i) { right *= nums[i]; answer[i - 1] *= right; } return answer; } }; ``` ### 134. Gas Station - gas[i]是抵達該加油站後可以加的油 - cost[i]是從這邊離開時要扣掉的油 - return從gas多少開始可以走完整一圈 - Solution: - 只要total_gas >= total_cost 就一定有解 - +gas-cost可以想成新的diff - 正+負定理: - 能走到3,那1一定是正的,同理1+2也必定正,卡在3表示3是一個超大負 - 如果1 ->2 ->3不行,那從2、3當起點也一定不行 - 直接測試從4當起點的情況 ```python class Solution(object): def canCompleteCircuit(self, gas, cost): """ :type gas: List[int] :type cost: List[int] :rtype: int """ # gas是你抵達時得到的gas # cost是你離開時消耗的gas # 只要total_gas >= total_cost 就一定有解 total_tank = 0 current_tank = 0 start_index = 0 for i in range(len(gas)): total_tank += gas[i] - cost[i] current_tank += gas[i] - cost[i] # 若目前的油量不足,重新選擇起點 if current_tank < 0: start_index = i + 1 current_tank = 0 # 重置當前油量 # 不用重置total if total_tank >= 0: return start_index else: return -1 ``` ### 135. Candy - arrays `ratings` of length `n`, rating[i]是小孩的價值 - 每個小孩至少一個糖果 - 小孩一定要比自己鄰居ratings更低的小孩拿更多糖 - 輸出至少需要多少糖果 - Solution: ![image](https://hackmd.io/_uploads/HkWqNESX-g.png =80%x) ```python class Solution(object): def candy(self, ratings): """ :type ratings: List[int] :rtype: int """ n = len(ratings) candy_L2R = [1]*n candy_R2L = [1]*n candy_result = [1]*n sum = 0 for i in range(1, n): if ratings[i]>ratings[i-1]: candy_L2R[i] = candy_L2R[i-1]+1 for j in range(n-2, -1, -1): if ratings[j]>ratings[j+1]: candy_R2L[j] = candy_R2L[j+1]+1 for k in range(n): candy_result[k] = max(candy_L2R[k], candy_R2L[k]) sum += candy_result[k] return sum ``` ### 42. Trapping Rain Water - arrays `height` of length `n`, `height[i]`是牆壁的高度,輸出最多可以搜集多少雨水 - Solution: ![image](https://hackmd.io/_uploads/rJq3V4HX-g.png =80%x) ```python class Solution(object): def trap(self, height): """ :type height: List[int] :rtype: int """ n = len(height) L2R_up = 0 R2L_up = 0 L2R_line = [0]*n R2L_line = [0]*n result = 0 result2 = 0 for i in range(n): L2R_up = max(L2R_up, height[i]) L2R_line[i] = L2R_up for i in range(n-1,-1,-1): R2L_up = max(R2L_up, height[i]) R2L_line[i] = R2L_up result = min(R2L_line[i], L2R_line[i]) result = result - height[i] if result > 0: result2 += result return result2 ``` ```c int trap(int* height, int heightSize) { int n = heightSize; int L2R_up = 0; int R2L_up = 0; int L2R_line[n]; // C創建list比C++簡單 int R2L_line[n]; int result = 0; int result2 = 0; for (int i = 0; i < n; i++) { L2R_up = (L2R_up > height[i]) ? L2R_up : height[i]; L2R_line[i] = L2R_up; } for (int i = n - 1; i >= 0; i--) { R2L_up = (R2L_up > height[i]) ? R2L_up : height[i]; R2L_line[i] = R2L_up; result = (R2L_line[i] < L2R_line[i]) ? R2L_line[i] : L2R_line[i]; result = result - height[i]; if (result > 0) result2 += result; } return result2; } ``` ### 13. Roman to Integer |Symbol|Value| |-|-| |I|1| |IV|4| |V|5| |VI|6| |X|10| |L|50| |C|100| |D|500| |M|1000| - input是string的羅馬數字,output數字 ```python class Solution(object): def romanToInt(self, s): """ :type s: str :rtype: int """ s_dict = { 'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000, } sum = s_dict[s[len(s)-1]] for i in range(len(s)-1): if s_dict[s[i]]< s_dict[s[i+1]]: sum -= s_dict[s[i]] else: sum += s_dict[s[i]] return sum ``` ### 12. Integer to Roman - input數字,output是string的羅馬數字 ```python class Solution(object): def intToRoman(self, num): """ :type num: int :rtype: str """ roman_dict = { 1: 'I', 4: 'IV', 5: 'V', 9: 'IX', 10: 'X', 40: 'XL', 50: 'L', 90: 'XC', 100: 'C', 400: 'CD', 500: 'D', 900: 'CM', 1000: 'M', } roman_list = sorted(roman_dict.keys()) # leetcode的dict沒有照上面的順序排 roman_number = '' for i in list(roman_list)[::-1]: # 從大到小 # 讓迴圈發生在roman_list roman_number += roman_dict[i] * (num // i) num -= (num // i) * i return roman_number ``` ### 58. Length of Last Word - input一個string,每個單字會用空格隔開,輸出最後一個單字的長度 - Python Solution: - 有可能最後一個元素是空格,所以設定一個spare,遇到空格就把result複製到spare - 最後如果是空格,result就會是0,輸出spare - 最後如果不是空格,就輸出result - C++ Solution: - 從後面往前,如果不是空格就+1 - 如果是空格且count不是0就直接輸出count ```python class Solution(object): def lengthOfLastWord(self, s): """ :type s: str :rtype: int """ result = 0 spare = 0 for i in range(len(s)): if s[i] == ' ': if result != 0: spare = result result = 0 else: result += 1 if result == 0: return spare else: return result ``` ### 14. Longest Common Prefix - 找共同的前綴詞 - Input: strs = `["flower","flow","flight"]` - Output: "fl" - Solution: - 每個詞的第一個單字開始比,如果不一樣就輸出答案 ```python class Solution(object): def longestCommonPrefix(self, strs): """ :type strs: List[str] :rtype: str """ ans = "" for i, char in enumerate(strs[0]): # 以第一個flower為基準 for word in strs: # 依序比對所有字元的i=1、i=2、i=3等 # 超出字串範圍,或字串不同,直接結束返回結果 if i >= len(word) or word[i] != char: return ans ans += char # 整個for word迴圈都沒return就把該char加入ans return ans # 所有字串都遍歷完後,返回完整的共同前綴 ``` ### 151. Reverse Words in a String - Input: s = "a good example" - Output: "example good a" - Python Solution: - 從前面開始遇到空格的話再把前面的詞放到後面 - `result[position-length+j] = s[i-length+j]` - C Solution: - 從後面開始統計不是空格的數量放到result ```python class Solution(object): def reverseWords(self, s): """ :type s: str :rtype: str """ n = len(s) result = [""] * (n+1) # list of string # n+1因為可能多一個空格 count = 0 position = n for i in range(n+1): if i == n or s[i] == " ": length = count if length > 0: for j in range(length): result[position-length+j] = s[i-length+j] # 把最前面的You放到最後面 position -= length position -= 1 if position >= 0: result[position] = " " # 在You的前面補一個空格 count = 0 else: count += 1 # 沒遇到空格就一直+1 return "".join(result).strip() # 把list裡面的子string都合起來 # .strip()會刪掉頭尾空格 #solution = Solution() #print(solution.reverseWords("You are very smart")) ``` ```c char* reverseWords(char* s) { int n = strlen(s); char* result = (char*)malloc(n + 1); int pos = 0; int i = n - 1; while (i >= 0) { while (i >= 0 && s[i] == ' ') i--; // 跳過空格 if (i < 0) break; int end = i; while (i >= 0 && s[i] != ' ') i--; // 找單字結尾 int start = i + 1; if (pos > 0) result[pos++] = ' '; // 補空格(不是第一個單字) for (int k = start; k <= end; k++) result[pos++] = s[k]; // 複製單字 } result[pos] = '\0'; // C 語言中的字串結尾的標記 // char s[] = {'H','i','\0'}; 等價 char s[] = "Hi"; return result; } ``` ### 6. Zigzag Conversion - input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] - output = [1, 5, 9, 13, 2, 4, 6, 8, 10, 12, 14, 3, 7, 11] ![image](https://hackmd.io/_uploads/B1mqCSqNWe.png) - Solution - 剛好會是numRows +numRows -2的循環 - 用找餘數的方式分類,餘數是1的是第一row,餘數是2的是第二row - 其中5跟3同一組、6跟2同組,所以`if x >= row: x = cycle - x` - 從小到大,把元素加入到不同string的分類裡 ```python class Solution(object): def convert(self, s, numRows): """ :type s: str :type numRows: int :rtype: str """ row = numRows n = len(s) ans = [""] * row # 可以把值存到ans_[0]、ans_[1] ... cycle = row + row - 2 if cycle == 0: return s for i in range(n): x = i % cycle if x >= row: x = cycle - x # 以N=4為例,6跟2同組,5跟3同組 ans[x] += s[i] if 0 <= x < numRows else "" return "".join(ans) ''' N = 3, 1個穿插: 1 5 9 13 2 4 6 8 10 12 14 3 7 11 1 1 1 2 4 2 4 2 4 3 3 3 N = 4, 2個穿插: 1 7 13 2 6 8 12 14 3 5 9 11 4 10 1 1 1 1 2 6 2 6 2 6 2 3 5 3 5 3 5 4 4 4 ''' ``` ### 28. Find the Index of the First Occurrence in a String - Input: haystack = "absadbutsad", needle = "sad" - Output: 2 - Solution: - 寫兩個迴圈逐一深入比對,把所有答案加入到list最後輸出最小的值 - 建立LPS表看有多少前綴詞重複,就不用重頭比對 - ![image](https://hackmd.io/_uploads/HkVPrN7HZx.png =45%x) ```python class Solution(object): def strStr(self, haystack, needle): """ :type haystack: str :type needle: str :rtype: int """ answer = [] for i in range(0, len(haystack)): count = 0 for j in range(i, len(haystack)): if haystack[j] == needle[count]: count += 1 elif haystack[j] != needle[count]: count = 0 if count == len(needle): x = j-count+1 answer.append(x) count = 0 if answer != []: return min(answer) return -1 ``` ```python class Solution(object): def strStr(self, haystack, needle): if needle == "": return 0 # ---------- build lps ---------- lps = [0] * len(needle) # 控制j的list,告訴你哪些前綴一樣 # needed = [a, b, c, a, b, c, d], lps = [0, 0, 0, 1, 2, 3, 0] j = 0 # j是你正在跟哪個index的元素比 # j-1是哪些index的元素已經一樣了 for i in range(1, len(needle)): while j > 0 and needle[i] != needle[j]: j = lps[j - 1] if needle[i] == needle[j]: j += 1 lps[i] = j # ---------- KMP search ---------- j = 0 for i in range(len(haystack)): while j > 0 and haystack[i] != needle[j]: j = lps[j - 1] if haystack[i] == needle[j]: j += 1 if j == len(needle): return i - j + 1 return -1 ''' haystack = "mississippi" needle = "issip" lps = [0, 0, 0, 1, 0] 當比到hystack[5]、j=3不一樣時,j不會直接歸0,根據lps j會變成1,直接跟needle[1]繼續比 ''' ``` ### 68. Text Justification - - Solution: - ```python ``` ```c ``` ## Two Pointers ### 125. Valid Palindrome - 給一個字串`s`,忽略標點符號英文大小寫,看是不是回文(正讀反讀都一樣) ```python class Solution: def isPalindrome(self, s): left, right = 0, len(s) - 1 while left < right: # 如果是非英數就額外+1 # not .sialnum 是 not False = True 就會跳過(+1) while left < right and not s[left].isalnum(): left += 1 while left < right and not s[right].isalnum(): right -= 1 if s[left].lower() != s[right].lower(): return False left += 1 right -= 1 return True ''' 'a'.isalnum() # True '7'.isalnum() # True ' '.isalnum() # False ','.isalnum() # False ''' ``` ### 392. Is Subsequence - 給兩個字串`s` `t`,如果s是t的 subsequence (子序列)就return True - 子序列可以不用相連但順序要對,如s = abc,t = ....a....b....c... ```python class Solution(object): def isSubsequence(self, s, t): n = len(s) m = len(t) if n == 0: return True t_l = 0 s_l = 0 while t_l < m: if s[s_l] == t[t_l]: s_l += 1 if s_l == n: return True t_l += 1 return False ``` ### 167. Two Sum II - Input Array Is Sorted - 給排序的數列,找兩數相加等於target - Solution: - 用`while`縮小範圍,我原本以為`leetcode1`就是有排序的 ```python class Solution(object): def twoSum(self, numbers, target): left = 0 right = len(numbers) - 1 while left < right: s = numbers[left] + numbers[right] # LeetCode 167 題目規定:回傳的是「1-based index」 if s == target: return [left + 1, right + 1] elif s < target: left += 1 else: right -= 1 ``` ### 11. Container With Most Water - 給你一個陣列 `height`,`height[i]` = 第 i 條直線的高度 - 每兩條線可以和 x 軸圍成一個容器 - 容器能裝的水量是:寬度 × 高度 = (右 index - 左 index) × min(左高, 右高) - 回傳最大能裝多少水? - Solution: - 區域:`area = width × min(height[left], height[right])` - 雙指標:只移比較矮的那邊,如果移動高的,容量只會被矮的那端限制住 - `if height[left] < height[right]: left += 1` - `else: right -= 1` ```python class Solution(object): def maxArea(self, height): left = 0 right = len(height) - 1 ans = 0 while left < right: area = (right - left) * min(height[left], height[right]) ans = max(ans, area) # 只移比較矮的那邊一定是對的 # 如果移動高的,容量只會被矮的那端限制住 if height[left] < height[right]: left += 1 else: right -= 1 return ans ``` ### 15. 3Sum - 給一個list,輸出list of list,哪三個數字加起來等於0,不能重複 - Solution: - 先排序,再用雙指標右邊的最右邊跟最左邊依序縮短距離 - 過程中只要跟下一個狀態重複就要跳過 - C的`malloc`語法補充在`python c c++.md`的`malloc`篇中 - C的`result`是2D array,`returnColumnSizes`是1D array ```python class Solution(object): def threeSum(self, nums): """ :type nums: List[int] :rtype: List[List[int]] """ nums.sort() n = len(nums) ans = [] Target = 0 for i in range(n): # 以第i個數字為主角,再去看右邊的範圍有沒有可以配合的兩個數字 if i > 0 and nums[i] == nums[i-1]: # 如果跟上一個數字一樣,就跳過不重複做 continue left = i + 1 right = n - 1 while left < right: total = nums[i] + nums[left] + nums[right] if total == Target: ans.append([nums[i], nums[left], nums[right]]) # List[List[int]]的方法 while left < right and nums[left] == nums[left+1]: left += 1 # 避免重複算 while left < right and nums[right] == nums[right-1]: right -= 1 # 避免重複算 left += 1 right -= 1 elif total < Target: left += 1 # 如果太小就左邊+1,因為有sort過左邊是小的 else: right -= 1 # 如果太大就右邊-1,因為sort過右邊是大的 return ans solution = Solution() print(solution.threeSum([-1,0,1,2,-1,-4])) ``` ```c // qsort 用的 int cmp(const void* a, const void* b) { // void表示不知道什麼類別 return (*(int*)a - *(int*)b); // 一開始宣告void,類別轉型(int*)a, 假設a是int整數 } int** threeSum(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) { // 指標符號 * 有兩種用途 1.解參考 2.宣告指標回傳值 // int* returnSize是指向int的指標 // Q: 為什麼這邊要int **returnColumnSizes才代表array,但void sort(int*arr)就代表array? // 怎麼知道int*arr是指向int的指標還是array退化成指標? // A: 一開始有宣告過arr是array就是後者,沒有宣告過的話就要用int** *returnSize = 0; if (numsSize < 3) return NULL; qsort(nums, numsSize, sizeof(int), cmp); // 最多不會超過 n^2 組 int capacity = numsSize * numsSize; int** result = (int**)malloc(sizeof(int*) * capacity); // int**是double pointer,宣告array of array // int**是指向int*的指標 // sizeof(int*)是指標的長度 *returnColumnSizes = (int*)malloc(sizeof(int) * capacity); // 因為returnColumnSizes不會被return,所以要用*A = size(int)的方式malloc for (int i = 0; i < numsSize - 2; i++) { if (i > 0 && nums[i] == nums[i - 1]) continue; // 重複不用算 int left = i + 1; int right = numsSize - 1; while (left < right) { int sum = nums[i] + nums[left] + nums[right]; if (sum == 0) { result[*returnSize] = (int*)malloc(sizeof(int) * 3); result[*returnSize][0] = nums[i]; result[*returnSize][1] = nums[left]; result[*returnSize][2] = nums[right]; (*returnColumnSizes)[*returnSize] = 3; // (*returnColumnSizes)[i] == *(*returnColumnSizes + i) (*returnSize)++; // 重複不用算 while (left < right && nums[left] == nums[left + 1]) left++; while (left < right && nums[right] == nums[right - 1]) right--; left++; right--; } else if (sum < 0) left++; else right--; } } return result; } ``` ## Sliding Window 兩個指標維持一個連續區間,邊移動邊更新答案 ### 209. Minimum Size Subarray Sum - 給一個array跟target,找連續的子陣列(subarray),子陣列的總和 ≥ target,return子陣列最短長度 ```python class Solution(object): def minSubArrayLen(self, target, nums): left = 0 curr_sum = 0 res = float('inf') # 無限大 for right in range(len(nums)): curr_sum += nums[right] while curr_sum >= target: res = min(res, right - left + 1) curr_sum -= nums[left] left += 1 return 0 if res == float('inf') else res ``` ```c int minSubArrayLen(int target, int* nums, int numsSize) { int left = 0; int sum = 0; int minLen = numsSize + 1; for (int right = 0; right < numsSize; right++) { sum += nums[right]; while (sum >= target) { int len = right - left + 1; if (len < minLen) minLen = len; sum -= nums[left]; left++; } } return minLen == numsSize + 1 ? 0 : minLen; } ``` ### 3. Longest Substring Without Repeating Characters - input一個string,output最長不重複的字母數量 - Python Solution: - 把string的字母加到key、第i個加到value - for迴圈一開始更新left邊界 - C Solution1: - 類似python,建立char_last_position的list當dict用 - `list['a']++` 等價 `freq[97]++`,freq的第97個位子會++ - list每個值預設是-1,因為python一開始就沒有值,C的list的128個位子一開始都要設定值 - for迴圈一開始更新left邊界 - 如果前面有出現過這個字母,left = 第一次出現該字母+1 - 補充 `list[s[right]] >= left` 的意思是: - list[s[right]]本來應該要沒有值或-1 - 如果有值且>=left,表示在原本left~right之間有重複的s[right] - left = 第一次出現該字母的位子+1 - 如abcdeabc,`s[5]`出現第二次b,則left邊界從`s[2]`開始 - ![image](https://hackmd.io/_uploads/H1TCbVmSZe.png =50%x) - C Solution2: - 建立list,如果有重複的如`freq['a']=2`,就照string的順序把前面都--,直到`freq['a']=1`為止就是left的邊界 - 跟查字典char_last_postion的位子+1更新left不同 ```python class Solution(object): def lengthOfLongestSubstring(self, s): """ :type s: str :rtype: int """ n = len(s) seen = {} start = 0 count = 0 max_len = 0 for i in range(n): # 更新left邊界 if s[i] in seen and seen[s[i]] >= start: start = seen[s[i]] + 1 # 如dvdf,遇到第二個d起點從v開始 count = i - start # count不見得會直接歸0, # 例如dvdf,遇到第二個d時,count = 2-1 = 1, # 後面馬上count += 1,count = 2,count會從v開始算 seen[s[i]] = i # 字串的字母加到key # 第i個加到value # seen[s[0]] = 0 →seen = {'a': 0} count += 1 if count > max_len: max_len = count results = s[start:i+1] # 字串切割範圍從start到i+1-1 return results solution = Solution() print(solution.lengthOfLongestSubstring("abbcdef")) ``` ```c int lengthOfLongestSubstring(char* s) { int char_last_position[128]; for (int i = 0; i < 128; i++) char_last_position[i] = -1; int left = 0; int maxLen = 0; for (int right = 0; s[right]; right++) { // 同for ( ; s[right] != '\0'; ) // 更新left邊界 if (char_last_position[s[right]] >= left) left = char_last_position[s[right]] + 1; char_last_position[s[right]] = right; int len = right - left + 1; if (len > maxLen) maxLen = len; } return maxLen; } ``` ```c int lengthOfLongestSubstring(char* s) { int freq[128] = {0}; // 128是所有字元 int left = 0; int maxLen = 0; for (int right = 0; s[right] != '\0'; right++) { freq[s[right]]++; // freq['a']++ 等價 freq[97]++ // freq的第97個位子會++ // 如果有重複,就縮小左邊邊界 while (freq[s[right]] > 1) { freq[s[left]]--; left++; // left會一直+到當left == right為止 // 此時再freq[s[left]]-- 才會停止迴圈 // 這段同python, start = seen[s[i]] + 1 } int currLen = right - left + 1; if (currLen > maxLen) maxLen = currLen; } return maxLen; } ``` ### 30. Substring with Concatenation of All Words ### 76. Minimum Window Substring ## Matrix ### 36. Valid Sudoku - 看數獨是否符合邏輯,row, column, 3x3區域內不能有重複的值 - Solution: - python建立2D array - C 的2D array column是實際value值-1 ```python class Solution(object): def isValidSudoku(self, board): rows = [set() for _ in range(9)] cols = [set() for _ in range(9)] boxes = [set() for _ in range(9)] for r in range(9): for c in range(9): val = board[r][c] if val == '.': continue # leetcode題目規定的無值 box_id = (r // 3) * 3 + (c // 3) # 把box變成9個區間:(0,3,6)+(0,1,2) if val in rows[r] or val in cols[c] or val in boxes[box_id]: return False rows[r].add(val) cols[c].add(val) boxes[box_id].add(val) return True ``` ```c bool isValidSudoku(char** board, int boardSize, int* boardColSize) { bool row[9][10] = {false}; bool col[9][10] = {false}; bool box[9][10] = {false}; for (int r = 0; r < 9; r++) { for (int c = 0; c < 9; c++) { char num = board[r][c]; if (num == '.') continue; int num2 = num - '0'; // 因為char的1是int的49,1-'0'同49-48 int box_id = (r / 3) * 3 + (c / 3); if (row[r][num2] || col[c][num2] || box[box_id][num2]) return false; row[r][num2] = true; col[c][num2] = true; box[box_id][num2] = true; } } return true; } ``` ### 54. Spiral Matrix - 給一個mn矩陣,回傳沿著邊邊往內走順序(spiral order)的list - Solution: - 右往左跟下往上要多寫一個if,不然走到終點後還會繼續計算重複的點 ```python class Solution(object): def spiralOrder(self, matrix): # mn matrix if not matrix: return [] res = [] top, bottom = 0, len(matrix) - 1 # m, row, y-axis left, right = 0, len(matrix[0]) - 1 # n, column, x-axis while top <= bottom and left <= right: for c in range(left, right + 1): res.append(matrix[top][c]) # left → right top += 1 # 縮小範圍,top起點是0、bottom起點是m-1 for r in range(top, bottom + 1): res.append(matrix[r][right]) # top → bottom right -= 1 if top <= bottom: # right → left for c in range(right, left - 1, -1): res.append(matrix[bottom][c]) bottom -= 1 if left <= right: # bottom → top for r in range(bottom, top - 1, -1): res.append(matrix[r][left]) left += 1 return res ``` ```c int* spiralOrder(int** matrix, int matrixSize, int* matrixColSize, int* returnSize) { if (matrixSize == 0) { *returnSize = 0; return NULL; } int m = matrixSize; int n = matrixColSize[0]; int total = m * n; int* res = (int*)malloc(sizeof(int) * total); int idx = 0; int top = 0, bottom = m - 1; int left = 0, right = n - 1; while (top <= bottom && left <= right) { for (int c = left; c <= right; c++) res[idx++] = matrix[top][c]; top++; for (int r = top; r <= bottom; r++) res[idx++] = matrix[r][right]; right--; if (top <= bottom) { for (int c = right; c >= left; c--) res[idx++] = matrix[bottom][c]; bottom--; } if (left <= right) { for (int r = bottom; r >= top; r--) res[idx++] = matrix[r][left]; left++; } } *returnSize = idx; return res; } ``` ### 48. Rotate Image - 給nn矩陣,順時針轉90度 - Solution: - 順時針轉90度:ij交換+反轉row - 逆時針轉90度:ij交換+反轉column - 轉180度:上下反轉+左右反轉 ```python class Solution: def rotate(self, matrix): n = len(matrix) for i in range(n): for j in range(i+1, n): matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j] # 轉置 for i in range(n): matrix[i].reverse() # 反轉每一row ``` ```c void rotate(int** matrix, int matrixSize, int* matrixColSize){ int n = matrixSize; for(int i = 0; i < n; i++){ for(int j = i+1; j < n; j++){ int tmp = matrix[i][j]; matrix[i][j] = matrix[j][i]; matrix[j][i] = tmp; } } for(int i = 0; i < n; i++){ for(int j = 0; j < n/2; j++){ int tmp = matrix[i][j]; matrix[i][j] = matrix[i][n-1-j]; matrix[i][n-1-j] = tmp; } } } ``` ### 73. Set Matrix Zeroes - 給mn矩陣,0的同一row跟同一column都變成0 - Solution: - 把0所在的row, column紀錄起來,再一次把所有row, column變成0,O(2mn) - 有更快的方法,但我覺得面試不會考這題,就不研究了 ```python class Solution(object): def setZeroes(self, matrix): m, n = len(matrix), len(matrix[0]) row = [False] * m col = [False] * n for i in range(m): for j in range(n): if matrix[i][j] == 0: row[i] = True col[j] = True for i in range(m): for j in range(n): if row[i] or col[j]: matrix[i][j] = 0 ``` ```c void setZeroes(int** matrix, int matrixSize, int* matrixColSize){ int m = matrixSize; int n = matrixColSize[0]; int row[m], col[n]; memset(row, 0, sizeof(row)); memset(col, 0, sizeof(col)); for(int i=0; i<m; i++){ for(int j=0; j<n; j++){ if(matrix[i][j] == 0){ row[i] = 1; col[j] = 1; } } } for(int i=0; i<m; i++){ for(int j=0; j<n; j++){ if(row[i] || col[j]){ matrix[i][j] = 0; } } } } ``` ### 289. Game of Life - 給一個 0/1 矩陣(死/活),同時依照規則更新成下一代 - 4條規則,對每一格,看 8 個鄰居: - 活細胞,活鄰居 < 2 → 死 - 活細胞,活鄰居 2 或 3 → 活 - 活細胞,活鄰居 > 3 → 死 - 死細胞,活鄰居 = 3 → 活 - 全部是同時發生,不能邊更新邊看下一個,類似non-blocking的效果,但不是non-blocking的機制 - Solution: - 讓存的值有四種狀態 - cell > 0 → 1 - cell <= 0 → 0 - 這題沒認真刻,很類似電腦視覺的作業 |原本|後來|value| |-|-|-| |0|0|0| |1|1|1| |1|0|-1| |0|1|2| ```python class Solution: def gameOfLife(self, board): m, n = len(board), len(board[0]) dirs = [(-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)] # 第一次遍歷:標記狀態 for i in range(m): for j in range(n): live = 0 for dx, dy in dirs: x, y = i + dx, j + dy if 0 <= x < m and 0 <= y < n and abs(board[x][y]) == 1: live += 1 if board[i][j] == 1 and (live < 2 or live > 3): board[i][j] = -1 # 活 → 死 elif board[i][j] == 0 and live == 3: board[i][j] = 2 # 死 → 活 # 第二次遍歷:還原成 0 / 1 for i in range(m): for j in range(n): if board[i][j] > 0: board[i][j] = 1 else: board[i][j] = 0 ``` ```c void gameOfLife(int** board, int boardSize, int* boardColSize) { int m = boardSize; int n = boardColSize[0]; int dirs[8][2] = { {-1,-1}, {-1,0}, {-1,1}, {0,-1}, {0,1}, {1,-1}, {1,0}, {1,1} }; // 第一輪:標記暫態 for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { int live = 0; for (int d = 0; d < 8; d++) { int x = i + dirs[d][0]; int y = j + dirs[d][1]; if (x >= 0 && x < m && y >= 0 && y < n && abs(board[x][y]) == 1) { live++; } } if (board[i][j] == 1 && (live < 2 || live > 3)) { board[i][j] = -1; // 活 → 死 } else if (board[i][j] == 0 && live == 3) { board[i][j] = 2; // 死 → 活 } } } // 第二輪:轉回 0 / 1 for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { board[i][j] = board[i][j] > 0 ? 1 : 0; } } } ``` ## Hashmap ### 383. Ransom Note - - Solution: - 5 ```python ``` ```c ``` ### 205. Isomorphic Strings - - Solution: - 5 ```python ``` ```c ``` ### 290. Word Pattern - - Solution: - 5 ```python ``` ```c ``` ### 242. Valid Anagram - - Solution: - 5 ```python ``` ```c ``` ### 49. Group Anagrams - Input: strs = ["eat","tea","tan","ate","nat","bat"] - Output: [["bat"],["nat","tan"],["ate","eat","tea"]] - Solution: - 用`defaultdict` value設定list - `sorted_str = "".join(sorted(s))` 排序詞彙 - `ans[sorted_str].append(s)` 排序一樣的詞彙放在同一個key的list裡面 ```python class Solution(object): def groupAnagrams(self, strs): """ :type strs: List[str] :rtype: List[List[str]] """ from collections import defaultdict ans = defaultdict(list) for s in strs: sorted_str = "".join(sorted(s)) ans[sorted_str].append(s) return list(ans.values()) ``` ### 1. Two Sum - 給你一個整數陣列 nums 和一個目標 target - 找出兩個不同 index,使得 nums[i] + nums[j] == target - 回傳這兩個 index,保證一定有解,只有一組解 - Solution: - 可以用雙迴圈O(n*2)、while雙指標縮小範圍、dict ```python class Solution(object): def twoSum(self, nums, target): # dict的key放nums的value, # key的value放nums的index # dict[key] -> value seen = {} for i, num in enumerate(nums): complement = target - num if complement in seen: return [seen[complement], i] seen[num] = i return [] ``` ### 202. Happy Number - - Solution: - 5 ```python ``` ```c ``` ### 219. Contains Duplicate II - - Solution: - 5 ```python ``` ```c ``` ### 128. Longest Consecutive Sequence - - Solution: - 5 ```python ``` ```c ``` ## Intervals 一堆區間 [start, end] 的演算法 ### 228. Summary Ranges - 給一個sorted num,有連續的區間用5->9表示,其他用單獨的數字表示 - Solution: - `if i == n or nums[i] != nums[i - 1] + 1:` 到尾巴或發現不連續 - 再看是重複的還是連續的 ```python class Solution(object): def summaryRanges(self, nums): res = [] n = len(nums) if n == 0: return res start = nums[0] for i in range(1, n + 1): if i == n or nums[i] != nums[i - 1] + 1: # 到尾巴 或 發現不連續 if start == nums[i - 1]: res.append(str(start)) else: res.append(str(start) + "->" + str(nums[i - 1])) if i < n: start = nums[i] return res ``` ```c char** summaryRanges(int* nums, int numsSize, int* returnSize) { char** res = (char**)malloc(sizeof(char*) * numsSize); *returnSize = 0; if (numsSize == 0) return res; int start = nums[0]; for (int i = 1; i <= numsSize; i++) { if (i == numsSize || nums[i] != nums[i - 1] + 1) { char* buf = (char*)malloc(25); if (start == nums[i - 1]) sprintf(buf, "%d", start); // 存成字串 else sprintf(buf, "%d->%d", start, nums[i - 1]); res[(*returnSize)++] = buf; if (i < numsSize) start = nums[i]; } } return res; } ``` ### 56. Merge Intervals - - Solution: - 5 ```python ``` ```c ``` ### 57. Insert Interval - - Solution: - 5 ```python ``` ```c ``` ### 452. Minimum Number of Arrows to Burst Balloons - - Solution: - 5 ```python ``` ```c ``` ## Stack ### 20. Valid Parentheses - - Solution: - 5 ```python ``` ```c ``` ### 71. Simplify Path - - Solution: - 5 ```python ``` ```c ``` ### 155. Min Stack - - Solution: - 5 ```python ``` ```c ``` ### 150. Evaluate Reverse Polish Notation - - Solution: - 5 ```python ``` ```c ``` ### 224. Basic Calculator - - Solution: - 5 ```python ``` ```c ``` ## Linked List ### 141. Linked List Cycle - Given head, the head of a linked list, determine if the linked list has a cycle in it. - Solution: 給一個盲Linked List判斷是否為迴圈,同時一次只走一步跟一次走兩步,如果有迴圈遲早兩個人會重疊 ```python # Definition for singly-linked list. # class ListNode(object): # def __init__(self, x): # self.val = x # self.next = None class Solution(object): def hasCycle(self, head): """ :type head: ListNode :rtype: bool """ slow = head # 慢指針,每次走一步 fast = head # 快指針,每次走兩步 while fast and fast.next: # fast.next有東西的話,fast.next.next才有意義 slow = slow.next fast = fast.next.next if slow == fast: return True # 相遇表示有 cycle return False ``` ```c /** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ bool hasCycle(struct ListNode *head) { struct ListNode *slow = head; // 慢指針,一次走一步 struct ListNode *fast = head; // 快指針,一次走兩步 while (fast != NULL && fast->next != NULL) { slow = slow->next; fast = fast->next->next; if (slow == fast) return true; // 相遇表示有 cycle } return false; // 都沒相遇 } ``` ### 2. Add Two Numbers - 給兩個linklist,return相加後的linklist,但個十百千萬都是相反的 - Input: l1 = [2,4,3], l2 = [5,6,4] - Output: [7,0,8] - Explanation: 342 + 465 = 807 - Solution: - 位數相反是好事,第一個就是個位數相加,第二個是十位數相加 - carry是看有沒有進位 - `while carry` `v1 = l1.val if l1 else 0` 都是怕最後一位相加後要進位 - 每建立新的點都要在heap上配置空間 - C宣告curr的方式 `struct ListNode* curr = l3;`;對比python `curr = l3` - C要多一個變數`node`做malloc;python會自動建立空間才不用多一個變數malloc ```python class Solution(object): def addTwoNumbers(self, l1, l2): carry = 0 l3 = ListNode(0) curr = l3 # current用來借位 while l1 or l2 or carry: v1 = l1.val if l1 else 0 v2 = l2.val if l2 else 0 total = v1 + v2 + carry carry = total // 10 curr.next = ListNode(total % 10) curr = curr.next if l1: l1 = l1.next if l2: l2 = l2.next return l3.next ``` ```c struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2) { int carry = 0; struct ListNode* l3 = (struct ListNode*)malloc(sizeof(struct ListNode)); l3->val = 0; l3->next = NULL; struct ListNode* curr = l3; // curr借位不要影響到l3 while (l1 != NULL || l2 != NULL || carry != 0) { int v1 = (l1 != NULL) ? l1->val : 0; int v2 = (l2 != NULL) ? l2->val : 0; int total = v1 + v2 + carry; carry = total / 10; // 每創建一個點就要多一次malloc // C要多一個node變數做malloc // python會自動建立空間才不用多一個變數malloc struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode)); node->val = total % 10; node->next = NULL; curr->next = node; curr = curr->next; if (l1 != NULL) l1 = l1->next; if (l2 != NULL) l2 = l2->next; } return l3->next; } ``` ### 21. Merge Two Sorted Lists - 給兩個排序的linklist,將兩個linklist合併,每個node由小到大 - Solution: - 建立dummy當開頭,不用建立新的linklist - C在stack建立dummy node作為開頭,後面都是更改l1, l2的排序 - python無論如何都會在heap建立空間,dummy node建立在heap ```python class Solution(object): def mergeTwoLists(self, l1, l2): dummy = ListNode(0) # python不管怎樣都會在heap建立空間 curr = dummy while l1 and l2: if l1.val <= l2.val: curr.next = l1 l1 = l1.next else: curr.next = l2 l2 = l2.next curr = curr.next curr.next = l1 if l1 else l2 return dummy.next ``` ```c struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2) { struct ListNode dummy; // 不用在heap配置linklist空間,所以不用malloc struct ListNode* curr = &dummy; dummy.next = NULL; while (l1 != NULL && l2 != NULL) { if (l1->val <= l2->val) { curr->next = l1; l1 = l1->next; } else { curr->next = l2; l2 = l2->next; } curr = curr->next; } curr->next = (l1 != NULL) ? l1 : l2; // 最後會剩下l1或l2最後一個node return dummy.next; } ``` ### 138. Copy List with Random Pointer - 特殊的 linked list有val, next, random,random是指向其中一個點或null - 完整複製一份新的 linked list,不能用到舊的點 - random 不是線性的,你在複製 A 時,A.random 可能指到還沒被複製的 C - Solution: - 在原本的linklist每個node後面都接新的node - `new.next = cur.next ` - `cur.next = new` - 新node的random也要接到新的node - `cur.next.random = cur.random.next` - 1,3,5,7是舊點;2,4,6,8是新點,最後分解成兩個linklist - `copy_cur.next = copy` - `cur.next = copy.next` ```python class Solution(object): def copyRandomList(self, head): # 如果原本 list 為空,直接回傳 None if not head: return None ''' 1 -> 2 -> 3 -> None | | | v v v 3 1 None ''' cur = head while cur: new = Node(cur.val) new.next = cur.next # 標準插入node的兩行 cur.next = new cur = new.next ''' 1 -> 1' -> 2 -> 2' -> 3 -> 3' -> None | | | v v v 3 1 None ''' cur = head while cur: if cur.random: cur.next.random = cur.random.next # random是舊點,再next是新點 cur = cur.next.next # 去下一個舊點 ''' 1 -> 1' -> 2 -> 2' -> 3 -> 3' | | | | | v v v v v 3 3' 1 1' None ''' new_head = Node(0) # 新 list 的假頭 copy_cur = new_head # 用來走訪新 list cur = head # 重新從舊 list 的頭開始 while cur: copy = cur.next # cur的第一個是舊點 # cur從下一個新點開始run copy_cur.next = copy # 接上copy的新點 copy_cur = copy # 移到剛接上的點 cur.next = copy.next # 接上copy的舊點 cur = cur.next # 移到剛接上的點 ''' 1 -> 2 -> 3 -> None | | | v v v 3 1 None 1' -> 2' -> 3' -> None | | | v v v 3' 1' None ''' return new_head.next ``` ```c struct Node* copyRandomList(struct Node* head){ if (!head) return NULL; struct Node* cur = head; while (cur) { struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); newNode->val = cur->val; newNode->next = cur->next; newNode->random = NULL; cur->next = newNode; cur = newNode->next; } cur = head; while (cur) { if (cur->random) cur->next->random = cur->random->next; cur = cur->next->next; } //struct Node* dummy = (struct Node*)malloc(sizeof(struct Node)); //dummy->next = NULL; //struct Node* copy_cur = dummy; struct Node dummy; dummy.next = NULL; // 只有指標的子項目用 -> struct Node* copy_cur = &dummy; cur = head; while (cur) { struct Node* copy = cur->next; copy_cur->next = copy; copy_cur = copy; cur->next = copy->next; cur = cur->next; } //return dummy->next; return dummy.next; } ``` ### 92. Reverse Linked List II - 給一個linklist,指定範圍反轉 - linklist的輸出就是一個點,每個點都有`next`所以會無限延伸 - Solution: - 變數很多,沒有好的命名方法,設定變數就是一直在縮短範圍 - 中間反轉同單向 Linked List 反轉 ```python class Solution: def reverseBetween(self, head, m, n): if not head or m == n: return head dummy = ListNode(0) dummy.next = head prev = dummy # 頭多一個0 for _ in range(m - 1): prev = prev.next # prev 移到 m-1 cur = prev.next tail = cur # 第m個之後會是尾巴 rev_prev = None # 反轉 m~n 節點 # 同單向 Linked List 反轉 for _ in range(n - m + 1): next_node = cur.next cur.next = rev_prev rev_prev = cur cur = next_node # 接回原 list prev.next = rev_prev # 上面prev在m-1,後面接反轉後的linklist tail.next = cur return dummy.next ``` ```c struct ListNode* reverseBetween(struct ListNode* head, int m, int n) { if (!head || m == n) return head; struct ListNode dummy; dummy.next = head; struct ListNode* prev = &dummy; // prev 最終指向 m 前一個節點 for (int i = 1; i < m; i++) prev = prev->next; // 移動 prev 到第 m-1 個節點 struct ListNode* cur = prev->next; // m 節點 struct ListNode* next; struct ListNode* tail = cur; // 反轉後的尾巴是 cur struct ListNode* rev_prev = NULL; // 反轉 m ~ n 節點 for (int i = 0; i <= n - m; i++) { next = cur->next; cur->next = rev_prev; rev_prev = cur; cur = next; } // 接回原鏈表 prev->next = rev_prev; // m-1 節點接反轉後的頭 tail->next = cur; // 反轉後尾巴接 n+1 節點 return dummy.next; } ``` ### 25. Reverse Nodes in k-Group - 給定一個 單向鏈結串列 `head`,每 `k` 個節點反轉一次,並返回修改後的鏈表 - Solution: - 用`while curr:`計算長度 - 當`while count >= k:`成立 - 執行子迴圈`for i in range(1, k):`標準單向linklist轉向 - 直到count < k ```python class Solution: def reverseKGroup(self, head, k): dummy = ListNode(0) dummy.next = head prev = dummy curr = head count = 0 while curr: count += 1 curr = curr.next while count >= k: curr = prev.next # 第 1 個節點 next = curr.next # 反轉 k 個節點 # 同單向 Linked List 反轉 for i in range(1, k): curr.next = next.next next.next = prev.next prev.next = next next = curr.next prev = curr count -= k return dummy.next ``` ```c struct ListNode* reverseKGroup(struct ListNode* head, int k) { if (!head || k == 1) return head; struct ListNode dummy; dummy.val = 0; dummy.next = head; struct ListNode *prev = &dummy, *curr, *next; // 同時宣告*prev, *curr, *next; // 計算長度 int count = 0; curr = head; while (curr) { count++; curr = curr->next; } while (count >= k) { curr = prev->next; // 第 1 個節點 next = curr->next; for (int i = 1; i < k; i++) { curr->next = next->next; next->next = prev->next; prev->next = next; next = curr->next; } prev = curr; count -= k; } return dummy.next; } ``` ### 19. Remove Nth Node From End of List - 給定一個 單向鏈結串列 `head`,刪掉倒數第`n`個元素,並返回修改後的鏈表 - Solution: - ```python class Solution(object): def removeNthFromEnd(self, head, n): """ :type head: Optional[ListNode] :type n: int :rtype: Optional[ListNode] """ dummy = ListNode(0) dummy.next = head curr = dummy.next count=0 while curr: count+=1 curr=curr.next curr = dummy # 一定要從dummy開始,不然linklist只有一項會錯 for _ in range(count - n): curr = curr.next curr.next = curr.next.next return dummy.next ``` ```c struct ListNode* removeNthFromEnd(struct ListNode* head, int n) { struct ListNode dummy; dummy.val = 0; dummy.next = head; struct ListNode* curr = dummy.next; int count = 0; while (curr) { count++; curr = curr->next; } curr = &dummy; for (int i = 0; i < count - n; i++) curr = curr->next; struct ListNode* temp = curr->next; curr->next = curr->next->next; free(temp); // C要釋放記憶體 return dummy.next; } ``` ### 82. Remove Duplicates from Sorted List II - 給一個已排序的 linked list,刪除所有出現超過一次的數字,只留下「完全沒重複過」的節點 - Solution: - 用`dup`紀錄重複的值,用`while`將該值都刪掉 ```python class Solution(object): def deleteDuplicates(self, head): dummy = ListNode(0) dummy.next = head prev = dummy cur = head while cur: if cur.next and cur.val == cur.next.val: dup = cur.val while cur and cur.val == dup: cur = cur.next prev.next = cur else: prev = cur cur = cur.next return dummy.next ``` ```c struct ListNode* deleteDuplicates(struct ListNode* head) { struct ListNode dummy; dummy.next = head; struct ListNode* prev = &dummy; // record struct ListNode* cur = head; // process while (cur) { if (cur->next && cur->val == cur->next->val) { int dup = cur->val; while (cur && cur->val == dup) { // 同一個值都要刪掉 struct ListNode* tmp = cur; cur = cur->next; free(tmp); } prev->next = cur; } else { prev = cur; cur = cur->next; } } return dummy.next; } ``` ### 61. Rotate List - 給一個已排序的 linked list,跟數字`k`,把最後一個元素放到第一個元素`k`次 - Solution: - 把linklist變成環狀,再找新的頭跟新的尾巴 - `while curr.next:` 計算linklist長度 - 從第一個元素走`長度-k-1`是新的尾巴,`.next`是新的頭 ```python class Solution(object): def rotateRight(self, head, k): if not head or not head.next: return head dummy = ListNode(0) dummy.next = head curr = head count = 1 while curr.next: curr = curr.next count += 1 k %= count if k == 0: return head curr.next = head # 接成環 curr = head # 找新尾巴:走 count - k - 1 步 for _ in range(count - k - 1): curr = curr.next dummy.next = curr.next curr.next = None return dummy.next ``` ```c struct ListNode* rotateRight(struct ListNode* head, int k) { if (head == NULL || head->next == NULL) return head; struct ListNode dummy; dummy.next = head; struct ListNode* curr = head; int count = 1; while (curr->next != NULL) { curr = curr->next; count++; } k %= count; if (k == 0) return head; curr->next = head; // 找新尾巴:走 count - k - 1 步 curr = head; for (int i = 0; i < count - k - 1; i++) { curr = curr->next; } dummy.next = curr->next; curr->next = NULL; return dummy.next; } ``` ### 86. Partition List - 給你一個 linked list head,還有一個整數 x,重新排列 linked list,使得: - 所有 < x 的節點在前面 - 所有 >= x 的節點在後面 - 重點:相對順序要保持(stable) - Solution: - 開兩條 `list`,一條裝 `< x`,一條裝 `>= x`,最後接起來 ```python class Solution(object): def partition(self, head, x): small_dummy = ListNode(0) large_dummy = ListNode(0) small = small_dummy large = large_dummy curr = head while curr: if curr.val < x: small.next = curr small = small.next else: large.next = curr large = large.next curr = curr.next large.next = None small.next = large_dummy.next return small_dummy.next ``` ```c struct ListNode* partition(struct ListNode* head, int x) { struct ListNode small_dummy, large_dummy; small_dummy.next = NULL; large_dummy.next = NULL; struct ListNode* small = &small_dummy; struct ListNode* large = &large_dummy; struct ListNode* curr = head; while (curr != NULL) { if (curr->val < x) { small->next = curr; small = small->next; } else { large->next = curr; large = large->next; } curr = curr->next; } large->next = NULL; small->next = large_dummy.next; return small_dummy.next; } ``` ### 146. LRU Cache - Least Recently Used - 實作一個快取系統,支援兩個操作: - `get(key)` → O(1) - `put(key, value)` → O(1),當容量滿了,要刪掉「最久沒被使用的」資料 - 正解只能是:Hash Table + Doubly Linked List - 只用 array / list → `get(key)`無法 O(1) - 只用 hash table → 不知道誰最久沒用 - Solution: - Hash Map:key → node,O(1) 找到資料 - Doubly Linked List:記錄使用順序,最近用的放前面,最久沒用的在後面(尾巴) - `get(key)` - key 不存在 → return -1 - key 存在:把該 node 移到最前面(因為最近使用) return value - `put(key, value)` - key 已存在:更新 value,把 node 移到最前面 - key 不存在:如果容量滿了:刪掉尾巴(LRU),hash 也要刪,插新 node 到最前面,hash 記錄 key → node - 把node移到最前面都是先刪掉node`_remove`,再插入最前面`_add_to_head` ```python class DLinkedNode: def __init__(self, key=0, value=0): self.key = key self.value = value self.prev = None self.next = None class LRUCache(object): def __init__(self, capacity): self.capacity = capacity self.cache = {} # key -> node self.head = DLinkedNode() self.tail = DLinkedNode() self.head.next = self.tail self.tail.prev = self.head def _remove(self, node): prev = node.prev nxt = node.next prev.next = nxt nxt.prev = prev def _add_to_head(self, node): node.prev = self.head node.next = self.head.next self.head.next.prev = node self.head.next = node def get(self, key): if key not in self.cache: return -1 node = self.cache[key] self._remove(node) self._add_to_head(node) return node.value def put(self, key, value): if key in self.cache: node = self.cache[key] node.value = value self._remove(node) self._add_to_head(node) else: if len(self.cache) == self.capacity: lru = self.tail.prev self._remove(lru) del self.cache[lru.key] node = DLinkedNode(key, value) self.cache[key] = node self._add_to_head(node) ``` # Reference - [Hello Algo](https://www.hello-algo.com/zh-hant/) - [GeeksforGeeks](https://www.geeksforgeeks.org/) - [Leetcode](https://leetcode.com/) - [Collegiate Programming Examination](https://cpe.mcu.edu.tw/) - [HackerRank](https://www.hackerrank.com/)