不会全排列算法(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,
aab
should return 2 because it has 6 total permutations (aab
,aab
,aba
,aba
,baa
,baa
), but only 2 of them (aba
andaba
) 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描述>学习心得
随机推荐
- wcf测试证书的创建
如何创建证书: makecert.exe -sr LocalMachine -ss My -a sha1 -n CN=JiangServer -sky exchange -pe (服务端证书) ...
- mysql免安装使用(win7 64位系统)
一.解压 二.以管理员身份运行cmd 三.cmd命令进入到解压后的mysql文件bin目录下 四.将mysql服务添加到windows服务中.cmd在bin目录下输入:mysqld -install ...
- PHP WAMP 文件上传 及 简单的上传预览
...... 使用特殊的表单类型file, 主(上传)页面: <form action="chuli.php" method="post" enctype ...
- A Quick Introduction to Linux Policy Routing
A Quick Introduction to Linux Policy Routing 29 May 2013 In this post, I’m going to introduce you to ...
- 在Excel中使用SQL语句查询和筛选
本文转自:http://blog.sina.com.cn/s/blog_5fc375650102e1g5.html 今天在微博上看到@数据分析精选 分享的一篇文章,是关于<在Excel中使用SQ ...
- 微博RPC框架motan入门笔记
Motan 是一套高性能.易于使用的分布式远程服务调用(RPC)框架. 功能 支持通过spring配置方式集成,无需额外编写代码即可为服务提供分布式调用能力. 支持集成consul.zookeeper ...
- yaf设置命名空间
修改yaf配置文件 文件是:yaf.ini extension=yaf.so yaf.use_namespace=1 index文件. 目录是application/controllers/Index ...
- Access应用笔记<四>-一个完整的自动化报表搭建过程
距离之前的三篇日志已经很久啦,今天终于完成了一个比较完整的自动化报表搭建过程 基于公司数据保密原则,样板就不放到网上来了,简单说一下背景: 这次access实现的功能包括: 1)为部门整体搭建了一个员 ...
- Linux C 字符串函数 sprintf()、snprintf() 详解
一.sprintf() 函数详解 在将各种类 型的数据构造成字符串时,sprintf 的强大功能很少会让你失望. 由于 sprintf 跟 printf 在用法上几乎一样,只是打印的目的地不同而已,前 ...
- PL/SQL Developer 连接 64位Oracle 服务端 解决方案
PL/SQL是32位程序,只能通过32位的Oracle驱动连接Oracle服务器.如果Oracle服务器是64位的,那么只能通过需要通过一个32位的Oracle客户端,来搭建连接. 到官网下载一个客户 ...