【算法】递归、回溯、剪枝、dfs 算法题练习(组合、排列、总和问题;C++)

慈云数据 8个月前 (03-12) 技术支持 137 0

文章目录

  • 1. 前言
  • 2. 算法题
    • 22.括号生成
    • 494.目标和
    • 39.组合总和
    • 784.字母大小写全排列
    • [526. 优美的排列](https://leetcode.cn/problems/beautiful-arrangement/)

      1. 前言

      后面的练习是接着下面链接中的文章所继续的,在对后面的题练习之前,可以先将下面的的文章进行了解👇:

      【算法】{画决策树 + dfs + 递归 + 回溯 + 剪枝} 解决排列、子集问题(C++)

      2. 算法题

      22.括号生成

      在这里插入图片描述


      思路

      • 题意分析:要求根据给出的数字,算出合法的括号组成个数。根据题目,我们可以总结出下面的规则:

        在这里插入图片描述

      • 解法:dfs + 根据决策树设计递归、回溯、剪枝
      • 决策树:

        在这里插入图片描述

        • 根据上图决策树,即可直接着手编写代码:
        • 细节问题:

          在这里插入图片描述

          代码

          class Solution {
          public:
              int left, right, _n; // 分别记录左右括号的数量
              vector ret; // 结果数字
              string path;
              vector generateParenthesis(int n) {
                  _n = n;
                  dfs();
                  return ret;
              }
              void dfs()
              {
                  if(right == _n) // 当前path序列有效,加入结果
                  {
                      ret.push_back(path);
                      return;
                  }
                  if(left  
          

          494.目标和

          在这里插入图片描述

          思路

          • 题意分析:对于数组给出的数字,通过向所有数字前补加减号,使最后的值为 target
          • 解法:dfs + 根据决策树设计递归、回溯、剪枝
            • 该解法并非最优解法,但这里依然用dfs做,并引出一些写法问题。
            • 决策树:(省略了后面的步骤)

              在这里插入图片描述

              - 根据决策树的思路,不难看出实际上与 题目 78.子集 非常相似,代码也是如此,但有一个需要注意的细节,先看代码:

              代码1

              class Solution {
              public:
                  int ret, path, _target;
                  
                  int findTargetSumWays(vector& nums, int target) {
                      _target = target;
                      dfs(nums, 0); // 从0位置开始
                      return ret;
                  }
                  void dfs(vector& nums, int pos)
                  {
                      // 终止条件
                      if(pos == nums.size())
                      {
                          if(path == _target) ret++;
                          return;
                      }
                      // 正号
                      path += nums[pos];
                      dfs(nums, pos + 1);
                      path -= nums[pos];
                      // 负号
                      path -= nums[pos];
                      dfs(nums, pos + 1);
                      path += nums[pos];
                  }
              };
              
              1. 最开始我们提到,深搜dfs并非该题的最优解法, 时间开销很大,对于上面的代码,更是面临超时的风险,上面的代码将 path作为全局变量
              2. 我们可以 进行优化:将path作为参数传递
              3. 更改后的代码,时间开销会小一些

              代码2

              class Solution {
              public:
                  int ret, _target;
                  
                  int findTargetSumWays(vector& nums, int target) {
                      _target = target;
                      dfs(nums, 0, 0); // 从0位置开始
                      return ret;
                  }
                  void dfs(vector& nums, int pos, int path)
                  {
                      // 终止条件
                      if(pos == nums.size())
                      {
                          if(path == _target) ret++;
                          return;
                      }
                      // 正号
                      dfs(nums, pos + 1, path + nums[pos]);
                      // 负号
                      dfs(nums, pos + 1, path - nums[pos]);
                  }
              };
              

              path定义形式的理由

              1. 频繁的执行 + - 操作,是一比不小的时间消耗,直接传参可以只需要将计算后的结果直接作为参数递归。
              2. 而对于 [78.子集],我们将path定义为全局变量,因为对于该题情况,path 本身为vector类型,作为参数传递会导致频繁的创建vector,不利于节省时间。

              39.组合总和

              在这里插入图片描述

              思路

              • 题意分析:即通过给定的数组任意分配,组成和为目标值的序列,求序列的种类。
              • 解法:dfs + 根据决策树设计递归、回溯、剪枝

                解法1

                • 决策树:如图所示:
                  • 即每次分支都进行所有数字的选择,符合条件的继续,由于题目要去重,同层下选过的不再复选
                  • 剪枝:同层的选过的剪掉 + 该路径和超出目标值或该位置超出数组范围的剪掉

                    在这里插入图片描述

                    代码

                    class Solution {
                    public:
                        int _target;
                        vector path;
                        vector ret;
                        vector combinationSum(vector& candidates, int target) {
                            _target = target;
                            dfs(candidates, 0, 0);
                            return ret;
                        }
                        void dfs(vector& nums, int pos, int pathSum)
                        {
                            // 路径和等于目标值,记录结果,向上返回
                            if(pathSum == _target)
                            {
                                ret.push_back(path);
                                return;
                            }
                            // 路径和大于目标值 / 当前位置超出数组; 向上返回
                            if(pathSum > _target || pos >= nums.size()) return;
                            for(int i = pos; i  
                    

                    解法2

                    • 决策树:

                      在这里插入图片描述

                      对于上图,有一个需要注意的细节问题:

                      - 对于恢复现场:

                      在这里插入图片描述

                      此时我们可以编写代码:

                      代码

                      class Solution {
                      public:
                          int _target;
                          vector path;
                          vector ret;
                          vector combinationSum(vector& candidates, int target) {
                              _target = target;
                              dfs(candidates, 0, 0);
                              return ret;
                          }
                          void dfs(vector& nums, int pos, int pathSum)
                          {
                              // 路径和等于目标值,记录结果,向上返回
                              if(pathSum == _target)
                              {
                                  ret.push_back(path);
                                  return;
                              }
                              // 路径和大于目标值 / 当前位置超出数组; 向上返回
                              if(pathSum > _target || pos >= nums.size()) return;
                              for(int k = 0; k * nums[pos] 
                                  if(k) path.push_back(nums[pos]); // 枚举 当前值的个数,添加到数组中
                                  dfs(nums, pos + 1, pathSum + k*nums[pos]);
                              }
                              // 恢复现场
                              for(int k = 1; k * nums[pos] 
                                  // 将每个元素添加过的个数值 都恢复
                                  path.pop_back();
                              }
                          }
                      };
                      
                      public:
                          string path; // 记录当前字母序列
                          vector
                              dfs(s, 0);
                              return ret;
                          }
                          void dfs(string s, int pos)
                          {
                              if(path.size() == s.size())
                              {
                                  ret.push_back(path);
                                  return;
                              }
                              char ch = s[pos];
                              // 不改变的情况:数字 以及 字母的第一种情况
                              path += ch;
                              dfs(s, pos + 1);
                              path.pop_back();
                              if(isalpha(ch)) // 需要改变  
                              {
                                  // 改变大小写
                                  if(ch 
                      public:
                          bool used[16];
                          int ret;
                          int countArrangement(int n) {
                              dfs(1, n); // 从下标为1处填n个数
                              return ret;
                          }
                          void dfs(int pos, int n)
                          {
                              if(pos == n + 1){ // 更新结果
                                  ret += 1;
                                  return;
                              }
                              // 全排列 + 判断是否符合优美排列
                              for(int i = 1; i 
                                  if(!used[i] && (i % pos == 0 || pos % i == 0))
                                  {
                                      used[i] = true;
                                      dfs(pos + 1, n);
                                      used[i] = false; // 恢复现场
                                  }   
                                  
                              }
                          }
                      };
                      
微信扫一扫加客服

微信扫一扫加客服

点击启动AI问答
Draggable Icon