Leetcode題解 Python & C#:五月挑戰DAY7 Cousins in Binary Tree

給一個二元樹的根節,找出 x、y是否為cousin(同深度,不同父)。
遍歷所有的節,再把找到的x、y資料(深度,父節)做對比,就可以辨別是否為cousin 。
要找遍二元樹上的每一個元素,除了可以用遞迴函數,也可以用一層一層的stack(queue皆可)來找。由於每節的值不會重複,不用擔心多個可能。
就效率來看,這題的情況單純,儘可能減少判斷,才是通往高效之路。

Python
class Solution:
def isCousins(self, root: TreeNode, x: int, y: int) -> bool:

def search(node, parent, depth, val):
if not node: return None, None
if node.val == val:
return parent, depth
else:
res = search(node.left, node, depth+1, val)
if res[0]: return res
return search(node.right, node, depth+1, val)

nodeX = search(root, None, 0, x)
nodeY = search(root, None, 0, y)
return (nodeX[0] != nodeY[0] and nodeX[1] == nodeY[1])

C#

public class Solution {
public bool IsCousins(TreeNode root, int x, int y) {
var queue = new Queue();
var memo = new Dictionary();
queue.Enqueue(root);
memo[root.val] = root;
while(queue.Count > 0)
{
int len = queue.Count;
for(int i = 0; i < len; i++)
{
var node = queue.Dequeue();
if(!(node.left is null))
{
memo[node.left.val] = node;
queue.Enqueue(node.left);
}
if(!(node.right is null))
{
memo[node.right.val] = node;
queue.Enqueue(node.right);
}
}
var hasX = memo.ContainsKey(x);
var hasY = memo.ContainsKey(y);
if(hasX | hasY)
{
if(hasX & hasY)
{
if(memo[x] != memo[y])
return true;
else
break;
}
else
{
break;
}
}
}
return false;
}
}

專案開發紀錄1:規劃使用環境

雖然我已經自己寫過好幾版的應用程式,不過卻很鬆散,沒有一個明確的SOP,想到什麼就做什麼。

每次都有一點突破,這次也要求自己要用上更多的進階技巧。
我在想打造一個方便跨平台,同時又能減少關卡的資訊傳送方式。不是那種要用IP位置的通訊協定、這太費工夫。
同時最好可以主機用API去修改內容,但不改變網址,如果每次更新都要開權限再重新分享,實在太麻煩。
對於跨平台是第一次嘗試,考量我之後沒辦法總是在我主機上使用,就算出門在外,也要能時時刻刻掌握動態。
線上DB + google sheet 本來是我第一個的組合。之前搭載虛擬機,可以用MSSQL,目前DB類的都不成問題。
不過,google sheet 暫時卡住我,因為我開新帳號,原因是覺得目前我用的帳號太過氣,取一個比較合身分的帳號。
我知道google新帳號,會被限縮很多功能,但主帳有很多私人的資料,我不想因為這次開發開一堆權限出去。
於是我的新帳號沒辦法使用 外掛功能 ,第一個嘗試就被迫停止在這裡。
接著我打算試試看 WordPress ,如果資料可上雲端,那麼我需要的是「隱私性」,只有我能看,不給一般尋常方式(google搜查)可找到。
WordPress有外掛功能,可以連接DB,也有權限設定。但,那需要商業版,一年300$,也就是9000台幣。
行不行?讓我不爽,因為我不知道WordPress,到底值不值,也許用起來跟我想像的不同,因為沒有試用,我的疑慮無法消。
「GIVE UP」換一種吧。
我希望主機要能即時影響所要呈現的資訊,不需要到每秒,但希望也能有每分鐘的更新頻率。

