回溯算法

回溯算法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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值