代码训练营 Day22 | 回溯算法理论基础 | 77. 组合 |216.组合总和III | 17.电话号码的字母组合

回溯算法理论基础

什么是回溯算法

1. 回溯和递归是相辅相成的,只要有递归就会有回溯

2. 递归函数的下面的部分就是回溯的逻辑

   1. 递归结束后向次级函数回退的过程

使用原因以及解决的问题

1. 回溯算法其实就是纯暴力搜索,并不是一个高效的算法

2. 因为有些问题暴力搜索是唯一办法

3. 能解决的问题:

   1. 组合问题: 给你个集合找出大小为n的组合

   2. 切割问题: 给一个字符串,如何切割才能保证子串都是回文字符串,并有多少种切割方式

   3. 子集问题: 罗列出来该集合的子集

   4. 排列问题:

   5. 棋盘问题: 解数独,N皇后

4. 组合是无序的(1,2)(2,1)算一种,排列是有序的(1,2)(2,1)算两种

   

如何理解回溯法

1. 回溯算法可以抽象成一个n叉树形结构

   1. 树的宽度,就是回溯法中集合的大小

   2. 树的深度,递归的深度

2. 递归函数没有返回值

回溯算法模板

// 参数一般是写完逻辑,再填写参数
void backtarcking(参数){
    // 递归终止条件
    if(终止条件){
        // 达到终止条件,说明应该收集结果
        return;
    }

    // 递归单层搜索逻辑
    for(集合的元素){//用来处理集合里的每一个元素
        // 处理节点
        // 递归函数
        // 回溯操作;撤销处理节点的操作
    } 
}

77. 组合

1. 回溯算法通过递归来控制多少层for循环

2. 因为这里是组合,我们去完一个元素就不要取前面的那个元素,不然会重复

   1. 1,2; 2,1是一个

3. 求组合的时候,元素是不能重复使用的

剪枝优化

n = 4, k = 4,元素:1,2,3,4

1. 由于只有4个元素,我们目标集需要4个元素为一个组合

2. 当我们选择完元素1之后,就没有必要再看从元素2开始选择了,因为后面都不符合要求,因此我们可以进行减枝

class Solution(object):
    def backtracking(self,n,k,startindex,path,result):
        # recursion end condition
        if len(path) == k:
            # we find our leaf,which is our answer set append it to our array
            result.append(path[:])
            return
        # each level recursion logic
        """  
            1. 已经选择的元素个数:path.size();
            2. 还需要的元素个数为:k - path.size();
            3. n-(k-len(path))这个是代表 剩下元素中还需要选取的元素个数
            4. +1的目的是因为for循环时左闭右闭区间,因为for循环要包含这个区间所以要+1
                这里python是+2,因为range是左关右开区间的所以要再+1的基础上再次+1
                
        """
        for i in range(startindex,n-(k-len(path))+2):
            # add element into our array
            path.append(i)
            # recursion start
            self.backtracking(n,k,i+1,path,result)
            # backtracking
            path.pop()

    def combine(self, n, k):
        """
        :type n: int
        :type k: int
        :rtype: List[List[int]]
        """
        result = [] # collect final answer set two dimension array
        self.backtracking(n,k,1,[],result)

        return result

216.组合总和III

0. 本题和77题类似

1. k等于几说明就需要几层for循环去遍历答案相加的值

2. 剪枝操作:

    1. 如果我们的sum大于targetsum的话,直接return没有必要再去做递归了(放在递归停止条件)

    2. 如果目前剩余的元素,不够k个目标元素了,说明后面集合没必要去遍历了(放在for循环取数的时候)

       1. 所以没有必要遍历到9,遍历到8就可以

       2. for循环里面的i是控制集合的起始位置

       3. k—path.size: 还需要多少个元素

       4. 如果还需要两个元素,i最多到8才能满足我们所却的元素

       5. 9-(k-path.size)+1: 代表后面还缺多少个元素,+1的目的是下标补齐

          1. 也就是说i最多指向8,才能保证后面元素满足k的要求

3. 假设从i开始取,则从i到n一共有n-i+1个元素,而当前还需要k-path.size()个元素,所以必须满足n-i+1>=k-path.size(),移项就可以得到i<=n+1-(k-path.size())

class Solution(object):
    def backtracking(self,k,n,currentSum,startindex,path,result):
        """  
            k:find all valid combination
            n: number that sum up to
            sum_: store sum of each array
            path: record indivual cobination
            result: final result set
        """
        # recursion end codition
        if len(path) == k:
            # reduce unncerssary calucaltion
            if currentSum > n:
                # if our sum bigger than target value,just return don't recursion
                return
            # check if they array number can add to n
            if currentSum == n:
                # we find our valid combination
                result.append(path[:])
            return
        
        # recursion logic
        """  
            suppose start from i, from i to n have this amount n-i+1 elements,
            currently we still need k-len(path) amount element's
            which, n-i+1 >= len(path) then, we can get i <= n-(k-len(path)+1)
        """
        for i in range(startindex,9-(k-len(path))+2):
            # add current value
            currentSum += i
            # process each case
            path.append(i)
            # recursion
            self.backtracking(k,n,currentSum,i+1,path,result)
            
            # backtracking
            currentSum -= i
            path.pop()

    def combinationSum3(self, k, n):
        """
        :type k: int
        :type n: int
        :rtype: List[List[int]]
        """
        result = []  # store our final answer result set
        self.backtracking(k,n,0,1,[],result)

        return result
        

17.电话号码的字母组合 

1. 数字到字符串还需要做一个映射

   1. 可以用二维数组或者map做一个映射

   2. 二维数组 0 和 1的位置对应的字符串是空

   3. 其他数字对应的字符串就是,电话上对应的字母

2. 输入的数字的个数,对应树的深度

3. 每一个数字所对应字母的长度,对应的是树的宽度

class Solution(object):
    def __init__(self):
        self.leeterMap = [
            "",     # 0
            "",     # 1
            "abc",  # 2
            "def",  # 3
            "ghi",  # 4
            "jkl",  # 5
            "mno",  # 6
            "pqrs", # 7
            "tuv",  # 8
            "wxyz"  # 9
        ]
        self.result = []
        self.s = ""
    def backtracking(self,digits,index):
        # recursion stop condition
        if index == len(digits):
            self.result.append(self.s)
            return 

        # convert index place string to int 
        digit = int(digits[index])
        # get stirng set
        letters = self.leeterMap[digit]

        for i in range(len(letters)):
            # process string
            self.s += letters[i]
            # recursion
            self.backtracking(digits,index+1)
            # backtracking
            self.s = self.s[:-1]

    def letterCombinations(self, digits):
        """
        :type digits: str
        :rtype: List[str]
        """
        # if digits set is empty
        if len(digits) == 0:
            return self.result
        self.backtracking(digits,0)

        return self.result
        

  • 11
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值