问题描述:给定一个字符串,输出该字符串所有排列的可能。如输入“abc”,输出“abc,acb,bca,bac,cab,cba”。

虽然原理很简单,然而我还是折腾了好一会才实现这个算法……这里主要记录的是解决问题中的思路。

我实现的是最普通的递归算法,也没有除重,嗯非递归及除重的算法以后再补上吧。

实现过程

首先明确函数的输入和输出,输入是一个字符串,输出么对于JS而言用数组来表示最恰当了,所以函数的雏形应该是这样的:

  1. function permutate(str) {
  2. var result = [];
  3.  
  4. return result;
  5. }

然后,确定是用递归的形式解决。递归的解法么,其实就是数学归纳法的寻找规律那一步。数学归纳法是什么样来着:第一步,给出基础值,比如输入为1的时候输出应该是成立的。第二步,假设对于输入n成立,证明输入n+1时也成立。

好了,所以先来完成第一步。对这个问题而言,基础情况应该是输入字符串为单个字符时的情况。这个时候输出应该是什么呢。当然是输入本身。但是,不要忘了输出应该是数组形式,所以接下来的样子:

  1. function permutate(str) {
  2. var result = [];
  3.  
  4. if(str.length > 1) {
  5. } else if (str.length == 1) {
  6. return [str];
  7. }
  8.  
  9. return result;
  10. }

中间用了else if 而没有用else的原因是不清楚到最后是否要处理空字符串的情况,所以先留着个else。逻辑上应该位于第一个if里的return语句,放到了最后,比较清晰。

接着进行第二步,假设我们已经知道了n-1的输出,要由这个输出得出n的输出。在这个问题里,n-1的输入,对应着长度比当前输入的字符串少1的输入字符串。也就是说,如果我已经知道了“abc”的全排列输出的集合,现在再给你一个“d”,要怎样得出新的全排列呢?

很简单,只要对于集合中每一个元素,把d插入到任意相邻字母之间(或者头部和尾部),就可以得到一个新的排列。例如对于元素“acb”,插入到第一个位置,即可得到“dacb”,插入其余位置,可得到“adcb”,“acdb”,“acbd”。容易证明这样形成的新元素不会有重复。

在这里,对于每一个输入的str,我们把它分为两部分,第一部分为字符串的第一个字母,定义为left,第二部分为剩余的字符串,定义为rest,根据以上的假设,现在可以把 permutate(rest) 作为一个已知量看待。

  1. function permutate(str) {
  2. var result = [];
  3.  
  4. if(str.length > 1) {
  5. var left = str[0];
  6. var rest = str.slice(1, str.length);
  7. var preResult = permutate(rest);
  8. /*
  9. Do some operation
  10. */
  11. } else if (str.length == 1) {
  12. return [str];
  13. }
  14.  
  15. return result;
  16. }

接着对permutate(rest)里的每一个排列进行处理,将left插入到每一个位置中,每得到一个排列,便将它push到result里面去。

  1. ......
  2. for(var i=0; i<preResult.length; i++) {
  3. for(var j=0; j<preResult[i].length; j++) {
  4. var tmp = preResult[i],slice(0, j) + left + preResult[i].slice(j, preResult[i].length);
  5. result.push(tmp);
  6. }
  7. }
  8. ......

有了字符串自带的slice方法省了不少事。一开始想到插入字符串想到的是splice方法,然而这个方法会对原始字符串进行修改,要是用它的话会出很无奈的bug……

到这里就告一段落了,把上面的片段插入到前面的注释的位置就是完整代码了。

然后这个函数对于空字符串的输入会输出空字符串,所以前面else if的if也可以去掉。

另一个问题

说起全排列我还想到了另一个问题:

给定一个数字字符串,输出组成这个字符串的每个数字的所有组合中,比当前数字大的下一个数字。

举例:对于输入“113”,它的所有不重复的排列组合为“113”,“131”,“311”,那么其中比当前数字大的下一个数字为“131”,所以输出为“131”。

我想到的第一个解法,首先求出当前字符串的所有排列组合,然后对返回的结果排序,再求出当前数字所在下标的下一个。

程序的样子差不多应该是这样:

  1. function permutate() {
  2. //......
  3. return result;
  4. }
  5.  
  6. function main(str) {
  7. var all = permutate().sort();
  8. var targetIndex = all.indexOf(str)+1;
  9. return result[targetIndex];
  10. }

思路非常直接,然而这个算法的缺点也是很明显的:复杂度太高了。对于输入长度为 n 的字符串,光是排列算法就得计算 n!次。

所以这个想法可以pass。

实际上比较正确的算法是可以在O(n)的复杂度内求出结果的。不过在这里就不详细说明了。各种解决途径可以点击以下链接查看:

链接 (注:注册了codewars账号并给出一种解法后方可查看对应的solution)

之所以提到这个问题,是因为虽然不推荐使用全排列的方法解决这个问题,但是我们可以通过这个问题的解法,反过来给出全排列的一种非递归式解法。

