回溯算法系列代码

2、组合问题

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

你可以按 任何顺序 返回答案。

示例 1:

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

输入:n = 1, k = 1 输出:[[1]]

题目分析:

套好公式就行 主要要记得这个是每个数字只能取一个,所有startindex,在深度与宽度都会增加

class Solution {
public:
        vector<vector<int>> result;//存放选择好的组合
        vector<int>path;//存放我们选择中的数组
        //回溯函数
        void backtracking(int n,int k, int startIndex)
        {
            if(path.size() == k)//当path数量到了,就存放result中去
            {
                result.push_back(path);
                return;//跳回本层
            }
            for(int i = startIndex;i <=n;i++)//每次for结束也加一,利用for循环结束跳到上一层
            {
              path.push_back(i);
              backtracking(n,k,i+1);//每次深入一次,i+1
              path.pop_back();

            }
        }
        vector<vector<int>>combine(int n,int k){
            backtracking(n,k,1);
            return result;

        }
    
};

4、组合总和||||

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

只使用数字1到9 每个数字 最多使用一次 返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

示例 1:

输入: k = 3, n = 7 输出: [[1,2,4]] 解释: 1 + 2 + 4 = 7 没有其他符合的组合了。 示例 2:

输入: k = 3, n = 9 输出: [[1,2,6], [1,3,5], [2,3,4]] 解释: 1 + 2 + 6 = 9 1 +
3 + 5 = 9 2 + 3 + 4 = 9 没有其他符合的组合了。 示例 3:

输入: k = 4, n = 1 输出: [] 解释: 不存在有效的组合。
在[1,9]范围内使用4个不同的数字,我们可以得到的最小和是1+2+3+4 = 10,因为10 > 1,没有有效的组合。

题目分析
这个与上面一样,只是多了一个sum而已

class Solution {
public:
     vector<int>path;
     vector<vector<int>> result;
     int sum = 0;
    vector<vector<int>> combinationSum3(int k, int n) {
        backtracking(k,n,1);
        return result;

    }
     void backtracking(int k,int n,int index){

        if(sum == n && path.size() == k){
            result.push_back(path);
            return;
        }
        for(int i  = index; i <= 9; i++){
            sum += i;
            path.push_back(i);
            backtracking(k,n,i+1);
            path.pop_back();
            sum-=i;
        }
    }
};

5、电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
在这里插入图片描述
示例 1:

输入:digits = “23” 输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”] 示例
2:

输入:digits = “” 输出:[]

在这里插入图片描述

//数字和字母如何映射
//与之前不同的是我们可以选择的数组不是一样的

//index 表示遍历我们输入字符的第几个数字
//digits是我们输入的字符
//

class Solution {
public:
   const string letterMap[10] = {
        "",//0
        "",//1
        "abc",//2
        "def",//3
        "ghi",//4
        "jkl",//5
        "mno",//6
        "pqrs",//7
        "tuv",//8
        "wxyz"//9                         
   };
   string s;
   vector<string> result;

   void backtracking(const string& digits, int index)
   {
        if(index ==digits.size()){
            result.push_back(s);
            return;
        }
        int digit = digits[index] -'0';//将字符型转成int,获取数字
        string letters = letterMap[digit];//取数字对应的字符集合
        for(int i = 0; i<letters.size();i++)
        {
          s.push_back(letters[i]);
          backtracking(digits,index+1);//index加一是要处理下一个数字
          s.pop_back();
        }

   }
    vector<string> letterCombinations(string digits) {
        if(digits.size() == 0) return result;
        backtracking(digits,0);
        return result;
                                                                     
    }
};

7、组合总和

这个是要求可以取重复的,
在这里插入图片描述

