在力扣的题解上看到一篇题解,里面提到了回溯类题目的判断方式,于是直接稍微做了一点关于回溯的题,得出了一些做题经验。
一、如何判断一个题目是否涉及回溯?
本学期第一次做到的回溯的题目是洛谷的P1036《选数》
我不清楚这道题是否存在数学上的公式通解,见到这种题目,在题目输入的数据量不大的情况下,我一般都是会直接想办法生成全组合,然后判断筛选出符合条件的答案。
除此之外,蓝桥杯模拟赛上有两道填空题如果是编程题的话也是可以用到回溯的。
以上我们做过的三道题都和回溯有关,都是可以通过生成全组合得出答案的。而第一题是求解的个数,其余两个是求解的详细信息。
二、回溯怎么写?
我们先看一下以上三道题的递归函数
(1)选数
(2)合法括号
(3)字母排序
可以看出一些问题:
1、三个题目的函数都是有共通之处的,参数都有left_number(括号那题是left,代表左括号数量),表示递归时当前的数据离结果还有次递归,都有start和end,表示本次函数的初始要处理的数据和结束的条件,函数内部第一部分肯定是跳出条件。
2、《选数》和《字母排序》函数内部都有一个for循环,但《合法括号》里没有,为什么呢?因为《合法括号》和另外两题不一样,另外两个题目需要把结果组合出来,再判断,而《合法括号》这一题是先判断再组合。所以有没有for循环,需要根据题目而定。
3、如果是求解的个数,那么只需要一个int变量就行了,而如果是求所有解的详细信息,就需要一个res数组来存储满足条件的信息。
4、函数第一部分是跳出条件,第二部分是调用自己的方式。
5、根据不同的题目和解题的方法,调用自己的方式也不一样。
含for循环的递归函数基本固定会有三个参数(递归的深度)、(开头)、(结尾),递归的深度是便于递归函数条件判断跳出,调用自己的时候递归深度一般要减一,结尾的作用是循环次数上限,递归深度这个参数目前我做过的题来讲作用基本都是固定的,结尾这个参数可能有些题目会动态判断循环结束动态上限,但我还没有遇见过。
就这三道题来讲start这个参数意义在《选数》中和在《字母排序》中是两种意思,在《选数》中意味着这是循环的开始,因为选数这题 1+3和3+1是一个意思,所以记录下循环的开始,就不会有组合重复的情况发生,但《字母排序》中,ab和ba是两种组合,如果还是从start开始的话ab和ba就只能出现其中一种了,所以要从0开始循环,start是记录上次找到的字母的坐标,表示已经组合的字符串是以这个字符作为结尾,这次循环要排除掉它。
以上这些都要根据具体情况具体分析。
三、具体说一些这三道题的递归思路。
《选数》:
这个请直接看题解https://www.luogu.com.cn/problemnew/solution/P1036
《合法括号》:
这道题的关键是,如何才能知道一个嵌套括号是不是合法的。
这个如果无法从数学角度想清楚,至少也能通过观察例子的规律来得出结论。
我们可以手算穷举出:
()只有两种组合()、)(,其中 )( 是不合法的。
(())有六种组合(())、()()、())( 、 )(()、() )(、 ) )((,后四种都是不合法的。通过观察我们可以知道,不合法的括号组合只有一种规律,任意一个右括号,其左边的括号数都小于右边(包括自己)的括号数。
所以,判断条件只有一个,即每一次组合的时候,当前的结果左括号的数量一定要多于右边括号的数量,如果等于就加左括号。在左右括号数量都达到上限就存进数组里面。由于递归条件自带筛选效果,所以无需加额外判断。
《字母排序》:
这道题很简单,就是穷举,先找字符串的第一个字母,然后第一个字母固定,从头开始找下一个字母,循环条件每一次都是从0开始,选过的字母,在完整字符串参数上将那个字母赋值为‘?’,传递给下个函数,后面遇见‘?’时跳过。这种做法,如果字符串中出现重复的字符,就会出现重复的字符串,用set容易去掉重复的字符串,然后用迭代器计算set容器里成员的数量即可。我不知道是否有在循环时就筛选掉重复的字符串的方法,从而避免使用set。
一些比较复杂涉及到数学的题目,如:
在数据量不大的情况下,是可以避免求公式,用回溯通解的。