倉頡輸入法與打字手勢的一周學習歷程

約莫上周二晚間,我測驗我的中打速度,結果只有七十出頭,相當不滿意。再加上那時我的英打一直有按錯鍵的問題,於是想改正我的打字手勢。

渴望變厲害的性格作祟,喚醒心中強大的精力去改變習慣。

習倉頡並不是平易近人的,我發覺網路是有教材,但是幾乎都蠻舊的,說實在也是可用的。

先記憶字根,光是這點就蠻花費時間的。我建議可以在練習中記憶,這樣也可以幫助到拆解字型取碼。

這裡我是使用《五色倉頡試用版》,達到第一階段「記字根」的基本功。一直打,然後配合對照表去理解。

直到裡面的字已經熟練如何拆解。順利的話,一天就能打出許多字。

之後可以進行生活化的練打,不過一定會碰到不會拆解的字,就使用《Follow Me倉頡字典》查詢。


練習的時候,一定要把每一個不會的用查詢的字再打個幾遍,因為下一次還是會碰到,不會就還是得再查詢。

接著,此時一定要去注意字體組合,大部分的字碼輸入是相當有規則的,例如:「見」、「刂」、「頁」、「寺」,熟悉後,可快上一點。

一開始一定很不順手,集中注意力時,也要順便將字碼的位置用肌肉記憶下來,換句話說,就是得不看鍵盤就能找到字碼的位置。

最好連帶將標準打字手勢擺出來,記憶相對位置。這裡推薦使用倉頡之星,使用按鍵練習模式,練到8秒內可以不用看就能盲打即可。(我從別人的網誌下載的倉頡之星 )

我覺得不必瘋狂練打,一天兩小時就可以休息。也不用透過這些軟體練習到每分鐘三十字,生活常用打熟來,自然也就足以擔當你平時用的中文輸入法。

生活常見的詞彙要一定要去用空白鍵去看選詞的種類,一旦背下來,就能打字速度更上一層。

如果可以每分鐘十字以上,接下來的打字就不可以看鍵盤,太注意自己的手指有沒有按對,反而會拖累自己的打字速度。

只要靜下心,能慢慢按出對的字碼來就可以了。錯誤率小於三成就可以盲打階段。

「絕對不可以看鍵盤﹐因為你要把心思拿去思考如何拆字。」一旦做到,每分鐘又可以多打五個字。

我目前大約打字速度落在每分鐘十七字左右,還是會在每次練打使用字典去查拆解。

據這個網頁說,二到四周就能突破每分鐘三十字,如果有心學習就可以。

最近用上番茄鐘,這兩天開始記錄,如果速度呈直線增長,大概可以在第二周時進入到每分鐘三十字,但可能會早一兩天也說不定。

Leetcode題解 Python & C#:四月挑戰DAY21 Leftmost Column with at Least a One

給一個 BinaryMatrix 類別的物件,該物件有個由 0, 1 組成的二維串列屬性,要找出至少有個1的欄位最小索引,沒有則回傳-1。

該物件有兩個方法,一 get(),只能透過此方法去取得特定位置上的值。二 dimensions(),此方法會回傳列欄大小。

每列的值有經過排序(由小至大)。

如果呼叫 get() 超過1000次,也算失敗。

欄列的大小不超過100。

開始思考要如何找出目標值,除了每排有經過排序,但每欄並沒有提及。

因此除非知道每排最早出現 1 的位置,不然也暫時沒想到更好的方法。

get() 次數限制之下,每排要在 10 次內找出第一個 1 出現位置,最糟要在一百個已經排序的元素中搜查。

使用二分搜查法,50 > 25 > 13 > 7 > 4 > 2 > 1 ,最糟也能在七次內找到第一個 1 的位置。 萬一沒有,索引也會在等於欄數。這樣也能在限制內完成搜查。

Python

class Solution:
def leftMostColumnWithOne(self, binaryMatrix: 'BinaryMatrix') -> int:
rows, cols = binaryMatrix.dimensions()
memo = []
for row in range(rows):
l, r = 0 , cols
while l < r:
m = (r-l)//2 + l
if binaryMatrix.get(row, m) == 0:
l = m + 1
else:
r = m
memo.append(r)
return min(memo) if min(memo) < rows else -1

C#

