全排列笔记

news2025/1/13 2:33:00

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

运行结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/114.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何在Linux上优雅地写代码-Linux生存指南

初入Linux&#xff0c;发现老是要面对一个命令行&#xff0c;大黑框&#xff0c;看不懂各种手册&#xff0c;写代码也是用vi/vim&#xff0c;难受的捉急。其实Linux下的各种工具&#xff0c;强大得超出你的想象&#xff0c;如果你初入Linux&#xff0c;那么你急需阅读这篇文章&…

操作系统的主要功能

目录 一. 处理机管理功能 1.1 进程控制 1.2 进程同步 1.3 进程通信 1.4 进程调度 二. 存储器管理功能 2.1 内存分配 2.2 内存保护 2.3 地址映射 2.4 内存扩充 三. 设备管理功能 3.1 缓冲管理 3.2 设备分配 3.3 设备处理 3.4 设备独立性和虚拟设备 四…

关于Python爬虫兼职,这里有一条高效路径

前言 昨天&#xff0c;一位00后前来报喜&#xff0c;也表达感谢。 他说&#xff0c;当初刚毕业啥也不会也找不到工作&#xff0c;最后听了我的&#xff0c;边学爬虫边做兼职项目&#xff0c;积极主动求职投简历&#xff0c;既可以兼职获得收益&#xff0c;也能积累项目经验谋求…

Linux:以K、M、G查看文件大小;

简介&#xff1a;灵活多变的查看文件的大小 历史攻略&#xff1a; Linux&#xff1a;sudo免密 python&#xff1a;执行dos命令、Linux命令 案例源码&#xff1a; # 以适当方式显示文件大小&#xff1a; ls -lh# 以byte显示文件大小&#xff1a; ls -l# 以M显示文件大小&am…

NR PUSCH(五) DMRS

微信同步更新欢迎关注同名modem协议笔记 PUSCH DMRS和PDSCH DMRS内容基本一样&#xff0c;但也有不同的地方&#xff0c;例如PUSCH 可能需要Transform precoding&#xff0c;port 对应0~11(DMRS configured type2)等等。先简单看看Transformprecoding的相关内容&#xff0c;Tr…

Excel数据分析实战之开宗明义: Excel与数据分析实战

大家好&#xff0c;我是爱编程的喵喵。双985硕士毕业&#xff0c;现担任全栈工程师一职&#xff0c;热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。喜欢通过博客创作的方式对所学的知识进行总结…

军用大数据 - Spark机器学习

文章目录第1关&#xff1a;Iris 分类任务描述相关知识1&#xff1a;观察数据集2&#xff1a;RFormula 特征提取3&#xff1a;pandas 的 concat 函数编程要求代码实现————————————————————————————————————————第2关&#xff1a;图片识…

网络原理 --- 传输层Ⅲ TCP协议中的滑动窗口,流量控制和拥塞控制

文章目录网络原理传输层TCP协议4.滑动窗口5.流量控制6.拥塞控制总结网络原理 介绍TCP/IP协议中每一层里面的核心内容~ 应用层传输层网络层数据链路层物理层 传输层TCP协议 4.滑动窗口 TCP能够保证可靠传输,但是失去了效率! 但是TCP希望能够在保证可靠性的前提下,尽可能地提…

达梦数据库在不修改SQL的情况下为SQL指定HINT

前言 在Oracle中可以使用outline、SQL PROFILE等手段去在无需修改SQL语句的情况下&#xff0c;来保证SQL执行计划在不同硬件环境下相同&#xff0c;从而保证SQL语句在不同环境的执行效率。那么&#xff0c;在达梦数据库中则可以使用SF_INJECT_HINT系统函数达到类似的效果。 SF…

Java学习笔记 --- 异常

一、基本介绍 Java语言中&#xff0c;将程序执行中发生的不正常情况称为“异常”。&#xff08;开发过程中的语法错误和逻辑错误不是异常&#xff09; 执行过程中所发生的异常事件可以分为两类 1、Error&#xff08;错误&#xff09;&#xff1a;Java虚拟机无法解决的严重问…