例如我们要给出字符串“aabccdde"的全排列,可以把对应字母替换为“11233445”,然后调用上面问题的解法,依次输出每一个排列即可。因为本身这个算法的复杂度很低,所以不会影响到最终全排列算法的复杂度。

缺点么,如果有十个以上的不同字符,那就没有办法了……

全排列算法的JS实现的更多相关文章

  1. 不会全排列算法(Javascript实现),我教你呀!

    今天我很郁闷,在实验室凑合睡了一晚,准备白天大干一场,结果一整天就只做出了一道算法题.看来还是经验不足呀,同志仍需努力呀. 算法题目要求是这样的: Return the number of total ...

  2. 常见算法是js实现汇总(转载)

    常见算法是js实现汇总 /*去重*/ <script> function delRepeat(arr){ var newArray=new Array(); var len=arr.len ...

  3. 常见排序算法(JS版)

    常见排序算法(JS版)包括: 内置排序,冒泡排序,选择排序,插入排序,希尔排序,快速排序(递归 & 堆栈),归并排序,堆排序,以及分析每种排序算法的执行时间. index.html <! ...

  4. PHP字符串全排列算法

    <?php /** * PHP字符串全排列算法 */ $results = []; $arr = []; function bfs($start) { global $arr; global $ ...

  5. 全排列算法--递归实现(Java)

    求一个n阶行列式,一个比较简单的方法就是使用全排列的方法,那么简述以下全排列算法的递归实现. 首先举一个简单的例子说明算法的原理,既然是递归,首先说明一下出口条件.以[1, 2]为例 首先展示一下主要 ...

  6. LeetCode 算法题解 js 版 (001 Two Sum)

    LeetCode 算法题解 js 版 (001 Two Sum) 两数之和 https://leetcode.com/problems/two-sum/submissions/ https://lee ...

  7. Twitter面试题蓄水池蓄水量算法(原创 JS版,以后可能会补上C#的)

    之前在群里有人讨论Twitter的面试题,蓄水池蓄水量计算,于是自己写了个JS版的(PS:主要后台代码还要编译,想想还是JS快,于是就使用了JS了.不过算法主要还是思路嘛,而且JS应该都没问题吧^_^ ...

  8. 常见排序算法基于JS的实现

    一:冒泡排序 1. 原理 a. 从头开始比较相邻的两个待排序元素,如果前面元素大于后面元素,就将二个元素位置互换 b. 这样对序列的第0个元素到n-1个元素进行一次遍历后,最大的一个元素就“沉”到序列 ...

  9. 获取所有组合算法、获取全排列算法(java)

    转载声明:原文转自:http://www.cnblogs.com/xiezie/p/5574516.html 受到ACM1015的影响,个人感觉,有必要对统计学上的 全组合和全排列 进行一个简单的总结 ...

随机推荐

  1. 在.NET中把项目从类库转为Web应用程序

    我们知道,在.NET中所有的项目文件以.csproj为扩展名.内容是xml格式. 类库项目文件.csproj: <Project DefaultTargets="Build" ...

  2. 51Nod 1278 相离的圆

    51Nod 1278 相离的圆 Link: http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1278 1278 相离的圆 基 ...

  3. Socket通信类

    package com.imooc; import java.io.BufferedReader; import java.io.IOException; import java.io.InputSt ...

  4. 敏捷组织中PMO应遵循的准则

    敏捷改变了人们的工作方式,不仅仅是开发部门,而且还包括其它的部门,例如HR.财务以及PMO等.在大多数组织中,PMO是一个控制体.它指导项目团队的规范.模板以及流程.目前,大多数的IT组织都敏捷化了. ...

  5. PHP函数

    2017.1.5 stream_get_contents函数:读取数据流中的剩余数据到字符串 [功能说明] 该函数同file_get_COntents()函数的作用相同,只不过该函数用于读取已经打开的 ...

  6. 多线程之异步编程: 经典和最新的异步编程模型, IAsyncInfo 与 Task 相互转换

    经典的异步编程模型(IAsyncResult) 最新的异步编程模型(async 和 await) 将 IAsyncInfo 转换成 Task 将 Task 转换成 IAsyncInfo 示例1.使用经 ...

  7. 聊聊 Apache 开源协议

    摘要 用一句话概括 Apache License 就是,你可以用这代码,但是如果开源你必须保留我写的声明:你可以改我的代码,但是如果开源你必须写清楚你改了哪些:你可以加新的协议要求,但不能与我所 公布 ...

  8. 谈iOS抓包:Mac下好用的HTTP/HTTPS抓包工具Charles

    在Mac下做开发,用Fiddler抓包由于离不开Windows比较痛苦,还好有Charles,到官网http://www.charlesproxy.com/可下载到最新版本(若不支持rMBP可拖到Re ...

  9. [Tip]重写PanGestureRecognizer

    目标 识别当前pan方向是指定方向 需要API 重写- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; 获取所在view ...

  10. python_九九乘法表

    # 九九乘法表 print(" 九九乘法表") for table_x in range(1,10): for table_y in range(1,table_x +1): pr ...