回溯算法leetcode
# 回溯算法是一种穷举所有可能的情况来找到所有解的算法
class Solution(object):
# leetcode17 电话号码的组合
def letterCombinations(self, digits):
"""
:type digits: str
:rtype: List[str]
"""
# 存储结果的数组
res = []
# phone 词典
phone = {
'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']
}
def helper(combined,next_digit):
if len(next_digit) == 0:
res.append(combined)
else:
for i in phone[next_digit[0]]:
helper(combined+i,next_digit[1:])
# 初始化combined
combined = ""
if digits:
helper(combined,digits)
# 返回res
return res
# leetcode22 括号生成
# 暴力方法:生成n对括号的全排列,然后检测复合要求的
def generateParenthesis(self, n):
"""
:type n: int
:rtype: List[str]
"""
# 存储最终的结果
output = []
# 生成全排列的方法
def helper(A = []):
if len(A) == 2*n:
if isVaild(A):
output.append("".join(A))
else:
A.append('(')
helper(A)
A.pop()
A.append(')')
helper(A)
A.pop()
# 检测方法
def isVaild(A):
a=b=0
for i in A:
if i == '(':
a+=1
if i == ')':
b+=1
return a==b
# 返回最终的结果
return output
# 采用回溯的方法进行操作
# 思想,只有我们知道添加左括号或者右括号之后是否有效之后,才添加进去,我们可以追踪已经添加进去的左括号或者右括号的数量来控制操作
# 如果还剩一个位置,我们可以添加一个左括号,如果右括号+1不大于左括号,我们也可以添加一个右括号
def generateParenthesis_2(self, n):
"""
:type n: int
:rtype: List[str]
"""
# 存储最终的结果
res = []
# 当前构成的符号组合
cur_str = ""
# dfs深度优先遍历,也可以称之为回溯,其中还存在剪枝操作
# 参数left和right表示cur_str中已经存在的左括号与右括号的个数
def dfs(cur_str,left,right,n):
# 如果left == right == n则说明已经可以了,加入到res中
if left == n and right == n:
res.append(cur_str)
return
# 剪枝操作,左括号数目比右括号数目少,一定不符合情理之中
if left < right:
return
# 递归调用函数
if left<n:
dfs(cur_str + "(",left+1,right,n)
if left<n:
dfs(cur_str + ")",left,right+1,n)
# 调用dfs函数
dfs(cur_str,0,0,n)
# 返回结果
return res
# 组合总和 leetcode39
# 思想:回溯+剪枝
def combinationSum(self, candidates, target):
"""
:type candidates: List[int]
:type target: int
:rtype: List[List[int]]
"""
if(not candidates):
return []
n=len(candidates)
res=[]
candidates.sort()
# 参数i表示candidates的索引,tmp已经有的组合,target:目标值
def helper(i,tmp,target):
# 找到复合要求的tmp,加入到最终结果中
if(target==0):
res.append(tmp)
return
# 剪枝操作
if(i==n or target<candidates[i]):
return
# 递归调用
helper(i,tmp+[candidates[i]],target-candidates[i])
helper(i+1,tmp,target)
helper(0,[],target)
return res
# 组合总和2 leetcode40
# 思想:回溯+剪枝,不能多次使用,并且不能重复
def combinationSum2(self, candidates, target):
"""
:type candidates: List[int]
:type target: int
:rtype: List[List[int]]
"""
if(not candidates):
return []
n=len(candidates)
candidates.sort()
res=[]
def helper(i,tmp,target):
print(i,tmp,target)
if(target==0):
res.append(tmp)
return
if(i==n or target<candidates[i]):
return
for j in range(i,n):
# 与上面不同的在于这里,避免重复的操作,会跳过去重复的数字进行下一轮的判断
if(j>i and candidates[j]==candidates[j-1]):
continue
helper(j+1,tmp+[candidates[j]],target-candidates[j])
helper(0,[],target)
return res
# leetcode46 全排列
def permute(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
res = []
def backtrack(nums, tmp):
if not nums:
res.append(tmp)
return
for i in range(len(nums)):
backtrack(nums[:i] + nums[i+1:], tmp + [nums[i]])
backtrack(nums, [])
return res
# leetcode47 无重复的全排列
def permuteUnique(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
res=[]
path=[]
nums.sort() #排序方便后面去除重复序列
def helper(nums,path):
if not nums: #helper函数的第一个参数为空,说明nums中的所有元素已经添加到path中
res.append(path) #将满足条件的path添加到res结果中
for i in range(len(nums)):
if i>0 and nums[i]==nums[i-1]:
#去除重复的序列,因为排序过,所以如果同一层的两个数相等,那么后一个的所以分支结果和前一个是相等的。
#例如对于【1,1,2】来说,0位1和1位1所能衍生的结果序列完全相同。
continue
helper(nums[:i]+nums[i+1:],path+[nums[i]])
#函数第一个参数nums每一次递归去除i位的数,将其加入path中,详细可写为:
#path.append(nums[i])
#helper(nums[:i]+nums[i+1:],path)
#path.pop()
helper(nums,path)
return res #res为存储结果序列的数组
# leetcode60 第k个排列
# 解题思路:方法:枚举所有的排列(图的深度遍历),有个全局的计数器,直到计数到k,返回该排列就可以。但是提交后运行超时。
# 问题:明显该题目是只求第K个排列,不需要枚举所有排列。那么如何提前返回(减枝)呢。
# 减支方法
# 每个分支的排列数量count是可以求出来的,如果k大于count,那么可以后移到下个分支,以此类推。
# 如果小于count,则进入下一层,继续按照上面的规则判断
def getPermutation(self, n, k):
"""
:type n: int
:type k: int
:rtype: str
"""
#
def fact(n):
ret = 1
while n:
ret *= n
n -= 1
return ret
# 深度优先遍历,也就是回溯
# 参数:remain:剩余需要遍历的节点,path:已经遍历的路径
def dfs(remain, path, k):
if not remain:
return path
l = len(remain)
count = fact(l-1) # 对于每一层,该层每个分支的数量相当于剩余数量-1的阶乘
for i in range(l):
if k > count: # 此处如果k大于该分支排列数量,那么减去该分支
k -= count
else:
return dfs(remain[:i]+remain[i+1:], path+remain[i], k)
return dfs([str(i) for i in range(1, n+1)], '', k)
# leetcode 77 组合
# 给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
def combine(self, n, k):
"""
:type n: int
:type k: int
:rtype: List[List[int]]
"""
def backtrack(first = 1, curr = []):
# if the combination is done
if len(curr) == k:
output.append(curr[:])
return
for i in range(first, n + 1):
# add i into the current combination
curr.append(i)
# use next integers to complete the combination
backtrack(i + 1, curr)
# backtrack
curr.pop()
output = []
backtrack()
return output
# leetcode79 单词搜索
# 定义上下左右四个行走方向
directs = [(0, 1), (0, -1), (1, 0), (-1, 0)]
def exist(self, board, word):
"""
:type board: List[List[str]]
:type word: str
:rtype: bool
"""
m = len(board)
if m == 0:
return False
n = len(board[0])
mark = [[0 for _ in range(n)] for _ in range(m)]
for i in range(len(board)):
for j in range(len(board[0])):
if board[i][j] == word[0]:
# 将该元素标记为已使用
mark[i][j] = 1
if self.backtrack(i, j, mark, board, word[1:]) == True:
return True
else:
# 回溯
mark[i][j] = 0
return False
def backtrack(self, i, j, mark, board, word):
if len(word) == 0:
return True
for direct in self.directs:
cur_i = i + direct[0]
cur_j = j + direct[1]
if cur_i >= 0 and cur_i < len(board) and cur_j >= 0 and cur_j < len(board[0]) and board[cur_i][cur_j] == word[0]:
# 如果是已经使用过的元素,忽略
if mark[cur_i][cur_j] == 1:
continue
# 将该元素标记为已使用
mark[cur_i][cur_j] = 1
if self.backtrack(cur_i, cur_j, mark, board, word[1:]) == True:
return True
else:
# 回溯
mark[cur_i][cur_j] = 0
return False
#
if __name__ == '__main__':
pass