Leetcode題解 Python & C#:五月挑戰DAY30 K Closest Points to Origin

給一串座標 Points ,回傳 K 個離(0, 0)最近的點。
要知道距離的算法,因為是找跟(0, 0)的距離,所以用 P[0]**2 + P[1]**2,就能作為比較值。(其開根號是距離)
不難,重點要放在如何排序。
使用內建的 sort() 功能就能快速排序,如同官方方法,用 sorted(points, key:lambda x: x[0]**2+x[1]**2),能實現排序,
又不會多使用空間,是最佳解之一。
時間是 O(nlogn),就是排序法(sort)的時間吧。
換個想法,有什麼排序法夠快,最好可以快速找到最小值? min heap。
不然用hashtable以距離作分類,由小開始,加到K個就回傳,這也不用把整個points重新排序。

Python

class Solution:
def kClosest(self, points: List[List[int]], K: int) -> List[List[int]]:
import heapq
heap = []
for p in points:
heapq.heappush(heap, [p[0]**2+p[1]**2, p])
return [heapq.heappop(heap)[1] for _ in range(K)]

C#

public class Solution {
public int[][] KClosest(int[][] points, int K) {
var rank = new Dictionary>();
foreach(var p in points)
{
int dis = p[0]*p[0] + p[1]*p[1];
if(!(rank.ContainsKey(dis)))
rank[dis] = new List();
rank[dis].Add(p);
}
var sortedKeys = new List(rank.Keys);
sortedKeys.Sort();
int count = 0;
List result = new List();
foreach(int key in sortedKeys)
{
foreach(var p in rank[key])
{
result.Add(p);
count += 1;
if(count == K)
{
return result.ToArray();
}
}
}

return result.ToArray();
}
}

Leetcode題解 Python & C#:五月挑戰DAY29 Course Schedule

給一個 numCourses 代表從編號 0 到 numCourses 的課程。有先修要求 prerequisites,其中元素為[a, b]代表要先修完 b 才能修 a 課程。
回傳 true 如果可以修完所有課程,否則 false。
一旦先修課程形成了迴圈,如:[a, b][b, a],那就無法完成所有課程。
這裡的課程數最多有 100000 個,數目很大,要是使用暴力破解會有超時的可能。
如果使用先修數作排列,少的先修,多的後修,但這樣可能用上 O(N^2) 的時間,直接放棄。
先串起 N 個課程,像是N-ary Tree或是Linked List等,再檢查有無迴圈產生,會用 O(N) 時 O(N) 空。
用 memo 紀錄到過的 node,如果往下的過程碰到的 node 出現在 memo 裡面,則代表有接回頭,頭尾相接就回傳 false。
用 DFS 去搜尋,如果到底,也就是沒有後續課程,那代表該條搜尋路線是沒有迴圈產生,是安全路線,之後若 DFS 來到安全路線,也不必再往下尋找。
我在想那個題目所講的方式到底是什麼?後來看懂了,大部分的解答都是用提示的想法出發。
那就是做拓樸排序,也就是把有向圖(有方向關係,如先修),轉換成排序。如果有環出現,就無法轉換。(參考)
找第一點開始,也就是沒有先修課程的課程。往下尋找,每個點到訪次數不能超過 numCourses。這樣算暴力破解法嗎?

Python

class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
memo = set()
this_memo = set()
nodeMap = defaultdict(list)

for c, p in prerequisites:
nodeMap[p].append(c)

def dfs(node):
if node in this_memo: return False
this_memo.add(node)
if not all([dfs(each) for each in nodeMap[node] if each not in memo]):
return False
memo.add(node)
return True

for root in range(numCourses):
if root not in memo and not dfs(root): return False

return True

C#

public class Solution {
public bool CanFinish(int numCourses, int[][] prerequisites) {
var safe_nodes = new HashSet();
var path = new HashSet();
var nodeMap = new Dictionary>();

for(int i = 0; i < numCourses; i++)
nodeMap[i] = new List();
foreach(var pair in prerequisites)
nodeMap[pair[1]].Add(pair[0]);

bool DFS(int node)
{
if(path.Contains(node)){return false;}
path.Add(node);
for(int i = 0; i < nodeMap[node].Count; i++)
if(!(safe_nodes.Contains(nodeMap[node][i])))
{
if(!(DFS(nodeMap[node][i]))){return false;}
}
safe_nodes.Add(node);
return true;
}

for(int i = 0; i < numCourses; i++)
{
if(!(safe_nodes.Contains(i)))
{
if (!DFS(i))
{
return false;
}
}
}

return true;
}
}

