*内容来自leetcode
1.括号生成
题目要求
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例 1:
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例 2:
输入:digits = ""
输出:[]
思路
没有思路
这个回溯算法,本质上也是递归,官方给出了三种解答。
暴力法
长度为n的序列就是在n-1序列上加一个(或者)。
可以在加完之后判断是否有效,有效则加入结果列表
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
def trackBack(result):
if len(result) == 2*n:
if valid(result):
results.append("".join(result))
else:
result.append("(")
trackBack(result)
result.pop()
result.append(")")
trackBack(result)
result.pop()
def valid(strs):
balance = 0
for str in strs:
if str == "(":
balance +=1
else:
balance -=1
if balance < 0:
return False
return balance == 0
results = list()
trackBack([])
return results
第二种即是对第一种算法的优化,可以在每次添加时判断左右括号数目关系,来判断添加何种字符,而不是最后进行判断,减少无用结果的生成
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
#对暴力法进行优化
def trackBack(result,left,right):
if len(result) == 2*n:
results.append("".join(result))
else:
if left < n:
result.append("(")
trackBack(result,left+1,right)
result.pop()
#当左括号多于右括号时才能添加右括号
if left > right:
result.append(")")
trackBack(result,left,right+1)
result.pop()
results = list()
trackBack([],0,0)
return results
第三种方法:
任何一个括号序列都一定是由 ‘(’开头,并且第一个 ‘(’一定有一个唯一与之对应的 ‘)’。这样一来,每一个括号序列可以用 (a)b 来表示,其中 a 与 b分别是一个合法的括号序列(可以为空)。
那么,要生成所有长度为 2n 的括号序列,我们定义一个函数 generate(n)来返回所有可能的括号序列。那么在函数 generate(n) 的过程中:
我们需要枚举与第一个 ‘(’的位置 2i+1;
递归调用 generate(i)即可计算 a 的所有可能性;
递归调用 generate(n−i−1) 即可计算 b 的所有可能性;
遍历 a 与 b的所有可能性并拼接,即可得到所有长度为 2n 的括号序列。
为了节省计算时间,我们在每次 generate(i)函数返回之前,把返回值存储起来,下次再调用 generate(i)时可以直接返回,不需要再递归计算。
贴上代码供学习
class Solution:
@lru_cache(None)
def generateParenthesis(self, n: int) -> List[str]:
if n == 0:
return ['']
ans = []
for c in range(n):
for left in self.generateParenthesis(c):
for right in self.generateParenthesis(n-1-c):
ans.append('({}){}'.format(left, right))
return ans
#评论区提出的另一种实现
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
if n == 1:
return list({'()'})
res = set()
for i in self.generateParenthesis(n - 1):
for j in range(len(i) + 2):
res.add(i[0:j] + '()' + i[j:])
return list(res)
2.全排列
题目要求
给定一个不含重复数字的数组 nums ,返回其所有可能的全排列 。可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]
思路
据说是一道很经典的回溯题。
记录一下我失败的代码
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
def trackBack(result,origin,stk):
if len(result) == len(nums):
results.append(result)
if origin:
stk.append(origin.pop())
trackBack(result,origin.copy(),stk.copy())
result = []
if stk:
result.append(stk.pop())
trackBack(result,origin.copy(),stk.copy())
stk = []
results = []
trackBack([],nums.copy(),stk)
return results
以及官方的代码
class Solution:
def permute(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
def backtrack(first = 0):
# 所有数都填完了
if first == n:
res.append(nums[:])
for i in range(first, n):
# 动态维护数组
nums[first], nums[i] = nums[i], nums[first]
# 继续递归填下一个数
backtrack(first + 1)
# 撤销操作
nums[first], nums[i] = nums[i], nums[first]
n = len(nums)
res = []
backtrack()
return res
值得一提的是,在向最后的结果列表添加重排列过后的数组时,采用的是如下的语句
if first == n:
res.append(nums[:])
而不是
if first == n:
res.append(nums)
这是因为nums是数组的对象,也就是说添加后nums发生变化时,结果列表中的nums也会随之发生变化,到最后保存的结果都是一样的。而nums[:]是保存列表元素,也就是在列表当前值,在后续列表变化时不会影响到已经保存的。效果类似于copy()函数。