
编码面试会带来许多可能的挑战。 不管你喜不喜欢,数据结构和算法问🧸题在软件开发过程中仍然很常见。 申请足够多的工作,你很可能会遇到这样的问题:
给定一个字符串,返回该字符串的所有唯一排列
起初看起来很简单,但很快就会发现隐藏在表面之下的一些复杂性。 让我们看看这个问题是什🦩么,它带来的挑战,以及它是如何解决的。
让我们从了解问题要求的内容开始:字符串的所有唯一排列。 排列是事物顺序的变化。 关于这个问题,排列🥃是一个包含所有相同字符但顺序不同的新字符串。 例如,给定字符串“abc”,排列将是♓:
“abc”, “acb”, “bac”, “bca”, “cab”, “cba”
期望的结果是来自给定字符串的所有不同字母组合。让我们分解问题并解决它。
查看“abc”对🐠结果的仔细检查表明,不同的组合是通过取一个字母,✤将其移到前面,然后将其余字母的不同组合附加到它来实现的。
以“a”作为前导字母,剩下的🅠两个字母“bc”将有两种可能的组合,“bc”和“cb”,从而产生“abc”和“acb”。
将“b”作为前导ꦅ字母,其余字母“ac”可🦋以重新排列为“ac”或“ca”,从而产生“bac”和“bca”。
最后,前导字母“c”具有剩余的字母“ab”和“ba”,从而产生“cab”和“cba”。
这种方法可以分解为迭代给定字符串中的每个字母,将其保存为前导字符,然后迭代剩余的字母以获得它们的不同组合并将这些组合附加到前导字符。这是很多迭代。在 for 循环中编写 for 循环可能很难阅读,因此这将是使用递归的好时机。递归是一个调用自♉𝓰身直到满足中断条件的函数。
function countdown(n) {
if (n < 1) {
console.log(n);
return
}
console.log(n);
return countdown(n - 1);
}
countdown(5);
// 5
// 4
// 3
// 2
// 1
// 0
倒计时函数使用每次调用减 1 的参数调用自身。 这一直持续到数字𝔉小于 1,函数返回,递归结��束。 这是中断条件。
回到字符串置换问题,我们将遍历每个字符串,获取当前迭代值的字母(我们称之为 char),然后使用递归获取剩余字母的所有组合,并将它们附加到 char . 结果将被推入一个数组。
function getPermutations(str) {
// break condition to stop recursion
if (str.length < 2) {
return str;
}
// array to store permutations
let permutations = [];
// iterate through string, get char at value i, get remaining letters
for (let i = 0; i < str.length; i++) {
let char = str[i];
let remainingChars = str.slice(0, i) + str.slice(i + 1, str.length);
// use recursion to get all combinations of remainingChars and append them to char
for (let permutation of getPermutations(remainingChars)) {
permutations.push(char + permutation);
}
}
return permutations;
}
该函数以中断条件开始,因为我们使用的是递归。 递归调用都使用剩余的字母作为参数来获得它们的各种组ജ合。 如果只提供一个字母( if(str.length < 2) ),那么只能有一个组合,因此递归停止并返回单个字母。
接下来我们有一个数组(排列)来存储结果。
现在遍历给定的字符串,将 str[i] 处的字母分配给变量 char。 由于我们知道 char 的索引,所以可以使用 slice() 来获取剩余的字母并将它们分配给剩余的字符。 剩余的字母将用作递归函数调用中的参数。
再次使用“abc”,在调用 getPermutations("abc") 时,第一次迭代将导致 char = "a" 和 remainingChars = "bc"。 我们现在到达函数中的递归部分:
for (let permutation of getPermutations(remainingChars)) {
permutations.push(char + permutation);
}
for...of 语句遍历递归🍎函数调用的每个返回值,并将它们附加到此时为“a”的 char 并推送到 permutations 数组。 剩余字符中的字母当前是“bc”,因此第一个递归调用等同于♋ getPermutations(“bc”),它将遍历这些字母,将它们分开,然后再次递归调用该函数,直到满足中断条件并且我们剩下 “bc”和“cb”被附加到“a”并推送到排列数组。
这个过程对给定字符串中的每个字母重复,并产生一个具有六个排列的数组。
let results = getPermutations("abc");
console.log(results);
// ["abc", "acb", "bac", "bca", "cab", "cba"]
该功能有效,但必须考虑其他一些ඣ事情。 在“abc”示例中,字符👍串中的每个字母都是不同的。 如果字符串有重复字符会发生什么?
let results = getPermutations("aab");
console.log(results);
// ["aab", "aba", "aab", "aba", "baa", "baa"]
记住问题状态以找到所有唯一的排列。 让我们解决这个问题。 我们需要一种方法来检查 char 的当前值是否已经被使用。 还有其他方法可以完成此操作,但我发现 indexOf() 方法在这里运行良好,因为它检查给定值的第一个索引。
for (let i = 0; i < str.length; i++) {
let char = str[i];
if (str.indexOf(char) !== i) {
continue;
}
let remainingChars = str.slice(0, i) + str.slice(i + 1, str.length);
for (let permutation of getPermutations(remainingChars)) {
permutations.push(char + permutation);
}
ꦬ现在我们有一个条件来检查 char 的值是否已经被使用过。 如果有,从 indexOf() 返回的索引将低于循环中的当前索引,我们使用 continue 跳到下一次迭代。
let results = getPermutations("zappa");
console.log(results);
/* [
"zappa", "zapap", "zaapp", "zpapa",
"zpaap", "zppaa", "azppa", "azpap",
"azapp", "apzpa", "apzap", "appza",
"appaz", "apazp", "apapz", "aazpp",
"aapzp", "aappz", "pzapa", "pzaap",
"pzpaa", "pazpa", "pazap", "papza",
"papaz", "paazp", "paapz", "ppzaa",
"ppaza", "ppaaz"
]
*/
很好,现在每个排列只出现一次,我们已经满足了问题的要求。 这是所有排列寻找荣耀的完整功能。
function getPermutations(str) {
if (str.length < 2) {
return str;
}
let permutations = [];
for (let i = 0; i < str.length; i++) {
let char = str[i];
if (str.indexOf(char) !== i) {
continue;
}
let remainingChars = str.slice(0, i) + str.slice(i + 1, str.length);
for (let permutation of getPermutations(remainingChars)) {
permutations.push(char + permutation);
}
}
return permutations;
}
这个问题在编程面试中经常出现。 仅出于这个原因学习很好,但了解如何解决它也有其他好处。 例如,这是学习递归以及何时使用它的好方法。 它还帮助编码人员注意和解决边缘情况。 研究它,理解它,然后把你学到的东西带到下一个面试🃏或项目中!
好了,这篇文章的内容发货联盟就和大家分享到这里,如果大家网络推广引流创业感兴趣,可以添加微信:80709525 备注:发货联盟引流学习; 我拉你进直播课程学习群,每周135晚上都是有实战干货的推广引流技术课程免费分享!