後來嘗試了用google API 上傳檔案到 drive,google 的管制很嚴,如果用得太爽,它總是能偵測出來,冷不防地鎖一個,我不知道會不會被標記為濫用?
但我只是嘗試要如何使用,如果只有google API,那達不到我想要的方便性。
接下來,發現了一個說可以把 google drive 或 one drive 當成網站的教學文章,還可以控制網域,那不就解決只用 google drive 的問題嗎?
固定的網域就不會老是更新位置,而且一行網址就能取得資料,也不會直接公開到網路上,即時更新的功能也可以靠Google API完成,還可以順利切換不同網頁。
其實網頁的功能,是瀏覽器下載原始碼後建構的,所以理論上,用google drive可以做到一般網頁的基本功能沒問題,
而動態加載的部分,如果主機可以直接影響,那也不必有查詢的功能,或者都放到json檔再篩選,就不需送互動的指令,我仍掌握要呈現什麼資訊的權力。
至於流量,我只打算是我一人使用,所以也不會流量暴表的異常被鎖吧?
決定架構是很初期的工作,先定好了框架,後續才能繼續開發。這次要用上我之前沒有搭配的工具,大致的code寫法我心裡有底,只有這一塊是不明不白的。

(這一版將使用 python scarpy + db(非access) + git + 網頁)
既然跨平台瀏覽 + 即時更新可行,那後續的進階規劃就可以再展開,下一步就是把細項與流程寫得更齊全。

Leetcode題解 Python & C#:五月挑戰DAY6 Majority Element

給一個大小為 n 的陣列,找出出現次數超過 n/2 的元素。(可以預期這個元素一定存在)

這個是計數題,數一遍 n 並且統計出現次數,就能找到 majority element。

 Python

class Solution:
def majorityElement(self, nums: List[int]) -> int:
import collections
counter = collections.Counter(nums)
n = len(nums)
for key in counter:
if counter[key] >= n/2:
return key

C#

public class Solution {
public int MajorityElement(int[] nums) {
var counter = new Dictionary();
int halfN = nums.Length/2 + (nums.Length%2==1 ? 1: 0);
foreach(var num in nums)
{
counter[num] = 1 + (counter.ContainsKey(num) ? counter[num]: 0);
if(counter[num] >= halfN)
{
return num;
}
}
return 0;
}
}

Leetcode題解 Python & C#:五月挑戰DAY5 First Unique Character in a String

給一個字串,找出第一個沒有重複的字符位置,如果沒有則回傳 -1 。

沒有重複,代表該字符的計數為 1,只要找出最早的計數為 1 個字符。

最快的情形是跑一遍 N ,再挑出符合的,沒有那就是 -1 。

Python

class Solution:    
def firstUniqChar(self, s: str) -> int:
import collections
counter = collections.Counter(s)
for key in counter:
if counter[key] == 1: return s.index(key)
return -1

C#

public class Solution {
public int FirstUniqChar(string s) {
var counter = new Dictionary();
foreach(var c in s)
{
counter[c] = 1 + (counter.ContainsKey(c) ? counter[c] : 0);
}
foreach(var item in counter)
{
if(item.Value == 1)
{
return s.IndexOf(item.Key);
}
}
return -1;
}
}

Leetcode題解 Python & C#:五月挑戰DAY4 Number Complement、Complement of Base 10 Integer

給一個整數,回傳一個bit位互補(即 OR 後的 bit 每一位都是1)的數。
(這題跟 1009 Complement of Base 10 Integer 是相同的,只是換了名)

這題的邏輯很簡單,找出一個與 num 相同 bit 位長度且每一位都是 1 的數,再用 XOR 去求解。

為了必避免C#的int溢位,所以使用推進的方式,而不是由高位減一。

Python

class Solution:
def findComplement(self, num: int) -> int:
return ((1 << num.bit_length()) - 1 ) ^ num

C#

public class Solution {
public int FindComplement(int num) {
int r = 1;
while(r < num)
{r = (r << 1) + 1;}
return r ^ num;
}
}

Leetcode題解 Python:Find the Kth Smallest Sum of a Matrix With Sorted Rows

給一個 m*n 的矩陣,跟整數 k,而且每行已經排列。從每行取一個出來作總和,求第 k 小的總和是多少。

