IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 关于“组合”“全排列”等问题的回溯解法 -> 正文阅读

[数据结构与算法]关于“组合”“全排列”等问题的回溯解法

本篇文章是关于leetcode的三道递归/回溯问题的探究,分别是“组合”“全排列”“字母大小写全排列”三个问题。

(1)组合

给定两个整数?n?和?k,返回范围?[1, n]?中所有可能的?k?个数的组合可以按?任何顺序?返回答案。

?看到本题,大多数人想到的是先使用暴力for循环来遍历这个数组,但是发现当所给的n和k足够大时,就会嵌套太多的for循环,不利于我们的解题。此时,遇到嵌套层数过多的情况,我们要选择回溯法递归来解决此类问题,我们可以将回溯法解决问题抽象为树类的树形结构来解决,我们将此类为题化成树形结构来解决,如图所示:

可以发现出,1,2,3,4依次取数,当前面取过的数字以后,后面取的数字就不能与之成为组合,每次选的元素都会变化,当我们搜寻到这棵树的叶子结点时,就能找到所有的集合。

下面写出我们的代码:

class Solution {
public :
         //创建一个符合条件结果集合的数组
         vector < vector < int > res;
         //创建一个用来存放符合条件结果的数组
         vector < int > temp;

         //主函数
         vector < vector < int >> combine ( int n, int k)
         {
                //从第一个数开始递归
                dfs ( temp , 1 , n);
                return res;
         }

         //dfs函数进行递归
         void dfs ( vector < int > & temp, int start, int n, int k) 
         {
                //递归终止条件:如果temp数组装满了k个数字,则返回并将temp数组装入到res数组中
                if( temp.size () == k )
                {    
                        res.push_back ( temp );
                        return;
                }
                for( int i = start; i <= n ; i++)
                {
                        //处理该结点
                        temp.push_back ( i );
                        //继续递归下一个结点
                        dfs ( temp, start+1 , n , k);
                        //回溯,一定要恢复到先前的状态,撤销处理的结点
                        temp.pop_back ();
                }
        }
};
                 
               

?通过观察发现,图中? for( int i = start; i <= n ; i++)处存在浪费的情形。举例说明,假如给定的n是4,k给定的值是3时,我们发现当i取值为2时,i满足条件2<=4,但是无论如何取值,则永远无法在2这个位置找到3个数的组合。

我们再次举出例子来找出规律,当n=4,k=3时:

temp.size() = 0 时还需要3个数构成组合,最后3个数为{2, 3, 4},故此时至多应遍历到2
temp.size() = 1 时还需要2个数构成组合,最后2个数为{3, 4},故此时至多应遍历到3
temp.size() = 2 时还需要1个数构成组合,最后1个数为{4},故此时至多应遍历到4

我们可以找出规律,最后的要遍历的数字和数组的当前元素个数和n、k的值有关系,即

lastnum=n-(k-temp.size())+1;

所以我们可以对上述代码段进行剪枝优化,来降低代码的复杂度。

class Solution {
public :
         //创建一个符合条件结果集合的数组
         vector < vector < int > res;
         //创建一个用来存放符合条件结果的数组
         vector < int > temp;

         //主函数
         vector < vector < int >> combine ( int n, int k)
         {
                //从第一个数开始递归
                dfs ( temp , 1 , n);
                return res;
         }

         //dfs函数进行递归
         void dfs ( vector < int > & temp, int start, int n, int k) 
         {
                //递归终止条件:如果temp数组装满了k个数字,则返回并将temp数组装入到res数组中
                if( temp.size () == k )
                {    
                        res.push_back ( temp );
                        return;
                }
                for( int i = start; i <= n - ( k - temp.size () ); i++)
                {
                        //处理该结点
                        temp.push_back ( i );
                        //继续递归下一个结点
                        dfs ( temp, start + 1 , n , k);
                        //回溯,一定要恢复到先前的状态,撤销处理的结点
                        temp.pop_back ();
                }
        }
};

