leetcode_回溯算法

news2024/11/23 8:15:03

回溯算法刷题总结

  • 回溯法理论基础
  • 回溯算法的模板
  • 组合问题
    • 77.组合
      • 优化版本
    • 216.组合总和III
    • 17.电话号码的字母组合
    • 组合总和
    • 组合总和II
  • 分割
    • 131.分割回文串
    • 93.复原IP地址
  • 子集
    • 78.子集
    • 90.子集II
    • 491.递增子序列(和子集问题很像)
  • 排列
    • 全排列
    • 全排列II
  • 其他问题
    • 332.重新安排行程(深搜和回溯相辅相成)

回溯法理论基础

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

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

回溯算法的模板

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

组合问题

77.组合

力扣题目链接
将结果集ans和路径集path设置为全局变量

class Solution {
public:
    vector<vector<int> > ans;
    vector<int> path;
    void backtracking(int n,int k,int starttIndex){
        if(path.size()==k){
            ans.push_back(path);
            return;
        }
        for(int i =starttIndex;i<=n;i++)
        {
            path.push_back(i);
            backtracking(n,k,i+1);
            path.pop_back();
        }
    }
    vector<vector<int>> combine(int n, int k) {
       backtracking(n,k,1);
       return ans;
    }
};

优化版本

class Solution {
public:
    vector<vector<int> > result;
    vector<int> path;
    void backtracking(int startindex,int n,int k)
    {
        if(path.size()==k){
            result.push_back(path);
            return;
        }
        for(int i=startindex;i<=n-(k-path.size())+1;i++){
            path.push_back(i);
            backtracking(i+1,n,k);
            path.pop_back();
        }
    }
    vector<vector<int>> combine(int n, int k) {
        backtracking(1,n,k);
        return result;

    }
};

216.组合总和III

class Solution {
public:
    vector<vector<int> > result;
    vector<int> path;
    void backtracking(int startindex,int k,int n)
    {
        if(path.size()==k)
        {   
            int sum=0;
            for(int i =0;i<k;i++){
                sum+=path[i];
            }
            if(sum==n){
                result.push_back(path);
            }
            return;
        }
        for(int i=startindex;i<=9-(k-path.size())+1;i++){
            path.push_back(i);
            backtracking(i+1,k,n);
            path.pop_back();
        }
    }
    vector<vector<int>> combinationSum3(int k, int n) {
        backtracking(1,k,n);
        return result;
    }
};

17.电话号码的字母组合

class Solution {
public:
    string str[10]={" "," ","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
    vector<string> result;
    string path="";
    void backtracking(int n,string digits)
    {
        if(path.length()==digits.length())
        {
            result.push_back(path);
            return;
        }
        int num = digits[n]-'0';
        for(int i=0;i<str[num].length();i++)
        {
            path+=str[num][i]; //处理节点
            backtracking(n+1,digits); //
            path=path.substr(0,path.length()-1); //回溯,撤销
        }
    }
    vector<string> letterCombinations(string digits) {
        if (digits.length()==0){
            return result;
        }
        else{
            backtracking(0,digits);     
            return result;
        }
        
    }
};

组合总和

class Solution {
public:
    vector<vector<int> > result;
    vector<int> path;
    void backtracking(int startindex,int sum,int target,vector<int>& candidates)
    {
        if(sum>=target){
            if(sum==target){
                result.push_back(path);
            }
            return ;
        }
        for(int i=startindex;i<candidates.size();i++){
            sum+=candidates[i];
            path.push_back(candidates[i]);
            backtracking(i,sum,target,candidates);
            sum-=candidates[i];
            path.pop_back();
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtracking(0,0,target,candidates);
        return result;
    }
};

组合总和II

这道题考察的是“回溯算法中的去重,树层去重or树枝去重"。根据题意,要求元素在同一个组合内可以重复,但是两个组合不能相同。所以我们要去重的是同一数层上使用过,同一树枝上不用去重。

