首页 > 其他分享 >回溯-切割

回溯-切割

时间:2024-04-16 16:57:40浏览次数:23  
标签:return 切割 nums int vector 子集 result 回溯

切割与子集

切割

切割问题类似于组合问题,区别在于组合是从给定集合选取单个元素用以组合,而切割问题则是决定给定集合中连续元素块并加以组合。例如对于字符串abcdef

  • 组合问题:选取a之后,在bcdef中选取第二个字符,选取 b之后在cdef中选取第三个字符.......
  • 切割问题:切割a之后,在bcdef中切割第二段,选取 bc之后再切割第三段......

给出两个leetcode原题加以辅助说明
leetcode131

对于给定的一个字符串,将其分割为一些子串,使得每个子串都是回文串

如何判断纵向遍历即递归的深度:只要判断截取子串的起始位置大于等于母串的整个长度,就说明找到了一种分割方案。而横向遍历的宽度则是由母串的大小决定。

如何剪枝:递归过程中,如果出现切割的一个子串不是回文串,那么就没有继续往下递归的必要。于是可以利用continue跳出该次循环。

其抽象树状结构如图所示:

class Solution{
private:
    vector<vector<string>> result;
    vector<string> path;//存放回文子串
    void backtracking(const string& s,int index){
        //起始位置大于s.size()-1,表明已经找到一组回文子串
        if(index >= s.size()){
			result.push_back(path);
            return;
        }
        for(int i = index;i<s.size();i++){
            if(isPalindrome(s,index,i)){ //判断子串是否是回文串
                //获取起止索引为index和i的子串
				string str = s.substr(index,i-index+1);
                path.push_back(str);
            }else{
                continue;
            }
            backtracking(s,i+1);//进入下一层for循环,此时起始索引是上一层终止索引加1
            path.pop_back();//回溯,清楚本次添加的子串
        }
    }
    bool isPalindrome(const string& s,int start,int end){
        //双指针法判断是否为回文串
        bool flag = true;
        while(end >= start){
			if(s[start]!=s[end]){
                flag = false;
                break;
            }
            end --;
            start ++;
        }
        return flag;
    }
public:
    vector<vector<string>> partition(string s){
        backtracking(s,0);
        return result;
    }
};

考虑其时间复杂度,设n为字符串长度,最坏情况是是字符串s有 n个相同的字符,任何一种划分均满足要求,长度为n的字符串的划分数为\(2^{n-1}\)=\(O(2^n)\), 而每一种划分需要\(O(n)\)的时间,因此时间复杂度为\(O(n*2^n)\).结果的存储需要\(O(n^2)\)的空间。

leetcode93

给定一个只包含数字的字符串s,用以表示一个IP地址,返回所有可能的IP地址。该问题本身也是一个切割问题,即在s 中插入三个"."来分割字符串使之符合IP地址。同时需要判断划分的子段数字的合法性。由于IP地址是四段数字,需要利用pointnum记录插入的"."数

class Solution{
private:
	vector<string> result;//记录结果
    void backtrack(string& s,int index,int pointnum){
        if(pointnum == 3){//此时字符串已经分割为四段
			//判断第四段子串的合法性
            if(isvalid(s,index,s.size()-1)){
				result.push_back(s);
            }
            return;
        }
        for(int i = index;i<s.size();i++){//i表示终止索引
            if(isvalid(s,index,i)){//判断[index,i]是否合法
                s.insert(s.begin()+i+1,'.');//第i的后面插入'.'
                pointnum ++;
                backtrack(s,i+2,pointnum);//由于先前已经插入'.',故下一个子串的起始位置是i+2
                pointnum --;//回溯
                s.erase(s.begin()+i+1);//回溯删除'.'
            }else{
                break;//子串不合法,结束当次循环
            }
        }
    }
   //判断数字段的合法性
   	bool isvalid(const string& s, int start, int end){
       	if(start > end){
           	return false;
      	}
       	if(s[start]=='0'&& start != end){//含前导0不合法
		   	return false;
       	}
        int num = 0;
        for(int i = start;i<=end;i++){
            if(s[i]>'9'||s[i]<'0'){//遇到非数字字符不合法
                return fslae;
            }
            num = num*10+(s[i]-'0');
            if(num>255){//大于255即不合法
                return false;
            }
        }
        return true;
   }
public:
    vector<string> restoreIpAddresses(string s){
        result.clear();
        if(s.size()<4||s.szie()>12) return result;//每个数字段至少一位数字至多三位数字
        backtrack(s,0,0);
        return result;
    }
}