這題跟 373. 是很類似的,不過這題多了很多行數列,但並不是什麼難點。

若採用全部枚舉的話,最多40**40個,這樣子會超時。

若分批進行,一樣從全部的索引 0 開始,每輪後索引總和+1,繼續枚舉,因為目標大小 k 最高200個,所以這方法是可行的?

只要每輪枚舉結束,枚舉的總數超過 k 就可以找到目標值?但並不是這麼一回事。

的確一開是從最小值開始枚舉,會得到各索引+1的組合。

若只是單純以索引+1為枚舉的方式,這不能保證一定能包含完整的最小總和順序。

例如:mat = [[1,2,3,4,5,6],[1,1,1,1,1,1]], k = 3


然而下一輪要枚舉的,是以什麼當基準呢?

如果一開始是最小值,那麼,第二小的值會在枚舉中被找到。

若換種角度,都以最小值的索引組合為基準,而第三小的值會在第二小的枚舉中被找到,第四小的也是同理。

因此我們需要參考最小值的索引組合做枚舉。使用 heap 的話,就能保持最小值總在第一個,也方便進行後面的枚舉。

如此連鎖,第二找第三,第三找第四,……,就能找到第 k 的值。

Python

class SolutionPass:
def kthSmallest(self, mat, k: int) -> int:
import heapq
result = []
n, m = len(mat), len(mat[0])
heap = [(sum(each[0] for each in mat) , *[0 for _ in range(n)])]
memo = set()
while k > 0 and heap:
sm, *idxs = heapq.heappop(heap)
result.append(sm)
k -= 1
for i in range(n):
idxs[i] += 1
if idxs[i] < m and tuple(idxs) not in memo:
memo.add(tuple(idxs))
heapq.heappush(heap, [sum([mat[j][idxs[j]] for j in range(n)]), *idxs])
idxs[i] -= 1
return result[-1]

Leetcode題解 Python:Find K Pairs with Smallest Sums

給兩個數列 nums1 nums2 ,要回傳 k 個總和由小排列的組合。參考

關於會作這一題, 是因為有一題更進階的在後頭,所以先把這個處理掉再進階。

之前我碰到 heap 都不是了解,但是又很多人會用,所以這次我得學著用 heap 去解問題。

關於 heap , 是一種「堆積法」,有 maxheap 和 minheap ,可想成是一個二元樹,把根節設為最大或最小。

好處是可以快速找到 max 或是 min ,在要用到這類的計算排列時,heap 就很好用了。

python的 heapq 是以 minheap 為主的,跟一般排序的 maxheap 是不同的。

而 minheap,也是這題會用到的 heap ,要找最小值。

如果用 heapq.heappush() 相當於 append()、sort(),這樣在枚舉時也能加速。

這題要找組合,所以要保存總和由小到大的組合的 result 。


由於 nums1 跟 nums2 都已經排序過,所以我們可以得知每個的第[0]位相加會是最小總和。

接下來第二小的,不是 nums1 的索引+1,就是 nums2 的索引+1。

這樣子會陷入一個難題,到底重複出現搜尋過索引組合的要如何避免?最簡單的方法是拿索引組合用hash類型做檢查。

由於 heap 的排序若有二維是用第[0]位做判斷(跟list一樣),因此 heap 中元素的第[0]位是總和,這樣也能符合由小到大的要求。

而後面幾位擺上索引組合,因為元素可能是相同數值,若只計算加總值可能會漏掉該出現的重複。

(這題的解法,已針對後面進階題做改良。)

Python

class Solution:
def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]:
result = []
if not nums1 or not nums2: return result
nums = [nums1, nums2]
heap = [(sum(each[0] for each in nums) , 0, 0)]
memo = {(0,0)}
n = len(nums)

while k > 0 and heap:
_, *idxs = heapq.heappop(heap)
result.append([nums[j][idxs[j]] for j in range(n)])
k -= 1
for i in range(len(idxs)):
idxs[i] += 1
if idxs[i] < len(nums[i]) and tuple(idxs) not in memo:
memo.add(tuple(idxs))
heapq.heappush(heap, [sum([nums[j][idxs[j]] for j in range(n)]), *idxs])
idxs[i] -= 1

