Leetcode題解 Python & C#:六月挑戰DAY9 Is Subsequence

給字串 s 和字串 t,找出 s 是否為 t 的子順序。子順序是相對位置不變,但中間可以刪減部分字元。

 這類的題目在LeetCode有不少,但變化性少,只會這個學會了,就能解部分類似的。
依 Follow up 的方式,我猜,要從 s 的組成字元逐一找該字元是否在 t 裡面,但我不是很懂它想表達的意思。
但沒關係,我的解法也是逐一去找的運作模式。
既然子順序的中間可以塞入非配對的字元,那可想成把那些字元拿掉後,剩下的字串跟 s 能全配對那就是 True。
從 t  的左至右搜查,如果匹對到 s[0] ,那就只能目前從 t 剩下的去匹配 s[1] ,中間沒匹配的都是可以拿掉的字元。
當 len(s) == 0 的時候,即為全匹配。如果還有 s 的字元但 t 已經到最後,那就沒有成功匹配。

Python
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
if not s: return True
for i, c in enumerate(t):
if c == s[0]:
return self.isSubsequence(s[1:], t[i+1:])
return False

C#

public class Solution {
public bool IsSubsequence(string s, string t) {
int n = s.Length;
if(n == 0){return true;}
int si = 0;
foreach(char c in t)
{
if(c == s[si])
{
si += 1;
if(si == n){return true;}
}
}
return false;
}
}

Leetcode題解 Python & C#:六月挑戰DAY8 Power of Two

給一個整數,回傳該數是否為 2 的次方。
除了要注意整數 0 與負整數,其他整數都比較沒有問題。
2 的零次方是 1 ,而負次方都位於[0, 1]之間,也不是整數。所以只需要考量零以上次方要怎麼找。
如果是二的次方,除以二的餘數皆為 0,用此條件,只要餘數為 1 就不是 2 的次方。一直除到變成 1 (2**0)時,確定輸入是 2 的次方。
換一種進位,以「二進位」來看,二的次方,只會有一個bit為 1,最高位的bit為1,其餘為 0 ,所以只要判斷是否bit只存在一個1。

Python(除二餘數)

