Leetcode刷题总结-4.回溯算法篇

Leetcode刷题总结

回溯算法刷题心得、总结

前言

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


一、回溯算法刷题思路

Notes:首先来说一说回溯算法模板,这是卡尔总结的,我只是学习到了做个记录,下面来看:
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}

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

}

  1. 力扣 77 题 组合,https://leetcode.cn/problems/combinations/;
    思路:题目描述中给定两个整数 n 和 k,让你返回范围 [1, n] 中所有可能的 k 个数的组合;这道题就是回溯算法的典型题目,递归的参数肯定就是n、k、startIndex(开始搜索的下标),当path的大小等于k的时候需要收货结果把path的内容放入result数组中,这道题目可以不给终止条件,因为遍历到第n个数结束即可,在单层遍历的逻辑中放入数字,继续从下一个数开始递归,然后回溯。

  2. 力扣 216 题 组合总和III,https://leetcode.cn/problems/combination-sum-iii/;
    思路:题目描述中找出所有相加之和为n的k个数的组合,且只使用数字1到9,每个数字最多使用一次,让你返回所有可能的有效组合的列表;这道题比组合这个题难度高了一点,但其实类似终止条件就是组合的大小长度为k的时候,再去判断是否等于n等于的话放入结果集,不等就返回;单层逻辑去遍历的时候再加入path中的时候也要累加一下放进去的值方便后面看是不是想要的结果,在递归遍历,回溯即可。

  3. 力扣 17 题 电话号码的字母组合,https://leetcode.cn/problems/letter-combinations-of-a-phone-number/;
    思路:题目描述中给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合,答案可以按任意顺序返回,给出数字到字母的映射如下(与电话按键相同),注意 1 不对应任何字母;这道题还是比较有难度的,关键在于每个数字对应的字符串怎么取,通过定义一个字符串类似于string s = {"a", "b", "c"}的形式来通过数字来找到对应的英文字符;递归的终止条件就是索引的下标超过给定的数字 2-9 的字符串的下标然后就收获结果,单层递归的逻辑就是找到字符 2-9 对应的英文字符串,for循环遍历整个字符串,放入path中,递归,回溯。

  4. 力扣 39 题 组合总和,https://leetcode.cn/problems/combination-sum/;
    思路:题目描述中给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回(同一个 数字可以 无限制重复被选取);这道题的思路和前面的组合问题类似,不再赘述,说一下有区别的地方,一点和216题 组合总和III不同的地方就是往下递归的时候不是从下一个数开始,而是还是当前数开始,因为数可以被用无限次

  5. 力扣 40 题 组合总和 II,https://leetcode.cn/problems/combination-sum-ii/;
    思路:题目描述中给定一个候选人编号的集合 candidates 和一个目标数 target ,找出candidates 中所有可以使数字和为 target 的组合,candidates 中的每个数字在每个组合中只能使用一次 ;这道题的思路还是和前面的组合问题类似,不再赘述,说一下有区别的地方,题目中很重要的一个点就是要去重,而且是树层去重,也就是在同一层递归取数的时候如果有相同的就不用再遍历下一个数了,那么需要去重所以需要对数组的数进行排序,这也是这道题和其他几道题不同的地方

  6. 力扣 131 题 分割回文串,https://leetcode.cn/problems/palindrome-partitioning/;
    思路:题目描述中给你一个字符串 s,让你将 s 分割成一些子串,使每个子串都是回文串,返回 s 所有可能的分割方案(回文串是正着读和反着读都一样的字符串);这道题目也是比较有难度的,首先你需要写一个函数类似于双指针,从前向后和从后向前遍历看是否相等,也就是判断是否是回文串;终止条件是startIndex大于字符串的大小,在for循环遍历的过程中,先判断是否为回文串,是回文串放入path中,继续进行递归,然后回溯,不是回文串的话就继续进行for循环,最后返回结果即可。

  7. 力扣 93 题 复原IP地址, https://leetcode.cn/problems/restore-ip-addresses/;
    思路:题目描述中要求有效IP 地址正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔,例如:“0.1.2.201” 和 “192.168.1.1” 是有效 IP 地址,但是 “0.011.255.245”、“192.168.1.312” 和 “192.168@1.1” 是 无效IP地址,你可以按任何顺序返回答案;这道题目还是很有难度的,首先你需要一个函数去判断你分割的子串是否是合法的,也就是每个子串位数大于1不能以0开头,最多三位、而且不能超过255,实现了这个之后继续往下做;因为IP地址是四个子串组成的,所以当字符'.'的个数为3的时候结束了,IP地址合法的话就放入结果集中;回溯的终止条件就很难想,接下来for循环遍历宽度的时候,需要判断子串是否合法,合法的话加入'.'分割已经判断过的子串,字符'.'的个数加1;从下一个字符开始继续递归遍历,然后再取出'.',字符'.'的个数减一;回溯结束后返回数组即可。

  8. 力扣 78 题 子集, https://leetcode.cn/problems/subsets/;
    思路:题目描述中给你一个整数数组 nums ,数组中的元素互不相同,返回该数组所有可能的子集(幂集),解集不能包含重复的子集,你可以按任意顺序返回解集;这道题目就是中规中矩的回溯算法解决的问题,比较常规,path暂时存放结果,最终的结果在result中,唯一一点易错的是path中的东西要在递归函数最开始的时候放进去,不这样的话,最后一个满数组的结果放不到结果集中,递归的终止条件是startIndex等于数组的长度,在for循环中加入元素,然后放到path中,回溯,取出元素,返回result数组即可。

  9. 力扣 90 题 子集II, https://leetcode.cn/problems/subsets-ii/submissions/;
    思路:题目描述中给你一个整数数组 nums ,其中可能包含重复元素,让你返回该数组所有可能的子集(幂集),解集不能包含重复的子集;这道题和子集那道题类似,只是要去重,请记住去重很重要的一点就是排序,对数组排序后去重很方便,通过定义一个unordered_set,放入path元素前也要放入集合中,然后每次在path中放入元素时候先判断集合中是否存在,如果存在了就不在放入,继续循环,其他的地方和子集没有区别。

  10. 力扣 491 题 递增子序列, https://leetcode.cn/problems/non-decreasing-subsequences/;
    思路:题目描述中给你一个整数数组 nums ,让你找出并返回所有该数组中不同的递增子序列,递增子序列中至少有两个元素(数组中可能含有重复元素,如出现两个整数相等);和子集II是一样的去重思路,但是题目要求的是递增子序列,所以在判断是否跳过放入path元素的时候,要多加一个判断条件就是:如果path不为空,而且要插入的元素小于path尾部的元素,那就说明白不是递增的序列,直接跳过即可,其他地方没有区别不再赘述。

  11. 力扣 46 题 全排列, https://leetcode.cn/problems/permutations/;
    思路:题目描述中给定一个不含重复数字的数组 nums ,返回其所有可能的全排列 ,你可以按任意顺序返回答案;这道题是排列问题,和组合问题的区别就是for循环的起始位置不再是startIndex而是0,因为我们要把所有没用过的元素都要用到;为了用过一次后不再重复使用,需要使用一个标记是否使用过的数组(记为used数组),其他的地方和组合问题类似,不再赘述。

  12. 力扣 47 题 全排列II, https://leetcode.cn/problems/permutations-ii/submissions/;
    思路:题目描述中给定一个可包含重复数字的序列 nums ,按任意顺序返回所有不重复的全排列;这道题目比全排列难一点,因为有重复的元素,之前已经说过,去重必须对数组进行排序,排序完成后方便去重,然后在判断是否跳过将数加入path中的时候,加一个是否有相邻的元素相同,如果相同也跳过即可,其余地方不再赘述。

