不会全排列算法(Javascript实现),我教你呀!
今天我很郁闷,在实验室凑合睡了一晚,准备白天大干一场,结果一整天就只做出了一道算法题。看来还是经验不足呀,同志仍需努力呀。

算法题目要求是这样的:
Return the number of total permutations of the provided string that don't have repeated consecutive letters. Assume that all characters in the provided string are each unique.For example,
aabshould return 2 because it has 6 total permutations (aab,aab,aba,aba,baa,baa), but only 2 of them (abaandaba) don't have the same letter (in this casea) repeating.
看到这个题目的第一反应就是赶紧扣扣大脑里关于排列组合的各种基础知识和公式,首先就是从上面的语句中抽象出一个数学模型:
n个队伍排成一排,每个队伍ai个人,每个人互不相同,相同队伍的人不能相邻,求可能排列数.
对于“不相邻”问题,我们的一般会采用“插空法”,举个简单的例子:
这里有三个笑脸

和两个哭脸
,每张脸互不相同,现在要将它们排成一排,要求相同脸型的小伙伴不能相邻。排列的详细步骤如下:
将三个笑脸排成一排,有3*2*1种排法,并且形成了4个间隔 —
—
—
—将两个哭脸插入到四个间隔中,但是由于要去相同脸型两两不相邻,所以这两个哭脸只能插入到中间两个间隔,此时有两种方案
所以总的方案数为12种。
按照这个思路,我先统计了字符串中每个字符出现的个数,然后试图通过“插空法”求出排列方案总数,却发现对于已知有限组的排列这个方案还是可枚举的,但是对于未知组时,计算相当混乱。就这样折腾了一上午也没有求出来。