  1. 树层去重首先需要对数组进行排序
  2. 树层去重中使用了一个trick,加入了used数组(bool 类型),表示该数在同一树层或同一树枝中是否被使用过
    used[i-1]=true,说明同一树枝candidates[i-1]使用过
    used[i-1]=false,说明同一树层candidates[i-1]使用过
class Solution {
public:
    vector<vector<int> > result;
    vector<int> path;
    void backtracking(int startindex, int sum, int target, vector<int>& candidates, vector<bool>& used)
    {
        if(sum==target){
            result.push_back(path);
            return;
        }
        for(int i=startindex;i<candidates.size()&&sum+candidates[i]<=target;i++){
            if(i>0&&candidates[i]==candidates[i-1]&&used[i-1]==false) continue; //注意i>0
            sum+=candidates[i];
            path.push_back(candidates[i]);
            used[i]=true;
            backtracking(i+1,sum,target,candidates,used);
            used[i]=false;
            path.pop_back();
            sum-=candidates[i];
        }
    }

    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<bool> used(candidates.size(),false);
        sort(candidates.begin(),candidates.end());
        backtracking(0,0,target,candidates,used);
        return result;
    }
};

分割

131.分割回文串

使用回溯法分割回文串的精髓在于切割线的确定

在这里插入图片描述
在每一层遍历的时候,startindex到 i 之间的子串即可被用来去判定是否是回文串,如果是则更新切割线位置,进入下一层枝干,如果不是继续 i+1更新切割线位置。

class Solution {
public:
    vector<vector<string> > res;
    vector<string> path;
    bool isPalindrome(string& str, int start, int end){
        for(int i =start,j=end;i<j;i++,j--){
            if(str[i]!=str[j]){
                return false;
            }
        }
        return true;
    }
    void backtracking(string& str, int startindex){
        if(startindex>=str.size()){
            res.push_back(path);
            return;
        }
        for(int i =startindex;i<str.size();i++){
            if(isPalindrome(str,startindex,i)){
                path.push_back(str.substr(startindex,i-startindex+1));
                backtracking(str,i+1);
                path.pop_back();
            }
            else continue; 
        }
    }
    vector<vector<string>> partition(string s) {
        backtracking(s,0);
        return res;
    }
};

93.复原IP地址

这里在判断isIP时候卡了一下,用了std::stoi函数,对于不能百分之百确定string转成int的,建议用s[i] - ‘0’一个一个转换。

class Solution {
public:
    vector<string> res;
    vector<string> path;
    bool isIP(string& s, int start, int end) {
        if(start>end||s[start]=='0'&&start!=end) return false;
        int num = 0;
        for(int i =start;i<=end;i++){
            if(s[i]>'9'||s[i]<'0') return false;
            num = num*10+(s[i]-'0');
            if(num>255) return false;
        }
        return true;
    }
    void backtracking(string& s, int startindex) {
        if (path.size() >= 4) {
            if (path.size() == 4 && startindex == s.size()) {
                string ans = "";
                for (auto p : path) ans = ans + p + ".";
                ans = ans.substr(0, ans.size() - 1);
                res.push_back(ans);
            }
            return;
        }
        for (int i = startindex; i < s.size(); i++) {
            if (isIP(s, startindex, i)) {
                path.push_back(s.substr(startindex, i - startindex + 1));
                backtracking(s, i + 1);
                path.pop_back();
            }
            else continue;
        }
    }
    vector<string> restoreIpAddresses(string s) {
        backtracking(s, 0);
        return res;
    }
};

子集

78.子集

如果把子集问题、组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点,子集问题也是一种组合问题,因为它的集合是无序的。

class Solution {
public:
    vector<vector<int> > res;
    vector<int> path;
    void backtracking(vector<int>& nums,int startindex){
        if(startindex>=nums.size()){
            res.push_back(path);
            return;
        }
        res.push_back(path);  // 子集问题由于是找所有节点,所以每次res每次都要存值
        for(int i = startindex;i<nums.size();i++){
            path.push_back(nums[i]);
            backtracking(nums,i+1);
            path.pop_back();
        }
    }
    vector<vector<int>> subsets(vector<int>& nums) {
        backtracking(nums,0);
        return res;
    }
};

90.子集II

数组里包含有重复元素,而返回的解集却不能包含重复元素,这让我想起了树层去重,先试一下使用used数组。ac掉了,说明这道题就是树层去重

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    void backtracking(vector<int>& nums, vector<bool>& used, int startindex){
        if(startindex>=nums.size()){
            res.push_back(path);
            return;
        }
        res.push_back(path);
        for(int i=startindex;i<nums.size();i++){
            if(i>0&&nums[i]==nums[i-1]&&used[i-1]==false){
                continue;
            }
            used[i]=true;
            path.push_back(nums[i]);
            backtracking(nums, used, i+1);
            path.pop_back();
            used[i]=false;
        }
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        vector<bool> used(nums.size(),false);
        sort(nums.begin(),nums.end());
        backtracking(nums,used,0);
        return res;
    }
};

