zoukankan      html  css  js  c++  java
  • 力扣_中级算法_回溯算法_2~4题

    一位C++小白的力扣刷题_成长记录_welcome to visit  ^_^  

    ( 从这一篇开始,调整一下排版顺序。将 “学习心得” 放到 “实现(C++)” 后面 )

     

    回溯算法_第2题:括号生成

    题目描述:

    数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

    举例 

    示例:

    输入:n = 3
    输出:[
           "((()))",
           "(()())",
           "(())()",
           "()(())",
           "()()()"
         ]

    解题思路:利用 容器+递归+指针(其实是下标)。观察其特征:在储存 “(” 和 “)” 的容器内, “(” 的最大数量为 n ,而 “)” 的数量总是 小于等于 “(” 的数量。

    要充分利用这一点来建立 回溯递归函数。

    学习心得:今天做得每道题,我都先自己尝试敲一遍,但还是“回溯功夫”不到家,有些算法思维

    没有掌握,所以后面都看了题解。但我没彻底理解前辈的谋篇代码,是不会轻易去学习的。这道题

    归我最大的感触就是, 那一句“ left >= right ”。

    实现:(C++)

    class Solution {
      public:
        string temp="";                     //临时字符串( 可以把它看成“伪”全局变量 ):用来储存含有“(”和“)”的字符串
        void helper( vector<string>& vec,int n, int left, int right )    //left:表示 “(” 的数量。 right:表示“)” 的数量
          {
              if( temp.size()==2*n )
           {
            vec.push_back( temp );       //满足要求时,将 临时字符串 压入 二维容器
            return ;
           }
           if( left<n )         //只要 “(” 的数量不超过 n,继续加“(”
          {
            temp.push_back( '(' );
            helper( vec,n,left+1,right );
            temp.pop_back();
             }
          if( left > right )        //建议画一画图,画几下就清晰明了了!
          {
             temp.push_back( ')' );
             helper( vec,n,left,right+1 );
             temp.pop_back();
            }
          }

        vector<string> generateParenthesis(int n)
        {
          vector<string> vec;
          if( n==0 ) return vec;      //特殊情况,特殊处理
               helper( vec,n,0,0 );
              return vec;
        }
    };

    运行结果: 

    代码执行结果:
    我的输入
    3
    我的答案
    ["((()))","(()())","(())()","()(())","()()()"]
    预期答案
    ["((()))","(()())","(())()","()(())","()()()"]

    学习心得:今天做得每道题,我都先自己尝试敲一遍,但还是“回溯功夫”不到家,有些算法思维

    没有掌握,所以后面都看了题解。但我没彻底理解前辈的谋篇代码,是不会轻易去学习的。这道题

    归我最大的感触就是, 那一句“ if( left > right) ”。太6了!

    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    回溯算法_第3题:全排列

    题目描述:

     给定一个 没有重复 数字的序列,返回其所有可能的全排列。

    举例:

    示例:

    输入: [1,2,3]
    输出:
    [
      [1,2,3],
      [1,3,2],
      [2,1,3],
      [2,3,1],
      [3,1,2],
      [3,2,1]
    ]

    解题思路:先参考一下 力扣里的一位前辈的 解题思路,太棒啦!。。。参考完了,听我说,主要就是那个 “做选择”,之后 “撤销选择” 这个 算法思维很赞!

    》》》》代码方面,回溯算法的框架:

    result = []
    def backtrack(路径, 选择列表):
    if 满足结束条件:
    result.add(路径)
    return

    for 选择 in 选择列表:
    做选择
    backtrack(路径, 选择列表)
    撤销选择
    其核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」,特别简单。

    作者:labuladong
    链接:https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-xiang-jie-by-labuladong-2/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。《《《《《《《

    实现:(C++) 

    class Solution {
      public:
        void helper( vector<vector<int>>&vec,vector<int>& nums,int start,int n)
         {
            if( start==n )          //终止条件。
           {
            vec.push_back( nums );
            return ;
           }
         int i;
           for( i=start;i<n;i++ )          //这三句话,真得费神去理解呢....也是最好作一作图。而且友情提醒:坚持循环下去。到了后面的循环你就会发现奥秘!
         {
          swap( nums[i],nums[start] );    //有很多次都是原地交换,我还没搞清楚这是什么原因...
          helper( vec,nums,start+1,n);     //当 start值为2 作为递归返回时,真正的故事就开始了。 那个时候 i 的值也为2,但要进行循环 i++ 后为3。
                          // 之后又递归到 这一句。 i为1,start值为1。i再次进行循环 i++ ,而start没变, 全排列 拉开帷幕。
          swap( nums[i],nums[start] );
           }
          return ;
         }
        vector<vector<int>> permute(vector<int>& nums)
         {
         vector<vector<int>> vec;
         if( nums.size()==0 ) return {};
         if( nums.size()==1 ) return { { nums[0]} };      //特殊情况,特殊处理
          helper( vec,nums,0,nums.size() );
        return vec;
      }
    };

    运行结果:

    代码执行结果:
    我的输入
    [1,2,3]
    我的答案
    [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,2,1],[3,1,2]]
    预期答案
    [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

    学习心得:这道回溯题,让我感受到,一种“简单到复杂思维”,也就是我们想就 比较简单的例子 而言

    来敲代码,然后可以把这个代码延伸到更复杂的例子上。而且那个 “回溯算法的框架”,太赞了!(注:这个

    全排列是从末尾开始逐渐 交换的。)

    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    回溯算法_第4题:子集

    题目描述:

    给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

    说明:解集不能包含重复的子集。

    举例:

    示例:

    输入: nums = [1,2,3]
    输出:
    [
      [3],
      [1],
      [2],
      [1,2,3],
      [1,3],
      [2,3],
      [1,2],
      []
    ]

    解题思路:和上一题 “全排列” 有很大的相似之处。连 “回溯算法的模板” 都一样。但是 终止条件和递归实参 不同。

     

    实现:(C++)

    class Solution {
      public:
        vector<int> temp;
        void backtrack( vector<vector<int>>&res, vector<int>& nums, int start)
          {
          res.push_back( temp );
          int i;
          for( i=start;i<nums.size();i++ )        //其实 i<nums.size() 也就是终止条件。
            {
               temp.push_back( nums[i] );        //“做出选择”
               backtrack( res,nums,i+1 );       //这里的 i+1 非同一般,为什么呢?在全排列那里, 是“start+1”, 而这里是 “i+1” 。作作图就好理解了。
               temp.pop_back();           //“撤销选择”
               }
             }
        vector<vector<int>> subsets(vector<int>& nums)
          {
          vector<vector<int>>res;
          if( nums.size()==0 ) return {{}};         //特殊情况,特殊处理
               backtrack( res,nums,0 );
          return res;
            }
    };

    运行结果:

    代码执行结果:
    我的输入
    [1,2,3]
    我的答案
    [[],[1],[1,2],[1,2,3],[1,3],[2],[2,3],[3]]
    预期答案
    [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

    学习心得:上一题的 全排列 和这道 子集 很相似,但想自己独立做出来,还是不容易。我发现它们都有个

    for循环,然后在这个for循环里面进行 回溯 。现在我还没太关注 时间复杂度 和 空间复杂度 ,我觉得题解里

    能把 空间时间复杂度 用 美丽的数学公式表示出来的,都厉害!回溯回溯,多练多记,然后 融会贯通。

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    回溯算法_第5题: 单词搜索

    题目描述:

    给定一个二维网格和一个单词,找出该单词是否存在于网格中。

    单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

    举例:

    示例:

    board =
    [
      ['A','B','C','E'],
      ['S','F','C','S'],
      ['A','D','E','E']
    ]
    
    给定 word = "ABCCED", 返回 true
    给定 word = "SEE", 返回 true
    给定 word = "ABCB", 返回 false

    提示:

    • board 和 word 中只包含大写和小写英文字母。
    • 1 <= board.length <= 200
    • 1 <= board[i].length <= 200
    • 1 <= word.length <= 10^3

    解题思路:两层for循环遍历二维数组,把二维数组里面的 每个元素作为 “火车头”,都来试一试,如果有满足情况的 “火车” ,就直接返回 true 了。另外在回溯函数中,其形参

    要有一个 下标 ,专门用来记录 目标单词里面字母的 下标位置。

    实现:(C++)

    class Solution {
      public:
        bool backtrack( vector<vector<char>>& board,string& word,int wordindex,int i,int j )      //wordindex:目标单词里面的字母下标
          {
             if( word[wordindex]!=board[i][j] )          //只要 下一节 “火车厢” 不对口,返回 false
              return false;
          if( wordindex==word.size()-1 )          //这是一个终止条件,不可缺少
              return true;
         char temp=board[i][j];
            board[i][j]='*';         // 前辈解题代码的 精髓,换一个符号,相当于做个标记,防止该节 车厢(或车头)被重复使用
            wordindex++; //下标向右移一位
            if( i>0 && backtrack(board,word,wordindex, i-1,j)           //向左找合适的“车厢”
          ||i<board.size()-1 && backtrack(board,word,wordindex,i+1,j)     //向右找合适的“车厢”
          ||j>0 && backtrack(board,word,wordindex,i,j-1)            //向上找合适的“车厢”
          ||j<board[0].size()-1 && backtrack(board,word,wordindex,i,j+1) )   //向下找合适的“车厢”
              return true;
            board[i][j]=temp;       //如果没找到合适的“车厢”,把标记过的前一节(或多节,因为递归时可能多次执行)“车厢”恢复原样。
         return false;
             }
        bool exist(vector<vector<char>>& board, string word) {
        int i,j;
        for( i=0;i<board.size();i++ )
           for( j=0;j<board[0].size();j++ )
            if( backtrack(board,word,0,i,j) )
              return true;
        return false;        //如果都不行,才返回false
      }
    };

    运行结果:

    代码执行结果:
    我的输入
    [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]]
    "ABCCED"
    我的答案
    true
    预期答案
    true


    学习心得:这道题很有趣的哟。我一看到,我就想用昨天学到的那个“感染函数”,但还是差那么一段距离

    写出正确的代码。没想到的是,那个 if 语句可以写这么 长,连我都被惊呆了!_  !。做了这道题后,再次

    加深了对 标记法 的认识与理解。

  • 相关阅读:
    Codeforces 469D Two Sets
    Codeforces1249D2 Too Many Segments (hard version)
    Codeforces 796D Police Stations
    Codeforces 617E XOR and Favorite Number
    Codeforces 900D Unusual Sequences
    Python底层(一):列表
    最短路径笔记(一):Floyd
    竞赛题笔记(二):剪邮票
    图论题笔记(三):最少中转次数
    图论题笔记(二):城市地图
  • 原文地址:https://www.cnblogs.com/Wang-dou-dou/p/13338264.html
Copyright © 2011-2022 走看看