return result

Leetcode題解 Python:Number of Ways to Wear Different Hats to Each Other

給一組二維串列,一維代表第 i 位的人,二維代表該位置上的人可選的帽子種類編號。回傳全部人都戴上帽子且都是不同種類的方法數。

這題是DP,最大的特徵就是數目很大,需要取餘。(以前提過)

但DP的精華就是如何設計DP在放些什麼,而我這次學到了「bit」的方法。(然而我沒有在contest解出來QQ)

由於帽子種類跟人數都是有限制範圍的,若要取DP,可以由帽子做分配,或是由人開始。

要選哪一個?這我當時沒有想清楚,結果用人為主體。然後backtrace都超時。

以人為主體的話:1人可能有40種﹐最多有10人,是 40**10 是嗎?不,最糟是40*39*38*…*30。

以帽子為主體的話:則是 40*10! ,40是帽子的種類,10的階乘是代表沒有戴上帽子的人。只要能提前戴上帽子,就可以提前結束遞迴函數。


而且我之前沒有想到bit法,用 set 是不能當 hashtable 的 key , 於是我用 tuple,太花時間。

bit法是相互獨立且非 1即 0 的狀態才適用,剛好這題適用,不論是用來表示帽子被選擇,或是誰戴上帽子,都可以。

我以為會有很多種的方法,結果沒有,而目前沒有看到更好的解法,大概都這一套。

Python

from functools import lru_cache
from collections import defaultdict

class Solution:
def numberWays(self, hats) -> int:
n = 40 # 題目限制的帽子種類
m = (1 << len(hats)) - 1 # 全部人都戴上帽子的狀態

hatToP = defaultdict(list)
for i, each in enumerate(hats):
for hat in each:
hatToP[hat].append(i)

@lru_cache(None)
def search(i, state):
if state == m: return 1
if i > n: return 0
ways = 0
for p in hatToP[i]:
if state < (state | 1 << p): # 選
ways += search(i+1, (state | 1 << p))
ways += search(i+1, state) # 不選
return ways % (10**9+7)

return search(0, 0)

Leetcode題解 Python & C#:五月挑戰DAY3 Ransom Note

給兩個字串:ransomNote、magazine,問 ransomNote 是否能從 magazine 裡面的宇符組成。

嗯,這是一個電影會見到的情節吧…

方法很簡單,就是 ransomNote 的每個字符出現次數要比 magazine 的少或等於。

Python

class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
for c in set(ransomNote):
if ransomNote.count(c) > magazine.count(c):
return False
return True

C#

public class Solution {
public bool CanConstruct(string ransomNote, string magazine) {
var charSet = new HashSet(ransomNote);
foreach(var c in charSet)
{
if(ransomNote.Count(p => p == c ) > magazine.Count(p => p == c ))
return false;
}
return true;
}
}

Leetcode題解 Pythonython:Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit

給一串整數陣列 nums 跟整數 limit,求連續子陣列的最大小值相減 <= limit 的最大子陣列個數。

我以前有碰到過這種問題,但我沒有在當下把 deque 的觀念內化,都選擇用dp,終於在今天吃苦頭。
(又或者可以用 two pointers 做解決。)

這種類型的問題有一個特色,就是有「連續子陣列(或串列)」,若條件不合,去掉頭再繼續。

用 時間複雜度 O(N) 解決掉,只給跑一次 nums 的機會。

基本架構是用一個串列,先把遇到的數加進串列後方,(while)若 max() – mix() > limit 則 del[0],在每輪最後檢查len()。

基本型(Python) ※會超時

class Solution:
def longestSubarray(self, nums, limit) -> int:
queue = []
result = 0
for i in range(len(nums)):
queue.append(nums[i])
while queue and max(queue) - min(queue) > limit:
del queue[0]
result = max(result, len(queue))
return result

