当前位置: 首页 > news >正文

全排列笔记

14天阅读挑战赛

 全排列

题目

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

示例:

输入: [1,2,3]

输出:

[

 [1,2,3],

 [1,3,2],

 [2,1,3],

 [2,3,1],

 [3,1,2],

 [3,2,1]

]

解答

方法一:回溯

思路

从高中的数学知识我们可以知道

从[1,2,3]中选出3个数进行排列(排序有顺序之分、组合没有顺序之分

结果有n!=3!=3*2*1种

1-2-3

1-3-2

2-1-3

2-3-1

3-1-2

3-2-1

怎么计算的呢?

假设现在我们有三个数:1、2、3

需要依次填入下面三个蓝色方块

那么第一个方块有三种选择:1或2或3

第二个有两种选择:2或者3(假设第一次选择了1)

那么第三个方块就只有一种选择:3(假设第一次选择1,第二次选择2)

综上:n个数(不重复),填入n个方块,一共有n!种不同排列方式。

那么为什么第一次有三种而第二次只有两种了呢?

这是因为第一次选择时,没有一个数被选,那么一个三个数,就有三种选择方法

到第二次选择时,因为第一次必定选择了一个数,这时剩下的可供选择的数就只有2个,选择就只有两种了

到第三次选择时,因为前面两次已经选择了两个数,可供选择的数就只有一个,选择就只有一种了

从上面提取核心思想:

每次进行选择时,我们都是从一些尚未选择的数中选择出一个数字填充方块,然后将该数标记为已选择,再进行下一轮新的选择。

根据上述思想,画出下面思路图:

上图若没有理解 没有关系 先看下面

这里我们可以利用一个全局变量temp数组,初始化为空,作为一个临时容器

开始时,1、2、3都是尚未选择的

此时我们可以选择 1 或 2 或 3,有三种选择方法

假设我们选择了1后

那么将1添加至temp中【temp:1】

此时1已经被选择

之后可供我们选择的就是 2、3

再选择2【temp:1-2】

最后再选择3【temp:1-2-3】

此时temp与我们预期的答案吻合【其实就是temp数组的长度与预期结果数组长度一样】

将其添加至res结果数组中即可

在我们的思想中

可以很简单的知道 1被选择 2、3未被选择

但是在程序中,我们如何实现这个呢?

这里我们利用一个数组isused来判定元素是否被选择

假如情况是:1被选择 2、3未被选择

那么原数组与isused数组关系如下:

所以在每次进行选择前,我们利用isused数组,判定某个元素是否被选择

只有未被选择 我们才可以对这个元素进行操作

所以在每次选择前,我们都会扫描isused数组,当为true时,才对该元素进行操作。【具体是true还是false进行操作依据个人习惯而定,这里海轰是true代表未被选择】

根据上面的思路,写出下面的代码:

vector<int> temp;
void backtracking(vector<vector<int>> &res,vector<bool> &isused,vector<int> &nums){
        // 当temp数组的长度等于期望数组的长度时 return
        if(temp.size()==nums.size()){
            res.push_back(temp);
            return ;
        }
        // 遍历所有选择
        for(int i=0;i<nums.size();++i){
            // 对没有选择过的元素再进行抉择:选择它|不选择它
            if(isused[i]){
            		// 选择该元素 选择后标记该元素为已选择 同时进行下一元素的抉择
                temp.push_back(nums[i]);
                isused[i]=false;
                backtracking(res,isused,nums);
                // 回复原来状态:未选择 同时从temp中pop出 
                isused[i]=true;
                temp.pop_back();
          }
   }
}
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int> > res;
        vector<bool> isused(nums.size(),true);
        backtracking(res,isused,nums);
        return res;
}

运行结果

疑问

疑问1:程序具体是怎么执行的呢?

这里只给出程序的运行简图,

对详细步骤感兴趣对小伙伴可以自己动手画画步骤图

疑问2:为什么当 temp.size()==nums.size()或所有的元素都被选择后 函数backtracking返回呢?

首先

当temp.size()==nums.size()时

代表此时的temp已经满足答案需要

可以将其添加至res中

如果temp.size()<nums.size()

那么意思就是还temp中还需要一些元素

比如要求数组长度是4

此时temp长度只有2

那么就还需要再选择两个数进入temp

其次

当所有的元素都被选择后

说明已经没有元素可以供加入temp中

则必须return

疑问3:为什么 backtracking函数的开头就需要判定temp.size()==nums.size()?而不是在函数中部或者尾部?

首先可以看出

在这里我们用了递归

那么必须要一个递归终止条件

不然递归怎么结束呢?

backtracking作为一个递归函数

我们可以想到

每次进入backtracking函数时

第一要务就是需要判定此时temp的size

如果满足答案要求的长度

将temp压入res数组

再return

后面步骤也就不需要再运行