十月了,请问2022届的同学们都找到工作了吗?

今年的就业大环境就不多说了&#xff0c;大家都知道。一边是超千万规模的应届毕业生&#xff0c;叠加教培、地产等行业裁员&#xff1b;另一边则是疫情反复影响之下&#xff0c;企业瘦身裁员、停招、缩招。在白领性质的劳动力市场&#xff0c;劳动力供给严重大于需求&#xff0…

【C语言】解题训练

目录 字符串左旋 方法1 方法2 字符串旋转结果判断 方法1 方法2 杨氏矩阵 位段 题目1 题目2 联合体 题目1 题目2 有序序列合并 变种水仙花 找单身狗 字符串左旋 实现一个函数&#xff0c;可以左旋字符串中的k个字符。 例如&#xff1a; ABCD左旋一个字符得到…

纷享销客联合B.P商业伙伴携手30+企业CEO走进南天信息

数字化智能化建设的当下&#xff0c;数字化服务商承担着承上启下的核心力量。企业数字化转型成为刚需&#xff0c;意味着ICT企业的市场前景持续乐观&#xff0c;但在疫情和竞争加剧之下&#xff0c;企业发展也遭遇增长的挑战&#xff0c;如何在数字中国的趋势之下&#xff0c;乘…

大学网课搜题公众号系统

大学网课搜题公众号系统 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 题库&#xff1a;题库后台&#xff08;点击跳转&#xf…

【从小白到大白05】c和c++内存管理

c和c内存管理 文章目录c和c内存管理c内存管理方式new/delete操作内置类型new申请动态空间delete释放空间new和delete操作自定义类型operator new与operator delete函数new[]和delete[]定位new&#xff08;placement-new&#xff09;总结以上内存泄露以上就是全部内容啦&#xf…

WPS-JS宏开发-基础知识-03-三大基本结构

系统&#xff1a;Windows 11 软件&#xff1a;WPS表格11 本系列介绍一款类Excel的软件&#xff0c;WPS表格当然也是介绍其宏开发&#xff0c;不同的是&#xff0c;使用的JS宏会同样介绍多个系列&#xff0c;本系列介绍一些基础知识 Part 1&#xff1a; 三大逻辑结构 一个具体的…

如何给字符串字段加索引?

1.引例 现在的系统中&#xff0c;很多都会包含邮箱字段&#xff0c;那要如何给这个字段建立索引呢&#xff1f; 假设&#xff0c;现在维护了一个用户表&#xff0c;其中包含邮箱&#xff0c;定义如下&#xff1a; mysql>create table SUser(ID int primary key,email var…

OpenGL之多边形偏移、雾效、纹理映射

1.1 OpenGL中可以设置物体的点、线、面绘制模式。如果需要同时绘制多种模式&#xff0c;如下以面和线模式绘制两遍模型&#xff0c;可以看到线不连续&#xff0c;当镜头推远推近时会出现闪烁现象。 void glPolygonMode(GLenum face,GLenum mode);face :GL_FRONT&#xff0c;GL…

分治暴力求解最近点对问题 + 时间性能量化分析

Catalogue1 Intro2 Problem3 Time performance analysis4 Solution5 Reference1 Intro 本文旨在讨论分治和暴力在求解最近点对问题时的时间性能问题&#xff0c;关于解题部分不做过多讲解&#xff0c;只附上相关代码。 2 Problem 给定平面上N个点&#xff0c;找出其中的一对…

【Linux】第七章 进程控制(进程创建+进程终止+进程等待+进程替换+min_shell)

&#x1f3c6;个人主页&#xff1a;企鹅不叫的博客 ​ &#x1f308;专栏 C语言初阶和进阶C项目Leetcode刷题初阶数据结构与算法C初阶和进阶《深入理解计算机操作系统》《高质量C/C编程》Linux ⭐️ 博主码云gitee链接&#xff1a;代码仓库地址 ⚡若有帮助可以【关注点赞收藏】…