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

参考:LeetCode 刷题笔记——递归与回溯的理解

生成括号

给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。

例如,给出 = 3,生成结果为:

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

我的解答:

思路:对于括号的组合,要考虑其有效性。比如说,)(, 它虽然也是由一个左括号和一个右括号组成,但它就不是一个有效的括号组合。 那么,怎样的组合是有效的呢?对于一个左括号,在它右边一定要有一个右括号与之配对, 这样的才能是有效的。所以,对于一个输出,比如说(()()), 从左边起,取到任意的某个位置得到的串,左括号数量一定是大于或等于右括号的数量, 只有在这种情况下,这组输出才是有效的。我们分别记左,右括号的数量为left和right, 如下分析可看出,(()())是个有效的括号组合。

参考:Leetcode22. Generate Parentheses(生成有效的括号组合)

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

猜你喜欢

转载自blog.csdn.net/dta0502/article/details/81057068
今日推荐