疑问4:为什么backtracking函数中每次要进行for(int i=0;i<nums.size();++i){}循环?

这里是因为每次进行抉择前

我们需要找出所有未被选择的元素

在这里我们使用的判定数组isused标志选择与否

所以每次都需要循环扫描isused数组【代码中:因为isused数组长度与nums数组一样,这里size写谁都可以】

疑问5:为什么会进行temp.pop_back()?

从代码中

我们可以发现

temp.pop_back()前面有句 backtracking(res,isused,nums,temp);

也就是说

只有backtracking(res,isused,nums,temp) 运行完后

才会进行temp.pop_back()

那么啥时候backtracking(res,isused,nums,temp)会结束呢?

参考疑问2

可以知道

backtracking(res,isused,nums,temp)结束时

代表某个元素已经被选择了,且引进被压入了temp中使用了

额外意思就是该元素已经被使用了

因为每次抉择都有两种:选择与不选择

那么选择结束了

之后肯定就是不选择了

但是此时元素已经被压入了temp中

那么需要怎么办呢?

简单,pop出即可,同时标记isuserd数组为true即可。

改进

改进方案:temp可以不定义为全局变量

代码如下:

void backtracking(vector<vector<int>> &res,vector<bool> &isused,vector<int> &nums,vector<int> &temp){
        // 选择完毕
        if(temp.size()==nums.size()){
            res.push_back(temp);
            return ;
        }
        for(int i=0;i<nums.size();++i){
            if(isused[i]){
                temp.push_back(nums[i]);
                isused[i]=false;
                backtracking(res,isused,nums,temp);
                isused[i]=true;
                temp.pop_back();
            }
    }
}
vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int> > res;
        vector<bool> isused(nums.size(),true);
        vector<int> temp;
        backtracking(res,isused,nums,temp);
        return res;
}

运行结果

改进方案:bool<->int push_back<->emplace_back

isused数组海轰使用的bool变量

在评论区有小伙伴说

当数据量大的时候使用int类型会好一点

同时

在将temp压入res时

使用emplace_back()会好一点

因为不会产生临时变量

代码如下:

vector<int> temp;
    void backtracking(vector<vector<int>> &res,vector<int> &isused,vector<int> &nums){
        // 当temp数组的长度等于期望数组的长度时 return
        if(temp.size()==nums.size()){
            res.emplace_back(temp);
            return ;
        }
        // 遍历所有选择
        for(int i=0;i<nums.size();++i){
            // 对没有选择过的元素再进行抉择:选择它|不选择它
            if(!isused[i]){
            		// 选择该元素 选择后标记该元素为已选择 同时进行下一元素的抉择
                temp.push_back(nums[i]);
                isused[i]=1;
                backtracking(res,isused,nums);
                // 回复原来状态:未选择 同时从temp中pop出 
                isused[i]=0;
                temp.pop_back();
          }
   }
}
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int> > res;
        vector<int> isused(nums.size());
        backtracking(res,isused,nums);
        return res;
}

运行结果

方式二:回溯(官方题解,动态交换原数组中的元素)

官方题解代码如下:

