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


算法题目要求是这样的:

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 (aabaababaababaa,baa), but only 2 of them (aba and aba) don't have the same letter (in this case a) repeating.

看到这个题目的第一反应就是赶紧扣扣大脑里关于排列组合的各种基础知识和公式,首先就是从上面的语句中抽象出一个数学模型:

n个队伍排成一排,每个队伍ai个人,每个人互不相同,相同队伍的人不能相邻,求可能排列数.

对于“不相邻”问题,我们的一般会采用“插空法”,举个简单的例子:

这里有三个笑脸和两个哭脸,每张脸互不相同,现在要将它们排成一排,要求相同脸型的小伙伴不能相邻。排列的详细步骤如下:

  1. 将三个笑脸排成一排,有3*2*1种排法,并且形成了4个间隔 —

  2. 将两个哭脸插入到四个间隔中,但是由于要去相同脸型两两不相邻,所以这两个哭脸只能插入到中间两个间隔,此时有两种方案

  3. 所以总的方案数为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++) {

             var temp=preResult[j].slice(0,k)+str[0]+preResult[j].slice(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实现),我教你呀!的更多相关文章

  1. 全排列算法 --javascript 实现

    var Ann = function a(arr){ if(arr.length == 1){return arr;} var rr = new Array(); for(var i = 0; i&l ...

  2. 全排列算法的JS实现

    问题描述:给定一个字符串,输出该字符串所有排列的可能.如输入“abc”,输出“abc,acb,bca,bac,cab,cba”. 虽然原理很简单,然而我还是折腾了好一会才实现这个算法……这里主要记录的 ...

  3. 数据结构与算法JavaScript (一) 栈

    序 数据结构与算法JavaScript这本书算是讲解得比较浅显的,优点就是用javascript语言把常用的数据结构给描述了下,书中很多例子来源于常见的一些面试题目,算是与时俱进,业余看了下就顺便记录 ...

  4. 《数据结构与算法JavaScript描述》

    <数据结构与算法JavaScript描述> 基本信息 作者: (美)Michael McMillan 译者: 王群锋 杜欢 丛书名: 图灵程序设计丛书 出版社:人民邮电出版社 ISBN:9 ...

  5. 翻阅《数据结构与算法javascript描述》--数组篇

    导读: 这篇文章比较长,介绍了数组常见的操作方法以及一些注意事项,最后还有几道经典的练习题(面试题). 数组的定义: JavaScript 中的数组是一种特殊的对象,用来表示偏移量的索引是该对象的属性 ...

  6. 数据结构与算法javascript描述

    <数据结构与算法javascript描述>--数组篇 导读: 这篇文章比较长,介绍了数组常见的操作方法以及一些注意事项,最后还有几道经典的练习题(面试题). 数组的定义: JavaScri ...

  7. 列表的实现-----数据结构与算法JavaScript描述 第三章

    实现一个列表 script var booklist = new List(); booklist.append('jsbook'); booklist.append('cssbook'); book ...

  8. 《数据结构与算法JavaScript描述》中的一处错误

    最近在看<数据结构与算法JavaScript描述>这本书,看到选择排序这部分时,发现一个比较大的错误. 原书的选择排序算法是这样的: function selectionSort() { ...

  9. 数据结构与算法 Javascript描述

    数据结构与算法系列主要记录<数据结构与算法 Javascript描述>学习心得

随机推荐

  1. Servlet和JSP学习指导与实践(三):JSP助阵

    前言: JSP(Java Server Page)虽然作为一门服务端的语言,但它并没有创新新的语言标准.有些人一接触jsp之后发现易学易懂.实际上,jsp的内部原理仍然是基于Servlet,它是Ser ...

  2. 数据结构图文解析之:直接插入排序及其优化(二分插入排序)解析及C++实现

    0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...

  3. 数据结构图文解析之:数组、单链表、双链表介绍及C++模板实现

    0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...

  4. 分布式服务框架dubbo原理解析(转)

    libaba有好几个分布式框架,主要有:进行远程调用(类似于RMI的这种远程调用)的(dubbo.hsf),jms消息服务(napoli.notify),KV数据库(tair)等.这个框架/工具/产品 ...

  5. 【poj2449】 Remmarguts' Date

    http://poj.org/problem?id=2449 (题目链接) 题意 求有向图K短路. Solution A*.g(x)为当前节点到起点的步数,h(x)为当前节点到终点的最短距离(也就是估 ...

  6. javascript学习

    代码放在E:\JS\js学习 学习中要学会多查手册 javascript基本介绍 js是用于web开发的脚本语言. 后面统称为 js 脚本语言是什么? 脚本语言不能独立使用,它和html/jsp/ph ...

  7. python远程连接paramiko 模块和堡垒机实现

    paramiko使用 paramiko模块是基于python实现了SSH2远程安全连接,支持认证和密钥方式,可以实现远程连接.命令执行.文件传输.中间SSH代理功能 安装 pip install pa ...

  8. Alpha阶段第七次Scrum Meeting

    情况简述 Alpha阶段第七次Scrum Meeting 敏捷开发起始时间 2016/10/28 00:00 敏捷开发终止时间 2016/10/29 00:00 会议基本内容摘要 跟助教进行了交流,明 ...

  9. CommonJS,AMD,CMD区别

    学得比较晕,再次看commonjs,amd, cmd时好像还是没完全弄清楚,今天再整理一下: commonjs是用在服务器端的,同步的,如nodejs amd, cmd是用在浏览器端的,异步的,如re ...

  10. PHP WAMP 文件上传 及 简单的上传预览

    ...... 使用特殊的表单类型file, 主(上传)页面: <form action="chuli.php" method="post" enctype ...