二、单调栈刷题思路

  1. 力扣 739 题 每日温度, https://leetcode.cn/problems/daily-temperatures/;
    思路:题目描述中给定一个整数数组 temperatures ,表示每天的温度,让你返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后,如果气温在这之后都不会升高,请在该位置用 0 来代替;这道题的思路就是用单调栈,题目要找的是右边大的元素,因此单调栈就必须是单调递增的,因为如果要入栈的元素比栈口元素大就要把它放入结果集中,并且把栈口元素出栈了;那就是入栈的元素比栈口元素大就要把它放入结果集中,比它小就一直往入栈即可,最后返回结果集。

  2. 力扣 496 题 下一个更大元素I, https://leetcode.cn/problems/next-greater-element-i/;
    思路:题目描述中n给你两个没有重复元素 的数组 nums1 和 nums2 ,下标从0开始计数,其中nums1是nums2 的子集,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j] 的 下一个更大元素 ,如果不存在下一个更大元素,那么本次查询的答案是 -1,
    让你返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的下一个更大元素 ;这道题还是比较有难度的,难点在于你需要找到nums1数组的元素和下标的映射,因为result数字和nums1是一样大小的,而且result数组的结果是刚好对应nums1中的每个元素,因此我们需要使用一个unoedered_map去找到这个映射关系,接下来的步骤就是遍历nums2中的元素,如果找到了比栈口元素大的,那就先在nums1中找该元素,如果能找到,那就用映射找到它在nums1数组的下标,在result数组把对应的下标的元素赋值为当前的nums2[i],然后继续遍历直到遍历完nums2数组,返回结果即可。

  3. 力扣 503 题 下一个更大元素II, https://leetcode.cn/problems/next-greater-element-ii/;
    思路:题目描述中给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素,数字 x 的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1 ;这道题的思路和每日温度类似,有一点细微的区别,这道题与每日温度的区别在于要循环的搜索更大的数,这时候就需要把索引的下标扩充为1原先的2倍,扩充后会超过数组的大小对吧,那就用这个索引值对数组的大小取余,这样就不会超过界限而且可以循环找了,其他地方不再赘述。

  4. 如何循环每隔一个数取一个数(循环的取);网易有道2017内推编程题(2.构造队列)

#include<iostream>
#include <vector>
using namespace std;

int main() {
    int t;
    cin >> t;
    bool flag = true;
    while(t--) {
        int n;
        cin >> n;
        vector<int> nums(n, 0);
        int seed = 1, i = 0;
        while(seed <= n) {
            if(nums[i] == 0) {
                flag = !flag;
            }
            if(nums[i] == 0 && flag) {
                nums[i] = seed;
                seed++;
            }
            i = ( i + 1) % n;
        }
        for(int i = 0; i < n; i++) {
            cout << nums[i] << " ";
        }
        cout << "\n";

    }


    return 0;
}

总结

本文主要是分享回溯算法求解问题的思路,回溯算法基本上也是递归三部曲解决,只是有些具体问题需要对细节分析一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值