网站首页 > 技术文章 正文
大家好我西瓜哥,今天做一道比较常考的算法题。
编写一种方法,计算某字符串的所有排列组合,字符串每个字符均不相同。
如输入 "qwe",要求返回 ["qwe", "qew", "wqe", "weq", "ewq", "eqw"] 。
LeetCode 题目链接:
https://leetcode-cn.com/problems/permutation-i-lcci/
回溯解法
常见的解法是回溯。
我们使用一个 toVisited 数组,用于维护递归过程中尚未访问的字符。
回溯函数的 TypeScript 签名如下:
(combs: string[], s: string, toVisit: string[]) => void
- combs:字符串数组。排列组合被保存的数组,回溯结束后用于返回
- s:字符串
- toVisit:尚未访问的字符数组。
先看下回溯函数的代码实现。
function r(combs, s, toVisit) {
if (toVisit.length === 0) {
combs.push(s);
return;
}
for (let i = 0; i < toVisit.length; i++) {
r(combs, s + toVisit[i], toVisit.filter(item => item != toVisit[i]));
}
}
我们会遍历 toVisit 数组,将这些尚未被访问的字符,追加到 s 末尾,然后生成一个新的移除了该字符的 toVisited,作为下一个递归调用的参数。
当所有字符都被访问过了(toVisite 数组为空)时,我们就拿到了一种排列方式,将它放到 combs,然后结束当前的这一个迭代。
当所有的迭代都结束后,我们的 combs 中就包含了所有的排列,将其返回即可。
完整代码实现如下:
function permutation(S: string): string[] {
const combs = [];
r(combs, '', [...S]);
return combs;
};
function r(combs: string[], s: string, toVisit: string[]) {
if (toVisit.length === 0) {
combs.push(s);
return;
}
for (let i = 0; i < toVisit.length; i++) {
r(combs, s + toVisit[i], toVisit.filter(item => item != toVisit[i]));
}
}
我们注意到,每次递归,都要拷贝一份待访问字符数组,即 toVisit.filter(item => item != toVisit[i]),这个效率实在是不怎么高啊。
其实这个可以优化一下。如果你实现过排序算法,比如选择排序,你就会想到一个原地使用原数组的思路。排序算法中将原数组的元素进行交换,分为 “排序区间” 和 “未排序区间”。
我们这里也可以致敬一下,也交换一下嘛,在原数组中原地分为 “已访问字符区间” 和 “待访问数组”。然后在下一个递归函数执行完后,再复原一下数组。
改良后的实现如下:
function permutation(S): string[] {
const combs = [];
r(combs, [...S], 0);
return combs;
};
function r(combs, chars, first) {
if (first === chars.length) {
combs.push(chars.join(''));
return;
}
for (let i = first; i < chars.length; i++) {
swap(chars, i, first);
r(combs, chars, first + 1);
swap(chars, i, first);
}
}
function swap(chars, i, j) {
const tmp = chars[i];
chars[i] = chars[j];
chars[j] = tmp;
}
结尾
本文讲解了如何用回溯的思考解全排列问题,回溯解法的核心思路是每次迭代,从待访问字符集中挑选字符,直到所有字符都被访问完。
为此你需要维护待访问字符集数组,这里如果想要高效一些,可以使用数组原地分区或是使用栈的方式,直接拷贝一份效率很低,但优点是简单直接(所以我做题时经常这么干)
我是每周做五道算法题的前端西瓜哥,欢迎关注我。
猜你喜欢
- 2025-06-13 8个步骤,创建项目管理时间表(建立项目管理系统)
- 2025-06-13 配电柜里最全电气原件 安装 排序 电气元件名称 让你一目了然 电工必备
- 2025-06-13 html基础必备-列表标记,前端小白一看就会
- 2025-06-13 家族坟墓的多种排列形式,墓葬布局的排列布局(图解)
- 2024-10-03 17种编程语言实现排序算法-插入排序
- 2024-10-03 前端工程师算法系列(4)-归并排序 归并排序js代码
- 2024-10-03 插入排序java java排序实现
- 2024-10-03 插入排序算法 插入排序算法c语言
- 2024-10-03 十大排序算法(javascript) 十大排序算法c语言
- 2024-10-03 职场人士必备的“重要紧急排序法”,你一直用错了
你 发表评论:
欢迎- 518℃Oracle分析函数之Lag和Lead()使用
- 517℃几个Oracle空值处理函数 oracle处理null值的函数
- 511℃Oracle数据库的单、多行函数 oracle执行多个sql语句
- 502℃0497-如何将Kerberos的CDH6.1从Oracle JDK 1.8迁移至OpenJDK 1.8
- 497℃Oracle 12c PDB迁移(一) oracle迁移到oceanbase
- 488℃【数据统计分析】详解Oracle分组函数之CUBE
- 469℃Oracle有哪些常见的函数? oracle中常用的函数
- 467℃最佳实践 | 提效 47 倍,制造业生产 Oracle 迁移替换
- 最近发表
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端缓存 (63)
- 前端react (48)
- 前端aes加密 (58)
- 前端脚手架 (56)
- 前端md5加密 (54)
- 前端富文本编辑器 (47)
- 前端路由 (61)
- 前端数组 (73)
- 前端排序 (47)
- 前端定时器 (47)
- Oracle RAC (73)
- oracle恢复 (76)
- oracle 删除表 (48)
- oracle 用户名 (74)
- oracle 工具 (55)
- oracle 内存 (50)
- oracle 导出表 (57)
- oracle 中文 (51)
- oracle的函数 (57)
- 前端调试 (52)
- 前端登录页面 (48)
本文暂时没有评论,来添加一个吧(●'◡'●)