解决不了问题,我的心总是放不下,下午再战。我看到题目下方有一个提示:
这个提示让我转变了思路,我们是在编程解决问题,不是在解算数学题,首先我们可以将所有的排列组合求出来,不管存不存在相不相邻的情况,然后使用正则表达式过滤掉相邻的情况不就解决问题了吗。现在问题就转变成了求一组给定字符的全排列问题,这就引出了这篇博客的重点。首先介绍一种普通的递归方法。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
function permutate(str) { var result=[]; if(str.length==1){ return [str] }else{ var preResult=permutate(str.slice(1)); for (var j = 0; j < preResult.length; j++) { for (var k = 0; k < preResult[j].length+1; k++) { var temp=preResult[j].slice(0,k)+str[0]+preResult[j].slice(k); result.push(temp); } } return result; } }console.log(permutate("abc")); |
如果能直接看懂上面的代码,就可以忽略下面的解析过程,下面的解析过程参考了全排列算法的JS实现,这篇博客写的调理很清晰,但是有一点点错误导致结果不完全正确,我在下面改正了过来:
实现过程
首先明确函数的输入和输出,输入是一个字符串,输出是各种排列组合形成的字符串组成的数组,所以函数的大体框架应该是这样的:
|
1
2
3
4
5
|
function permutate(str) { var result = []; return result;} |
然后,确定是用递归的形式解决。递归的解法,倒过来想就是数学归纳法:第一步,给出基础值,比如输入为1的时候输出应该是成立的。第二步,假设对于输入n成立,证明输入n+1时也成立。好了,所以先来完成第一步。对这个问题而言,基础情况应该是输入字符串为单个字符时的情况。这个时候输出应该是什么呢。当然是输入本身。但是,不要忘了输出应该是数组形式,所以接下来的样子:
|
1
2
3
4
5
6
7
8
9
10
|
function permutate(str) { var result = []; if(str.length===1){ return [str]; }else{ ... return result; } } |
接着进行第二步,假设我们已经知道了n-1的输出,要由这个输出得出n的输出。在这个问题里,n-1的输入,对应着长度比当前输入的字符串少1的输入字符串。也就是说,如果我已经知道了“abc”的全排列输出的集合,现在再给你一个“d”,要怎样得出新的全排列呢?
很简单,只要对于集合中每一个元素,把d插入到任意相邻字母之间(或者头部和尾部),就可以得到一个新的排列。例如对于元素“acb”,插入到第一个位置,即可得到“dacb”,插入其余位置,可得到“adcb”,“acdb”,“acbd”。这也就是上文提到的"插空法"的思想,这不过这里我们不用考虑是否相邻的问题,所以操作起来会比较方便。
在这里,对于每一个输入的str,我们把它分为两部分,第一部分为字符串的第一个字母str[0],(注意ES5之前是不能直接通过下标来访问字符的,需要使用codeAt()方法,这里没有考虑兼容性仅做演示用)第二部分为剩余的字符串str.slice(1),根据以上的假设,现在可以把 permutate(str.slice(1)) 作为一个已知量看待。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
function permutate(str) { var result=[]; if(str.length==1){ return [str] }else{ var preResult=permutate(str.slice(1)); ... .... return result; } } |
接着对permutate(str.slice(1))里的每一个排列进行处理,将str[0]插入到每一个位置中,每得到一个排列,便将它push到result里面去。
|
1
2
3
4
5
6
|
for (var j = 0; j < preResult.length; j++) { for (var k = 0; k < preResult[j].length+1; k++) {
result.push(temp); }} |
在读懂上述代码时,时刻不要忘了preResult是个什么样的数组,当递归到最后一个字符时,preResult为[ 'c' ],再上一层的为[ 'bc', 'cb' ]。
上述代码比较难理解的是:
|
1
|
var temp=preResult[j].slice(0,k)+str[0]+preResult[j].slice(k); |
这里是将str[0]插入到上一次的某个排列方案结果中,采用的是字符串拼接的方案,返回一个新字符串给temp,注意这里不能直接在preResult[j]上操作,否则会修改preResult[j]的长度导致内层的循环永远不接结束。
另外需要注意的是代码中高亮的部分preResult[j].length+1这里必须加上1,考虑到slice()方法的截取范围是“左闭右开”区间,这样当k取值为preResult[j].length时才能将str[0]添加到字符串尾部。
通过上面的过程,我们就能求出给定字符串形成的排列组合的所有情况:[ 'abc', 'bac', 'bca', 'acb', 'cab', 'cba' ]
不要忘了,这不是我们的最终目的,我们的最终目的是找出所有不相邻的情况。这个问题可以很方便的采用正则表达式来过滤:
|
1
|
var regex = /(.)\1+/g; |
这个正则表达式使用了一个回溯操作匹配前面的字符出现一次否则多次。这样我们就能完整的解决问题了,完整的代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
//同一个字母不相邻的排列组合/*先组合出所有的情况,再使用正则表达式过滤掉不符合的情况*/function permAlone(str) { var regex = /(.)\1+/g; var permutate=function(str) { var result=[]; if(str.length==1){ return [str]; }else{ var preResult=permutate(str.slice(1)); for (var j = 0; j < preResult.length; j++) { for (var k = 0; k < preResult[j].length+1; k++) { var temp=preResult[j].slice(0,k)+str[0]+preResult[j].slice(k); result.push(temp); } } return result; } }; var permutations= permutate(str); var filtered = permutations.filter(function(string) { return !string.match(regex); }); return filtered.length;}console.log(permAlone('aab')); |
参考:
全排列算法的JS实现 - 迷路的约翰 - 博客园
扩展阅读:
JS实现的数组全排列输出算法_javascript技巧_脚本之家
JavaScript全排列的六种算法 具体实现_javascript技巧_脚本之家
不会全排列算法(Javascript实现),我教你呀!的更多相关文章
- 全排列算法 --javascript 实现
var Ann = function a(arr){ if(arr.length == 1){return arr;} var rr = new Array(); for(var i = 0; i&l ...
- 全排列算法的JS实现
问题描述:给定一个字符串,输出该字符串所有排列的可能.如输入“abc”,输出“abc,acb,bca,bac,cab,cba”. 虽然原理很简单,然而我还是折腾了好一会才实现这个算法……这里主要记录的 ...
- 数据结构与算法JavaScript (一) 栈
序 数据结构与算法JavaScript这本书算是讲解得比较浅显的,优点就是用javascript语言把常用的数据结构给描述了下,书中很多例子来源于常见的一些面试题目,算是与时俱进,业余看了下就顺便记录 ...
- 《数据结构与算法JavaScript描述》
<数据结构与算法JavaScript描述> 基本信息 作者: (美)Michael McMillan 译者: 王群锋 杜欢 丛书名: 图灵程序设计丛书 出版社:人民邮电出版社 ISBN:9 ...
- 翻阅《数据结构与算法javascript描述》--数组篇
导读: 这篇文章比较长,介绍了数组常见的操作方法以及一些注意事项,最后还有几道经典的练习题(面试题). 数组的定义: JavaScript 中的数组是一种特殊的对象,用来表示偏移量的索引是该对象的属性 ...
- 数据结构与算法javascript描述
<数据结构与算法javascript描述>--数组篇 导读: 这篇文章比较长,介绍了数组常见的操作方法以及一些注意事项,最后还有几道经典的练习题(面试题). 数组的定义: JavaScri ...
- 列表的实现-----数据结构与算法JavaScript描述 第三章
实现一个列表 script var booklist = new List(); booklist.append('jsbook'); booklist.append('cssbook'); book ...
- 《数据结构与算法JavaScript描述》中的一处错误
最近在看<数据结构与算法JavaScript描述>这本书,看到选择排序这部分时,发现一个比较大的错误. 原书的选择排序算法是这样的: function selectionSort() { ...
- 数据结构与算法 Javascript描述
数据结构与算法系列主要记录<数据结构与算法 Javascript描述>学习心得
随机推荐
- NodeJS:Error: Cannot find module 'jshint/src/cli'
以前命令:npm install gulp-jshint --save-dev 实质上是安装jshint失败,缺少该模块. 更换命令 :npm install --save-dev jshint gu ...
- bzoj2228[ZJOI2011]礼物(gift)
据说联赛之前写题解可以涨RP 这题的输入格式半天没看懂-其实是有q层摞在一起,每一层大小都是p*r,依次输入q层的情况.那么首先我们枚举三种挖方块的姿势,分别使切出的方块的上面/前面/右面是正方形的面 ...
- Web项目学习
首先配好jdk,tomcat,下载eclipse,下载bootstrap模板,进行JDBC连接 创建项目 打开Eclipse,选择左上角的File->NEW->最后一个other,选择如下 ...
- Google 地图 API V3 使用入门
Google官方教程: Google 地图 API V3 使用入门 Google 地图 API V3 针对移动设备进行开发 Google 地图 API V3 之事件 Google 地图 API V3 ...
- 百度编辑器 Ueditor 下拉处增加字体
左百度,添加到同右钉邮那么多: 1.\ueditor\lang\zh-cn\zh-cn.js 文件中找到: ...
- 以空白符结尾的 alias
网上经常有人问这个问题:为什么我写的 alias 在 sudo 下就不管用了? $ alias 'll=ls -l' $ sudo ll a-private-dir sudo: ll: command ...
- 将.dat文件导入数据库
*最近在搞文本分类,就是把一批文章分成[军事].[娱乐].[政治]等等. 但是这个先需要一些样本进行训练,感觉文本分类和"按图索骥"差不多,训练的文章样本就是"图&quo ...
- vtkAnimationCue、vtkCommand和vtkAVIWriter
1. 用vtkAnimationCue自定义一个vtkCustomAnimationCue类,用来实现球体逐渐张开的过程: 2.用vtkCommand自定义衍生一个vtkCustomAnimation ...
- 解决在ScrollView中套用ListView显示不正常
最近在设计Android程序时,因为需要在ScrollView中添加一个ListView列表来显示一些信息.刚开始并没有想太多,但添加进去后才发现ListView不论怎样定义都只能显示一行,显示效果很 ...
- Excel—身份证生日提取
一.只有18位的身份证号码 如: 身份证号 330682199302264000 41120019890823729X 231081199002256839 131101198203154666 36 ...