class Solution {
public int LeftMostColumnWithOne(BinaryMatrix binaryMatrix) {
int rows = binaryMatrix.Dimensions()[0];
int cols = binaryMatrix.Dimensions()[1];
// 使用 Binary search,取得每行上最小的1位置
int minPos = cols;
int r,m,l;
for(int i = 0; i < rows; i++)
{
l = 0;
r = cols;
while(l < r)
{
m = (r-l)/2+l;
if(binaryMatrix.Get(i,m)==0)
{
l = m + 1;
}
else
{
r = m;
}

}
minPos = r < minPos ? r: minPos;
}
return minPos < cols ? minPos: -1;
}
}

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

給一串依 preorder 順序遍歷二分樹的值,要還原成二分樹,並回傳根。

這二分樹是嚴格的那種,代表左邊子樹皆小於自身,右邊皆大於自身。

依照 preorder 的順序,先自身,再左邊,再右邊。還原也依此進行。

說到要依順序還原,不管是哪種,都要有保留父節的規劃,因為子節是沒辦法往上回溯的。(或者使用遞迴函數)

※一開始先將 root 生成,最後要回傳它。

這裡使用 stack 的概念來保存父節,一旦目前迴圈的值小於 stack[-1] ,那就添加至左邊,並加入該節至 stack。

如果該節大於 stack[-1],則檢查 stack[-2] 是否小於目前的值,是則不斷刪去 stack[-1] 直到 stack[-2] 大於目前的值或 stack 只剩一個。

然後把該節指派給 stack[-1].rihgt 後刪除 stack[-1],並添加該節至 stack 中。

這做法類似實際 preorder 的途徑。

Python3

class Solution:
def bstFromPreorder(self, preorder: List[int]) -> TreeNode:
root = TreeNode(preorder.pop(0))
stack = [root]
while preorder:
node = TreeNode(preorder.pop(0))
if node.val < stack[-1].val:
stack[-1].left = node
stack.append(node)
else:
while len(stack) >= 2 and stack[-2].val < node.val:
del stack[-1]
stack.pop().right = node
stack.append(node)
return root

C#

public class Solution {
public TreeNode BstFromPreorder(int[] preorder) {
TreeNode root = new TreeNode(preorder[0]);
List stack = new List{root};
for(int i = 1; i < preorder.Length; i++)
{
TreeNode node = new TreeNode(preorder[i]);
if(node.val < stack[stack.Count-1].val)
{
stack[stack.Count-1].left = node;
stack.Add(node);
}
else
{
while(stack.Count>=2 && stack[stack.Count-2].val < node.val)
stack.RemoveAt(stack.Count-1);
stack[stack.Count-1].right = node;
stack.RemoveAt(stack.Count-1);
stack.Add(node);
}
}
return root;
}
}

除了 stack ,也可以使用「遞迴」解決。
Python 搭配二分搜查

class Solution:
def bstFromPreorder(self, preorder: List[int]) -> TreeNode:
if not preorder: return None
def nextNode(l, r):
if l==r: return None
node = TreeNode(preorder[l])
l2, r2 = l+1, r
while l2 < r2:
m = (r2-l2)//2 + l2
if preorder[m] < node.val:
l2 = m+1
else:
r2 = m
node.left = nextNode(l+1, l2)
node.right = nextNode(l2, r)
return node
return nextNode(0, len(preorder))

C# Linq篩選

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#:四月挑戰DAY19 Search in Rotated Sorted Array

今天有C#,因為我看蠻多ASP的工作缺,練習ASP也得加強C#。

給一串數組,經過排序但旋轉過(向右移動?次),且每個數字不重複,並且要求在時間複雜度 O(log n) 解決。

最直覺地使用 for 迴圈,但時間複雜度會是 O(n) ,不符合要求。

說到 O(log n) ,二分搜尋是最典型的方法。用兩次二分搜尋,一次是找到排序的分界(最大值),二是從分界往左或右找到目標值。O(log 2n) => O(log n)

設左界 l 右界 r,中間為 m = (r-l)/2 + l 。第一次找將比對目標 t 先設為 nums[0] ,如果 nums[m] > t ,更新 t 並移動左界 l = m+1,否則不更新 t 但移動右界 r = m。

直到 l == r,此時為 nums[l] or nums[r] 都是最大值。