?

(2)全排列

给定一个不含重复数字的数组?nums?,返回其?所有可能的全排列?。你可以?按任意顺序?返回答案。

此题与上题有异曲同工之妙,都是需要将数组中的数字进行组合并且进行返回。但是本题是给定一个数组,让你返回不同的排列顺序。?每一个数组中都存在三个元素,但是每个数组不允许有重复的元素出现,所以我们第一时间就是想到了回溯法。回溯法允许试错,发现下一步得不到答案时,就会返回上一步再次寻找问题的答案,重点是回退这一步骤,和深度优先遍历算法很类似,我们同样也画出树形图来观察。

如果取了1,2,3中的一个数字后,怎么保证下次取值避开这个值呢,我们想到可以使用状态数组bool类型来记录是否取到该值,具体代码如下:

?

class Solution {
public :
         //创建一个符合条件结果集合的数组
         vector < vector < int > res;
         //创建一个用来存放符合条件结果的数组
         vector < int > temp;
         //创建一个状态数组来判断该结点是否取过
         vector < bool > status;
         //存放题目给定数组的元素个数
         int n;

         //主函数
         vector < vector < int >> permute ( vector < int > &nums)
         {
                //获取数组的大小
                n = nums.size ();
                //重置状态函数为未读状态
                status.resize ( n, false);
                //调用函数
                dfs ( nums );
                return res;
         }

         //dfs函数进行递归
         void dfs ( vector < int > & nums) 
         {
                //递归终止条件:如果temp数组装满了n个数字,则返回并将temp数组装入到res数组中
                if( temp.size () == n )
                {    
                        res.push_back ( temp );
                        return;
                }
                for( int i = 0; i < n; i++)
                {
                        //如果没有访问,则处理该结点
                        if( !status [i])
                        {
                            //则访问,并且标记为true
                            status[i] = true;
                            temp.push_back ( i );
                            dfs ( nums );
                        //回溯,一定要恢复到先前的状态,撤销处理的结点
                            temp.pop_back ();
                            //将结点设置为未访问状态
                            status[i] = false;
                         }
                }
         }
};

(3)字母大小写全排列

给定一个字符串S,通过将字符串S中的每个字母转变大小写,我们可以获得一个新的字符串。返回所有可能得到的字符串集合。

?本题同样是要求将给定字符串组合排列得到新的字符串的集合,我们同样可以采用回溯算法,下面给出相应的代码:

class Solution {
public:
    //返回结果的字符串集合
    vector < string > res;
    //主函数
    vector < string > letterCasePermutation ( string s ) 
    {
       int n = s.size ();
       //将原串加入到结果集合中
       res.push_back ( s );
       dfs ( s, 0, n );
       return res;
    }
    //子函数
    void dfs ( string &s, int start, int n)
    {
        //从第一个字符开始遍历
        for( int i =s tart; i < n; i++)
        {
            //当前字符为小写字母时将其转化为大写字母
            if( s[i] >= 'a' && s[i] <= 'z')
            {
                s[i] = s[i] + ( 'A'-'a' );
                //加入到数组中
                res.push_back ( s );
                //开始递归
                dfs (s, i + 1, n);
                //回溯状态
                s[i] = s[i] - ( 'A' - 'a' );
            }
            //当前字符为大写字母时,转化为小写字母
            else if( s[i] >= 'A' && s[i] <= 'Z' )
            {
                s[i] = s[i] - ( 'A' - 'a' );
                res.push_back ( s );
                dfs ( s, i + 1, n );
                s[i] = s[i] + ( 'A' - 'a' );
            } 
        }
    }
};

以上三题都是回溯的经典题型,其实可以找到规律,我们可以把解题步骤分为四部:找到递归终止条件-->满足递归条件式->开始递归->恢复现场 这四部,还需多多练习,掌握思想才是王道!

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-08-15 15:50:18  更:2021-08-15 15:51:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 20:19:00-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码