首页 > 其他分享 >day22-back tracking-part01-7.24

day22-back tracking-part01-7.24

时间:2024-07-29 15:54:45浏览次数:16  
标签:tracking part01 7.24 self startIndex result 回溯 path backtracking

tasks for today:

1.回溯理论基础

2.77.组合

3.216.组合总和III

4.17.电话号码的字母组合

-------------------------------------------------------------------

1.回溯理论基础

- 什么是回溯:在二叉树系列中,我们已经不止一次,提到了回溯,回溯是递归的副产品,只要有递归就会有回溯。所以以下讲解中,回溯函数也就是递归函数,指的都是一个函数

- 回溯法的效率:虽然回溯法很难,很不好理解,但是回溯法并不是什么高效的算法因为回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,如果想让回溯法高效一些,可以加一些剪枝的操作,但也改不了回溯法就是穷举的本质。

- 回溯法可以解决的问题:回溯法,一般可以解决如下几种问题:

  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 棋盘问题:N皇后,解数独等等

- 理解回溯法:回溯法解决的问题都可以抽象为树形结构,所有回溯法的问题都可以抽象为树形结构。因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度就构成了树的深度。递归就要有终止条件,所以必然是一棵高度有限的树(N叉树)。

- 回溯法模板:

  • 回溯函数模板返回值以及参数

回溯算法中函数返回值一般为void。对于参数,因为回溯算法需要的参数不像二叉树递归的时候那么容易一次性确定下来,所以一般是先写逻辑,然后需要什么参数,就填什么参数。

  • 回溯函数终止条件

既然是树形结构,就知道遍历树形结构一定要有终止条件。所以回溯也有要终止条件。什么时候达到了终止条件,树中就可以看出,一般来说搜到叶子节点了,也就找到了满足条件的一条答案,把这个答案存放起来,并结束本层递归。

  • 回溯搜索的遍历过程

在上面我们提到了,回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成的树的深度。

for循环就是遍历集合区间,可以理解一个节点有多少个孩子,这个for循环就执行多少次。

backtracking这里自己调用自己,实现递归。

大家可以从图中看出for循环可以理解是横向遍历,backtracking(递归)就是纵向遍历,这样就把这棵树全遍历完了,一般来说,搜索叶子节点就是找的其中一个结果了。

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

2.77.组合

This practice is good for understanding the back tarcking. If we use search method, there should be k layers loops for finding all the solutions, this will bring complicated embedded code structure, especially when k is large.

Therefore, the back tracking is suitable for this operation, the for-loop traverse in horizon direction while the backtracking function help proceed next level's traverse.

One notable point is the optimization of the algorithm which is branch-cutting. This is caused by the value of k, for example, when n = 4, k = 4, only [1,2,3,4] is valid with startpoint at 1, the [2,3,4] search with start point of 2 is unnecessary. so, these search can be cut by limitting the end index in for-loop.

class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        result = []
        path = []
        self.backtracking(n, k, 1, path, result)

        return result
    
    def backtracking(self, n, k, startIndex, path, result):
        if len(path) == k:
            # !!!!!!! path more attention here, this "result.append(path[:])" must use path[:], instead of directly use path, that is because: 
            # "result.append(path[:])" This appends a copy of the list path to result.
            # while, "result.append(path)" This appends a reference to the original list path to result
            result.append(path[:])
            return
        
        # choose for-loop condition from A or B
        # A. this version cut branches
        for i in range(startIndex, (n - (k - len(path)) + 1) + 1):
        # B. this version does not cut branch:
        for i in range(startIndex, n + 1):
            path.append(i)
            self.backtracking(n, k, i + 1, path, result)
            path.pop()

3.216.组合总和III

This practice, compared with the practice 77, the main difference is an additional condition which require the sum of the path is n while the length of the path is k. The ordinary backtracking is easy to finish just with an additional condition in terminal logic, as blow:

class Solution:
    def combinationSum3(self, k: int, n: int) -> List[List[int]]:
        result = []
        path = []
        self.backtracking(k, n, 1, path, result)
        return result
    
    def backtracking(self, k, n, startIndex, path, result):
        if len(path) == k and sum(path) == n:
            result.append(path[:])

        for i in range(startIndex, 10):
            path.append(i)
            self.backtracking(k, n, i + 1, path, result)
            path.pop()

The notable point in this practice is the branch cutting operation. there are two condtion that can contribute to the branch cutting which is the sum n and the length k. 

Through trail, solely use length k to do cutting will report error, while solely use sum n do cutting can be valid, the best efficiency will be derived by using both the sum n and the length k, as below:

class Solution:
    def combinationSum3(self, k: int, n: int) -> List[List[int]]:
        result = []
        path = []
        self.backtracking(k, n, 1, path, result)
        return result
    
    def backtracking(self, k, n, startIndex, path, result):
        if sum(path) > n:
            return

        if len(path) == k and sum(path) == n:
            result.append(path[:])
            return

        # when cutting branch, if only use the k do cut, it will report error, this should be used with sum n together.
        for i in range(startIndex, (9 - (k - len(path)) + 1) + 1):
            path.append(i)
            self.backtracking(k, n, i + 1, path, result)
            path.pop()

        # in this practice, the branch cutting would be better if the condition is the sum instead of the number k, because, there might be the sum reach n frist while the number is still less than k, then it is unnecessary to keep searching

4.17.电话号码的字母组合