Leetcode題解 Python & C#:五月挑戰DAY28 Counting Bits

給一個非負整數 num,要找出 [0, num] 之間(包含兩端)的所有整數在二進位下有的 1 個數。
因為很簡單,所以有限制條件,在 O(n) 時空內解決。
如果是 0,那其二進位不含 1 ,是 0 個。
如果是 1,那其二進位只有 1 個 1 。
如果是 2,那進位,二進位變成 10,只有 1 個 1。
如果是 3,那二進位是 11,有 2 個 1。
如果是 4,那進位,二進位是 100,只有 1 個 1。 

一旦進位,接下來的 1 個數,會是將前面的所有的 1 個數再加 1。可把前面視作最高位為 0 的版本,而進位是變成最高位為 1 的版本,
000
001 ( _ + 1)
010 (0的答案 + 1))
011 (1的答案 + 1))
100 (00的答案 + 1)
101 (01的答案 + 1)
110 (10的答案 + 1)
111 (11的答案 + 1)
接下來換奇偶方法。
若用長除法,數字不斷除以 2 ,最後可以用長除法的結果把該數字(10進位)轉換成 2 進位的型式。
若過程有餘數出現,相當於有二進位的某位有 1 的出現。
0 (長除法) 出現餘數 1 次數: 0
1 (長除法) 出現餘數 1 次數: 1
2 (長除法) 出現餘數 1 次數: 1
3 (長除法) 出現餘數 1 次數: 2
4 (長除法) 出現餘數 1 次數: 1
5 (長除法) 出現餘數 1 次數: 2
6 (長除法) 出現餘數 1 次數: 2
7 (長除法) 出現餘數 1 次數: 3
因為數字被除2之後是計算是之前有過的,也就是重複計算。
舉個例好懂:
像 7 / 2 = 3 … 1 , 後面還要拿 3 繼續長除法,但 3 的計算是出現過的,可以直接取 3 的答案,所以奇數就是整除2的答案+1,而偶數是整除2的答案。

Python(計數)

class Solution:
def countBits(self, num):
result = [0]
i = 0
while True:
for number in range(len(result)):
if i == num: return result
result.append(result[number] + 1)
i += 1

Python(奇偶)

class Solution:
def countBits(self, num: int) -> List[int]:
result = [0]
memo = {0:0}