复杂度分析:IP地址的每段位数至多为3,递归的每一层最多有三种情况,同时IP地址有四个数字段,对应递归层数为四层,递归本身的时间复杂度为 \(O(3^{4})\),复原出一种满足要求的IP地址为\(O(|s|)\),因此总的时间复杂度是 \(O(|s|\times 3^4)\),递归层数为IP地址的数字段数,因此空间复杂度为\(O(h)\),即递归数的高度

子集

组合和分割问题都是根据题目要求选取合适的叶子节点,而子集问题则是找树的所有结点。子集问题是一种组合问题,因为集合是无序的({1,2}与{2,1}相同),无序意味着取过的元素不会重复取,回溯时需要循环从startindex开始。对于排列问题,集合有序({1,2}与{2,1}不同),故for循环从0开始取。

组合问题需要进行适当剪枝从而降低问题的复杂度,而子集问题不需要任何的剪枝,因为子集需要遍历整颗树。重新总结一下:

  • 子集是收集树形结构中树的所有节点结果
  • 组合、分割问题是收集树形结构中叶子节点的结果

leetcode 78

给定一个整数数组num,数组中元素互不相同,返回该数组中所有可能的子集,解集中子集不可重复

class Solution{
private:
	vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int index){
        result.push_back(path);//无需加终止条件,因为for循环结束也就遍历了整个树的节点
        for(int i = index;i<nums.size();i++){
            path.push_back(nums[i]);
            backtracking(nums,i+1);
            path.pop_back();
        }
    }
public:
    vector<vector<int>> subsets(vector<int>& nums){
        backtracking(nums,0);
        return result;
    }
};

时间复杂度:按照每个元素是否出现,一共有\(2^n\)个子集,构造单个子集最多需要\(O(n)\)时间,故时间复杂度为\(O(n\times 2^n)\)

空间复杂度:递归时栈空间代价为\(O(n)\)

leetcode 90

给定一个整数数组nums,其中可能包含重复元素,返回该数组所有可能的子集,解集中不能包含重复的子集

上述集合中不存在重复元素,此问题集合中存在重复元素,即要考虑去重,而去重分为纵向的树枝去重和横向的树层去重,简要分析,应该是树层去重。例如nums = [1,1,2,3,4],nums[0]和剩下四个元素组成各种子集就包含了nums[1]与剩下三个元素组成的各种子集。

class Solution{
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtrack(vector<int>& nums, int index){
        result.push_back(path);
        for(int i = index;i<nums.size();i++){
            if(i>index && nums[i]==nums[i-1]){//树层元素去重
				continue;
            }
            path.push_back(nums[i]);
            backtrack(nums,i+1);
            path.pop_back();
        }
    }
public:
    vector<vector<int>> subsetWithDup(vector<int>& nums){
		sort(nums.begin(),nums.end());//树层去重需要对集合排序
        backtrack(nums,0);
        return result;
    }
};

同样的树层去重思路,但不能利用排序来实现树层去重:

leetcode491

给定一个整数数组nums ,其可能包含重复数字,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。与上述子集问题去重不同的是,该问题不能利用排序实现树层去重,我们可利用哈希表记录每层的元素使用情况,从而实现去重。

class Solution{
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums,int index){
		if(path.size()>1){
			result.push_back(path);//由于取树的节点,不用加return
        }
        unordered_set<int> uset; //记录每一层元素便于去重
        //uset只记录本层元素是否重复,对于新的一层uset会重新定义
        for(int i = index;i<nums.size();i++){
            //若子序列并不递增或者当前元素已重复,跳过当前循环
			if(!path.empty()&&nums[i]<path.back()
               ||uset.find(nums[i])!=uset.end()){
				continue;	
            }
            path.push_back(nums[i]);
            uset.insert(nums[i]);
            backtracking(nums,i+1);
            path.pop_back();
        }
    }
