Javascript 如何使用递归返回N个硬币翻转的所有组合?

Javascript 如何使用递归返回N个硬币翻转的所有组合?,javascript,recursion,combinations,coin-flipping,Javascript,Recursion,Combinations,Coin Flipping,请求: 使用JavaScript编写一个接受整数的函数。整数表示硬币被翻转的次数。仅使用递归策略,返回一个包含所有可能的投币组合的数组。用“H”表示头部,“T”表示尾部。组合的顺序并不重要 例如,传入“2”将返回: [“HH”、“HT”、“TH”、“TT”] 上下文: 我对JavaScript和递归概念都比较陌生。这纯粹是为了实践和理解,因此解决方案不一定需要与下面代码的方向相匹配;任何有用的方法或其他思考方法都是有用的,只要它是纯递归的(没有循环) 尝试: 我的尝试一开始很简单,但是随着我增加

请求:

使用JavaScript编写一个接受整数的函数。整数表示硬币被翻转的次数。仅使用递归策略,返回一个包含所有可能的投币组合的数组。用“H”表示头部,“T”表示尾部。组合的顺序并不重要

例如,传入“2”将返回:
[“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);

如果对此感兴趣,这里有一个解决方案,它不使用递归,而是使用类型


除n为1时外,所有可能组合的列表是通过组合每次抛硬币的所有可能结果获得的:

  • 二十二→ [H,T]×[H,T]→ [HH,HT,TH,TT]
  • 二十三→ [H,T]×[H,T]×[H,T]→ [HHH,HHT,HTH,HTT,THH,THT,TTH,TTT]
一个可以包含n个字符的函数,可以这样编写:

const concat=(…n)=>n.join(“”);
海螺('H','H');//=>'HH'
海螺('H','H','T');//=>'HHT'
海螺('H','H','T','H');//=>'hht'
//...
生成n次抛硬币结果列表的函数可以这样编写:

const-outcouts=n=>Array(n).fill(['H','T']);
结果(2);//=>[H',T'],[H',T']]
结果(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')
,海螺('H','T','T')
,concat('T','H','H')
,concat('T','H','T')
,concat('T','T','H')
,concat('T','T','T')
]
在函数式编程中,我们说我们想要
concat
。在这个例子中,我将使用Ramda的函数

const flip=n=>{
const concat=liftN(n,(…x)=>x.join(“”));
返回concat(…数组(n).fill(['H','T']);
};
控制台日志(翻转(1));
控制台日志(翻转(2));
控制台日志(翻转(3));
控制台日志(翻转(4))


常数{liftN}=R
const getFlips=(n)=>
n[r+'H',r+'T'])

确定我们的算法 要递归地解决这样的问题,我们需要回答几个问题:

我们的价值是什么? 对于简单递归,它通常是单个数值参数。在所有情况下,都必须有一种方式证明我们正在朝着某种最终状态取得进展

这是一个简单的例子,很明显,我们希望在翻转次数上再次出现;让我们称之为
n

我们的递归何时结束? 我们最终需要停止复发。这里,我们可以考虑当<代码> n>代码>为0时,或者当代码> N< /代码>为1时停止。任何一种选择都可能奏效。让我们暂缓一下这个决定,看看哪一个更简单

我们如何将一个步骤的答案转换为下一个步骤的答案? 为了让递归做任何有用的事情,重要的部分是根据当前步骤计算下一步的结果

(同样,对于更复杂的递归,这里可能存在复杂性。例如,我们可能必须使用所有较低的结果来计算下一个值。例如,查找。这里我们可以忽略这一点;我们的递归很简单。)

那么,我们如何将
['HH',HT',TH',TT']
转换为下一步,
['HH',HHT',HTH',HTT',THH',THT',TTH',TTT']
?如果我们仔细观察下一个结果,我们可以看到,在前半部分,所有元素都以“H”开头,在第二部分,它们以“T”开头。如果忽略前半个字母,则每半个字母都是输入的副本,
['HH',HT',TH',TT']
。看起来很有希望!因此,我们的递归步骤可以是复制前一个结果的两个副本,第一个副本的每个值前面都有
'H'
,第二个副本前面有
'T'

我们的基本案例的价值是什么? 这与我们跳过的问题有关。我们不能在不知道什么时候结束的情况下说出它的结束。但要让双方都下定决心,一个好办法是倒退

要从
['HH','HHT','HTH','HTT','THH','THT','TTH','TTT']
返回到
['HH','HT','TH','TT']
,我们可以取前半部分并从每个结果中删除初始的'H'。让我们再来一次。从
['HH',HT',TH',TT']
中,我们取上半部分,并从每个部分中删除首字母“H”,得到
['H',T']
。虽然这可能是我们的停站点,但如果我们再往前走一步,会发生什么呢?取上半部分并从剩下的一个元素中删除初始的
H
,我们就剩下了JU
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;
}
const getFlips = (n) =>
  n <= 0
    ? ['']
    : <something here>