Leetcode題解 Python & C#:五月挑戰DAY9 Valid Perfect Square

給一個正整數,問其是否為整數的平方。
若以不轉換資料型態來計算,即只用 int 去解決,只要判斷 num 是否在(枚舉)整數的平方。
(這是一種挑戰,不然用Python內建的計算,一句就能非常迅速的解決。)
不過枚舉這樣子太慢了,如果是完美,那 num 可以用兩個相同的因數相乘。
用上找因數,就能避免枚舉很多不必要的數字,也不會有怕溢位的問題。
如果使用二分搜尋法加速,又會面臨溢位的問題,但處理好後速度會快不少。

Python(基本)

class Solution:
def isPerfectSquare(self, num: int) -> bool:
def isValid(x):
ans = x*x
if ans < num: return isValid(x+1)
elif ans > num: return False
else: return True
return isValid(1)

C#(因數拆解)

public class Solution {
public bool IsPerfectSquare(int num) {
int a = num; int b = 1;
while(a>b)
{
a /= 2;
b *= 2;
}
for(int i = a; i <= b; i++)
if(i*i == num){return true;}
return false;
}
}

Leetcode題解 Python & C#:五月挑戰DAY8 Check If It Is a Straight Line

檢查是否所有座標都在一條直線上。
這是很簡單的數學題,只要細節不漏掉就好。
寫出方程式: ax + b = y。但這不適用於垂直直線,因為同 x 會出現兩個以上的 y。 
這兩種情況要分開判斷,只要不是垂直直線的,靠解方程式就可以判斷。
若是,則看所有的座標的x是否為相同值。
(打完題解後,發現我本來寫好的是錯的但卻可以接受,只好幫提供testcase,默默改正code。

Python
class Solution:
def checkStraightLine(self, coordinates: List[List[int]]) -> bool:
dx = coordinates[1][0] - coordinates[0][0]
if dx == 0:
x = coordinates[0][0]
return all(map(lambda c: c[0] == x, coordinates))
else:
a = (coordinates[1][1] - coordinates[0][1]) / dx
b = coordinates[0][1] - a*coordinates[0][0]
return all(map(lambda c: a*c[0] == c[1]-b, coordinates))

C#

public class Solution {
public bool CheckStraightLine(int[][] coordinates) {
int dx = coordinates[1][0] - coordinates[0][0];
if(dx == 0)
{
int x = coordinates[0][0];
foreach(var c in coordinates)
{
if(c[0] != x)
{
return false;
}
}
}
else
{
double a = (coordinates[1][1] - coordinates[0][1]) / dx;
double b = coordinates[0][1] - a * coordinates[0][0];
foreach(var c in coordinates)
{
if(c[0]*a != c[1]-b)
{
return false;
}
}
}
return true;
}
}

Leetcode題解 Python:Reverse Nodes in k-Group

給一個 Linked List,從左至右以 k 個節為一組作反轉,若不滿 k 個,則該維持原有排序。
像是在 List 中作位移的題目,多半跟 TwoPointer 的技巧是相關的。
這題也不例外,用 TwoPointer 控制兩個點互換,按題目要求解出所求。
如果由左至右,考量到後方未必有 k 個節,使用「深度優先」的遞迴函數,對於求解會比較好進行。(個人覺得比較好理解。
先檢查目前是否還有 k 個節,沒有則不逆序,有則逆序。

Python

class Solution:
def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
nextKNode = FP = head
for _ in range(k):
if not nextKNode: return head
nextKNode = nextKNode.next
lastNode = self.reverseKGroup(nextKNode, k)
for _ in range(k):
SP, FP = FP, FP.next
SP.next, lastNode = lastNode, SP
return SP

Leetcode題解 Python:Merge k Sorted Lists

合併 k 個 linked list 成一個 linked list,並且值由小到大排序。
說到要保持由小而大,不外乎就想到 min heap,關於 heap 的介紹,這裡就不多說了。
因為每次新的加入就得重新找出最小的,選擇 heap 是快多了。
不過不論是 heap 還是 sort() 都無法對於 ListNode 做比較,所以不是重新生成ListNode,
不然就是得找另外一種的保存方式。
我選擇用 dict() 以Node的值為鍵去存放 ListNode。但可能會出現同值有二個以上的ListNode,所以得用list存放,
同值的順序並不重要,抓哪個都可以。為了方便,使用 collections.defaultdict(list)。
用 heap 的 list 只有放 ListNode.val,只要拿值去對就可以取出下一個要接的 ListNode。

Python

class Solution:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
# 使用 min heap
import heapq
import collections
nodeMap = collections.defaultdict(list)
stack = []
for node in lists:
if node:
nodeMap[node.val].append(node)
heapq.heappush(stack, node.val)
if not stack: return None
root = nowNode = ListNode()
while stack:
nowNode.next = nodeMap[heapq.heappop(stack)].pop()
nowNode, nextNode = nowNode.next, nowNode.next.next
if nextNode:
nodeMap[nextNode.val].append(nextNode)
heapq.heappush(stack, nextNode.val)
return root.next

Leetcode題解 Python & C#:4Sum

如果有做出 3Sum 的人,這類題有一套公式,即使用 TwoPointer 的技巧,靠迴圈、加速搜遍數列。
3Sun是選定一個 i(由前往後),4Sum就多選一個 j(由後往前),剩下用 l、r,即 TwoPointer,用總和值來決定移動 l 還是 r。
先把數列排序,若 nums[i] + nums[l] + nums[r] + nums[j] > target,則我們要縮小總和,因此讓 r – 1,反則 l + 1,
這使得每次移動都讓總和與目標值靠近。如果跟總和跟目標值相同,記錄組合,然後移動 l 與 r 。  
由於不能重複,存放組合的資料型態可以選hash家族,這樣就能有效率的避免有同樣組合被計到二次以上。
又或者將重複的狀況跳過,不僅可加速迴圈,也可以直接用 list 放結果,不用轉換資料型態。 

Python(基本)

class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
n = len(nums)
result = set()
nums.sort()
for i in range(n-3):
for j in range(n-1,i+2,-1):
l, r = i+1, j-1
while l < r:
v = nums[i] + nums[l] + nums[r] + nums[j]
if v > target:
r -= 1
elif v < target:
l += 1
else:
result.add((nums[i],nums[l],nums[r],nums[j]))
l += 1
r -= 1
return result

C#

public class Solution {
public IList> FourSum(int[] nums, int target) {
Array.Sort(nums);
int n = nums.Length;
IList> result = new List>();
for(int i = 0; i < n - 3; i++)
{
for(int j = n - 1; j > i + 2; j--)
{
var l = i + 1; var r = j - 1;
while(l < r)
{
var nsum = nums[i] + nums[l] + nums[r] + nums[j];
if(nsum == target)
{
result.Add(new List(){nums[i], nums[l], nums[r], nums[j]});
while(l < r && nums[l] == nums[l+1] && nums[r] == nums[r-1])
{
l++;
r--;
}
l += 1;
r -= 1;
}
else if(nsum > target)
{
r -= 1;
}
else
{
l += 1;
}
}
while(j > i + 2 && nums[j] == nums[j-1]){j--;}
}
while(i < n - 3 && nums[i] == nums[i+1]){i++;}
}
return result;
}
}
不過會發現,Python這樣子的寫法不夠快,有沒有什麼可以加速的條件可用。
例如重複跳過,若接下來找不到目標值,則進入下一個迴圈。這些條件都很好理解,就不多敘述了。
在添加了一些提前中止的條件後,速度可以壓在100ms以內。

Python

class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
n = len(nums)
result = []
nums.sort()
for i in range(n-3):
if( i > 0 and nums[i] == nums[i-1]): continue
if nums[i] * 4 > target: break
for j in range(n-1,i+2,-1):
if(j target:
if curSum + 2 * nums[l] > target: break
r -= 1
elif v < target:
if curSum + 2 * nums[r] < target: break
l += 1
else:
result.append([nums[i],nums[l],nums[r],nums[j]])
while(l < r and nums[l] == nums[l+1] and nums[r] == nums[r-1]):
l += 1
r -= 1
l += 1
r -= 1
return result

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