class Solution:
def isPowerOfTwo(self, n: int) -> bool:
if n <= 1: return n == 1
return self.isPowerOfTwo(n//2) if n % 2 == 0 else False

Python(bit存在只有1個1)

class Solution:
def isPowerOfTwo(self, n: int) -> bool:
if n < 1: return False
while n & 1 == 0:
n >>= 1
return n >> 1 == 0

Python(最高位為1)

class Solution:
def isPowerOfTwo(self, n: int) -> bool:
return n > 0 and n == (1 << (n.bit_length()-1))

C#

public class Solution {
public bool IsPowerOfTwo(int n) {
if(n <= 1){return n == 1;}
return n % 2 == 0 ? IsPowerOfTwo(n/2): false;
}
}

Leetcode題解 Python & C#:六月挑戰DAY7 Coin Change 2

給各硬幣的幣值 coins,與目標金額 amount ,要找出有幾種硬幣組合的總金額會等於 amount。
在這題琢磨了好幾小時,因為對於 DP 速度感到好奇,這題用 DFS 就是慢。
對於這種求特定組合數的題目,最重要的就是去看限制條件,再決定 DP 要取什麼要比較快。
「0 <= amount <= 5000」這意味著每道題目的答案並不是大數目,從「the answer is guaranteed to fit into signed 32-bit integer」也可得知。
如果是天文數字,就會有 % (10**9+7) ,這樣的題型不可能遍歷所有位置去解。但這題最快的方法是去跑遍所有位置。

這題用 DFS 無法達到「收斂」的效果,收斂要把幾種可能答案做取捨並向上傳遞,用 DFS 只能開支散葉,也就不需要用遞迴函數。
用各硬幣累加,在總金額 0 的地方預設為 1 ,各幣從幣值處開始,dp[i] += dp[i-coin],直到 i 超出 amount。
如 5, [1,2,5],在 dp[2] == 2,到達此處的方法有 1*2, 2*1,或寫 1 + 1, 0 + 2。固定增加的方式,輪到幣值永遠加在已計路逕的最後方,也使得不會有重複出現。 
1: [1,1,1,1,1,1]
2: [1,1,2,2,3,3]
5: [1,1,2,2,3,4]
「1」:1+1+1+1+1 => dp[4] 
「2」:1+1+1+2 => dp[3]
「2」:1+2+2 => dp[3] => 1+2(dp[1]) + 2
「5」:5 => dp[0]

Python

class Solution:
def change(self, amount: int, coins: List[int]) -> int:
dp = [0] * (amount+1)
dp[0] = 1
for coin in coins:
for i in range(coin, amount+1):
dp[i] += dp[i - coin]
return dp[-1]

C#

public class Solution {
public int Change(int amount, int[] coins) {
var dp = new int[amount+1];
dp[0] = 1;
foreach(int coin in coins)
{
for(int i = coin; i <= amount; i++)
{
dp[i] += dp[i - coin];
}
}
return dp[amount];
}
}

Leetcode題解 Python:Next Greater Element I & II & III

在 contest 前,來抽一題暖手感,剛好就中這一題,有系列就依序解完吧。
(這次的contest有解完)

【Next Greater Element I】

給二個陣列 nums1 和 nums2,nums1 是 nums2 的子集合。
依 nums1 的元素找出(對應nums2位置)下一個比自身大的值。如果沒有則 -1 ,回傳所有的結果 List。
由於 nums1 並非照著原來在 nums2 的相對關係排序,最快的方法是依照 nums1 的元素,在 O(N) Time 解決。
但如果從 nums2 對應的位置開始照規則找,最糟會到 O(N^2) Time。
先找出所有的答案再依 nums1 重組,時間複雜度恰好 O(MN) ,也不用在找答案過程做是否在 nums1 的判斷。

每個元素只要找後方第一個大於它的元素,用一個暫時的「遞增數列」,因為要從後方找,所以從後方開始,就能確保沒有比它大的而馬上填 -1。
(位置不能動,找之於每個元素大或小,或一區段內最大或或最小面積,就可能用上遞增遞減數列)

Python(後往前)

class Solution:
def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
if not nums1 or not nums2: return []
memo = dict()
result = []
while nums2:
num = nums2.pop()
while result and result[0] <= num:
del result[0]
memo[num] = result[0] if result else -1
result.insert(0, num)

return [memo[num] for num in nums1]

Python(Stack)

class Solution:
def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
if not nums1 or not nums2: return []
memo = dict()
result = []
for i in range(len(nums1)):
memo[nums1[i]] = i
result.append(-1)

stack = []
for num in nums2:
while stack and stack[-1] < num:
if stack[-1] in memo:
result[memo[stack[-1]]] = num
del memo[stack[-1]]
del stack[-1]
stack.append(num)
return result

【Next Greater Element II】

一樣的規則,但是變成 circle array,也就是頭接尾。
circle 也可以用系列I的方法,只要把輸入變二倍去解,最後再取原本的長度(題目所求)回傳。
但是擺成二倍等於要跑二偣 N ,也不好。同樣在 O(N) Time ime 也是有高下之分的。
有什麼數字後方一定找不到更大的?最大值,也只有最大值找不到,其餘找到的必 <= 最大值。
把最大值移到最後,就可以用「 Next Greater Element I 」的方法找,再把順序換回所給的順序就能解了。
如 :[1,2,1] => [1,1,2] 。只有最大值的解會是 -1 ,因此把最大值放到最後,可確保每個位置答案都正確。
 [2,2,-1],需要換回成 [2,-1,2],依當初往後挪動多少,就把答案往前挪,舉例是 1 ,故往前挪 1。 [2,2,-1] => [2,-1,2]

Python

class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
mi = nums.index(max(nums))
array = []
n = len(nums)
result = [-1 for _ in range(n)]
for i in range(n,0,-1):
ix = (i+mi)%n
num = nums[ix]
while array and array[0] <= num:
del array[0]
if array: result[ix] = array[0]
array.insert(0, num)

return result

Python(Stack)

class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
if not nums: return nums
n = len(nums)
mi = nums.index(max(nums))
result = [-1 for _ in range(n)]
stack = []
for i in range(n):
ix = (i+mi+1)%n
num = nums[ix]
while stack and nums[stack[-1]] < num:
result[stack.pop()] = num
stack.append(ix)
return result

【Next Greater Element III】

這題就不在 I、II 的後續,而是一個獨立的題型。
給一個正整數 n ,要找出數字集合相同,下一個更大的數字。更大的可能有很多,要回傳其中最小的。
前面的解法都基於「遞增數列」,這題也是得用這概念去解。
 
有一個數字集合,其最大值的排法為「非遞增數列」的時候,即 nums[i] >= nums[i+1]。
若反過來從後方看,就是非遞減數列。要使 n 變大一些,從後方開始找是最好的方式。
一旦找到一個數字破壞了非遞減數列,那就代表需要交換此數字、從後方選一個最小的大於此數字交換,然後反轉後方順序。
要交換的數字後方是非遞增數列(排序組合中的最大值),然而在交換數字後也能保持排序規則。
因此再將順序反轉後,非遞增數列就會變成非遞減數列(排序組合中的最小值),也就是最小增加。

Python

class Solution:
def nextGreaterElement(self, n: int) -> int:
stack = [n % 10]
n //= 10
while n and n % 10 >= stack[-1]:
stack.append(n % 10)
n //= 10
if not n: return -1

num_change = n % 10
stack.sort()
l, r = 0, len(stack)
while l < r:
m = (r-l)//2 + l
if stack[m] <= num_change:
l = m+1
else:
r = m
if r < len(stack):
n, stack[r] = (n-n%10+stack[r]), num_change

for num in stack:
n = n*10 + num

return n if n <= 2**31-1 else -1

Leetcode題解 Python & C#:六月挑戰DAY6 Queue Reconstruction by Height

給一個串列 people , 其中每一個元素為 (h, k),h 代表自身的高度, k 代表自身前方有 k 個人的 h >= 自身。
people目前是亂的,要回傳照規則(即每個位置與其 h, k 值皆是對的)排列的串列。 
我沒有第一時想通,看了一下別人的題解
卡在我只有想到 brute force,但沒有想到要如何簡化。
按照逆向的想法,預想本來會有一個原形只有身高的排列,然後從身高小到大從後方取出,並標上其位置(前方有位置號個比自己 h >=)
所以逆向回去,即身高由高到矮,從前方排,依序一個個放入。
由於是按身高高到矮放入,因此直接把整理好的 people 按其 people[i][1] 去 insert(people[i][1], people[i]) ,即可確保前方有 people[i][1] 比自身高或同。
而輪到較矮的 insert() 也不會影響其排列的正確性,就能順利逆向回所求。

Python

class Solution:
def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:
people.sort(key = lambda x: (-x[0], x[1]))
result = []
for p in people:
result.insert(p[1], p)
return result

C#

public class Solution {
public int[][] ReconstructQueue(int[][] people) {
var sorted = people.OrderBy(x => (-x[0], x[1])).ToArray();
var result = new List();
foreach(var pair in sorted)
{
result.Insert(pair[1], pair);
}
return result.ToArray();
}
}

Leetcode題解 Python & C#:六月挑戰DAY5 Random Pick with Weight

給一串正整數權重 w ,w[i] 代表 i 的權重。然後要寫一個 pickIndex 方法,以加權的機率抽出一個 i 。
雖然說 Python 的 random 模組有方法可以模擬做加權抽樣,但是速度不夠快,以致於超時,所以要減少 random 的使用,靠自己寫更有效率的代碼。
(這也是很少見的專門模組效率會遜於自撰的)
加權抽樣若以 1 為單位,把所有 index 放入一個假想的箱子,權重為 1 放1個,權重為 3 放3個,最後從中隨機抽取一個出來。
抽中 i 的機率為 w[i] / total(總權重) ,所有的機率加起來會等於 1 。若把機率從頭逐一累加,保存累進序列,這序列以權重把[0,1]之間切割區間。
用 random.random() ,可以得到一個 [0, 1] 之間的 float ,然後我們要用這個 [0, 1]之間的 random數 去對應出一個 i。
以輸入[1,4]為例,對照累進機率,若 random數 位於 [0, 0.2],那代表抽到 0 。若 random數 位於 [0.2, 1],那代表抽到 1。
要找出 random數 落於哪個區間,可以用二分法去搜尋,預期 O(logN) Time 。(不知道random的方法是用到 O(N) Time嗎?至少二分法比O(N)快)
如果剛好落在區間界線(如 == 0.2)要算前面的 i 還是後面的 i ?其實都沒差,根據「積分」,剛好落在 0.2 的機率為 0 ,所以不會影響到權重。

Python

class Solution:

def __init__(self, w: List[int]):
total = sum(w)
self.w = []
accu = 0
for i in range(1, len(w)):
accu += w[i] / total
self.w.append(accu)

def pickIndex(self) -> int:
l, r = 0, len(self.w)
t = random.random()
while l < r:
m = (r - l)//2 + l
if self.w[m] < t:
l = m + 1
else:
r = m
return l

C#

public class Solution {

double[] ws;
Random random = new Random();

public Solution(int[] w) {
var total = w.Sum() * 1.0;
ws = new double[w.Length];
ws[0] = w[0] / total;
for(int i = 1; i < w.Length; i++)
{
ws[i] = ws[i-1] + w[i] / total;
}
}

public int PickIndex() {
var t = random.NextDouble();
int l = 0; int r = ws.Length;
while(l < r)
{
int m = (r-l)/2 + l;
if(ws[m] < t)
l = m + 1;
else
r = m;
}
return l;
}
}

Leetcode題解 Python & C#:六月挑戰DAY4 Reverse String

寫一個功能反轉 char 陣列,在內部修改,不用回傳值。
對於初學物件導向的人,很容易用新的實例來取代原本的,不過如果是 C# 等,就得宣告才會產生新的,自然不會有混淆的問題。(比較少)
反轉字串的方式,就是最前與最後開始兩兩交換,換到 len()//2 之前停止。

Python

class Solution:
def reverseString(self, s: List[str]) -> None:
"""
Do not return anything, modify s in-place instead.
"""
ls = len(s)
for i in range(ls//2):
s[i], s[ls-1-i] = s[ls-1-i], s[i]

C#

public class Solution {
public void ReverseString(char[] s) {
int l = s.Length-1;
for(int i = 0; i < (l+1)/2; i++)
{
char lc = s[i];
s[i] = s[l-i];
s[l-i] = lc;
}
}
}

Leetcode題解 Python & C#:六月挑戰DAY3 Two City Scheduling

要把 2n 個人,平分給 cityA 和 cityB,costs[i] 代表第 i 個人,costs[i][0] 代表第 i 個人到 cityA 的花費,而 costs[i][1] 則是到 cittyB。要回傳最小總花費。※去 cityA 和 去 cityB 都應該有 n 個人。
一看到題目,馬上用DP解,因為我解的上一題是也是分配的問題。不過一看到用時,就知道這不是好的寫法。
對總體來說,如果每個人都以「相對代價」較低的為目的地,就可以找到最佳分配的情形。
相對代價 costs[i][0] – costs[i][1],我們讓前 n 個相對代價較小的到 cityA ,後面的到 cityB,就是最佳方案。  
(若有 3 個以上的選擇,就不能用這算法。)

Python(DP)

class Solution:
def twoCitySchedCost(self, costs: List[List[int]]) -> int:

n = len(costs)//2

@lru_cache(None)
def dfs(n1, n2):
if n1 + n2 == n*2:
return 0

i = n1 + n2
path = set()
if n1 < n:
path.add(costs[i][0] + dfs(n1+1, n2))
if n2 < n:
path.add(costs[i][1] + dfs(n1, n2+1))
return min(path)

return dfs(0,0)

Python

class Solution:
def twoCitySchedCost(self, costs: List[List[int]]) -> int:
costs.sort(key = lambda x: x[0] - x[1])
n = len(costs)//2
result = 0
for i in range(n):
result += costs[i][0] + costs[i+n][1]
return result

C#

public class Solution {
public int TwoCitySchedCost(int[][] costs) {
int n = costs.Length / 2;
int result = 0;
int[][] sorted_costs = costs.OrderBy(x => (x[0]-x[1])).ToArray();
for(int i = 0; i < n; i++)
result += sorted_costs[i][0] + sorted_costs[i+n][1];
return result;
}
}

Leetcode題解 Python:Probability of a Two Boxes Having The Same Number of Distinct Balls

有 2n 個球,球有 k 個顏色,寫成一串 k 長度 balls 陣列,balls[i] 代表第 i 顏色有多少顆球。
今天要隨機分到兩箱子,分完打開看,回傳最後兩箱子球的顏色數目相同的機率。
因為是隨機,所以拿到顏色順序不會按照顏色種類排列,例:[1,3] [3,1] ,而二者都是合規則的可能情形。 
按照真實隨機分配的方式,即一顆顆隨機抽出,運算次數會隨 k 與 sum(balls) 呈指數爆炸性成長。
如:[6,6,6,6,6,6],會作 36! 次探討,自然會超時,要想辦法改進。
對於隨機排列組合,像是抽籤等情況,要掌握到這是「邊抽邊開」還是「抽完再開」?二者在計算上是不同的。
「抽完再開」的如此題,同顏色的順序就不重要,如:白1白3、白3白1,都視作 白白,視為同一種。

如果有6顆球白白白白紅紅,那抽完再看的組合數有 6!/4!/2! 個,因同顏色間排列都視為一個,所以用除去 4!(白) 2!(紅) 來排除同色順序的影響。
為了要加速計算,勢必得抛棄直觀的一顆顆隨機分配,朝著直接抽一把起來的方向分配會快個多。
照順序一次次抽,這裡的順序選擇「顏色」,從第 0 位抽到第 k-1 位顏色,最後再把組合數 n! / C0balls! / C1balls! / … / Ck-1balls! 算出來。
拿 [6,6,6,6,6,6] 為例,在 [0] 位顏色時, box1 可拿 0-6 顆,算作 7 種拿法,那麼最糟要探討次數為 7**6 ,感覺很多,但相比 36!,
算快上非常顯著的。
接下來有二種DP:
1.紀錄BOX1的各色球數,最後可反推BOX2。 (i, tuple([B1C0balls, B1C1balls, …, B1Ck-1balls]))
2.紀錄BOX1與BOX2的目前的球數與顏色數。 (i, color1, balls1, factorial1, color2, balls2, factorial2)
我用 2. 。 (2. 是 1. 的攤平化,用tuple是非常沒有效率的)
寫遞迴函數時,先思考中止條件,也就是分完了 k 色。此時算組合數的 n! 是已知,但後面的除數是上層分球時決定的,
因此最深層的DFS需要把除數一路往深傳遞。
算出 box1 組合數,再乘上 box2 組合數,就能得到該分配下的隨機總可能數,納入總數。要是 c1 == c2,就納入合格數。
回傳 合格數  /  總數

Python

class Solution:
def getProbability(self, balls: List[int]) -> float:
n = sum(balls) // 2
bs = len(balls)

@lru_cache(None)
def fl(num):
if num <= 1: return 1
return num * fl(num - 1)

@lru_cache(None)
def dfs(i, c1, n1, f1, c2, n2, f2):
if i == bs:
combo = fl(n)*2 / f1 / f2
return combo * (c1 == c2), combo
maxTake = min(balls[i], n - n1)
minTake = max(balls[i] - (n - n2), 0)
s, p = 0, 0
for b in range(minTake, maxTake + 1):
res = dfs(i+1, c1+(1 if b > 0 else 0), n1+b, f1*fl(b), c2+(1 if b < balls[i] else 0), n2 + balls[i] - b, f2*fl(balls[i] - b))
s += res[0]
p += res[1]
return s, p

return dfs(0, 0, 0, 1, 0, 0, 1)[0] / dfs(0, 0, 0, 1, 0, 0, 1)[1]

Leetcode題解 Python & C#:六月挑戰DAY2 Delete Node in a Linked List

寫一個功能去從一 linked list 中刪除一個 node,會給那個要被刪除的 node。
這題真的難到我了,在第一眼看到的時候。明明有兩個input,怎麼只傳入一個node?一旦看懂之後就很簡單了。
如果要刪除一個node,那最簡單的方式就是把它上一個跟它下一個接起來,但是這裡沒有上一個,而是直接給要刪除的node。
沒辦法修改實例位置完成,那就修改屬性。

原:A「B」CD -> A「」CD -> ACD
此:A「B」CD -> A「C」CD (修改值) -> AC「C」D -> AC「」D -> ACD
要刪除的node的值改成下一個的值,next改成下一個的next,即可。(但是無法刪最後一個node,題目也有說不會刪最後一個)

Python

class Solution:
def deleteNode(self, node):
"""
:type node: ListNode
:rtype: void Do not return anything, modify node in-place instead.
"""
node.val = node.next.val
node.next = node.next.next

C#

public class Solution {
public void DeleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
}

Leetcode題解 Python & C#:六月挑戰DAY1 Invert Binary Tree

反轉二元樹。

這題被啟發的述說蠻好笑的,會寫軟體,但不會在白板寫出反轉二元樹。
有時候會被簡單卡關是難免的。

要反轉二元樹,也就是左右對調,node.left, node.right = node.right, node.left,如此運作下去到底。

我也沒有一次到位,不過很快就察覺到該怎麼寫。


Python

class Solution:
def invertTree(self, root: TreeNode) -> TreeNode:
stack = [root]
while stack:
node = stack.pop()
if node:
node.left, node.right = node.right, node.left
stack.extend([node.left, node.right])
return root

C#

public class Solution {
public TreeNode InvertTree(TreeNode root) {
if(root is null){return root;}
var stack = new Stack();
stack.Push(root);
while(stack.Count > 0)
{
var node = stack.Pop();
var temNode = node.right;
node.right = node.left;
node.left = temNode;
if (!(node.right is null))
stack.Push(node.right);
if (!(node.left is null))
stack.Push(node.left);
}
return root;
}
}

Leetcode題解 Python & C#:五月挑戰DAY31 Edit Distance

給兩個字串,要找最小的編輯次數讓 word1 變成 word2。編輯只有三種操作:insert、delete、replace。
這是hard題,蠻有意思的,可以找出像是基因序列相似的程度,或是字典中找相似的詞。
如果用人的概念去想,要怎麼選,要用哪種換法,基準點在哪裡,似乎不是那麼好想像的。
但是能確定朝著每個操作,是 word1 和 word2 之間的交換。
舉例:word1 = ABC, word2 = BABC。
可以想到把 word2[0]做delete,或 word1 insert(0,”B”),就可以得到 word1 == word2。
為了好講,這裡不探討 word2 上做操作,統一以 word1 當基準,也就是只看 word1 -> word2。
像是 word1[0] == word2[1] == “A”,在這位置上不用操作,因為二者相等。說到「位置」這裡換個思考 DP[p1][p2] ,
二者的方向關係是一個表格DP,若 word1[p1] == word2[p2] , 則 dp[p1][2] 的基本值等於0,即不需要任何操作,反之則 1。
  
題目要找出最小操作次數,也可以想像把操作數往右下角,一路取最小壘加,最後的結果就是最小操作次數。
如果 word1[p1] != word2[p2],代表要進行操作,在 DP[p1][p2] 取 {DP[p1-1][p2-1], DP[p1-1][p2], DP[p1][p2-1]}三者最小值加 1 (基本值)。
分別對應{replace, delete, insert}三種操作中取最佳方案到DP[p1][p2] ,看 DP[p1][p2] 的「相對位置」來決定是哪種操作,本身基本值 1 有三種操作的可能,至少一定是其中一個。

 BABC
A11
B1X
在位置[0][1],[0][0] 是 [p1][p2-1],即[0][0] insert(0, Char) (B)
在位置[1][0],[0][0] 是 [p1-1][p2],即[0][0] del word1[0] (A)
在X位置[1][1],對X位置來[0][0]位罝是 replace (A -> B) ,[0][1]位置是 del word1[0],而 [1][0] insert(1, Char)。
那 word1[p1] == word2[p2] 時呢? DP[p1][p2] = ?
我們取右下角代表經過操作次數使從頭到p1,p2位置都對上,最右下角即從頭到尾都對上的操作次數。
一旦 word1[p1] == word2[p2],所需要的次數,即前面都對上所需要的次數,DP[p1][p2] = DP[p1-1][p2-1]。(左上那個 相當於子問題(二字串編輯)的解)
如果在 row = 0 或 col = 0 時 word1[p1] == word2[p2] 相等, DP[p1-1][p2-1] 會超出範圍。那該等於多少?
要是相等,那麼要加上使前面相等的次數。如果是[0][0],那麼就是0 次, 如果是 [1][0] 或 [0][1],那麼前面要一樣,不是 delete 就是 insert 前面的,為 1 次。
同理[p1][0]、[0][p2],邊際無法做 replace,建議獨立出來處理掉。
像這種有邊際問題的,可以增加表格範圍(邊際欄列),又或者先拿出來處理。如果把邊際的條件寫在一起,不僅不方便讀,也拖累效率。

Python(邊際欄列)

class Solution:
def minDistance(self, word1: str, word2: str) -> int:
from functools import lru_cache
rows, cols = len(word1)+1, len(word2)+1
@lru_cache(None)
def dfs(row, col):
if row == 0 or col == 0:
return max(row, col)
other = set()
if row > 0 or col > 0:
if word1[row-1] == word2[col-1]:
ans = dfs(row-1, col-1)
else:
if row > 0 and col > 0:
other.add(dfs(row-1, col-1))
if row > 0:
other.add(dfs(row-1, col))
if col > 0:
other.add(dfs(row, col-1))
ans = min(other)+1
return int(ans)

return dfs(rows-1, cols-1)

C#

public class Solution {
public int MinDistance(string word1, string word2) {
var memo = new int[word1.Length+1, word2.Length+1];
for(int i = 0; i < word1.Length+1; i++)
memo[i, 0] = i;
for(int j = 0; j < word2.Length+1; j++)
memo[0, j] = j;
for(int i = 1; i < word1.Length + 1; i++)
{
for(int j = 1; j < word2.Length + 1; j++)
{
if(word1[i-1] == word2[j-1])
memo[i, j] = memo[i-1, j-1];
else
{
memo[i, j] = Math.Min(memo[i-1, j], memo[i, j-1]);
memo[i, j] = Math.Min(memo[i, j], memo[i-1, j-1]) + 1;
}
}
}
return memo[word1.Length, word2.Length];
}
}