不会全排列算法(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描述>学习心得
随机推荐
- MapReduce实现手机上网流量分析(业务逻辑)
一.问题背景 现在的移动刚一通话就可以在网站上看自己的通话记录,以前是本月只能看上一个月.不过流量仍然是只能看上一月的. 目的就是找到用户在一段时间内的上网流量. 本文并没有对时间分组.下一节进行分区 ...
- 深夜重温JavaScript中的对象和数组
这一块实际上已经学过了,因为没有学好,在工作过程中遇到一些对象或者数组的操作,会去百度查找,浪费了许多宝贵的时间,所以特地再拐过头来重新学习. 对象 基本概念: 对象这种基本的数据结构还有其他很多种叫 ...
- 介绍对称加密的另一个算法——PBE
除了DES,我们还知道有DESede(TripleDES,就是3DES).AES.Blowfish.RC2.RC4(ARCFOUR)等多种对称加密方式,其实现方式大同小异,这里介绍对称加密的另一个算法 ...
- Windows7微软官方原版镜像系统文件
Windows7微软官方原版镜像系统 Windows 7 是由微软公司(Microsoft)开发的操作系统,核心版本号为Windows NT 6.1.Windows 7可供家庭及 商业工作环境.笔记本 ...
- [Unity3d]调试问题之UI/Image不显示
问题描述 在项目中添加的UI/Image资源,在PC和通过Unity Remove测试都没有问题: PC上的效果 手机上Unity Remove测试结果 可真正发布到手机上运行则如下显示,说明imag ...
- asp.net core 使用EF7 Code First 创建数据库,同时使用命令创建数据库
1.首先下载vs2015的Asp.Net Core(RC2)的插件工具(https://www.microsoft.com/net/core#windows)2.创建一个asp.net Core的项目 ...
- 【C语言入门教程】目录/大纲
第一章 C语言编程基础 1.1 基本程序结构 1.2 函数库 和 链接 1.3 C语言“32个”关键字 第二章 数据类型.运算符和表达式 2.1 数据类型(5种基本数据类型),聚合类型与修饰符 2.2 ...
- PCA本质和SVD
一.一些概念 线性相关:其中一个向量可以由其他向量线性表出. 线性无关:其中一个向量不可以由其他向量线性表出,或者另一种说法是找不到一个X不等于0,能够使得AX=0.如果对于一个矩阵A来说它的列是线性 ...
- Java Native Interfce三在JNI中使用Java类的普通方法与变量
本文是<The Java Native Interface Programmer's Guide and Specification>读书笔记 前面我们学习了如何在JNI中通过参数来使用J ...
- 帝国CMS视频
http://list.youku.com/albumlist/show?id=17602333&ascending=1.html