IN this practice, if there are n digits, then there woule be n set of letters, each set containing 3 letters, the target is to find all the letters combination, each element in the combination comes from a different digit's set

In this practice, the termination condition is reaching the length of digits str.

each for-loop traverse's length is decided by the size of the corresponding letter set of the digits, which do not involve branch cutting, the startIndex in this practice is used to decide which digit's set is being traversed.

The difference between this practice and the previous two combinarion problem is that, the previous two practice is finding combination from one single set, while in this practice, the combination's elements come from different set. This make difference in the definition of the startIndex.

class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        result = []
        path = []
        letterMap = {
            '0': "",
            '1': "",
            '2': "abc",
            '3': "def",
            '4': "ghi",
            '5': "jkl",
            '6': "mno",
            '7': "pqrs",
            '8': "tuv",
            '9': "wxyz",
        }
        self.backtracking(digits, 0, letterMap, path, result)
        return result
    
    # pay attention to the startIndex, it means different from the previous combination finding problem
    def backtracking(self, digits, startIndex, letterMap, path, result):
        if len(path) == len(digits):
            if len(digits) == 0:
                return
            result.append(''.join(path[:]))
            return
        
        for i in letterMap[digits[startIndex]]:
            path.append(i)
            self.backtracking(digits, startIndex + 1, letterMap, path, result)
            path.pop()

标签:tracking,part01,7.24,self,startIndex,result,回溯,path,backtracking
From: https://blog.csdn.net/bbrruunnoo/article/details/140655654

相关文章

  • day27-greedy-part01-7.29
    tasksfortoday:1.理论基础2.455.分发饼干3.376.摆动序列4.53.最大子序和------------------------------------------------------------------1.理论基础(1)贪心的本质是选择每一阶段的局部最优,从而达到全局最优。经常与贪心算法放在一起进行比较的就是动态规划,以下是......
  • 代码随想录算法训练营第22天-leetcode-回溯算法part01:
    #回溯算法理论基础能解决的问题:组合问题:N个数里面按一定规则找出k个数的集合切割问题:一个字符串按一定规则有几种切割方式子集问题:一个N个数的集合里有多少符合条件的子集排列问题:N个数按一定规则全排列,有几种排列方式棋盘问题:N皇后,解数独等等第77题.组合力扣题目链接(op......
  • DAY10 栈与队列part01
     理论基础文章讲解:https://programmercarl.com/%E6%A0%88%E4%B8%8E%E9%98%9F%E5%88%97%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html232.用栈实现队列 注意为什么要用两个栈题目链接/文章讲解/视频讲解:https://programmercarl.com/0232.%E7%94%A8%E6%A0%88%E5%AE%9E%E7%8E%......
  • 7.24作业题
    1.定义一个整形数组arr,长度为5,终端输入5个数,依次存入数组中。#include<stdio.h>#include<string.h>intmain(intargc,constchar*argv[]){ intarr[5]; inti; for(i=0;i<5;i++) { printf("请输入第%d个数\n",i+1); scanf("%d",&arr[i]); } f......
  • 7.24 之后的考试总结
    7.24\(100+100+100+0=300\)。整体:前三题发挥稳定,最后一题确实超出了我的知识范围,但是较为简单的30pts没有细想,比较可惜。T1:两个0-1串,每次选中两个串上的两个等长(位置不一定相等)区间,询问两个区间不相等的位置数量,对\(2\)取模。Solution:考虑对2取模经典问题模型,这类问......
  • JAVA集合 day7.24
    一.Collections类1.1Collections常用功能常用方法:publicstaticvoidshuffle(List<?>list):打乱集合顺序。publicstaticvoidsort(Listlist):将集合中元素按照默认规则排序。publicstaticvoidsort(Listlist,Comparator<?superT>com):将集合中元素......
  • 代码随想录算法训练营第二十二天|回溯算法part01
    第77题.组合在单个集合1-n中,寻找所有个数为k的组合。和所有递归一样,都有三部曲。确定参数与返回值确定终止条件单层逻辑首先,回溯算法的返回值一般是void,参数依照题目要求而增加,在这一题中,参数有n,k还有startIndex。终止条件是path的size等于k,将path存放在result中。......
  • Day 22 回溯算法 part01
    77.组合我的这个解法还算挺简单易懂的,还是看注释classSolution{List<List<Integer>>ans=newArrayList();//存储最终结果集合List<Integer>tmp=newArrayList();//记录单次的pathpublicList<List<Integer>>combine(intn,intk){backTr......
  • 2024.7.24 test
    A给定序列\(A\),满足对于\(i\)为奇数的\(A_i=\frac{i+1}{2}\),\(i\)为偶数的\(A_{i}=n+1-\frac{i}{2}\)。多次给出\(s\),求有多少\(l,r\in[1,n]\)满足\(\sum_{i=l}^rA_i=s\)。\(n\le10^9,s\le10^{18}\)。简单分讨,判断\(s\)是否为\(n+1\)或\(n+2\)的倍数。B定......
  • 7.24日进制转换测试总结
    7.24日进制转换测试总结比赛传送门补充知识点:\(1.\)\(X\)进制\(\to\)十进制位值累加法所有进制位的最小单位都是1①写出所有位的位号②基数的位号次方\(\implies\)位权③十进制数字\(=\)位权\(\times\)该位上的数字之和\(Code:\)intto_ten(stringop,intx)......