491.递增子序列(和子集问题很像)

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    void backtracking(vector<int>& nums, int startindex){
        if(path.size()>1){
            res.push_back(path);
        }
        unordered_set<int> uset;
        for(int i =startindex;i<nums.size();i++){
            if((!path.empty()&&nums[i]<path.back())||uset.find(nums[i])!=uset.end()) continue;
            uset.insert(nums[i]);
            path.push_back(nums[i]);
            backtracking(nums,i+1);
            path.pop_back();
        }
    }
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        backtracking(nums,0);
        return res;
    }
};

排列

全排列

有组合问题改成排列问题需要注意两点,第一点是不需要startindex,第二点是需要使用一个used数组来记录元素是否被使用过

class Solution {
public:
    vector<vector<int> > res;
    vector<int> path;
    void backtracking(vector<int>& nums,vector<bool>& used){
        if(path.size()==nums.size()){
            res.push_back(path);
            return;
        }
        for(int i =0;i<nums.size();i++){
            if(used[i]!=true){
                path.push_back(nums[i]);
                used[i] = true;
                backtracking(nums,used);
                used[i] = false;
                path.pop_back();
            }
            else continue;
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        vector<bool> used(nums.size(),false);
        backtracking(nums,used);
        return res;
    }
};

全排列II

class Solution {
public:
    vector<vector<int> > res;
    vector<int> path;
    void backtracking(vector<int>& nums,vector<bool> used)
    {
        if(path.size()==nums.size()){
            res.push_back(path);
            return;
        }
        for(int i =0;i<nums.size();i++){
            if(i>0&&nums[i]==nums[i-1]&&used[i-1]==false){
                continue;
            }
            if(used[i]==false){
                path.push_back(nums[i]);
                used[i] = true;
                backtracking(nums,used);
                used[i] = false;
                path.pop_back();
            }
        }
    }
    vector<vector<int> > permuteUnique(vector<int>& nums) {
        vector<bool> used(nums.size(), false);
        sort(nums.begin(),nums.end());
        backtracking(nums,used);
        return res;
    }
};

其他问题

332.重新安排行程(深搜和回溯相辅相成)

class Solution {
public:
    unordered_map<string,map<string,int>> target;
    vector<string> res;
    bool backtracking(vector<vector<string>>& tickets){
        if(res.size()==tickets.size()+1){
            return true;
        }
        for(auto &tar:target[res[res.size()-1]]){
            if(tar.second>0){
                res.push_back(tar.first);
                tar.second--;
                if(backtracking(tickets)) return true;
                res.pop_back();
                tar.second++;
            }
        }
        return false;
    }
    vector<string> findItinerary(vector<vector<string>>& tickets) {
        for(auto ticket:tickets){
            target[ticket[0]][ticket[1]]++;
        }
        res.push_back("JFK");
        backtracking(tickets);
        return res;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/384357.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RK3568平台开发系列讲解(Linux系统篇)伪文件系统目录详细介绍

🚀返回专栏总目录 文章目录 一、procfs文件系统二、sysfs文件系统沉淀、分享、成长,让自己和他人都能有所收获!😄 📢除了专门用于存储设备记录文件的文 件系统外,Linux 内核还提供了procfs、sysfs 等伪文件系统。 伪文件系统存在于内存中,通常不占用硬盘空间,它以文…

QML 键盘事件

作者: 一去、二三里 个人微信号: iwaleon 微信公众号: 高效程序员 和鼠标一样,键盘同样也提供了用户交互的能力,所以在介绍完《QML 鼠标事件》之后,是时候深入键盘事件了。 在 QML 中,有一个附加属性 - Keys,是专供可视元素进行按键处理的。当用户按下或释放一个按键时…

DDD:统一语言

目录一、统一语言的作用阐述二、统一语言与领域分析2.1、统一的领域术语2.2、统一的领域行为描述三、统一语言落地执行一、统一语言的作用阐述 【统一语言】&#xff0c;怎么强调都不为过&#xff01;&#xff01; 日常沟通中&#xff0c;时常会出现这么一幕&#xff1a;A同学…

第四章 reactive对象的简单实现以及reactive的依赖收集和触发依赖

reactive对象的简单实现 主要通过reactive.spec.ts这个测试案例来实现功能 import { reactive } from "../reactive"describe(reactive,()>{it(happy path,()>{const original {foo:1}const observed reactive(original)expect(observed).not.toBe(origina…

Unity常见面试题详解(持续更新...)

一丶声明、定义、实例化、初始化 1、首先我们来讨论在C/C中的声明和定义.. 1&#xff09;我们先从函数声明和定义说起... 一般我们在C里都会先定义一个函数&#xff0c;然后再Main函数前将函数声明&#xff0c;比如&#xff1a; //函数声明 int Add(int);int Main {} //函数…

tmux终端复用软件

一、安装[rootpool-100-1-1-159 test]# yum install tmux [rootpool-100-1-1-159 test]# yum search tmux Repository extras is listed more than once in the configuration Last metadata expiration check: 0:33:52 ago on Fri 03 Mar 2023 09:10:34 AM CST.Name Exactly M…

【C++】适配器模式 -- stack/queue/dqueue

一、适配器模式 设计模式 设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结&#xff1b;Java 语言非常关注设计模式&#xff0c;而 C 并没有太关注&#xff0c;但是一些常见的设计模式我们还是要学习。 迭代器模式 其实我们在前面学习 strin…

call、apply、bind的区别以及源码实现

首先&#xff0c;需要明确&#xff0c;call()、apply()、bind()这三个方法的作用都是 改变this指向它们之间的不同点在于&#xff1a;call和apply是直接调用的&#xff0c;而bind不会立即调用&#xff0c;需要手动调用&#xff08;原因在于bind返回的是一个改变了this指向的函数…

请你喝一杯 Corretto?谈一谈 JDK 的新选择

前言如果以20年的时间为限&#xff0c;让我们选出一个影响力最大的程序语言&#xff0c;我的答案应该只有一个 – 那就是 Java。这个1995年诞生的全新的计算机语言&#xff0c;以“一次编写&#xff0c;到处运行”的跨平台特性以及面向对象、泛型编程等现代语言的特性迅速成为了…

Allegro172版本如何通过规则设置检查器件的热平衡问题

Allegro172版本如何通过规则设置检查器件的热平衡问题 在做PCB设计的时候,器件的热平衡问题是必须考虑到的一个设计要点,尤其小封装的阻容器件,热平衡问题没有考虑好,直接影响到装配的可靠性 如下图 小封装器件,一边线宽粗并且铺铜,另外一端是一根细线 Allegro172及以上…

c语言指针怎么理解 第一部分

不理解指针&#xff0c;是因为有人教错了你。 有人告诉你&#xff0c;指针是“指向”某某某的&#xff0c;那就是误导你&#xff0c;给你挖了个坑。初学者小心不要误读这“指向”二字。 第一&#xff0c;“指针”通常用于保存一个地址&#xff0c;这个地址的数据类型在定义指…

ASGCN之依存句法图的构建

文章目录前言1.理论部分1.1 依存句法理论1.2 依存句法分析1.3 依存句法的应用2. 代码实践2.1 数据集2.2 代码实现2.3 效果查看总结前言 本文首先介绍依存句法理论&#xff0c;之后通过代码实现ASGCN中的依存句法图数据的构建。 1.理论部分 1.1 依存句法理论 词与词之间存在主…

Vue3电商项目实战-购物车模块2【04-头部购物车-商品列表-本地、05-头部购物车-删除操作-本地、06-购物车页面-基础布局】

文章目录04-头部购物车-商品列表-本地05-头部购物车-删除操作-本地06-购物车页面-基础布局04-头部购物车-商品列表-本地 目的&#xff1a;根据本地存储的商品获取最新的库存价格和有效状态。 大致步骤&#xff1a; 定义获取最新信息的API定义修改购物车商品信息的mutations定义…

Mybatis工作原理及流程

1、MyBatis介绍 MyBatis是一款优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。MyBatis免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。MyBatis可以通过简单的XML或注解来配置和映射原始类型、接口和JavaPOJO(PlainOldJavaObjects,普通老式Java对象)为…

OSSFs挂载工具简介

OSSFs挂载工具 OSSFs挂载工具简介 ​ ossfs允许您在Linux系统中将对象存储OSS的存储空间&#xff08;Bucket&#xff09;挂载到本地文件系统。挂载完成后&#xff0c;您能够像操作本地文件一样操作OSS的对象&#xff08;Object&#xff09;&#xff0c;从而实现数据共享。 ​…

RT_Thread Nano 简介, 移植

官网介绍 RT_Thread Nano 1.简介 RT-Thread Nano 是一个极简版的硬实时内核&#xff0c;它是由 C 语言开发&#xff0c;采用面向对象的编程思维&#xff0c;具有良好的代码风格&#xff0c;是一款可裁剪的、抢占式实时多任务的 RTOS。其内存资源占用极小&#xff0c;功能包括…

打怪升级之MFC变量小实验

按惯例&#xff0c;这一篇文章主要还是作者读《深入浅出MFC》整理的一些笔记。不过本次还加上了一些作者自己的理解。 实验的前期准备 做实验前&#xff0c;你最好了解一下MFC的执行流程&#xff0c;从winapp到各类控件的实际变化过程&#xff0c;可以参考博主之前的笔记。 …

SOTA!目标检测开源框架YOLOv6 3.0版本来啦

近日&#xff0c;美团视觉智能部发布了 YOLOv6 3.0 版本&#xff0c;再一次将目标检测的综合性能推向新高。YOLOv6-L6 检测精度和速度超越 YOLOv7-E6E&#xff0c;取得当前实时目标检测榜单 SOTA。本文主要介绍了 YOLOv6 3.0 版本中引入的技术创新和优化&#xff0c;希望能为从…

64. 最小路径和

64. 最小路径和 给定一个包含非负整数的 m∗nm * nm∗n 网格 grid &#xff0c;请找出一条从左上角到右下角的路径&#xff0c;使得路径上的数字总和为最小。 说明&#xff1a;每次只能向下或者向右移动一步。 示例 1&#xff1a; 输入&#xff1a;grid [[1,3,1],[1,5,1],[…

Hudi的7种索引

1、Bloom Index Bloom Index (default) 使用根据记录键构建的bloom过滤器&#xff0c;也可以使用记录键范围修剪候选文件.原理为计算RecordKey的hash值然后将其存储到bitmap中&#xff0c;为避免hash冲突一般选择计算3次 HoodieKey 主键信息&#xff1a;主要包含recordKey 和p…