C++ 回溯算法

什么时候不需要startIndex?

  • 全排列:1在[1,2]中已经使用过了,但是在[2,1]中还要在使用一次1,所以处理排列问题就不用使用startIndex了;
  • 电话号码的字母组合:如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex;
  • 二维的回溯--单词搜索。

如果是一个集合来求组合的话,就需要startIndex。

组合

给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。

示例: 输入: n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]​​​​​​​

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

 优化的地方--剪枝:本题可以理解为横向遍历一棵树(n)及纵向遍历一棵树(k),对叶子节点的path加入res数组。在for循环中,i<=n-k+path.size()+1说明i的最大值是n-k+path.size()+1,即i从该值开始遍历,再大就不能凑够k个数字了。而backtrack的返回条件就是path凑够k个数的时候。

组合总和III

找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:

  • 只使用数字1到9
  • 每个数字 最多使用一次 

返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    void backtrack(int k,int n,int sum,int start)
    {
        if(sum>n)
        {
            return;
        }
        if(sum==n&&path.size()==k)
        {
            res.push_back(path);
            return;
        }
        for(int i=start;i<=(9-k+path.size()+1);i++)
        {
            sum+=i;
            path.push_back(i);
            backtrack(k,n,sum,i+1);
            path.pop_back();
            sum-=i;
        }
    }
    vector<vector<int>> combinationSum3(int k, int n) {
        backtrack(k,n,0,1);
        return res;
    }
};

本题由于要求回溯过程中的和,因此多了一个参数sum。回溯返回有两个:sum>n或者sum==n&&path.size()==k。

电话号码的组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

class Solution {
public:
    const vector<string> letters= {
    "", // 0
    "", // 1
    "abc", // 2
    "def", // 3
    "ghi", // 4
    "jkl", // 5
    "mno", // 6
    "pqrs", // 7
    "tuv", // 8
    "wxyz", // 9
};
    vector<string> res;
    string path;
    void backtrack(string& digits,int index)
    {
        if(index==digits.size())
        {
            res.push_back(path);
            return;
        }

        int digit=digits[index]-'0';
        string letter=letters[digit];

        for(int i=0;i<letter.size();i++)
        {
            path+=letter[i];
            backtrack(digits,index+1);
            path.pop_back();
        }
    }
    vector<string> letterCombinations(string digits) {
        if (digits.size() == 0) {
            return res;
        }
        backtrack(digits,0);
        return res;
    }
};

注意本题有个测试用例是digits为空,则返回空,需要特殊处理。另外,由于本题是多个集合,所以不需要startIndex。index是遍历digits的下标,因此返回条件是index==digits.size()。

在回溯过程中,我们需要取出每一个字符数字对应的字符串,并且从0下标开始遍历。

组合总和-无限制重复被选取

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 

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

由于candidates 中的 同一个 数字可以 无限制重复被选取 ,因此在纵向的遍历backtrack中i并不需要加1,而是每次从start开始。start的意思是从candidates 第几个数开始取数,属于横向遍历。

 注意这里的剪枝操作:for循环中,candidates[i]+sum<=target.

组合总和II-去重

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

说明: 所有数字(包括目标数)都是正整数。解集不能包含重复的组合 

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    void backtrack(vector<int>& candidates, int target,int sum,int start)
    {
        if(sum>target)
        {
            return;
        }
        if(sum==target)
        {
            res.push_back(path);
            return;
        }
        for(int i=start;i<candidates.size()&&sum+candidates[i]<=target;i++)
        {
            if(i>start&&candidates[i]==candidates[i-1])
            {
                continue;
            }
            sum+=candidates[i];
            path.push_back(candidates[i]);
            backtrack(candidates,target,sum,i+1);
            path.pop_back();
            sum-=candidates[i];
        }
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        // 首先把给candidates排序,让其相同的元素都挨在一起。
        sort(candidates.begin(), candidates.end());
        backtrack(candidates,target,0,0);
        return res;
    }
};

由于要进行去重操作,因此首先要把candidates进行排序操作。其次,在for循环中,i控制的是横向的遍历,因此当i>start时说明,已经至少遍历到第二个数字了。当后一个和前一个相等时,直接continue。

分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串​​​​​​​。返回 s 所有可能的分割方案。

示例 1:

输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
class Solution {
public:
    vector<vector<string>> res;
    vector<string> path;

