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

Leetcode題解 Python:Check if There is a Valid Path in a Grid

給一個二維串列 grid,每格代表一種道路,其值可以對應下表,要回傳是否能從左上角走到右下角。

1 which means a street connecting the left cell and the right cell.
2 which means a street connecting the upper cell and the lower cell.
3 which means a street connecting the left cell and the lower cell.
4 which means a street connecting the right cell and the lower cell.
5 which means a street connecting the left cell and the upper cell.
6 which means a street connecting the right cell and the upper cell.

對於這種地圖或是面積的圖像問題,我總是感到棘手,也許有一套通用思路,不過可能需要很多經驗值累積才能解得順手。

從左上角開始,但是沒有規定左上角一定是從外面延伸進來,如果是 grid[0][0]  == 5,一開始就死局。如果等於 4,那一開始就會有兩條路線。

要怎麼解決起始有兩條路線的問題,就會是一個難題。又或者拋開心中虛擬的小車車,直接用遞迴 + memo 去從(0, 0)拔起所有合格座標,再看看右下角是否存在。

街道有六種形式,而且具有方向問題,困難點在此,要怎麼妥善解決轉向,這就值得思考。


每一種街道只能接受兩種方向,例如 street 1,如果從右邊來,通過後方向朝左邊;如果從左邊來,通過後方向朝右邊。如果從其他方向,那就代表無法接到一起。

街道對應的方向就只能老實打出,無法再快,這是題目的定義。

如果在當前位置向右走,對下一個位置是從左來,對於制定方向前進的人,要留意方向上的切換。

思考一下之後,使用遞迴拔出所有與(0,0)連接的街道會比較泛用。

class Solution:
def hasValidPath(self, grid):
"""
1 which means a street connecting the left cell and the right cell.
2 which means a street connecting the upper cell and the lower cell.
3 which means a street connecting the left cell and the lower cell.
4 which means a street connecting the right cell and the lower cell.
5 which means a street connecting the left cell and the upper cell.
6 which means a street connecting the right cell and the upper cell.
"""
# 0 up 1 left 2 down 3 right
streets = {1:(1,3), 2:(0,2), 3:(1,2), 4:(3,2), 5:(1,0), 6:(3,0)}

path = set()
rows, cols = len(grid), len(grid[0])

def searchMap(y,x):
if y < 0 or x < 0: return
if y == rows or x == cols: return
path.add((y,x))
ds = streets[grid[y][x]]

for d in ds:
x2,y2=x,y
if d == 0:
y2 = y-1
elif d == 1:
x2 = x-1
elif d == 2:
y2 = y+1
else:
x2 = x+1
d = (d+2)%4 #換方向(下一個位置的相對方向)
if (y2, x2) not in path:
if y2 < 0 or x2 < 0 or y2 == rows or x2 == cols:
continue
elif d in streets[grid[y2][x2]]:
searchMap(y2,x2)

searchMap(0,0)
return (rows-1, cols-1 ) in path

Leetcode題解 Python:Number of Ways to Paint N × 3 Grid

給一個 n,代表 (n , 3) 的二維串列,每個位置可以填入R、G、Y三色,而每個顏色不能相鄰(上下左右)相同,要求出一共有幾種填法。

這是我第二次做到要取餘的題目,代表方法數真的很大。

如果 n = 7,就會超時。

我左右一看,感覺到這只是個數學問題,可以代公式求解,不用真的計算。結果真的找到公式,代上去只用 一百多毫秒 就能通過測試。

在我心中,用公式解,就像拋棄電腦強大計算能力不用,背棄了用程式語言邏輯去解決問題的宗旨,跟作弊是沒兩樣的。

於是我還是老實地寫出一個合格的解法。

先看題目,第一層有12種組合。每一層的所有組合,都在這12種之內。

第二層有多少種組合,取決於第一層,也就是最後一層相鄰的那一層。

每種組合後面能接上的組合是固定的,只要找出來,就不必去費心再去搜尋下一層有甚麼可能。

所有A組合能接的下一層組合,都加上A組合的數目。n層的總和就是n層所有組合的加總。

不論是三種顏色還是四種顏色,寬三格還是四格,都可以套用這樣的邏輯去處理。

class Solution:
def numOfWays(self, n: int) -> int:
# 建立第一層
memo = dict()
memo["now"] = {}
memo["next"] = {}
colors = set(["r","g","y"])
# 找到所有顏色組合
def findColorSet(stats):
if len(stats) == 3:
memo["now"][stats] = 1
return
for c in colors:
if not stats or c != stats[-1]:
findColorSet(stats+c)
findColorSet("")

#
c2c = {}
for c1 in memo["now"]:
c2c[c1] = []
for c2 in memo["now"]:
isLegal = True
for ch1, ch2 in zip(c1,c2):
if ch1 == ch2:
isLegal = False
break
if isLegal:
c2c[c1].append(c2)
#
if n > 1:
keys = memo["now"].keys()
for row in range(2,n+1):
memo["next"] = {}.fromkeys(keys,0)
for key in keys:
v = memo["now"][key]
for key2 in c2c[key]:
memo["next"][key2] += v
memo["now"].update(memo["next"])

return sum(memo["now"].values()) % (10**9+7)

這樣子符合要求我自身要求,用程式邏輯找到答案。

下面附贈公式解,也是我第一個想出來的解法。

class Solution2:
def numOfWays(self, n: int) -> int:
self.ans = 0
table = dict()
table2 = dict()
table2[1] = 12
table[1] = 0
table2[2] = table2[1]*9//2+table[1]
table[2] = 3

if n >= 3:
for i in range(3, n+1):
table2[i] = table2[i-1]*9//2+table[i-1]
table[i] = table2[i-2]+table[i-1]

return table2[n] % (10**9+7)