class Solution {
public:
    vector<int> path;
    vector<vector<int>> result;
    void backtracking(vector<int>& candidates,int sum,int target,int startindex)
    {
        //终止条件
        if(sum > target) return;//直接回上一层
        if(sum == target){
            result.push_back(path);//保存
            return;//回上一层
        }
        //单层递归逻辑

        for(int i = startindex; i<candidates.size();i++){
            path.push_back(candidates[i]);
            sum+=candidates[i];
            backtracking(candidates,sum,target,i);
            sum-=candidates[i];//回溯,踢出一个
            path.pop_back();

        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        
        backtracking(candidates,0,target,0);
        return result;
        
    }
    
};

8、组合总数||

这个要求每个数字只能出现一次,但是可以选择的地方有重复数字,所有要将结果去重
重也简单就是只要将我们容器排序,在单步插入的时候,判断要插入到path中的与上一个是否一样,要是一样就是continue

class Solution {
public:
    vector<int> path;
    vector<vector<int>> result;
    void backtracking(vector<int>& candidates,int sum,int target,int startindex)
    {
        //终止条件
        if(sum > target) return;//直接回上一层
        if(sum == target){
            result.push_back(path);//保存
            return;//回上一层
        }
        //单层递归逻辑

        for(int i = startindex; i<candidates.size();i++){

            // 要对同一树层使用过的元素进行跳过
            if (i > startindex && candidates[i] == candidates[i - 1])   continue;
    
            path.push_back(candidates[i]);
            sum+=candidates[i];
            backtracking(candidates,sum,target,i+1);
            sum-=candidates[i];//回溯,踢出一个
            path.pop_back();

        }
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {

        //给candidates排序,去重
        sort(candidates.begin(),candidates.end());
        backtracking(candidates,0,target,0);
  
        
        return result;
        
    }
    
};

9、分割回文串

分析:
终止条件
startindex 是从0,当startindex >=s.size(),已经切空了,终止
在这里插入图片描述

我们在切割好前要判断,切下的是不是回文字符串,其他地方与组合差不多,两个地方需要使用startindex +1

s.substr(begin(),n)代表切割从零下标开始的n个字符
回文字符串
首尾两个指针,同时从两端开始遍历,两者结果一样

class Solution {
public:
vector<vector<string>> result; //存放结果
vector<string>path;//放已经回文的子串

void backtracking(const string& s, int startindex)
{
    // 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了
    if(startindex >=s.size())
    {
        result.push_back(path);
        return;
    }
    //当层逻辑
    for(int i = startindex; i<s.size();i++)
    {
        if(isPalindrome(s,startindex,i))//判断下一步生成的子串是不是回文串
        {
            // 获取[startindex,i]在s中的字串
            string str = s.substr(startindex,i-startindex + 1);//从下标为startindex开始,截取i-startindex + 1个字符
            path.push_back(str);
        }
        else//不是回文字符跳过
            continue;
        backtracking(s,i+1);
        path.pop_back();//回溯过程,弹出本次已经在填的字符
    }
}

//判断是不是回文字符
bool isPalindrome(const string& s,int start,int end)
{
    for(int i = start, j = end; i < j; i++,j--)
    {
        if(s[i] != s[j])   return false;
    }
    return true;
}

    vector<vector<string>> partition(string s) {

           backtracking(s,0);
           return result;
    }
};

10、复原IP地址

• 段位以0为开头的数字不合法 • 段位里有非正整数字符不合法(一般只要大于9小于0,就可以会自动转换成sacll码计算) •
段位如果大于255了不合法 for(int i = 0; i < = end;i++) num = 10*num + 单个元素 注意
在划分结束后,加一个逗号,一共要有三个逗号,分四段 在划分之前要看看划分的那段合不合法
不合法的话直接break,因为前面不合法,后面也是不合法,同树层没必要继续下去

class Solution {
public:
 vector<string> result;//记录结果
 //startindex:搜索的起始位置,pointnum:添加逗号的数量
   
     void backtracking(string& s,int startindex,int pointnum){
         if(pointnum == 3)//单逗号符号数目为三时,分隔结束
         {
             //判断第四段是否合法
             if(isVaild(s,startindex,s.size()-1)){
                 result.push_back(s);
             }
             return;
         }
         for(int i = startindex; i < s.size();i++){
             if(isVaild(s,startindex,i))//判断[startindex,i]区间的字符串是否合法
             {
                 s.insert(s.begin() + i+1,'.');//在后面插入一个逗号
                 pointnum++;//逗号数目加一
                 backtracking(s,i+2,pointnum);//插入逗号后下一个起始位置是i+2
                 s.erase(s.begin()+i+1);//删掉逗号
                 pointnum--;
             }
             else//已经有不合法了,通树层再移动也是不合法的
               break;
         }
     }

