Leetcode——中級部分——回溯演算法部分——Python實現
電話號碼的字母組合
給定一個僅包含數字 2-9
的字串,返回所有它能表示的字母組合。
給出數字到字母的對映如下(與電話按鍵相同)。注意 1 不對應任何字母。
示例:
輸入:"23"
輸出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
說明:
儘管上面的答案是按字典序排列的,但是你可以任意選擇答案輸出的順序。
我的解答:
主要的思路是將輸入的數字字串從後向前遍歷,每個字元進行單字元的對映,並將所有單字元對映與目標集合中的所有字串拼接,形成新的字串集合。拼接的過程,就是遞迴實現的部分。
具體的實現思路如下:
(1)建立字典對映表;
(2)從後向前遍歷當前數字字串;
(3)若當前數字字串長度超過 1,則從當前字串的第 2 位到末尾作為子字串,將該子串作為輸入引數,重新輸入該函式,這裡即為遞迴的實現。
(4)字典中查詢當前字串的首位數字對應的所有字元,並對目標集合進行雙重遍歷,實現首位數字對應字元與目標集合中所有字串的拼接;
class Solution: def letterCombinations(self, digits): """ :type digits: str :rtype: List[str] """ resultStr = [] dicts = {2:['a','b','c'], 3:['d','e','f'], 4:['g','h','i'], 5:['j','k','l'], 6:['m','n','o'], 7:['p','q','r','s'], 8:['t','u','v'], 9:['w','x','y','z']} #數字是空的情況 if len(digits) == 0: return [] #只剩下一位數字的情況(遞迴終止條件) if len(digits) == 1: return dicts[int(digits[0])] #遞迴(除了數字的首位,其餘均給遞迴函式) result = self.letterCombinations(digits[1:]) #字串拼接操作 for i in dicts[int(digits[0])]: for j in result: resultStr.append(i+j) return resultStr
生成括號
給出 n 代表生成括號的對數,請你寫出一個函式,使其能夠生成所有可能的並且有效的括號組合。
例如,給出 n =3,生成結果為:
[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
我的解答:
思路:對於括號的組合,要考慮其有效性。比如說,)(, 它雖然也是由一個左括號和一個右括號組成,但它就不是一個有效的括號組合。 那麼,怎樣的組合是有效的呢?對於一個左括號,在它右邊一定要有一個右括號與之配對, 這樣的才能是有效的。所以,對於一個輸出,比如說(()()), 從左邊起,取到任意的某個位置得到的串,左括號數量一定是大於或等於右括號的數量
class Solution:
def generateParenthesis(self, n):
"""
:type n: int
:rtype: List[str]
"""
res = []
self.DFS(n,n,'',res)
return res
def DFS(self,left,right,s,res):
if left == 0 and right == 0:
res.append(s)
else:
if left > 0:
self.DFS(left-1,right,s+'(',res)
if right > left:
self.DFS(left,right-1,s+')',res)
這道題整體程式碼邏輯非常清晰,主函式+遞迴函式, DFS的left , right 分別表示左右括號的剩餘數量,s表示目前製造的括號。高含金量的邏輯出現在DFS函式中的else部分。首先我們都知道base條件是左括號和右括號的數量都為0時。巧妙在於,我們的DFS一開始只會進入 if left > 0:裡的函式,第一次函式遞迴完畢返回後,還是停留在left = 1 , s='(('的狀況下,而且返回點在if left>0:下。接著它會進入right>left裡,進行一次遞迴後又會進入if left > 0:裡的遞迴函式。這就是DFS的魔力,它通過2個看起來平行的遞迴入口,通過程式碼順序製造遞迴的先後,最終達到了深度優先搜尋。
全排列
給定一個沒有重複數字的序列,返回其所有可能的全排列。
示例:
輸入: [1,2,3]
輸出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
我的解答:
class Solution:
def permute(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
self.res = []
subList = []
self.DFS(nums,subList)
return self.res
def DFS(self,nums,subList):
#判斷subList是否全滿
if len(subList) == len(nums):
self.res.append(subList[:]) #在最後的結果中新增這個全滿的子列
for m in nums:
#如果子列中已經含有了m,就跳出這個迴圈
if m in subList:
continue
#如果子列中沒有m,就新增m
subList.append(m)
#遞迴
self.DFS(nums,subList)
#從子列中移除m(回溯?)
subList.remove(m)
假設我們輸入的nums是[1,2,3],下面簡單講下順序執行過程:
DFS(nums,[])
m = 1
subList = [1]
#下面進入第二層DFS(nums,[1])
m = 2
subList = [1,2]
#下面進入第三層DFS(nums,[1,2])
m = 3
subList = [1,2,3]
#下面進入第四層DFS(nums,[1,2,3])
self.res.append(subList[:])
#下面返回到第三層DFS
subList.remove(3)
subList = [1,2]
#下面返回到第二層DFS
subList.remove(2)
subList = [1]
#關鍵在這裡!這時候m=2,所以下一個for迴圈後m=3
m = 3
subList = [1,3]
#下面進入到第三層DFS(nums,[1,3])
m = 2
subList = [1,3,2]
#下面進入第四層DFS(nums,[1,3,2])
self.res.append(subList[:])
......
......
每次subList.append(m)以後,遞迴返回之後都會對應一個subList.remove(m),這個大概就是回溯的體現。
子集
給定一組不含重複元素的整數陣列 nums,返回該陣列所有可能的子集(冪集)。
說明:解集不能包含重複的子集。
示例:
輸入: nums = [1,2,3] 輸出: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]
我的解答:
三層巢狀,不過每一層迴圈的起點是上一層對應位置+1,這樣避免出現123,132這種重複情況
處理流程:
遍歷開始前,先把[]加入list
第一層i1=0,”1“
第二層i2=i1+1=1,”12“
第三層i3=i2+1=2,”123“
回溯到第二層,i2++後i2=2,”13“
進入到第三層由於i3=i2+1=3,到邊界之外了,直接返回到第二層
第二層i2++後,i2=3,越界返回第一層
第一層i1++後,i1=2,”2“
第二層i2=i1+1=2,“23“,回退
第一層i1++後,i1=3,”3“,退出
class Solution:
def subsets(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
self.res = []
s = []
self.DFS(nums,s,0)
return self.res
def DFS(self,nums,s,index):
if len(nums) == index:
self.res.append(s)
return
self.DFS(nums,s+[nums[index]],index+1)
self.DFS(nums,s,index+1)
下面是順序執行過程:
#第一層
search(nums,[],0)
#第二層
search(nums,[1],1)
#第三層
search(nums,[1,2],2)
#第四層
search(nums,[1,2,3],3)
index == len(nums)
#這裡新增[1,2,3]到結果,然後return
#執行第三層中的下一個search
search(nums,[1,2],3)
#(新的)第四層
index == len(nums)
#這裡新增[1,2]到結果,然後return
#這時候第三層執行到終點,然後return
#執行第二層下一個search
search(nums,[1],2)
#(新的)第三層
search(nums,[1,3],3)
#(新的)第四層
index == len(nums)
#這裡新增[1,3]到結果,然後return
#執行第三層中的下一個search
search(nums,[1],3)
#(新的)第四層
index == len(nums)
#這裡新增[1]到結果,然後return
#這時候第三層執行到終點,然後return
#這時候第二層執行到終點,然後return
#執行第一層下一個search
search(nums,[],1)
#(新的)第二層
search(nums,[2],2)
#(新的)第三層
search(nums,[2,3],3)
#(新的)第四層
index == len(nums)
#這裡新增[2,3]到結果,然後return
......
......
單詞搜尋
給定一個二維網格和一個單詞,找出該單詞是否存在於網格中。
單詞必須按照字母順序,通過相鄰的單元格內的字母構成,其中“相鄰”單元格是那些水平相鄰或垂直相鄰的單元格。同一個單元格內的字母不允許被重複使用。
示例:
board = [ ['A','B','C','E'], ['S','F','C','S'], ['A','D','E','E'] ] 給定 word = "ABCCED", 返回 true. 給定 word = "SEE", 返回 true. 給定 word = "ABCB", 返回 false.
我的解答:
DFS,典型的深度優先遍歷,對每一點的每一條路徑進行深度遍歷,遍歷過程中一旦出現:
1.陣列越界。
2.該點已訪問過。
3.該點的字元和word對應的index字元不匹配。
就要對該路徑進行剪枝。
class Solution:
def exist(self, board, word):
"""
:type board: List[List[str]]
:type word: str
:rtype: bool
"""
visited = [[False for j in range(len(board[0]))] for i in range(len(board))]
for i in range(len(board)):
for j in range(len(board[0])):
if self.DFS(board,i,j,word,0,visited):
return True
return False
def DFS(self,board,i,j,word,k,visited):
#判斷是否搜尋到最後一個字母
if k == len(word):
return True
#判斷是否越界/是否訪問過/是否匹配,剪枝操作
if i<0 or j<0 or i>=len(board) or j>=len(board[0]) or visited[i][j] == True or board[i][j] != word[k]:
return False
##上面都不符合,說明匹配
#標記該點為訪問過
visited[i][j] = True
#遞迴搜尋
result = self.DFS(board,i-1,j,word,k+1,visited) or\
self.DFS(board,i+1,j,word,k+1,visited) or\
self.DFS(board,i,j-1,word,k+1,visited) or\
self.DFS(board,i,j+1,word,k+1,visited)
#遍歷過後,將該點還原為未訪問過
visited[i][j] = False
return result