其中最耗費時間的函數是 max() 跟 min() ,如果queue的所含的個數很多,就會在這裡因為一直找而超時。

所以改善這一塊,用兩個變數 maxValue 跟 minValue 去保存最大小值就能順利通過。

改善型(Python)

class Solution:
def longestSubarray(self, nums, limit) -> int:
queue = []
result = 0
maxValue = minValue = nums[0]
for i in range(len(nums)):
queue.append(nums[i])
maxValue = max(maxValue, nums[i])
minValue = min(minValue, nums[i])
while queue and maxValue - minValue > limit:
v = queue.pop(0)
if queue:
if v == maxValue: maxValue = max(queue)
if v == minValue: minValue = min(queue)
else:
maxValue = nums[i+1]
minValue = nums[i+1]
result = max(result, len(queue))
return result
class Solution:
def longestSubarray(self, nums, limit) -> int:
sp = 0
result = 0
maxValues = []
minValues = []
for i in range(len(nums)):
num = nums[i]
if not maxValues or num >= maxValues[-1]: maxValues.append(num)
if not minValues or num <= minValues[-1]: minValues.append(num)
print(i, sp, maxValues, minValues)

while maxValues and minValues and i>sp and maxValues[-1] - minValues[-1] > limit:
v = nums[sp]
sp += 1
if v == maxValues[0]:
del maxValues[0]
if not maxValues: maxValues.append(max(nums[sp:i+1]))
if v == minValues[0]:
del minValues[0]
if not minValues: minValues.append(min(nums[sp:i+1]))

result = max(result, i-sp+1)
return result

Leetcode題解 Python & C#:五月挑戰DAY2 Jewels and Stones

給兩個字串 J、S ,J裡面的字符是代表jewel,問 S 裡面有幾個 jewel。

這是一個相當簡單的問題,重點會放在用什麼作為搜尋的方式。

為了快速,這裡會使用 hash 家族的資料型態讓搜尋更快速。

如果是Python會用 set 或 dict 都可以,我用 collections.Counter, 只是換一點寫法。collection.Counter 是 dict 類的。

如果是C#,我直接用 string 的 Contains() ,其實做法都大同小異。 


Python

class Solution:
def numJewelsInStones(self, J: str, S: str) -> int:
counter = Counter(S)
return sum([counter[j] for j in set(J)])

C#

public class Solution {
public int NumJewelsInStones(string J, string S) {
int result = 0;
foreach(var s in S)
{
if(J.Contains(s))
result += 1;
}
return result;
}
}

Leetcode題解 Python & C#:五月挑戰DAY1 First Bad Version

給一個 n 代表目前有 n 個版本,要透過 isBadVersion(version) 的函數去找到第一個 True(即BadVersion)的版本號。

這題如果是有使用git去控制版本,那應該是很生活化的搜尋思維。

首先決定搜尋法,這裡使用二分搜尋法去解。

左為 l 右為 r,每次都從 m = (r-l)//2+l 位置找,我之前不太能掌握 l, r 與 m 的替換,到底是 >=, <=, m+1, m 的關係要如何連結起來,當我試著想用 r 去貼齊 target 的位置 ,就比較了解該怎麼寫對。

不過這題也不會有這樣的困擾。

由於版本會從 1 開始, l (L)= 1 ,而 r 要等於 n + 1 ,不過最壞的陷阱來了。若 n = 2147483647 ,那麼就會發生溢位,所以要做一點小修改。(如果是Python就不用擔心了)

Python

class Solution:    
def firstBadVersion(self, n):
l, r = 1, n+1
while l < r:
m = (r-l)//2 + l
if isBadVersion(m):
r = m
else:
l = m+1
return r

C#

public class Solution : VersionControl {
public int FirstBadVersion(int n) {
int l=1; int r= n;
int m = (r-l+1)/2+l;
while(l < r)
{
if (IsBadVersion(m))
{
r = m;
}
else
{
l = m+1;
}
m = (r-l)/2+l;
}
return r;
}
}