*���ϰ����ǵ��������������� ɨ���츣�� ����С�������� �ڹ��������������У��в��Ծ��е������������һֱ��������ϵģ��ĸ�Ƶ���㣬���������������ļ����Ժ�ǿ�����������ʹ��������ѧ�Ļ����������֪ʶ�������������������ǽ�����Ҫ����һ��������������е�����ⷨ���Թ������Dzο��� һ������ 1���������� �����������Ŀ�г��֣�“����”��“����”��“��һ��”�������ƴ���ʱ�����ɿ���ʹ�������н��⡣ 2�����⼼�� ��������������⣬�����ĿҪ��һ����Ԫ�ر�����һ����Ҫ�Ƚ�Ҫ����һ��IJ�����Ϊһ�����壬Ȼ����������Ԫ��һ��������С����ֳ������ߣ�“����������”�� 3�����⾫�� ����1��ij���Ƽ���̳��5G���˹����ܡ��������������ݺ��Ƽ���5�����⣬ÿ��������2λ���Լα������Ҫ��ÿ������ļα����Դ���������ڣ��ʹ��ж����ֲ�ͬ�ķ��Դ���? A.120B.240 C.1200D.3840 ������˼·��������Ŀ�г���“����”�����Կ���ʹ��������һ���������Ȱ�ÿ�������2����������һ��������5�����⣬ÿ�������ڲ����Լα�������˳��ģ�����false;�ڶ��������У����Ѹղ��γɵ�5������������У����� ���ֲ��ó˷��������ܹ��ķ��Դ�����(��)����ˣ�ѡ��Dѡ�����2��Ϊ��ǿ�����Ļ����裬ij��ֱ������ϵͳ�ھٰ��ݽ�������3�����ŷֱ��ɳ�3��2��4��ѡ�ֲμӱ�����Ҫ��ÿ�����ŵIJ���ѡ�ֱ���˳������������ʲ�ͬ����˳��������������ĸ���Χ֮��? A.����20000B.5001~20000 C.1000~5000D.С��1000 ������˼·��������Ŀ�г���“����”�����Կ���ʹ��������һ���������Ȱ�ÿ�����ŵIJ���ѡ��������һ��������3�����ţ�ÿ�������ڲ�������˳��ģ����� ;�ڶ��������У����Ѹղ��γɵ�3������������У��������ֲ��ó˷��������ܹ��IJ���˳����(��)����ˣ�ѡ��Cѡ�������շ� 1���������� �����������Ŀ�г��֣�“������”��“������”��“����һ��”�������ƴ���ʱ�����ɿ���ʹ�ò�շ����н��⡣ 2�����⼼�� ��������������⣬�����ĿҪ��һ����Ԫ�ز�����һ������Ҫ�������������壬Ȼ��Ѳ�����һ���Ԫ�ز�յ��Ѿ����кõ�Ԫ���м䡣���ֳ������ߣ�“��������Ԫ�أ�����벻����Ԫ��”�� 3�����⾫�� ����3��ijѧϰƽ̨��ѧϰ�����ɹۿ���Ƶ���Ķ����¡��ղط�������̳���������Դ������������ɡ�ijѧԱҪ�Ⱥ�ѧ����������֣����ۿ���Ƶ���Ķ����²����������У���ѧԱѧϰ˳���ѡ���У� A.24��B.72�� C.96��D.120�� ���𰸡�B ������˼·��������Ŀ�г���“������������”�����Կ���ʹ�ò�շ�����һ����������Ԫ�أ����Ȱ��ղط�������̳�����Ϳ��Դ������кã����� (��)��ʽ;�ڶ�������벻����Ԫ�أ��ղ������������кû��γ�4���գ�����4�����в���“�ۿ���Ƶ”��“�Ķ�����”����(��)��ʽ���ֲ��ó˷�������6×12=72(��)ѧϰ˳����ˣ�ѡ��Bѡ��������巨 1���������� �����������Ŀ�г��֣�“��ͬ��Ԫ�طֳ��������ȵ�������”��“ÿ������һ��Ԫ��”ʱ�����ɿ���ʹ�ø��巨���н��⡣ 2�����⼼�� �����Ŀ����Ϊһ����ͬ��Ԫ�طֳ��������ȵ������飬Ҫ��ÿ������һ��Ԫ�أ��������Ԫ��֮�䣬��������������� 3�����⾫�� ����1����8����С��ͬ��ƻ���ָ�4��С���ѣ�Ҫ��ÿ��С�������ٵõ�1��ƻ����һ���м��ַ��䷽��?() A.24B.18 C.35D.20 ���𰸡�C ������˼·��������Ŀ�г���“8����С��ͬ��ƻ���ָ�4��С����”��“ÿ��С�������ٵõ�1��ƻ��”�����Կ���ʹ�ø��巨��m����ͬ����Ʒ�ָ�n���ˣ�ÿ������һ������m≥nʱ��ÿ�����ٷ�һ���� �ַַ�����˱����й������֡���ˣ�ѡ��Cѡ�����2��ij��λ������30��ѧϰ���Ϸ��Ÿ�3�����ţ�ÿ���������ٷ���9�ݲ��ϡ���һ���ж����ֲ�ͬ�ķ��ŷ���?() A.7B.9 C.10D.12 ���𰸡�C ������˼·��������Ŀ�г���“30��ѧϰ���Ϸ��Ÿ�3������”��“ÿ���������ٷ���9�ݲ���”�����Կ���ʹ�ò�շ���30�����Ϸָ�3�����ţ���ÿ�������ȷֵ�8�ݣ�ʣ��6�����Ϸָ�3�����ţ�ÿ������1�ݣ����ø��巨����˹��� �֡���ˣ�ѡ��Cѡ���֮�������������ļ����Ժ�ǿ������ϣ����λͬѧ�ڼ���ϰ���������⣬�������´�����Ŀ�ķ���! ��������Ƽ��� ���༭��Nk�� 前言上一篇「一文学会递归解题」一文颇受大家好评,各大号纷纷转载,让笔者颇感欣慰,不过笔者注意到后台有读者有如下反馈 确实,相信很多人(包括我自己)都有类似的感慨,对某个知识点,看确实是看懂了,但如果真的再用同样的套路再去解一些带有同样解题思路,但稍加变形的题,往往会束手无策。对这种情况有啥好的解决办法吗? 除了勤加练习,还有一良策! 鲁迅先生说:如果学习算法,最好一段时间内只刷某种算法思想或某种数据结构的题,啥意思呢?比如说你上次学了递归,那就持续找递归的题来刷,学了链表,这段时间就专门刷链表的题,千万不可今天刷递归,明天刷动态规划,后天又开始学习贪心算法。。。新手最怕的就是以为自己懂了,浅尝辄止,这是新手的大忌!一定要对同一类型的题穷追猛打,形成肌肉记忆,这样之后再碰到同一类型的题就会条件反射地一看:哦,这题用 xxx 思想应该可以靠谱。 言归正转,排列组合是面试中的热门考点 接下来我们看看如何用 「递归四步曲」来解排列组合,本文会从以下几个方面来讲解排列组合
什么是排列
看到这个公式,大家是不是回忆起了高中的排列公式啦 我们重新温习一下,以 1, 2, 3 这三个数字的全排列有多少种呢。 第一位我们可以选择 3 个数字,由于第二位不能与第一位相等,所以第二位只能选 2 个数字,第一,第二位既然选完了,那么第三位就只有 1 个数字可选了,所以总共有 3 x 2 x 1 = 6
种排列。 既然知道了什么是全排列,那我们来看看怎么用程序来打印全排列的所有情况: 排列的常用解法这道题如果暂时没什么头绪,我们看看能否用最简单的方式来实现全排列,什么是最简单的方式,暴力穷举法! 暴力穷举法大家仔细看上文中 1,2 ,3 的全排列,就是把所有情况全部列举出来了,所以我们用暴力穷举法怎么解呢,对每一位的每种情况都遍历出来组成所有的排列,再剔除重复的排列,就是我们要的全排列了
时间复杂度是多少呢,做了三次循环,很显然是 这里说句题外话,大家在学习的过程中一定要视场景选择合适的技术方案,有句话说:过早的性能优化是万恶之源,说的就是这个道理,这就好比,一个初创公司,dau 不过千,却要搞分布式,中间件,一个 mysql 表,记录不过一千,却要搞分库分表。。。这就搞笑了,记住没有最牛逼的技术,只有最合适的技术!能解决当前实际问题的技术,就是好技术! 递归解题这是笔者写此文的根本目的!就是为了讲清楚怎么用递归来更好地理解排列组合!因为我发现很多网友都觉得排列组合的递归解法实在不能 Get 到点上, 当初笔者也是看了好几遍代码才勉强理解,不过过了一段时间再看又忘了,后来根据笔者悟出的一套递归四步曲来理解,容易多了,现与各位分享!仔细看好啦 我们先来观察一下规律,看下怎样才能找出排列是否符合递归的条件,因为如前文 所述,必须要找出题目是否能用递归才能再用递归四步曲来解题 乍一看确实看不出什么所以然出来,那我们假设第一个数字已经选中了(假定为1),问题是不是转化为只求后面三位数的全排列了,发现没有,此时全排列从前面 n 位数的全排列转化成了求之后 n-1 位数的全排列了,问题从 n 变成了 n-1,规模变小了!而且变小的子问题与原问题具有相同的解决思路,都是从求某位开始的全排列!符合递归的条件! 既然我们发现排列符合递归条件,那我们就可以用递归四步曲来解了 1、定义函数的功能
2、寻找递推公式 注意上面形成递归的条件:第一个数字已经选中了!那第一位被选中有哪些情况呢,显然有以下几种情况 即在第一位上把所有的数字都选一遍,怎么做才能把所有的数字都在第一位上都选一遍呢,把第一位与其他 n-1
位数分别交换即可(注意每一次交换前都要保证是原始顺序),如下 画外音:第一步交换自己其实就是保持不变,因为我们要保证在第一位所有数字都能取到,如果移除了这一步,则第一位少了数字 1 ,全排列就漏了 这样我们就把第一位的所有数字都选了遍,之后只要对剩余的 n-1 位数做全排列即可(即调用第一步的函数),切忌再对 n-1 再做展开,只要我们发现递推关系就行了,千万不要陷入层层展开子问题的陷阱当中去!注意要从函数的功能来理解,因为问题与子问题具有相同的解决思路,所以第 1 步定义的函数对子问题(求 n-1 ,n-2 ... 的全排列)同样适用! 那递归的终止条件是什么呢 ,显然是从 n 缩小到对最后一位的全排列(此时 k 指向 arr 的最后一个元素) 于是我们可以得出递推关系为: 3、将第二步的递推公式用代码表示出来补充到步骤 1 定义的函数中,补充后的函数如下
我看网上有不少人对最后一步(如图示)不理解 回过头去看上面的递归过程图中我们特意强调了注意每一次交换时都要保证是原始顺序 注定一定要从函数的功能去理解递归,全排列的函数从功能上可以这么理解,选中第 k 位 + 计算之后的 n-k 位的全排序, 而且由于是递归,之后的 n-k 位也可以重复调用同样的函数持续求解! 4、求时间/空间复杂度 f(n) = n * f(n-1) = n * (n-1) * f(n-2) = n!,所以时间复杂度是 O(n!),注意不可能有比这个更好的时间复杂度了!因为全排列的组合本身就有 n! 次,再怎么优化都肯定会有这么多次 在 n 较大的情况下显然是不可接受的,所以我们要想办法进行优化 字典序法除了递归解法,还有一种常用的解法:字典排序法 举个例子: 1 2 3 这三位数字的全排列如下 1 2 3 , 1 3 2 , 2 1 3 , 2 3 1 , 3 1 2 , 3 2 1 以上排列满足从小到大依次递增,按这种方式排列的算法就叫字典排序法。 所以我们只要从排列的最小值开始,依次按从小到大依次递增的顺序找寻下一个全排列的数字即可,直到最大值!就能找到所有全排列。 假设我们定义了一个叫 nextPermutation 的函数,根据字典排序法,则从最小值 123 开始,持续调用这个函数即可求出所有全排列的组合,如图示 那么这个函数该怎么实现呢 有 4 个步骤 举个例子: 假设当前给的数字是 124653, 按这四个步骤来看如何寻找这个数按字典排序法的下一个全排列数字 1、从右到左(从个位数往高位数)寻找第一个左邻小于右邻的数,显然是 4 2、再从右往左找第一个比第一步找出的数(4)更大的数, 显然是
5 3、交换上面两个步骤中的数,即交换 4, 5,此时数字为
125643 4、 再对 643 从小到大进行排序,显然应该为 125346,,这一步的排序我们用了快排 整体思路还是很清晰的,如果不太清楚,建议大家多看几遍。 思路清楚了,代码写起来就快了,直接贴上按以上步骤来实现的代码吧,注释写得很详细了,大家可以对照着看
注:以上第四步的排序用到了快排(quicksort),限于篇幅关系没有贴出快排的完整代码,如果不了解快排,建议大家网上查查看,这里不做详细展开 那 next_permutation 的时间复杂度是多少呢,从以上的步骤中其实可以看到是第四步做快排时的时间复杂度,即 O(nlogn)。 next_permutation 我们写好了,接下来要寻找全排列就容易了,思路如下 1、 首先对参与全排列的数字数组作排序,保证初始的排列数字一定是最小的 2、持续调用定义好的 next_permutation 函数,直到最大值
可以看到如果定义好了 next_permutation,在算全排列还是很简单的,那用字典序法的时间和空间复杂度是多少呢 快排的时间复杂度为 O(nlogn),而 next_permutation 由于要计算 n! 次, 且根据以上分析我们已经知道了 next_permutation 的时间复杂度是 O(nlogn), 所以整体的时间复杂度是 O(nlog) + O(n! * nlogn) = O(n! * nlogn)。 看起来字典序法比递归的时间复杂度更高,所以我们应该使用倾向于使用递归吗? 所以在时间复杂度差不多的情况下,优化选择非递归的实现方式 什么是组合看完了排列,我们来看看组合,首先我们还是先看看组合的定义
假设有数字1, 2, 3, 4, 要从中选择 2 个元素,共有多少种组合呢 共有 6 种 排列与组合最主要的区别就是排列是有序的,而组合是无序的,12 和 21 对组合来说是一样的 现在我们来看看如果从 n 个元素中选出 m 的组合共有几种,之前详细地讲解了如何用递归解排列,相信大家应该对组合怎么使用递归应该有一个比较清晰的思路。 我们一起来看看,假设要从 n 选 m
的组合的解题思路 这里需要注意的是相对于全排列的每个元素都能参与排列不同,组合中的每个元素有两种状态,选中或未选中,所以形成递归分两种情况。
递归条件既然找到了,接下来我们就按递归四步曲来解下组合。 1、定义函数的功能
这里我们额外引入了一个 select 数组,这个数组里的元素如果为1,则代表相应位置的元素被选中了,如果为 0 代表未选中 如图示,以上表示 arr 的 第 2,3 元素被选中作为组合 2、寻找递推公式
那么终止条件呢,有两个
4、求时间/空间复杂度 面试中排列组合的一些变形经过以上的讲解,我相信大家对排列组合的递归解法应该是很明白了,不过面试中面试官可能还会对排列组合稍加变形,以进一步考察你的算法水平。 考虑以下情况
期待你的回答!我们下篇见 如有帮助,欢迎关注公众号「码海」 |