    bool isPalidrom(string s,int left,int right)
    {
        // size_t left=0,right=s.size()-1;
        while(left<=right)
        {
            if(s[left]!=s[right])
            {
                return false;
            }
            left++;right--;
        }
        return true;
    }

    void backtrack(string s,int start)
    {
        if(start==s.size())
        {
            res.push_back(path);
            return;
        }
        for(int i=start;i<s.size();i++)
        {
            if(isPalidrom(s,start,i))
            {
                string str=s.substr(start,i-start+1);
                path.push_back(str);
            }
            else
                continue;
            backtrack(s,i+1);
            path.pop_back();
        }
    }
    vector<vector<string>> partition(string s) {
        backtrack(s,0);
        return res;
    }
};

时间复杂度和空间复杂度

子集问题分析

时间复杂度:O(n× 2^n)因为每一个元素的状态无外乎取与不取,一共 2^n种状态,每种状态都需要 O(n)的构造时间,最终时间复杂度为O(n× 2^n)。

空间复杂度:O(n),递归深度为n,所以系统栈所用空间为O(n)。

排列问题分析

时间复杂度:O(n×n!)因为一共n!种排列,每种排列都需要O(n)的构造时间,最终时间复杂度为O(n×n!)。

空间复杂度:O(n),递归深度为n,所以系统栈所用空间为O(n)。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

景区导航导览系统:基于AR技术+VR技术的功能效益全面解析

在数字化时代背景下&#xff0c;游客对旅游体验的期望不断提升。游客们更倾向于使用手机作为旅行的贴身助手&#xff0c;不仅因为它能提供实时、精准的导航服务&#xff0c;更在于其融合AR&#xff08;增强现实&#xff09;、VR&#xff08;虚拟现实&#xff09;等前沿技术&…

汽车免拆诊断案例 | 卡罗拉急加速抖动故障排除

车型信息 2017年改款卡罗拉&#xff0c;排量1.2T&#xff0c;行驶里程48800公里。 故障现象 车辆不管在什么状态下&#xff0c;只要是平缓加速&#xff0c;都不会有抖动。车辆静止时&#xff0c;急加速时&#xff0c;也不会有抖动。但是车速达40公里/小时以上&#xff0c;急加…

SQL注入问题