     //判断字符串s在左闭右闭区间[start,end]所组成的数字是否合法
     bool isVaild(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')//遇到非数字字符不合法,其他符号会自动转成ask码计算 
               return false;
             num = num *10 + (s[i] - '0');
             if(num > 255)//大于255不合法
              return false;
         }
         return true;
     }
     vector<string> restoreIpAddresses(string s) {
       
         backtracking(s,0,0);
         return result;
    }
};

11、子集

集树形结构中树的所有节点的结果,就是收集过程量,而不是只收集最后结果 每次进入

终止条件,startindex >=nums.size() 已经大于数组长度了,没有元素可以取了,已经到底了

1、为什么不需要终止条件? 因为只要遍历到最后一个,在继续backtracking机会i+1,机会结束函数,跳回本层
当然 我们也可以写这个终止函数,我觉得还是写吧
2、为甚么result的记录放在那个位置? ,只要我们运行了backtracking,就会跳到result就会记录一次

class Solution {
public:
    vector<int>path;
    vector<vector<int>>result;
  void backtracking(vector<int> &nums,int startindex)
  {
      //每一步的结果,都是子串,都要收集
      result.push_back(path);
      if(startindex >= nums.size()) return;
        
      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 result;


    }
};

13、子集||

当给定的一个数组包含重复数字,结果机会产生重复(在横向遍历的时候会重复)
1、将输入字符数组进行sort排序
1、使用一个used数组,要是以及使用过了,就赋给1,不然默认都是零
区分树枝去重合树层去重,不可以只写num[i] == nums[i-1]就continue,会去除额外部分
树层去重是不可以的,那个时候used【i-1】 = false//前一个没使用了
树枝去重是可以的,那个时候used[i-1] = true;//前一个还在使用
2、在插入的时候,判断现在这个数是否和之前的i一样,要是一样就continue,继续横向寻找下一个


class Solution {
public:
    vector<int>path;
    vector<vector<int>>result;
  void backtracking(vector<int> &nums,int startindex,vector<bool>& used)
  {
      //每一步的结果,都是子串,都要收集
      result.push_back(path);
      if(startindex >= nums.size()) return;
        
      for(int i = startindex ; i < nums.size(); i++)
      {
          if( i > 0 && nums[i] == nums[i-1] && used[i-1] == false) continue;
          path.push_back(nums[i]);
          used[i] =true;
          backtracking(nums,i+1,used);
          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,0,used);
      return result;

    }
};
   

14、递增子序列

要求子序列是递增的,长度至少是2,还要去重
使用哈希表去重,横向遍历中的元素存入哈希表中,要是在该树层发现重复就continue,要是发现不是递归也continue
每次进行递归之前都将哈希表清空,这样就不会影响一个树枝

class Solution {
public:
  vector<int> path;
  vector<vector<int>> result;
  void backtracking(vector<int>& nums,int startindex)
  {
      if(path.size() > 1) result.push_back(path);//长度至少为2才要
      //终止条件
      if(startindex >= nums.size()) return;

      unordered_set<int> uset;//使用set给本层去重每次递归都会清零

      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 result;

    }
};

16、全排列

排列问题与之前的组合不同,{1,2} 与 [2,1}是不同的数组,所以startindex就不需要了,但是一个排列中不是一个元素多次使用,所有需要要used数组,记录那个被使用了
每次都是从零开始搜索的,而不是startindex
终止条件
就是path中大小等于nums大小
单层逻辑
used数组存放我们判断是否使用的数字,要是第一个被用了,就将数组第一个元素赋给true,没有被使用就false,开始默认构造是false
一个元素只能使用一次,要是以及使用就直接continue跳过for横向遍历下一个
不管是深度加一还是横向遍历加一,都是找到没有被遍历的元素

class Solution {
public:
  vector<int> path;
  vector<vector<int>> result;
  void backtracking(vector<int>& nums,int startindex)
  {
      if(path.size() > 1) result.push_back(path);//长度至少为2才要
      //终止条件
      if(startindex >= nums.size()) return;

      unordered_set<int> uset;//使用set给本层去重每次递归都会清零

      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 result;

    }
};

17、全排列||(数组重复)

在原来全排列的基础上,加上当前元素若是与前一个相同,并且当前元素前一个元素已经遍历过了也急速used[i-1] = false;就continue跳过,
总之就是横向选择的时候不能选择相同的元素

class Solution {
public:
 vector<vector<int>> result;
    vector<int> path;
    void backtracking (vector<int>& nums, vector<bool>& used) 
    {
        // 此时说明找到了一组
        if (path.size() == nums.size()) 
        {
            result.push_back(path);
            return;
        }
        for (int i = 0; i < nums.size(); i++)
        {
            // used[i - 1] == true,说明同一树枝nums[i - 1]使用过
            // used[i - 1] == false,说明同一树层nums[i - 1]使用过
            // 如果同一树层nums[i - 1]使用过则直接跳过
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) 
            {
                continue;
            }
            if (used[i] == false)
           {
                used[i] = true;
                path.push_back(nums[i]);
                backtracking(nums, used);
                path.pop_back();
                used[i] = false;
            }
        }
    }
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<bool> used (nums.size(),false);
        sort(nums.begin(),nums.end());
        backtracking(nums,used);
        return result;

    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何具体存储。例如,数组的连续存储,链表的动态分配节点,树和图的邻接矩阵或邻接表表示等。 基本操作:针对每种数据结构,定义了一系列基本的操作,包括但不限于插入、删除、查找、更新、遍历等,并分析这些操作的时间复杂度和空间复杂度。 算法算法设计:研究如何将解决问题的步骤形式化为一系列指令,使得计算机可以执行以求解问题。 算法特性:包括输入、输出、有穷性、确定性和可行性。即一个有效的算法必须能在有限步骤内结束,并且对于给定的输入产生唯一的确定输出。 算法分类:排序算法(如冒泡排序、快速排序、归并排序),查找算法(如顺序查找、二分查找、哈希查找),图论算法(如Dijkstra最短路径算法、Floyd-Warshall算法、Prim最小生成树算法),动态规划,贪心算法回溯法,分支限界法等。 算法分析:通过数学方法分析算法的时间复杂度(运行时间随数据规模增长的速度)和空间复杂度(所需内存大小)来评估其效率。 学习算法与数据结构不仅有助于理解程序的内部工作原理,更能帮助开发人员编写出高效、稳定和易于维护的软件系统。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值