1. 程式人生 > >Leetcode——中級部分——回溯演算法部分——Python實現

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 代表生成括號的對數,請你寫出一個函式,使其能夠生成所有可能的並且有效的括號組合。

例如,給出 =3,生成結果為:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]

我的解答:

思路:對於括號的組合,要考慮其有效性。比如說,)(, 它雖然也是由一個左括號和一個右括號組成,但它就不是一個有效的括號組合。 那麼,怎樣的組合是有效的呢?對於一個左括號,在它右邊一定要有一個右括號與之配對, 這樣的才能是有效的。所以,對於一個輸出,比如說(()()), 從左邊起,取到任意的某個位置得到的串,左括號數量一定是大於或等於右括號的數量

, 只有在這種情況下,這組輸出才是有效的。我們分別記左,右括號的數量為left和right, 如下分析可看出,(()())是個有效的括號組合。

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