若目標值 target 小於 nums[0] ,表示目標值在最大值右方,否則在最大值左方。針對情況移動右界至nums的長度或左界至0。

再用一次二分搜尋法,這次的比對目標就是 target,當 l 等於 r 時沒有找到就回傳 -1 ,其餘為當下的索引 m 。


PYTHON3

class Solution:       
def search(self, nums: List[int], target: int) -> int:
if not nums: return -1
maxNum = nums[0]
n = len(nums)
l, r = 0, n
while l < r:
m = (r-l)//2 + l
if nums[m] >= maxNum:
maxNum = nums[m]
l = m+1
else:
r = m
#
if target < nums[0]: r = n
else: l = 0
#
while l < r:
m = (r-l)//2 + l
if nums[m] == target:
return m
elif nums[m] > target:
r = m
else:
l = m + 1
return -1

C#

public class Solution {
public int Search(int[] nums, int target) {
int r = nums.Length;
if(r==0){return -1;}
int l = 0;
int maxNum = nums[0];
// 先找出最大值的位置
while(l < r){
int m = (r-l)/2+l;
if(nums[m] >= maxNum){
maxNum = nums[m];
l = m+1;
}else{
r = m;
}
}
//從中間開始找
if(target < nums[0]){
r = nums.Length;
}else{
l = 0;
}
//
while(l < r){
int m = (r-l)/2+l;
if(nums[m] == target){
return m;
}else if(nums[m] < target){
l = m+1;
}else{
r = m;
}
}
return -1;
}
}

Leetcode題解 Python:四月挑戰DAY18 Minimum Path Sum

給一串二維數列(格線),每個元素為非零整數,路徑只能往右或往下,找出從左上角到右下角的最短距離。

一看到往右或往下的二種可能性,DP就蠢蠢欲動,而且同個位置上的最短距離是固定的,完全可以使用DP去解。

dfs遞迴函數保存兩個訊息 y, x 記錄回傳值。自身加往下或住右較小的,即當前位置的最小距離。

如果到達邊界,由於 min() 不能比較 int 與 None , 因此得針對邊界的下個距離做處理,這裡使用 [] 把下個距離放入串列再做比較。

class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
rows, cols = len(grid), len(grid[0])

from functools import lru_cache
@lru_cache(None)
def dfs(y,x):
if y == rows or x == cols:return []
d2 = dfs(y+1, x) + dfs(y, x+1)
distance = grid[y][x] + (min(d2) if d2 else 0)
return [distance]

return dfs(0,0)[0]

又或者迴圈所有位置,每個位置加上左方與上方的最小值,等到迴圈結束,此時右下角為最短距離。

Leetcode題解 Python:四月挑戰DAY17 Number of Islands

給一個二維串列,”1″代表是陸地,”0″則為水,周圍(超出範圍)都是水,每個不相連的陸地為一塊島,回傳該二維串列有幾塊島?

這題若用迴圈,要如何判定當前遇到的”1″是獨立的島嶼?是否曾經有找過?

使用 memo 去記錄已經搜尋過的”1″,並且遇到”1″的時候用遞迴函數再往四周找出所有鄰近的”1″

class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
if not grid: return 0
landNums = 0
memo = set()
rows, cols = len(grid), len(grid[0])

def search(y,x):
if y == rows or y < 0 or x == cols or x < 0:
return
if (y,x) in memo: return
else: memo.add((y,x))
if grid[y][x] == "1":
search(y-1,x)
search(y+1,x)
search(y,x-1)
search(y,x+1)

for y in range(rows):
for x in range(cols):
if (y,x) not in memo and grid[y][x] == "1":
search(y,x)
landNums += 1

return landNums

如果想省點空間,把傳入的二維串列當成 memo 使用也是一種選擇,還能稍微加速。

Leetcode題解 Python:四月挑戰DAY16 Valid Parenthesis String

給一字串,只有「 ()* 」組成,( 的右邊要對應一個 ),而 * 可以是 ( 或 ) 或 “”,如果()都有對應則回傳True;否則False。

這題前身是只有 () 的條件,只需要計 ( 與 ) 的數量。

多了一個 * 有三種可能性,光是看到這個條件就直覺想到用DP。

這裡嘗試用計數的方法︰

若 ( 與 * 的數量加總小於 ) ,則可以中途回傳False。