一、什么是sql注入 public class TestSql {public static void main(String[] args) {Scanner inScanner new Scanner(System.in);System.out.println("请输入用户名");String username inScanner.nextLine();System.out.println("请输入密码");String …

python-区间内的真素数(赛氪OJ)

[题目描述] 找出正整数 M 和 N 之间&#xff08;N 不小于 M&#xff09;的所有真素数。真素数的定义&#xff1a;如果一个正整数 P 为素数&#xff0c;且其反序也为素数&#xff0c;那么 P 就为真素数。 例如&#xff0c;11&#xff0c;13 均为真素数&#xff0c;因为 11 的反序…

JuiceFS缓存特性

缓存 对于一个由对象存储和数据库组合驱动的文件系统&#xff0c;缓存是本地客户端与远端服务之间高效交互的重要纽带。读写的数据可以提前或者异步载入缓存&#xff0c;再由客户端在后台与远端服务交互执行异步上传或预取数据。相比直接与远端服务交互&#xff0c;采用缓存技…

vue3配置代理

vite.config.js中的内容&#xff1a; 在这里配置访问后台的地址 import { defineConfig } from vite import vue from vitejs/plugin-vueexport default defineConfig({plugins: [vue()],server: {open: false,port: 3000,proxy: {/api: {target: http://127.0.0.1:9001, // 后…

【论文阅读】MCTformer+:弱监督语义分割的多类令牌转换器

【论文阅读】MCTformer:弱监督语义分割的多类令牌转换器 文章目录 【论文阅读】MCTformer:弱监督语义分割的多类令牌转换器一、介绍1.1 WSSS背景1.2 WSSS策略 二、联系工作2.1 弱监督语义分割2.2 transformers的可视化应用 三、MULTI-CLASS TOKEN TRANSFORMER3.1 Multi-class t…

【Apache POI】Java解析Excel文件并处理合并单元格-粘贴即用

同为牛马&#xff0c;点个赞吧&#xff01; 一、Excel文件样例 二、工具类源码 import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.xssf.usermodel.XSSFWorkbookFactory; import org.springframework.web.multip…

【Linux】进程信号 --- 信号产生

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…

【Android】活动的生命周期与启动模式

【Android】活动的生命周期与启动模式 活动的生命周期 返回栈 返回栈&#xff08;Back Stack&#xff09;是Android操作系统中用于管理用户在应用中导航历史的一种数据结构。它允许用户通过按下硬件返回键或调用系统返回功能来回到之前的操作步骤。以下是返回栈的一些关键特…

R与机器学习系列|15.可解释的机器学习算法(Interpretable Machine Learning)(中)

在上次推文中我们介绍了几种可解释机器学习算法的常见方法&#xff0c;包括置换特征重要性、偏依赖图和个体条件期望及其实现。本次我们将继续介绍其他的用来解释机器学习算法的方法。 1.特征交互&#xff08;Feature interactions&#xff09; 1.1介绍 在机器学习中&#xff0…

SpringCache介绍

SpringCache是Spring提供的缓存框架。提供了基于注解的缓存功能。 SpringCache提供了一层抽象&#xff0c;底层可以切换不同的缓存实现&#xff08;只需要导入不同的Jar包即可&#xff09;&#xff0c;如EHCache&#xff0c;Caffeine&#xff0c;Redis。 2个重要依赖已经导入&a…

肿瘤微生态研究利器——5R 16S rDNA测序

肿瘤微生物组&#xff08;Tumor Microbiome&#xff09;是肿瘤微环境中不可或缺的成员&#xff0c;肿瘤内微生物群通过多种机制影响肿瘤发展&#xff0c;在不同类型的肿瘤中&#xff0c;肿瘤内微生物群的组成和丰度具有高度异质性。由于它们的低生物量和其他障碍&#xff0c;全…

Web常见漏洞之po解

暴力破解 概述应用场景实验工具实训准备实训开始四种模式 验证码绕过前端验证码验证码有存活周期 概述 暴力破解是Web漏洞里常见的一种渗透方式&#xff0c;攻击者会试图通过尝试各种可能的用户名和密码组合来猜测密码或密钥&#xff0c;直到猜对为止123。攻击者会经常使用自动…

使用base64通用文件上传

编写一个上传文件的组件 tuku,点击图片上传后使用FileReader异步读取文件的内容&#xff0c;读取完成后获得文件名和base64码&#xff0c;调用后端uploadApi,传入姓名和base64文件信息&#xff0c;后端存入nginx中&#xff0c;用于访问 tuku.ts组件代码&#xff1a; <templa…

win10删除鼠标右键选项

鼠标右键菜单时&#xff0c;发现里面的选项特别多&#xff0c;找一下属性&#xff0c;半天找不到。删除一些不常用的选项&#xff0c;让右键菜单变得干净整洁。 1、按下键盘上的“winR”组合按键&#xff0c;调出“运行”对话框&#xff0c;输入“regedit”命令&#xff0c;点击…

mybatisPlus和mybatis的版本冲突问题、若依换成MP、解决git无法推送、使用若依框架的swagger、以后再遇到团队项目应该怎么做。

20240716 一. mybatisPlus和mybatis的版本冲突问题1. 使用前的准备2. 我遇到了一个很严重的问题。3. 解决问题&#xff0c;好吧也没解决&#xff0c;发现问题&#xff01;&#xff01; 二、该死的git&#xff01;&#xff01;&#xff01;&#xff01;1. 解决无法在idea中使用g…

2.RabbitMQ相关概念

介绍 RabbitMQ是一个消息中间件&#xff0c;接受并转发消息。它接收、存储和转发消息数据。 四大核心概念&#xff1a; 1.生产者 产生数据发送消息的程序是生产者。 2.消费者 3.队列 每一个队列对应一个消费者。 如果两个消费者对应同一个队列&#xff0c;那么队列中的…

R绘制Venn图及其变换

我自己在用R做各种分析时有不少需要反复用到的基础功能&#xff0c;比如一些简单的统计呀&#xff0c;画一些简单的图等等&#xff0c;虽说具体实现的代码也不麻烦&#xff0c;但还是不太想每次用的时候去找之前的代码。 索性将常用的各种函数整成了一个包&#xff1a;pcutils…

深度刨析程序中的指针

前面我们已经学习过了指针的一下性质&#xff1a; 指针就是个变量&#xff0c;用来存放地址&#xff0c;地址唯一标识的一块内存空间指针的大小是固定的4/8个字节&#xff08;32位平台/64位平台&#xff09;指针是有类型&#xff0c;指针的类型决定了指针的加减整数的步长&…