public:
    vector<vector<int>> findSubsequences(vector<int>& nums){
        backtracking(nums,0);
        return result;
    }
};

标签:return,切割,nums,int,vector,子集,result,回溯
From: https://www.cnblogs.com/perngfey-note/p/18138585

相关文章

  • 28天【代码随想录算法训练营34期】第七章 回溯算法 (● 93.复原IP地址 ● 78.子集
    93.复原IP地址classSolution:defrestoreIpAddresses(self,s:str)->List[str]:result=[]self.backtracking(s,[],0,result)returnresultdefbacktracking(self,s,path,index,result):ifindex>=len(s......
  • 27天【代码随想录算法训练营34期】第七章 回溯算法part03(● 39. 组合总和 ● 40.组合
    39.组合总和怎么才能避免重复?比现在数小的数就别append到path里面了,之前肯定都试过了classSolution:defcombinationSum(self,candidates:List[int],target:int)->List[List[int]]:result=[]candidates.sort()self.backtracking(cand......
  • 25天【代码随想录算法训练营34期】第七章 回溯算法part02 ( ● 216.组合总和III ● 17
    **216.组合总和III**classSolution:defcombinationSum3(self,k:int,n:int)->List[List[int]]:result=[]self.backtracking(k,n,1,[],result,n)returnresultdefbacktracking(self,k,n,startingIndex,path,result,......
  • 24天【代码随想录算法训练营34期】第七章 回溯算法part01 ( ● 理论基础 ● 77. 组合
    **理论基础**voidbacktracking(参数){if(终止条件){存放结果;return;}for(选择:本层集合中元素(树中节点孩子的数量就是集合的大小)){处理节点;backtracking(路径,选择列表);//递归回溯,撤销处理结果}}......
  • 回溯-组合
    回溯-组合回溯法的本质是穷举,其主要是为了解决n个for循环的问题,在for循环中套着递归从而不断实现for循环。回溯法解决的问题都可以抽象为树形结构,一般是在给定集合是取出符合一定规则的数据。集合的大小构成树的宽度,也即第一层的for循环遍历的范围,递归的深度构成树的深度,也就暴力......
  • 回溯之全排列
    前言回溯算法是一种通过逐步构建解决方案来解决问题的方法。全排列问题是其中一个经典的应用之一。它的基本思想是尝试所有可能的排列方式,并逐步构建解决方案,如果发现当前尝试的排列不符合条件,则回溯到上一步,尝试其他可能的选择。回溯算法本质就是DFS,深搜。全排列就是可......
  • nginx高级篇之日志切割
    一、日志切割(shell脚本)nginx日志默认是不切割的,网站运行久了自然生成大量日志,导致单文件的处理,太麻烦,因此工作里一般定期切割,一般按天切割。切割理念1.给nginx进程发送信号,让nginx生成一个新的日志文件,这就是一个日志切割2.手动切割,修改日志准备好旧的日志文件,测试写入大......
  • nginx日志切割之logrotate
    配置详解logrotate是基于crond服务(定时任务)来运行的,有几个重要的配置:1、/etc/logrotate.conf(主配置)和/etc/logrotate.d/*(子配置)/etc/logrotate.conf是全局配置,logrotate.conf里面包含include/etc/logrotate.d这句,加载子配置文件的意思,说明/etc/logrotate.d/目录下是具体的配......
  • C++数据结构与算法——回溯算法组合问题
    C++第二阶段——数据结构和算法,之前学过一点点数据结构,当时是基于Python来学习的,现在基于C++查漏补缺,尤其是树的部分。这一部分计划一个月,主要利用代码随想录来学习,刷题使用力扣网站,不定时更新,欢迎关注!文章目录一、77.组合二、216.组合总和III三、17.电话号码的字......
  • 【三十五】【算法分析与设计】综合练习(2),22。 括号生成,77。 组合,494。 目标和,模拟树递
    22.括号生成数字n代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且有效的括号组合。示例1:输入:n=3输出:["((()))","(()())","(())()","()(())","()()()"]示例2:输入:n=1输出:["()"]提示:1<=n<=8【三十五】【算法分析与设计】综合练习(2),......