接下來要考慮 **(() 與 ((**) 要如何抓出剩餘沒有配對到的 ( 。

同樣遇到 ( 而計數加一,有沒有能忽略情況一前面的 * 而遇到 ( 時才開始計數?而且情況二也能適用?

如果先 ( 後 *,* 可抵 ( 的計數,如果不夠,( 的計數最小為 0 ,當作 * 為 “” 出現。

那如果是情況二,那 ( 計數被 ) 抵消到 -1 該怎麼看?由於在一開始的條件判斷下已經不會有 ) 找不到左邊對的情況,因此可視為遇到 * 來處理。

整理一下有兩種計數:
1. ( 和 * 的加總去抵消 ) , 一旦小於 0 即 False。
2. ( 去抵消 * 和 ),最小值為 0,最後若有剩,代表 ( 找不到右邊可對。

class Solution:
def checkValidString(self, s: str) -> bool:
c1, c2 = 0, 0
for c in s:
c1 += 1 if c != ')' else -1
c2 += 1 if c == '(' else (-1 if c2 else 0)
if c1 < 0: return False
return c2 == 0

Leetcode題解 Python:四月挑戰DAY15 Product of Array Except Self

給一串數列,回傳一串除了對應[i]以外的元素乘積數列。

如果要在空間複雜度 O(1) 完成,那麼只能透過修改傳入的數列。不過也有說回傳值不算在內。

最險惡的地方在於 0 的出現,否則直接用全部元素乘積除每個就好。除 0 會發生錯誤,所以要避開有 0 的情況。

列舉所有情況有三種︰
1. 0 出現兩次以上︰全部為 0 。
2. 0 出現一次︰只有 [i] == 0 為總乘積,其餘為 0。
3. 沒有 0 出現︰總乘積除[i]。

遍歷一次,取得非0元素的所有乘積與 0 出現次數,然後針對三種情形決定回傳值。

class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
product = 1
count = 0
for num in nums:
if num: product *= num
else: count += 1
if count >= 2: return [0 for _ in nums]
elif count == 1: return [0 if num else product for num in nums]
return [product // num for num in nums]

Leetcode題解 Python:四月挑戰DAY14 Perform String Shifts

給一個字串 s,已經一串指令 shift,指令為 [direction, amount] 代表向 direction 轉 amount 次,direction {0(左), 1(右)}。

由於只需要最終結果,而且左右又可以抵銷,因此不需要傻傻跟著每個指令轉,只需要統計最後要轉哪個方向幾次就好。

如果向同個方向轉 s長度 次,就等於轉一圈,因此統計次數可以取 len(s) 餘。

若向右轉 t 次,在位置[len(s)-t]之後的字母會跑到前方,在前方的字母會移到後方,可以寫成 s[len(s)-t:] + s[:len(s)-t]

也可以簡化成 s[-t:] + s[:-t] 。

class Solution:
def stringShift(self, s: str, shift: List[List[int]]) -> str:
right = 0
for each in shift:
right += each[1] if each[0] else -each[1]
right %= len(s)
return s[-right:] + s[:-right]

Leetcode題解 Python:Regular Expression Matching

這裡的表達式的特殊符號只有 “.”, “*” 所以蠻簡化的。

想到正則表達式,如果能夠 import re,那絕對相當地快速。用 re 的函式就不需要去寫判斷了。

今天拋棄 re,這題限制居多,輸入字串不能局部配對,一定要全部吻合。難度會比真的re運作還簡單不少。

在我心目中,不能只是解題,還要符合 re 的運作模式,才是我要的解法。

但還是先解題優先吧。

首先有 si 跟 pi,代表兩個字串的索引,兩者都從 0 找起,如果 si 到底(等於s長度),而且也 pi 到底,代表 s 跟 p 全部匹配。

說到特殊符號,”.”相對容易處理,這裡就不提了,不過”*”就困難一些,”*”代表前面字符可以出現 0或1次或1次以上,因此產生了許多可能性。

由於”*”僅影響前面的字符,所以當我們在匹配 s[si] 與 p[pi] 時,要考慮 p[pi+1]是否為”*”,同時也要小心是否 pi+1 超出邊界,以免p[pi+1]報錯。

當後面是”*”時,匹配有三種變化,分別對應 0 或 1次 或 1次以上 的情形
第一種出現0次,si保持不到,而pi直接+2跳過”*”,繼續遞迴下去。
第二種出現1次,若有匹配到,si+1 pi+1,往深遞迴。
第三種出現多次,若有匹配到,si+1 pi不變,往深遞迴。

由於”*”使得可能性開枝散葉變多,而造成重複計算,而且每組 si pi都只會有一種結果,因此可以使用dp去減少計算時間。(這裡使用lru_cache)

這讓我更了解dp的使用時機:會重複計算,而且相同參數只有會一種結果。

接下來要考慮例外,如果 (s,p) = (“”,””)、(“”,”.*”)、(“a”,””)、(“”,”b”),這些情況下能順利解決?

這裡先看到 si == len(s),搜尋到了底,這時要提防 p 是否還有長度可以搜尋,如果剩下”.*”要如何解決。

當 si == len(s),代表只剩 “” 可以拿來比較,如果剩餘的p可以匹配””字串,那代表兩者相同。

而p能匹配空字串的,只有p[pi]後面是”*”。

空字串只會匹配後面有”*”的第一種情形,於是pi+2。如果剛好是”.*”也能匹配到,但匹配到的結果會使 si +1 而往下會使得 si > len(s),超出範圍就不用匹配。

這樣子寫,也不需要在開頭做例外處理。

class Solution:
def isMatch(self, s: str, p: str) -> bool:
from functools import lru_cache
n, n2 = len(s), len(p)
self.res = False

@lru_cache(None)
def nextMatch(si, pi):
if si > n or pi >= n2:
if si == n and pi == n2: self.res = True
return False
elif si == n:
c = ""
else:
c = s[si]
#
ch = p[pi]
# 如果後面是 "*" 字,分別採取不同搜尋
if pi+1 < n2 and p[pi+1] == "*":
if c == ch or ch == ".":
nextMatch(si+1, pi) # 原地不動
nextMatch(si+1, pi+2) # 或向前走
nextMatch(si, pi+2) # 當作沒有配對到
else:
# 後面沒有 *
if c == ch or ch == ".":
nextMatch(si+1, pi+1)

nextMatch(0, 0)
return self.res

Leetcode題解 Python:Pizza With 3n Slices

一個pizza切成3n片,每片面積大小不同,每片面積當作一串數列傳入。當你選一片之後,上下兩片會被朋友拿走,請問能拿到的最大面積的為何?

這是一道難題,能解出來的不多。

對於這個 pizza 3n 片這般的難題,除了學會解法之外,更應該去探索解題思路。

暴力解法是很容易,有 3n 片,會有 3n 種取法,3n 種取法後面會衍生出 3n-3 種的取法,如此到底,時間複雜度會是 O(N**N),所以超過10片就會跑到超時。

不僅運算時間久,衍生出的各種可能也需要保存,空間複雜度也是很大,即便用上 backtrace 降低記憶體使用量,時間複雜度的問題依舊沒有解決。

得用「空間換取時間」,那麼空間要保存甚麼?對,這就是動態規劃的問題。

有沒有甚麼辦法,可以保存各種pizza的拿取情形?參考

若把pizza攤成一排,000000 代表尚未拿取任何一片,100000 代表拿取了第一塊,下一塊不能拿取第一塊鄰近,因此只能選[2]與之後的位置。※注意※Pizza是圓的!

如此類推,只要兩個1不相鄰即可,於是會有 101000、100100、100010、……的拿法,不過這樣還是太多了。

但是繼續往深想,如果拿了第一片,而剩下的組合是固定的,只會出現一種最大取法,不需要分開討論。

於是當前要保存的空間可以推演成 dp[pos][times] ,pos是當前位置,times目前是拿取的次數。

接下來講遞迴函數,經過前面的發想,會是深度優先的dfs,從底搜到頭。

dfs(pos, tiems),而 dfs(0, 0) 是起點,也代表從0開始取n片的最大值,所以回傳值為當前最大值。

如果還能夠取,就比對 slices[pos] + dfs(pos+2, tiems+1), dfs(pos+1, times),看誰大取 max()。

如果不能取,當前最大值即 dfs(pos+1, times) 亦作 dfs(pos+1, n) 。

到達最深處,一旦 pos == 3n 或是 times == n,代表無法再取,回傳 0,這要寫在前方。

基本架構完成,問題點來了,前面用※注意※提示Pizza是圓的,萬一同時取到第1片跟最後1片怎麼辦?

當遞回到深處的時候,是無法知道前面是否有取到第一片,那麼最後1片可取或不可取便是疑問。

這有兩個策略,1是分別考慮,從第一片取到第3n-1片與第2片取到第3n片,這裡就不會出現第一片跟最後一片都取到的狀況。2是讓最後一片絕對不取到,甚麼情況下絕對不會取到最後一片,如果最後一片是最小的那塊。

為求簡潔好懂,所以採取2。

class Solution:
def maxSizeSlices(self, slices):
memo = dict()
sl = len(slices)
n = sl//3

minSliceIdx = slices.index(min(slices))
slices = slices[minSliceIdx+1:] + slices[:minSliceIdx+1] # 把最小片的移到最後一塊

def maxSize(pos, times):
if pos >= sl or times == n: return 0
if pos in memo and times in memo[pos]: return memo[pos][times]
else:
if pos not in memo: memo[pos] = {}
if times not in memo[pos]: memo[pos][times] = 0

if times < n:
score = max(slices[pos] + maxSize(pos+2, times+1), maxSize(pos+1, times))
else:
score = maxSize(pos+1, times)
memo[pos][times] = score
return score

return maxSize(0, 0)

做了幾次題目之後,對於DP要建立多維字典實在感到厭倦,如果Python要快速建立多維字典,用 collections.defaultdict 會省下不少時間。

class Solution:
def maxSizeSlices(self, slices):

sl = len(slices)
from collections import defaultdict
memo = defaultdict(dict)
minSliceIdx = slices.index(min(slices))
slices = slices[minSliceIdx+1:] + slices[:minSliceIdx+1] # 把最小片的移到最後一塊

def maxSize(pos, remain):
if pos+remain*2-1 >= sl or remain == 0:
return 0
elif remain in memo[pos]:
return memo[pos][remain]
else:
memo[pos][remain] = max(slices[pos] + maxSize(pos+2, remain-1), maxSize(pos+1, remain))
return memo[pos][remain]
return maxSize(0, sl//3)

Leetcode題解 Python:四月挑戰DAY13 Contiguous Array

這題有難度,礙於我現在的時間管理策略,若沒能在時間內想到解法,就去找別人的理念講解,然後自己突破。參考

給一串數列,只有 0, 1,回傳 0 與 1 數量相等的子串列的最大長度。

0 與 1 數量相等,第一直覺就是要想到如何計數。如果不帶技巧,暴力解法需要 O(n**3) 時間解決。

如果要減少時間,那麼「用空間換取時間」的想法就可以派上用場,那麼空間要記錄些甚麼呢?

如果一個子串內的 0, 1 數量相等,總和會是長度的一半。也許派得上用場。(但沒有用)

先假設個 000111 ,若走到[2],0會是 3 個,走到[4],遇到 1 才會出現第一個合格長度 2。再遇到 1 變成長度 4,再遇到 1 變成長度 6。

由於 0、1 一定要都出現才會有長度,出現整排1跟0都不會出現合法長度,因此跟 0 與 1 數量差相關,數量差可以為 0,為 1,為 -1,這裡先繼續探索「數量差」是否能用。

0、1數量差為0,第一次出現在位置[-1],也就是起始值。下次 數量差0 出現在位置[5],因此 5 – (-1) = 6。數量差看起來是有用的。

若第一次紀錄後出現同樣的數量差,其中一定出現成對的 0、1。長度多少,因為只需要取最大長度,因此只要保留第一次出現的位置即可。

於是空間拿去紀錄各 0、1數量差 的出現位置。

回頭看 000111 走到 [3] 的時候,此時 0與1數量差 為2,上次出現 2 的位置為[1],長度為 3 – 1 = 2。

class Solution:
def findMaxLength(self, nums: List[int]) -> int:
memo = {0:-1} # 在 -1 位置,數量差一定為0
counter0 = 0
maxLength = 0
for i in range(len(nums)):
if nums[i] == 0: counter0 += 1
else: counter0 -= 1
if counter0 not in memo:
memo[counter0] = i
else:
maxLength = max(maxLength, i - memo[counter0])

return maxLength