void backtrack(vector<vector<int>>& res, vector<int>& output, int first, int len){
        // 所有数都填完了
        if (first == len) {
            res.emplace_back(output);
            for(int i=0;i<output.size();++i){
                cout<<output[i]<<" ";
            }
            cout<<endl;
            return;
        }
        for (int i = first; i < len; ++i) {
            // 动态维护数组
            swap(output[i], output[first]);
            // 继续递归填下一个数
            backtrack(res, output, first + 1, len);
            // 撤销操作
            swap(output[i], output[first]);
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int> > res;
        backtrack(res, nums, 0, (int)nums.size());
        return res;
    }

运行结果

思路

在官方题解中

并没有向我们使用的依次将元素添加至一个临时数组temp中

而是直接在原数组上进行变换

将变换结果依次加入结果数组res中。

程序具体执行过程如下:

观察每个节点生成的下一个节点,元素是如何交换的?

对for循环进行思考:

当first=0时,此时for循环会进行:swap(nums[0],nums[0])/swap(nums[1],nums[0])/swap(nums[2],nums[0])

当first=1时,此时for循环会进行:swap(nums[1],nums[1])/swap(nums[2],nums[1])

当first=2时,此时for循环会进行:swap(nums[2],nums[2])

        for (int i = first; i < len; ++i) {
            // 动态维护数组
            swap(output[i], output[first]);
            // 继续递归填下一个数
            backtrack(res, output, first + 1, len);// 注意:这里是first+1
            // 撤销操作
            swap(output[i], output[first]);
        }

这里可能会有点难度

不好想象程序时如何进行的?

建议首先自己对照程序跑一次

明白每一次运行的结果

然后再对照上面几张图寻找规律

之后对官方题解就会有所理解了

变式

从[1,2,3,4,5,6]中,选择3个数进行排列,输出所有排列方式。

代码(修改backtracking函数中的返回条件即可)

vector<int> temp;
    void backtracking(vector<vector<int>> &res,vector<bool> &isused,vector<int> &nums){
        // 选择完毕 
        if(temp.size()==3){
            res.push_back(temp);
            return ;
        }
        for(int i=0;i<nums.size();++i){
            if(isused[i]){
                temp.push_back(nums[i]);
                isused[i]=false;
                backtracking(res,isused,nums);
                isused[i]=true;
                temp.pop_back();
            }
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int> > res;
        vector<bool> isused(nums.size(),true);
        backtracking(res,isused,nums);
        return res;
    }

运行结果

代码2(对官方题解进行修改):

void backtrack(vector<vector<int>>& res, vector<int>& output, int first, int len){
        // 所有数都填完了
        // 修改结束条件
        if (first == 3) {
            vector<int> temp(output.begin(),output.begin()+3);//利用临时数组存储
            res.emplace_back(temp);
            return;
        }
        for (int i = first; i < len; ++i) {
            // 动态维护数组
            swap(output[i], output[first]);
            // 继续递归填下一个数
            backtrack(res, output, first + 1, len);
            // 撤销操作
            swap(output[i], output[first]);
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int> > res;
        backtrack(res, nums, 0, (int)nums.size());
        return res;
    }

运行结果

相关文章:

  • Python环境变量与引包错误
  • Mysql内置函数整理--基础类型函数
  • 万字爽文一篇带你掌握Java8新特性
  • Node.js的Web后端开发调研
  • Spring、MySQL、日期、BigDecimal、集合、反射、序列化中的坑与使用指南
  • DC 交换机 buffer 的平方反比律
  • 编程神器Copilot逐字抄袭他人代码?
  • 【数据结构】初始集合框架
  • “今天星期五“-SAP SE09/STMS 请求号传输中遇到的错误及解决方案
  • @Bean, @Component, @Configuration简析
  • 2022 年InfoWorld 精选最佳开源软件
  • 如何打造一个可躺赚的网盘项目,每天只需要2小时
  • python中的装饰器(基础装饰器)
  • 多级缓存与局部性原理
  • NLP冻手之路(2)——文本数据集的下载与各种操作(Datasets)
  • 《深入 React 技术栈》
  • 2019.2.20 c++ 知识梳理
  • axios请求、和返回数据拦截,统一请求报错提示_012
  • JDK9: 集成 Jshell 和 Maven 项目.
  • LintCode 31. partitionArray 数组划分
  • MySQL用户中的%到底包不包括localhost?
  • Swoft 源码剖析 - 代码自动更新机制
  • - 概述 - 《设计模式(极简c++版)》
  • 构建二叉树进行数值数组的去重及优化
  • 精益 React 学习指南 (Lean React)- 1.5 React 与 DOM
  • 前端每日实战:70# 视频演示如何用纯 CSS 创作一只徘徊的果冻怪兽
  • 微信小程序上拉加载:onReachBottom详解+设置触发距离
  • 学习笔记:对象,原型和继承(1)
  • k8s使用glusterfs实现动态持久化存储
  • ​ 无限可能性的探索:Amazon Lightsail轻量应用服务器引领数字化时代创新发展
  • ### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTr
  • #LLM入门|Prompt#3.3_存储_Memory
  • #图像处理
  • (175)FPGA门控时钟技术
  • (C)一些题4
  • (js)循环条件满足时终止循环
  • (Redis使用系列) Springboot 实现Redis消息的订阅与分布 四
  • (Redis使用系列) Springboot 使用redis实现接口Api限流 十
  • (二)换源+apt-get基础配置+搜狗拼音
  • (二开)Flink 修改源码拓展 SQL 语法
  • (附源码)ssm基于jsp的在线点餐系统 毕业设计 111016
  • (附源码)计算机毕业设计高校学生选课系统
  • (全部习题答案)研究生英语读写教程基础级教师用书PDF|| 研究生英语读写教程提高级教师用书PDF
  • (三)docker:Dockerfile构建容器运行jar包
  • (十二)springboot实战——SSE服务推送事件案例实现
  • (一)Neo4j下载安装以及初次使用
  • (译) 理解 Elixir 中的宏 Macro, 第四部分:深入化
  • (转)linux下的时间函数使用
  • (转)全文检索技术学习(三)——Lucene支持中文分词
  • (转)详解PHP处理密码的几种方式
  • .jks文件(JAVA KeyStore)
  • .net core使用ef 6
  • .NET DevOps 接入指南 | 1. GitLab 安装
  • .NET Micro Framework 4.2 beta 源码探析
  • .net MVC中使用angularJs刷新页面数据列表