题目
给一个任意长度的字符串,从中取任意n个元素,输出这些元素的所有可能的组合。
前提:会确保输入的字符串没有重复元素
示例:
输入字符'abc', 任意取2个元素 输出: 'ab','ac','bc','ba','ca','cb'
思路
题目可以分解为两步:
- 求M个元素中取N个的所有子集
- 求N个不重复元素的全排列可能性 拆分之后,其实是两个比较基础的题目了,leetcode上都有类似的题目。 这里讲述一下自己的思路。
求M个元素中取N个的所有子集
这个使用递归思路。比如,从5个中取任意3个,可以拆解为
- 先取第1个元素,然后需要从剩下4个中取2个
- 再取剩下4个中第1个,然后从剩下3个中取1个
- 此时,只需要按顺序将3个全部取出即可 这样就是一个递归了,递归的结束条件是 n===1。 我们先写一段代码:
function picked(str: string, m: number, outPut: string[]) {
if (m == 1) {
Array.from(str).forEach((s) => {
console.log([...outPut, s]);
});
return;
}
outPut.push(str[0]);
str = str.slice(1);
m = m - 1;
picked(str, m, outPut);
}
picked('abcdef',3,[])
运行一下,发现一个问题,它只输出了'a'开头的所有可能性。我们思考一下我们的递归存在的问题: 我们每次都取的是剩余元素中的第1个元素,我们还需要再以b为开头跑一遍,依次类推:
function getPicked(str: string, m: number) {
for (let i = 0; i < str.length - m + 1; i++) {
let outPut = [];
picked(str.slice(i), m, outPut);
}
}
这里要注意,我们不会把'bcdef'全部遍历一次,因为你发现到'd'的时候,就只剩下'def'一种可能性了。后面就不用遍历了。 这样,就OK啦
这个算法是一种比较朴素的算法,其实可以再次提取一下。
求N个不重复元素的全排列可能性
思路跟上一个问题类似,假如有'abcd'一个字符串,想一下它的排列:
- 假如'a'是固定的,那么我们其实是对'bcd'求全排列
- 假如'b'也是固定的,那么其实就是求'cd'的全排列,那就比较简单了,只有两种 我们尝试写一下逻辑
function sort(str: string, outPut: string[]) {
if (str.length == 2) {
console.log([...outPut, str[0], str[1]]);
console.log([...outPut, str[1], str[0]]);
return;
}
outPut.push(str[0]);
str = str.slice(1);
sort(str, outPut);
}
sort('abc',[])
出现了上一个问题一样的情况,只有以a开头的情况,类似的,我们再补一个遍历:
function getSort(str: string) {
for (let i = 0; i < str.length; i++) {
let outPut = [];
let subStr = str.slice(i)
sort(subStr, outPut);
}
}
感觉可以啦,但是运行一下发现有问题,因此每一次遍历,字符串在变短,这成了子元素排列,不是全排列。 思考一下,其实我们应该把已经遍历过的元素,追加到子字符串的末尾,形成一个新的字符串。新字符串跟原字符串的长度是一致的。
unction getSort(str: string) {
for (let i = 0; i < str.length; i++) {
let outPut = [];
//我们改了这里
let subStr = str.slice(i) + str.slice(0, i);
sort(subStr, outPut);
}
}
OK啦。
综述
我查了查,解决这种问题一般称之为回溯算法。回溯算法确实更加简洁,但是有点不太好理解。大家可以按照这个思路,先实现一下。实现之后,再去看一下回溯算法,就更容易理解了。