def ones(number):
if number in memo: return memo[number]
ans = number % 2 + ones(number//2)
result.append(ans)
memo[number] = ans

for number in range(num+1):
ones(number)

return result

C#(奇偶)

public class Solution {
public int[] CountBits(int num) {
var result = new List();
result.Add(0);
for(int i = 1; i <= num; i++)
{
result.Add(result[i/2] + i % 2);
}
return result.ToArray();
}
}

Leetcode題解 Python & C#:五月挑戰DAY27 Possible Bipartition

有 N 個人(編號 1, 2, …, N),要依照 dislikes 去分成二組,在 dislikes 中,每組 [a, b] 代表該編號的二人不能在同一組。
回傳 True 如果可以順利分成二組,否則 False。
這題蠻有意思的,我第一念頭想到 backtrace 可以硬解,但太慢,我放棄用 DP 與 遞迴函數。
說到兩兩成對,就想到最近的表格式DP,這題一樣可以用表格式DP,最後用 DFS 的遞迴函數可以比 backtrace 快上不少。
只要表格上可以選成每列每欄都只有一組配對,那就有可行的分組,也就是True。
抛開遞迴,因為平面化的話,一有答案可以馬上回傳,而遞迴函數會一路收斂到最上方,才能停止。要是深度深,會多花上一些時間。
被自我侷限,得用別的方法來。

如果 [a,b] 是 [1, 3],代表 1, 3 不能同組。這個分組沒有順序,也就是 組1、組2 對調是沒差的。
但如果用人的思維去硬分,肯定要選一組當基準去分配,就會產生順序的問題。
[1,3],[2,4],[3,4],答案是 True 分成 [1,4],[2,3] 。但如果都選 組1 當基準,輪到 [2,4] 分配的時候,要是組別分成 [1,2],[3,4],
那下一個遇到分[3,4],就會無法依規則分,然後回傳 False 。
要是換個順序 [1,3],[3,4],[2,4],選基準去分,就會得到一組可行的,回傳 True。
[1,3] 後面接 [3,4] ,頭尾相接,可以想是一條討厭鏈,不同討厭鏈之間任意分配是沒關係的,但在同一條就該按順序來,如果可以串起來,就該優先分配。
依題目所限,dislikes 已經有排列過,所會先拿到討厭鏈的頭,想辦法後面依序先分配。
如果開頭是 [1,3] ,下一個要以 3 為開頭,[3, 4],再以 4 開頭,再以 2 開頭,這不是一個有固定順序的,但要把 N 個開頭都找過。
討厭鏈一定要順利分完,依討厭鏈去分配是很簡單的,二人不要在同一組,分不下去就一定是回傳 False。
首先全部人都先在 組1 ,如果 [a, b] 都在 組1,把 b 移到 組2。如果 [a,b] 都在 組2 ,即無法分,回傳 False。
(用這思考看看:N = 3, dislikes = [[1,2],[1,3],[2,3]])
要依照討厭鏈順序去分的方法︰
用一個 set(1,2,…,N) ,跟 stack,遇到數字就從 set 中取到 stack。若 stack 為空,就從 set 任取一個放入stack,直到把 set 取光。

Python

class Solution:
def possibleBipartition(self, N: int, dislikes: List[List[int]]) -> bool:

memo = defaultdict(list)
for me, you in dislikes:
memo[me].append(you)
memo[you].append(me)

group1 = set(range(1,N+1))
group2 = set()
candidate = set(range(1, N+1))
stack = []

while candidate:
stack.append(candidate.pop())
while stack:
i = stack.pop(0)
for hate in memo[i]:
if i in group1 and hate in group1:
group1.discard(hate)
group2.add(hate)
elif i in group2 and hate in group2:
return False
if hate in candidate:
stack.append(hate)
candidate.discard(hate)

return True

C#

public class Solution {
public bool PossibleBipartition(int N, int[][] dislikes) {
var relation = new Dictionary>();
var group1 = new HashSet();
var group2 = new HashSet();
var candidate = new HashSet();
for(int i = 1; i <= N; i++)
{
relation[i] = new HashSet();
group1.Add(i);
candidate.Add(i);
}
for(int i = 0; i < dislikes.Length; i++)
{
relation[dislikes[i][0]].Add(dislikes[i][1]);
relation[dislikes[i][1]].Add(dislikes[i][0]);
}
var queue = new Queue();
while(candidate.Count > 0)
{
queue.Enqueue(candidate.First());
candidate.Remove(candidate.First());
while(queue.Count > 0)
{
int head = queue.Dequeue();
foreach(int node in relation[head])
{
if(group1.Contains(head) && group1.Contains(node))
{
group1.Remove(node);
group2.Add(node);
}
else if(group2.Contains(head) && group2.Contains(node))
{
return false;
}
if(candidate.Contains(node))
{
candidate.Remove(node);
queue.Enqueue(node);
}

}
}

}
return true;
}
}

Leetcode題解 C#:五月挑戰DAY26 Contiguous Array

給一個二進位串列,回傳 0 與 1 數量相等的連續子陣列的最長長度。
這次補寫 C# 版的 code。

C# 
public class Solution {
public int FindMaxLength(int[] nums) {
int result = 0;
int counter0 = 0;
var dp = new Dictionary();
dp.Add(0,-1);
for(int i = 0; i < nums.Length; i++)
{
counter0 += nums[i] == 0 ? 1 : -1;
if(dp.ContainsKey(counter0))
result = Math.Max(result, i - dp[counter0]);
else
dp[counter0] = i;
}
return result;
}
}

Leetcode題解 Python & C#:五月挑戰DAY26 Uncrossed Lines

給兩個數列 A B 分別寫成上下平行,如果 A[i] == B[j] ,那可以用一直線相連,要找出最大沒有交叉的連線數。
做完上一個 contest ,再看這個問題,很自然就會以相似的解法去進行。
這裡有一個特色「兩數列,各取一個兩兩配對,配對後不能重複再用到。」這樣想到可能會有一個表格狀的DP,再應題目做處理。
這題也是這樣。
DP[i][j] 定為該位置上的最大的沒交叉的相連數。
如果位置 A[i] == B[j] ,那 DP[i][j] = 1 再加右下方 DP[i+1][j+1] ,即加上後方的最大沒交叉相連數。
然後 DP[i][j]  = 從自身、右、下三者取最大值,這樣 DP[i][j] 會是最大的沒交叉的相連數。
最後回傳 DP[0][0] 即可。

Python

class Solution:
def maxUncrossedLines(self, A: List[int], B: List[int]) -> int:
from functools import lru_cache
ia, ib = 0, 0
na, nb = len(A), len(B)

@lru_cache(None)
def dfs(ia, ib):
if ia > na - 1 or ib > nb - 1:
return 0
ans = 1 if A[ia] == B[ib] else 0
ans = max(ans + dfs(ia+1, ib+1), dfs(ia+1, ib), dfs(ia, ib+1))
return ans

return dfs(0, 0)

C#

public class Solution {
public int MaxUncrossedLines(int[] A, int[] B) {
int[,] dp = new int[A.Length, B.Length];
dp[A.Length-1, B.Length-1] = A[A.Length - 1] == B[B.Length - 1] ? 1 : 0;
int lastNum = B[B.Length - 1];
for(int i = A.Length - 2; i >= 0; i--)
{
if(A[i] == lastNum)
dp[i, B.Length-1] = 1;
else
dp[i, B.Length-1] = dp[i+1, B.Length-1];
}
lastNum = A[A.Length - 1];
for(int j = B.Length - 2; j >= 0; j--)
{
if(B[j] == lastNum)
dp[A.Length-1, j] = 1;
else
dp[A.Length-1, j] = dp[A.Length-1, j+1];
}

for(int i = A.Length - 2; i >= 0; i--)
{
for(int j = B.Length - 2; j >= 0; j--)
{
dp[i, j] = dp[i+1, j+1] + (A[i] == B[j] ? 1 : 0);
dp[i, j] = Math.Max(dp[i, j], dp[i+1, j]);
dp[i, j] = Math.Max(dp[i, j], dp[i, j+1]);
}
}
return dp[0, 0];
}
}

Leetcode題解 Python:Max Dot Product of Two Subsequences

給兩個陣列 nums1 和 nums2。回傳兩陣列中非空子序列(皆相同長度)的最大的點積(Dot Product)。
子序列可以是以原陣列刪除部分(也可以不刪),但元素的相對順序不變。
dot product的運算:
假設從 nums1 跟 nums2 取出 A 序列 跟 B序列。
A1、A2、A3
B1、B2、B3
=> A1*B1 + A2*B2 + A3*B3 => value
我又沒在時限內做完contest,QQ。
這題使用 DP 的觀念,用 p1 p2 代表兩陣列的點乘位置並加上後續的最大值,同時也是規劃 DFS 的遞迴函數。
由於不能有空值,一定要有個兩位置相乘作為開頭。即 nums1[p1] * nums2[p2] 。
面對 DFS 的設計,要從底開始想,也就是到底的情形。即 nums1[len(nums1)-1] * nums2[len(nums2)-1]。因為不能為空串列,該位置只有 nums1[p1] * nums2[p2] 的可能。
DP[len(nums1)-1][len(nums2)-1] ==  nums1[len(nums1)-1] * nums2[len(nums2)-1]
往上回傳,上面會是 p1-1 或是 p2-1 的,這樣不是 p2 會被重複乘到,不然就是 p1 。有共用的情況下,應該要取最大的可能,自身點乘跟下一個(下方、右方)取最大值,然後決定當前位置的 DP 值。
重點來了,那要如何再取更多,也就是要變成多組的情況。
每一對 p1, p2 都是一個點乘,其左上一個 p1-1,  p2-1 。
對 p1-1,  p2-1 來說,除了乘積外,還要加上 DP[p1][p2]的值,也就是後面的最大可能,如果後方最大值小於零,則不加。
這樣就能用到多組,從多組中找出最大的數值。
 
整理一下順序。
先算出 nums1[p1] * nums2[p2] ,視狀況加上 DP[p1+1][p2+1] 的值,成為該位置的基礎值。
跟下方(共用一個)的取最大值,即 DP[p1][p2] = max(DP[p1][p2], DP[p1+1][p2], DP[p1][p2+1])。

Python

class Solution:
def maxDotProduct(self, nums1, nums2) -> int:

from functools import lru_cache
n1, n2 = len(nums1), len(nums2)
@lru_cache(None)
def dfs(p1, p2):
if p1 > n1-1 or p2 > n2-1:
return 0
ans = nums1[p1] * nums2[p2]
if p1 < n1 - 1 and p2 < n2 - 1:
ans += max(dfs(p1+1, p2+1), 0)
if p1 < n1 - 1:
ans = max(ans, dfs(p1+1, p2))
if p2 < n2 - 1:
ans = max(ans, dfs(p1, p2+1))
return ans

return dfs(0,0)

Leetcode題解 Python & C#:五月挑戰DAY24 Construct Binary Search Tree from Preorder Traversal

給一個以「Preorder」得到的值陣列,重構出二元樹,並回傳 root 。
這是一個嚴謹的二元樹,所以左邊子樹皆小於自身,右邊皆大於自身。
preorder 是以 自身 → 左 → 右 ,因此陣列的第一個元素會是root。
接著下一個會是左邊第一個,直到比root大。
第一個比root大的是右方第一個,直到最後。
因此可用從陣列中篩選(比第一個)大小,並把 solution 當遞迴函式用,完成二元樹的重構。
(因為本題重複過,所以更多說明請點這裡。)

Python

class Solution:
def bstFromPreorder(self, preorder: List[int]) -> TreeNode:
if not preorder: return None
node = TreeNode(preorder[0])
node.left = self.bstFromPreorder([v for v in preorder if v < node.val])
node.right = self.bstFromPreorder([v for v in preorder if v > node.val])
return node

C#

public class Solution {
public TreeNode BstFromPreorder(int[] preorder) {
if(preorder.Length == 0){return null;}
TreeNode node = new TreeNode(preorder[0]);
node.left = BstFromPreorder((from num in preorder where num < preorder[0] select num).ToArray());
node.right = BstFromPreorder((from num in preorder where num > preorder[0] select num).ToArray());
return node;
}
}

Leetcode題解 Python & C#:五月挑戰DAY23 Interval List Intersections

要找出 A 與 B 的區間,因為不一定會按順序交疊,所以用 indexA , indexB 來記錄目前 Traverse 的位置。
如果有交疊,範圍會是從二者的左側取大,右側取小的範圍。
那要怎麼移動 index ? 
如果有一個的右側很大,對方的下一個右側可能還沒超過它。因此,此時應該是要繼續移動右側較小的。

Python
class Solution:
def intervalIntersection(self, A: List[List[int]], B: List[List[int]]) -> List[List[int]]:
ia = ib = 0
na = len(A)
nb = len(B)
result = []
while ia < na and ib < nb:
if A[ia][1] B[ib][1]:
pass
#continue
else:
l = max(A[ia][0], B[ib][0])
r = min(A[ia][1], B[ib][1])
result.append([l, r])
if A[ia][1] < B[ib][1]:
ia += 1
else:
ib += 1
return result

C#

public class Solution {
public int[][] IntervalIntersection(int[][] A, int[][] B) {
int idxA = 0;
int idxB = 0;
var result = new List();
while(idxA < A.Length && idxB < B.Length)
{
int l = Math.Max(A[idxA][0], B[idxB][0]);
int r = Math.Min(A[idxA][1], B[idxB][1]);
if(l <= r)
{
result.Add(new int[2]{l, r});
}
if(A[idxA][1] <= B[idxB][1])
{
idxA += 1;
}
else
{
idxB += 1;
}
}
return result.ToArray();
}
}

Leetcode題解 Python & C#:五月挑戰DAY22 Sort Characters By Frequency

給一個字串,要以字母的出現次數重新排列。
首先要有可以統計字母的方式,用 hash table 可以很容易記錄哪些字的有出現與其次數。
python 有 collections.Counter 可以快速轉換。

Python
class Solution:
def frequencySort(self, s: str) -> str:
counter = Counter(s)
result = ""
for each in sorted(counter.items(), key = lambda x: x[1], reverse = True):
result += each[0] * each[1]
return result

C#

public class Solution {
public string FrequencySort(string s) {
var counter = new Dictionary();
foreach(char c in s)
{
if(counter.ContainsKey(c))
{
counter[c] += 1;
}
else
{
counter[c] = 1;
}
}
var result = new StringBuilder();
foreach(var item in counter.OrderByDescending(x=>x.Value))
{
result.Append(item.Key, item.Value);
}
return result.ToString();
}
}

Leetcode題解 Python & C#:五月挑戰DAY21 Count Square Submatrices with All Ones

給一個由 0 1 組成的矩陣,回傳有多少個正方形全部都是 1 。
這題蠻容易的,就算用暴力法 + dp ,也不會被判超時。
如果本身是 1 ,自己能形成一個正方形,然後往四角延伸去看是否能構成更大的正方形。
用 y x 由上至下,由左至右,所以選右下作基準點。那麼能構成更大的正方形,是其左上、左、上都是同一個數字。
如構成 2 * 2:
[[1,1],
[1,2]] 
位置[1][1]的點,其左上、左、上都是 1 ,所以可以構成 2(1+1)。
3 * 3:
[[1,1,1],
[1,2,2],
[1,2,3]]
位置[2][2]的點,其左上、左、上都是 2 ,所以可以構成 3(2+1)。3 表示該位置有(1*1、2*2、3*3)的正方形。
位置[2][1]的點,其左上2、左1、上1,所以可以構成 2(1+1)。
也就是說,是左上、左、上中取最小值+1。前提也要該位置本來是 1 才可以這樣處理。

Python
class Solution:
def countSquares(self, matrix: List[List[int]]) -> int:
result = 0
cols = len(matrix[0])
for y in range(len(matrix)):
for x in range(cols):
if matrix[y][x] == 1:
if y - 1 >= 0 and x - 1 >= 0:
matrix[y][x] = min(matrix[y-1][x-1], matrix[y][x-1], matrix[y-1][x]) + 1
result += matrix[y][x]
return result

C#

public class Solution {
public int CountSquares(int[][] matrix) {
int result = 0;
for(int y = 0; y < matrix.Length; y++)
{
for(int x = 0; x < matrix[0].Length; x++)
{
if(matrix[y][x] == 1)
{
if(y >= 1 && x >= 1)
{
matrix[y][x] = 1 + Math.Min(matrix[y-1][x], Math.Min(matrix[y-1][x-1], matrix[y][x-1]));
}
result += matrix[y][x];
}
}
}
return result;
}
}

Leetcode題解 Python & C#:五月挑戰DAY20 Smallest Element in a BST

給一串二元樹,在找出第 k 個小的元素。
這題目給的二元樹是比較嚴謹的,子樹左邊皆小於自身,右邊皆大於自身。
因此,左邊最底層的是最小的,接著其父,其父之右子樹,這與「in-order」的 traversal 方式是一樣的。
所以用上這樣的方法,並且加入「第幾小的序數」就能得到題目所求。
對於二元樹的題目,各種traversal模式都要熟練,才會很快地想到寫法。

Python(遞迴函數)
class Solution:
def kthSmallest(self, root: TreeNode, k: int) -> int:

self.result = 0
def dfs(node, rank):
if not node: return rank
rank = dfs(node.left, rank) + 1
if rank == k: self.result = node.val
rank = dfs(node.right, rank)
return rank

dfs(root, 0)
return self.result

Python(官方)

class Solution:
def kthSmallest(self, root: TreeNode, k: int) -> int:

stack = []
while True:
while root:
stack.append(root)
root = root.left
root = stack.pop()
k -= 1
if k == 0: return root.val
root = root.right

C#

public class Solution {
public int KthSmallest(TreeNode root, int k) {
var stack = new Stack();
while(true)
{
while(!(root is null))
{
stack.Push(root);
root = root.left;
}
root = stack.Pop();
k--;
if(k==0){return root.val;}
root = root.right;
}
}
}