如何使用递归return N次掷硬币的所有组合?
How to return all combinations of N coin flips using recursion?
要求:
使用JavaScript,编写一个接受整数的函数。整数表示抛硬币的次数。仅使用递归策略,return 一个包含所有可能的抛硬币组合的数组。用“H”代表正面,用“T”代表反面。组合的顺序无关紧要。
例如,传入“2”将 return:
["HH", "HT", "TH", "TT"]
上下文:
我对 JavaScript 以及递归的概念都比较陌生。这纯粹是为了练习和理解,所以解决方案不一定需要符合我下面代码的方向;任何有用的方法或其他思考方式都是有帮助的,只要它是纯递归的(无循环)。
尝试:
我的尝试一开始很简单,但是随着输入的增加,“动作”逐渐变得更加复杂。我相信这适用于 2、3 和 4 的输入。但是,5 或更高的输入在输出中缺少组合。非常感谢!
function coinFlips(num){
const arr = [];
let str = "";
// adds base str ("H" * num)
function loadStr(n) {
if (n === 0) {
arr.push(str);
return traverseArr();
}
str += "H";
loadStr(n - 1);
}
// declares start point, end point, and index to update within each str
let start = 0;
let end = 1;
let i = 0;
function traverseArr() {
// base case
if(i === str.length) {
console.log(arr);
return arr;
}
// updates i in base str to "T"
// increments i
// resets start and end
if(end === str.length) {
str = str.split('');
str[i] = "T";
str = str.join('');
i++;
start = i;
end = i + 1;
return traverseArr();
}
// action
let tempStr = str.split('');
tempStr[start] = "T";
tempStr = tempStr.join('');
if(!arr.includes(tempStr)){
arr.push(tempStr);
};
tempStr = tempStr.split('');
tempStr.reverse();
tempStr = tempStr.join('');
if(!arr.includes(tempStr)){
arr.push(tempStr);
};
tempStr = str.split('');
tempStr[end] = "T";
tempStr = tempStr.join('');
if(!arr.includes(tempStr)){
arr.push(tempStr);
};
tempStr = tempStr.split('');
tempStr.reverse();
tempStr = tempStr.join('');
if(!arr.includes(tempStr)){
arr.push(tempStr);
};
tempStr = str.split('');
tempStr[start] = "T";
tempStr[end] = "T";
tempStr = tempStr.join('');
if(!arr.includes(tempStr)){
arr.push(tempStr);
};
tempStr = tempStr.split('');
tempStr.reverse();
tempStr = tempStr.join('');
if(!arr.includes(tempStr)){
arr.push(tempStr);
};
// recursive case
start++;
end++;
return traverseArr();
}
loadStr(num);
}
coinFlips(5);
function getFlips(n) {
// Helper recursive function
function addFlips(n, result, current) {
if (n === 1) {
// This is the last flip, so add the result to the array
result.push(current + 'H');
result.push(current + 'T');
} else {
// Let's say current is TTH (next combos are TTHH and TTHT)
// Then for each of the 2 combos call add Flips again to get the next flips.
addFlips(n - 1, result, current + 'H');
addFlips(n - 1, result, current + 'T');
}
}
// Begin with empty results
let result = [];
// Current starts with empty string
addFlips(n, result, '');
return result;
}
如果这有任何意义,这里有一个不使用递归但使用 Applicative
类型的解决方案。
除了n为1时,所有可能组合的列表由每次抛硬币的所有可能结果组合得到:
- 22 → [H, T] × [H, T] → [HH, HT, TH, TT]
- 23 → [H, T] × [H, T] × [H, T] → [HHH, HHT, HTH, HTT, THH, THT, TTH , TTT]
- ...
一个可以接受 n 个字符并将它们连接起来的函数可以这样写:
const concat = (...n) => n.join('');
concat('H', 'H'); //=> 'HH'
concat('H', 'H', 'T'); //=> 'HHT'
concat('H', 'H', 'T', 'H'); //=> 'HHTH'
//...
生成 n 次抛硬币结果列表的函数可以这样写:
const outcomes = n => Array(n).fill(['H', 'T']);
outcomes(2); //=> [['H', 'T'], ['H', 'T']]
outcomes(3); //=> [['H', 'T'], ['H', 'T'], ['H', 'T']]
// ...
我们现在可以在这里看到一个解决方案:要获得所有可能组合的列表,我们需要在所有列表中应用 concat
。
但是我们不想那样做。相反,我们希望 concat
使用值容器而不是单个值。
这样:
concat(['H', 'T'], ['H', 'T'], ['H', 'T']);
产生与以下相同的结果:
[ concat('H', 'H', 'H')
, concat('H', 'H', 'T')
, concat('H', 'T', 'H')
, concat('H', 'T', 'T')
, concat('T', 'H', 'H')
, concat('T', 'H', 'T')
, concat('T', 'T', 'H')
, concat('T', 'T', 'T')
]
在函数式编程中我们说我们想要lift
concat
. In this example I'll be using Ramda's liftN
函数。
const flip = n => {
const concat = liftN(n, (...x) => x.join(''));
return concat(...Array(n).fill(['H', 'T']));
};
console.log(flip(1));
console.log(flip(2));
console.log(flip(3));
console.log(flip(4));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
<script>const {liftN} = R;</script>
下面是关于如何创建此类递归函数的详细说明。我认为所描述的步骤有助于解决大量问题。它们不是灵丹妙药,但它们可能非常有用。但首先,我们将努力实现以下目标:
const getFlips = (n) =>
n <= 0
? ['']
: getFlips (n - 1) .flatMap (r => [r + 'H', r + 'T'])
确定我们的算法
要递归解决这样的问题,我们需要回答几个问题:
我们的循环价值是多少?
对于简单的递归,它通常是单个数字参数。在所有情况下,都必须有一种方法来证明我们正在朝着某个最终状态取得进展。
这是一个简单的案例,很明显我们想要在翻转次数上进行递归;我们称它为 n
.
我们的递归什么时候结束?
我们最终需要停止重复发生。在这里,我们可能会考虑在 n
为 0 或可能在 n
为 1 时停止。两种选择都可行。让我们暂时搁置这个决定,看看哪个可能更简单。
我们如何将一步的答案转换为下一步的答案?
要让递归做任何有用的事情,重要的部分是根据当前步骤计算下一步的结果。
(同样,对于更多涉及的递归,这里可能存在复杂性。例如,我们可能必须使用 all 较低的结果来计算下一个值。有关示例,请查找Catalan Numbers。在这里我们可以忽略它;我们的递归很简单。)
那么我们如何将 ['HH', 'HT', 'TH', 'TT']
转换为下一步 ['HHH', 'HHT', 'HTH', 'HTT', 'THH', 'THT', 'TTH', 'TTT']
?好吧,如果我们仔细观察下一个结果,我们可以看到前半部分所有元素都以 'H' 开头,而在后半部分它们以 'T' 开头。如果我们忽略前几个字母,每一半都是我们输入的副本,['HH', 'HT', 'TH', 'TT']
。这看起来很有希望!所以我们的递归步骤可以是复制先前结果的两个副本,第一个副本的每个值前面都有 'H'
,第二个副本前面有 'T'
.
我们的基本案例的价值是多少?
这与我们跳过的问题有关。我们不能在不知道什么时候它结束的情况下说什么它结束。但确定两者的一个好方法是向后工作。
要从 ['HHH', 'HHT', 'HTH', 'HTT', 'THH', 'THT', 'TTH', 'TTT']
倒退到 ['HH', 'HT', 'TH', 'TT']
,我们可以取前半部分并从每个结果中删除初始的 'H'。让我们再来一次。从 ['HH', 'HT', 'TH', 'TT']
开始,我们取前半部分并从每个中删除初始的 'H' 以获得 ['H', 'T']
。虽然这可能是我们的停止点,但如果我们更进一步会发生什么?取前半部分并从剩下的一个元素中删除初始 H
只剩下 ['']
。这个答案有意义吗?我认为它确实如此:有多少种方法可以将硬币掷零次?只有一个。我们如何将其记录为 H
和 T
的字符串?作为空字符串。因此,仅包含空字符串的数组对于 0 的情况是一个很好的答案。这也回答了我们的第二个问题,即递归何时结束。当 n
为零时结束。
正在为该算法编写代码
当然现在我们必须将该算法转化为代码。我们也可以通过几个步骤完成此操作。
声明我们的函数
我们从函数定义开始编写。我们的参数称为 n
。我要调用函数 getFlips
。所以我们从
开始
const getFlips = (n) =>
<something here>
添加我们的基本案例。
我们已经说过我们将在 n
为零时结束。我通常更喜欢通过检查任何小于或等于零的 n
来使其更具弹性。如果有人传递负数,这将停止无限递归。在这种情况下,我们可以选择抛出异常,但我们对 ['']
对零情况的解释似乎也适用于负值。 (此外,我绝对 讨厌 抛出异常!)
这给了我们以下信息:
const getFlips = (n) =>
n <= 0
? ['']
: <something here>
我在这里选择使用 conditional (ternary) expression 而不是 if-else
语句,因为我更喜欢尽可能使用表达式而不是语句。如果您觉得更自然,可以使用 if-else
轻松编写相同的技术。
处理递归情况
我们的描述是“将之前的结果复制两份,第一份的每个值都以 'H'
开头,第二份以 'T'
开头。”我们之前的结果当然是getFlips (n - 1)
。如果我们想在该数组中的每个值之前加上 'H'
,我们最好使用 .map
。我们可以这样来标识:getFlips (n - 1) .map (r => 'H' + r)
。当然,下半场只是 getFlips (n - 1) .map (r => 'T' + r)
。如果我们想把两个数组合二为一,有很多技巧,包括.push
和.concat
。但是现代的解决方案可能是使用传播参数并且只是 return [...first, ...second]
.
将所有这些放在一起,我们得到这个片段:
const getFlips = (n) =>
n <= 0
? ['']
: [...getFlips (n - 1) .map (r => 'H' + r), ...getFlips (n - 1) .map (r => 'T' + r)]
console .log (getFlips (3))
检查结果
我们可以在几个案例中对此进行测试。但我们应该对代码相当信服。它似乎有效,它相对简单,没有明显的边缘情况丢失。但我仍然看到一个问题。我们正在计算 getFlips (n - 1)
两次,没有充分的理由。在递归的情况下,它通常很有问题。
对此有几个明显的修复。首先是放弃我对 expression-based 编程的迷恋,并简单地使用 if-else
逻辑和局部变量:
用 if-else
语句替换条件运算符
const getFlips = (n) => {
if (n <= 0) {
return ['']
} else {
const prev = getFlips (n - 1)
return [...prev .map (r => 'H' + r), ...prev .map (r => 'T' + r)]
}
}
(从技术上讲,else
不是必需的,有些 linter 会抱怨它。我认为包含它的代码阅读起来更好。)
计算默认参数以用作局部变量
另一种方法是使用早期定义中的参数默认值。
const getFlips = (n, prev = n > 0 && getFlips (n - 1)) =>
n <= 0
? ['']
: [...prev .map (r => 'H' + r), ...prev .map (r => 'T' + r)]
这可能被正确地视为 over-tricky,当您的函数在意外情况下使用时,它可能会导致问题。例如,不要将其传递给数组的 map
调用。
重新思考递归步骤
以上任何一个都可以。不过还有更好的解决办法。
如果我们能看到另一种将 ['HH', 'HT', 'TH', 'TT']
转换为 ['HHH', 'HHT', 'HTH', 'HTT', 'THH', 'THT', 'TTH', 'TTT']
的方法,我们也可以使用不同的递归方法编写相同的代码。我们的技术是从中间拆分数组并删除第一个字母。但是在数组版本中还有该基本版本的其他副本,但没有一个字母。如果我们从每个字符串中删除 last 个字母,我们会得到 ['HH', 'HH', 'HT', 'HT', 'TH', 'TH', 'TT', 'TT']
,这只是我们的原始版本,每个字符串出现两次。
第一个想到的实现它的代码就是 getFlips (n - 1) .map (r => [r + 'H', r + 'T'])
。但这会被巧妙地关闭,因为它将 ['HH', 'HT', 'TH', ' TT']
转换为 [["HHH", "HHT"], ["HTH", "HTT"], ["THH", "THT"], [" TTH", " TTT"]]
,并带有额外的嵌套级别,并且递归应用只会产生无意义的结果。但是有一个 .map
的替代方法,它删除了额外的嵌套级别,.flatMap
.
这使我们找到了一个我非常满意的解决方案:
const getFlips = (n) =>
n <= 0
? ['']
: getFlips (n - 1) .flatMap (r => [r + 'H', r + 'T'])
console .log (getFlips (3))
要求:
使用JavaScript,编写一个接受整数的函数。整数表示抛硬币的次数。仅使用递归策略,return 一个包含所有可能的抛硬币组合的数组。用“H”代表正面,用“T”代表反面。组合的顺序无关紧要。
例如,传入“2”将 return:
["HH", "HT", "TH", "TT"]
上下文:
我对 JavaScript 以及递归的概念都比较陌生。这纯粹是为了练习和理解,所以解决方案不一定需要符合我下面代码的方向;任何有用的方法或其他思考方式都是有帮助的,只要它是纯递归的(无循环)。
尝试:
我的尝试一开始很简单,但是随着输入的增加,“动作”逐渐变得更加复杂。我相信这适用于 2、3 和 4 的输入。但是,5 或更高的输入在输出中缺少组合。非常感谢!
function coinFlips(num){
const arr = [];
let str = "";
// adds base str ("H" * num)
function loadStr(n) {
if (n === 0) {
arr.push(str);
return traverseArr();
}
str += "H";
loadStr(n - 1);
}
// declares start point, end point, and index to update within each str
let start = 0;
let end = 1;
let i = 0;
function traverseArr() {
// base case
if(i === str.length) {
console.log(arr);
return arr;
}
// updates i in base str to "T"
// increments i
// resets start and end
if(end === str.length) {
str = str.split('');
str[i] = "T";
str = str.join('');
i++;
start = i;
end = i + 1;
return traverseArr();
}
// action
let tempStr = str.split('');
tempStr[start] = "T";
tempStr = tempStr.join('');
if(!arr.includes(tempStr)){
arr.push(tempStr);
};
tempStr = tempStr.split('');
tempStr.reverse();
tempStr = tempStr.join('');
if(!arr.includes(tempStr)){
arr.push(tempStr);
};
tempStr = str.split('');
tempStr[end] = "T";
tempStr = tempStr.join('');
if(!arr.includes(tempStr)){
arr.push(tempStr);
};
tempStr = tempStr.split('');
tempStr.reverse();
tempStr = tempStr.join('');
if(!arr.includes(tempStr)){
arr.push(tempStr);
};
tempStr = str.split('');
tempStr[start] = "T";
tempStr[end] = "T";
tempStr = tempStr.join('');
if(!arr.includes(tempStr)){
arr.push(tempStr);
};
tempStr = tempStr.split('');
tempStr.reverse();
tempStr = tempStr.join('');
if(!arr.includes(tempStr)){
arr.push(tempStr);
};
// recursive case
start++;
end++;
return traverseArr();
}
loadStr(num);
}
coinFlips(5);
function getFlips(n) {
// Helper recursive function
function addFlips(n, result, current) {
if (n === 1) {
// This is the last flip, so add the result to the array
result.push(current + 'H');
result.push(current + 'T');
} else {
// Let's say current is TTH (next combos are TTHH and TTHT)
// Then for each of the 2 combos call add Flips again to get the next flips.
addFlips(n - 1, result, current + 'H');
addFlips(n - 1, result, current + 'T');
}
}
// Begin with empty results
let result = [];
// Current starts with empty string
addFlips(n, result, '');
return result;
}
如果这有任何意义,这里有一个不使用递归但使用 Applicative
类型的解决方案。
除了n为1时,所有可能组合的列表由每次抛硬币的所有可能结果组合得到:
- 22 → [H, T] × [H, T] → [HH, HT, TH, TT]
- 23 → [H, T] × [H, T] × [H, T] → [HHH, HHT, HTH, HTT, THH, THT, TTH , TTT]
- ...
一个可以接受 n 个字符并将它们连接起来的函数可以这样写:
const concat = (...n) => n.join('');
concat('H', 'H'); //=> 'HH'
concat('H', 'H', 'T'); //=> 'HHT'
concat('H', 'H', 'T', 'H'); //=> 'HHTH'
//...
生成 n 次抛硬币结果列表的函数可以这样写:
const outcomes = n => Array(n).fill(['H', 'T']);
outcomes(2); //=> [['H', 'T'], ['H', 'T']]
outcomes(3); //=> [['H', 'T'], ['H', 'T'], ['H', 'T']]
// ...
我们现在可以在这里看到一个解决方案:要获得所有可能组合的列表,我们需要在所有列表中应用 concat
。
但是我们不想那样做。相反,我们希望 concat
使用值容器而不是单个值。
这样:
concat(['H', 'T'], ['H', 'T'], ['H', 'T']);
产生与以下相同的结果:
[ concat('H', 'H', 'H')
, concat('H', 'H', 'T')
, concat('H', 'T', 'H')
, concat('H', 'T', 'T')
, concat('T', 'H', 'H')
, concat('T', 'H', 'T')
, concat('T', 'T', 'H')
, concat('T', 'T', 'T')
]
在函数式编程中我们说我们想要lift
concat
. In this example I'll be using Ramda's liftN
函数。
const flip = n => {
const concat = liftN(n, (...x) => x.join(''));
return concat(...Array(n).fill(['H', 'T']));
};
console.log(flip(1));
console.log(flip(2));
console.log(flip(3));
console.log(flip(4));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
<script>const {liftN} = R;</script>
下面是关于如何创建此类递归函数的详细说明。我认为所描述的步骤有助于解决大量问题。它们不是灵丹妙药,但它们可能非常有用。但首先,我们将努力实现以下目标:
const getFlips = (n) =>
n <= 0
? ['']
: getFlips (n - 1) .flatMap (r => [r + 'H', r + 'T'])
确定我们的算法
要递归解决这样的问题,我们需要回答几个问题:
我们的循环价值是多少?
对于简单的递归,它通常是单个数字参数。在所有情况下,都必须有一种方法来证明我们正在朝着某个最终状态取得进展。
这是一个简单的案例,很明显我们想要在翻转次数上进行递归;我们称它为 n
.
我们的递归什么时候结束?
我们最终需要停止重复发生。在这里,我们可能会考虑在 n
为 0 或可能在 n
为 1 时停止。两种选择都可行。让我们暂时搁置这个决定,看看哪个可能更简单。
我们如何将一步的答案转换为下一步的答案?
要让递归做任何有用的事情,重要的部分是根据当前步骤计算下一步的结果。
(同样,对于更多涉及的递归,这里可能存在复杂性。例如,我们可能必须使用 all 较低的结果来计算下一个值。有关示例,请查找Catalan Numbers。在这里我们可以忽略它;我们的递归很简单。)
那么我们如何将 ['HH', 'HT', 'TH', 'TT']
转换为下一步 ['HHH', 'HHT', 'HTH', 'HTT', 'THH', 'THT', 'TTH', 'TTT']
?好吧,如果我们仔细观察下一个结果,我们可以看到前半部分所有元素都以 'H' 开头,而在后半部分它们以 'T' 开头。如果我们忽略前几个字母,每一半都是我们输入的副本,['HH', 'HT', 'TH', 'TT']
。这看起来很有希望!所以我们的递归步骤可以是复制先前结果的两个副本,第一个副本的每个值前面都有 'H'
,第二个副本前面有 'T'
.
我们的基本案例的价值是多少?
这与我们跳过的问题有关。我们不能在不知道什么时候它结束的情况下说什么它结束。但确定两者的一个好方法是向后工作。
要从 ['HHH', 'HHT', 'HTH', 'HTT', 'THH', 'THT', 'TTH', 'TTT']
倒退到 ['HH', 'HT', 'TH', 'TT']
,我们可以取前半部分并从每个结果中删除初始的 'H'。让我们再来一次。从 ['HH', 'HT', 'TH', 'TT']
开始,我们取前半部分并从每个中删除初始的 'H' 以获得 ['H', 'T']
。虽然这可能是我们的停止点,但如果我们更进一步会发生什么?取前半部分并从剩下的一个元素中删除初始 H
只剩下 ['']
。这个答案有意义吗?我认为它确实如此:有多少种方法可以将硬币掷零次?只有一个。我们如何将其记录为 H
和 T
的字符串?作为空字符串。因此,仅包含空字符串的数组对于 0 的情况是一个很好的答案。这也回答了我们的第二个问题,即递归何时结束。当 n
为零时结束。
正在为该算法编写代码
当然现在我们必须将该算法转化为代码。我们也可以通过几个步骤完成此操作。
声明我们的函数
我们从函数定义开始编写。我们的参数称为 n
。我要调用函数 getFlips
。所以我们从
const getFlips = (n) =>
<something here>
添加我们的基本案例。
我们已经说过我们将在 n
为零时结束。我通常更喜欢通过检查任何小于或等于零的 n
来使其更具弹性。如果有人传递负数,这将停止无限递归。在这种情况下,我们可以选择抛出异常,但我们对 ['']
对零情况的解释似乎也适用于负值。 (此外,我绝对 讨厌 抛出异常!)
这给了我们以下信息:
const getFlips = (n) =>
n <= 0
? ['']
: <something here>
我在这里选择使用 conditional (ternary) expression 而不是 if-else
语句,因为我更喜欢尽可能使用表达式而不是语句。如果您觉得更自然,可以使用 if-else
轻松编写相同的技术。
处理递归情况
我们的描述是“将之前的结果复制两份,第一份的每个值都以 'H'
开头,第二份以 'T'
开头。”我们之前的结果当然是getFlips (n - 1)
。如果我们想在该数组中的每个值之前加上 'H'
,我们最好使用 .map
。我们可以这样来标识:getFlips (n - 1) .map (r => 'H' + r)
。当然,下半场只是 getFlips (n - 1) .map (r => 'T' + r)
。如果我们想把两个数组合二为一,有很多技巧,包括.push
和.concat
。但是现代的解决方案可能是使用传播参数并且只是 return [...first, ...second]
.
将所有这些放在一起,我们得到这个片段:
const getFlips = (n) =>
n <= 0
? ['']
: [...getFlips (n - 1) .map (r => 'H' + r), ...getFlips (n - 1) .map (r => 'T' + r)]
console .log (getFlips (3))
检查结果
我们可以在几个案例中对此进行测试。但我们应该对代码相当信服。它似乎有效,它相对简单,没有明显的边缘情况丢失。但我仍然看到一个问题。我们正在计算 getFlips (n - 1)
两次,没有充分的理由。在递归的情况下,它通常很有问题。
对此有几个明显的修复。首先是放弃我对 expression-based 编程的迷恋,并简单地使用 if-else
逻辑和局部变量:
用 if-else
语句替换条件运算符
const getFlips = (n) => {
if (n <= 0) {
return ['']
} else {
const prev = getFlips (n - 1)
return [...prev .map (r => 'H' + r), ...prev .map (r => 'T' + r)]
}
}
(从技术上讲,else
不是必需的,有些 linter 会抱怨它。我认为包含它的代码阅读起来更好。)
计算默认参数以用作局部变量
另一种方法是使用早期定义中的参数默认值。
const getFlips = (n, prev = n > 0 && getFlips (n - 1)) =>
n <= 0
? ['']
: [...prev .map (r => 'H' + r), ...prev .map (r => 'T' + r)]
这可能被正确地视为 over-tricky,当您的函数在意外情况下使用时,它可能会导致问题。例如,不要将其传递给数组的 map
调用。
重新思考递归步骤
以上任何一个都可以。不过还有更好的解决办法。
如果我们能看到另一种将 ['HH', 'HT', 'TH', 'TT']
转换为 ['HHH', 'HHT', 'HTH', 'HTT', 'THH', 'THT', 'TTH', 'TTT']
的方法,我们也可以使用不同的递归方法编写相同的代码。我们的技术是从中间拆分数组并删除第一个字母。但是在数组版本中还有该基本版本的其他副本,但没有一个字母。如果我们从每个字符串中删除 last 个字母,我们会得到 ['HH', 'HH', 'HT', 'HT', 'TH', 'TH', 'TT', 'TT']
,这只是我们的原始版本,每个字符串出现两次。
第一个想到的实现它的代码就是 getFlips (n - 1) .map (r => [r + 'H', r + 'T'])
。但这会被巧妙地关闭,因为它将 ['HH', 'HT', 'TH', ' TT']
转换为 [["HHH", "HHT"], ["HTH", "HTT"], ["THH", "THT"], [" TTH", " TTT"]]
,并带有额外的嵌套级别,并且递归应用只会产生无意义的结果。但是有一个 .map
的替代方法,它删除了额外的嵌套级别,.flatMap
.
这使我们找到了一个我非常满意的解决方案:
const getFlips = (n) =>
n <= 0
? ['']
: getFlips (n - 1) .flatMap (r => [r + 'H', r + 'T'])
console .log (getFlips (3))