如何使用递归返回N个抛硬币的所有组合? 我们重复使用什么价值?我们的递归何时结束?我们如何将答案从一个步骤转换为下一个步骤?基本情况的价值是什么?声明我们的功能添加基本情况处理递归案例用if-else语句替换条件运算符计算默认参数以用作局部变量重新考虑递归步骤

如何解决如何使用递归返回N个抛硬币的所有组合? 我们重复使用什么价值?我们的递归何时结束?我们如何将答案从一个步骤转换为下一个步骤?基本情况的价值是什么?声明我们的功能添加基本情况处理递归案例用if-else语句替换条件运算符计算默认参数以用作局部变量重新考虑递归步骤

请求:

使用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);

解决方法

下面是有关如何创建此类递归函数的详细说明。我认为所描述的步骤有助于解决许多问题。它们不是万能药,但它们可能非常有用。但首先,这是我们将努力的方向:

const getFlips = (n) =>
  n <= 0
    ? ['']
    : getFlips (n - 1) .flatMap (r => [r + 'H',r + 'T'])

确定我们的算法

要递归解决此类问题,我们需要回答几个问题:

我们重复使用什么价值?

对于简单的递归,它通常是一个数字参数。在所有情况下,都必须有一种方法来证明我们正在朝着某种最终状态迈进。

这是一个简单的情况,很明显,我们想重复翻转的次数;我们称之为n

我们的递归何时结束?

我们最终需要停止重复发生。在这里,我们可以考虑在n为0或n为1时停止。让我们暂缓此决定,看看哪个可能更简单。

我们如何将答案从一个步骤转换为下一个步骤?

对于递归来说,做任何有用的事情,重要的是根据当前步骤计算下一步的结果。

(同样,此处涉及更多的递归可能会很复杂。例如,我们可能必须使用 all 较低的结果来计算下一个值。例如,请查看{{3} }。在这里我们可以忽略这一点;我们的递归很简单。)

那么我们如何将['HH','HT','TH','TT']转换为下一步['HHH','HHT','HTH','HTT','THH','THT','TTH','TTT']?好吧,如果我们仔细观察下一个结果,我们可以看到,上半部分所有元素均以“ H”开头,而下半部分中所有元素均以“ T”开头。如果我们忽略前几个字母,那么每半部分都是我们输入['HH','TT']的副本。看起来非常有前途!因此,我们的递归步骤可以是为先前的结果制作两个副本,第一个副本的每个值都以'H'开头,第二个副本的值为'T'

基本情况的价值是什么?

这与我们跳过的问题有关。我们不能说什么结束,而又不知道什么时候结束。但是,要确定这两者的一个好方法就是往后工作。

要从['HHH','TTT']返回到['HH','TT'],我们可以取前半部分,并从每个结果中删除初始的'H'。让我们再来一次。在['HH','TT']中,我们取前半部分,并从每个半数中删除初始的'H',得到['H','T']。虽然这可能是我们的停车点,但如果再向前迈一步,会发生什么呢?取上半部分并从剩余的一个元素中删除初始的H,只剩下['']。这个答案有意义吗?我认为确实如此:有多少种方法可以将硬币零次翻转?只有一个。我们如何将其记录为HT的字符串?作为空字符串。因此,仅包含空字符串的数组对于0的情况是一个很好的答案。这也回答了我们的第二个问题,即递归何时结束。当n为零时结束。

该算法的编写代码

当然,现在我们必须将该算法转换为代码。我们也可以通过几个步骤来做到这一点。

声明我们的功能

我们首先从函数定义开始编写。我们的参数称为n。我将调用函数getFlips。所以我们从

开始
const getFlips = (n) =>
  <something here>

添加基本情况。

我们已经说过n为零时我们将结束。我通常更喜欢通过检查任何小于或等于零的n来使其更具弹性。如果有人传递一个负数,这将停止无限递归。相反,在这种情况下,我们可以选择引发异常,但是对于零的情况,我们对['']的解释似乎也适用于负值。 (此外,我绝对讨厌抛出异常!)

这给我们以下内容:

const getFlips = (n) =>
  n <= 0
    ? ['']
    : <something here>

我在这里选择使用Catalan Numbers而不是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。但是现代的解决方案可能是使用散布参数并只返回[...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)。在递归的情况下,这通常很成问题。

对此有一些明显的修复。首先是放弃对基于表达式的编程的迷恋,而只对局部变量使用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不是必需的,有些短毛猫会抱怨它。我认为其中包含的代码读起来会更好。)

计算默认参数以用作局部变量

另一种方法是在较早的定义中使用参数默认值。

const getFlips = (n,prev = n > 0 && getFlips (n - 1)) =>
  n <= 0
    ? ['']
    : [...prev .map (r => 'H' + r),...prev .map (r => 'T' + r)]

这可能被正确地视为过分棘手,当在意外情况下使用您的函数时,可能会导致问题。例如,请勿将其传递给数组的map调用。

重新考虑递归步骤

以上任何一种都可以。但是有更好的解决方案。

如果我们看到将['HH','TT']转换为['HHH','TTT']的另一种方法,那么我们也可以使用不同的递归步骤方法编写相同的代码。我们的技术是将数组拆分为中间并删除第一个字母。但是数组版本中还有该基本版本的其他副本,但没有字母之一。如果我们从每个字母中删除 last 字母,则会得到['HH','HH','TT','TT'],它只是我们的原始版本,每个字符串出现两次。

想到的实现此功能的第一个代码就是getFlips (n - 1) .map (r => [r + 'H',r + 'T'])。但这将是微妙的,因为它将['HH',' 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))

,
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,current + 'H');
            addFlips(n - 1,current + 'T');
        }
    }
    // Begin with empty results
    let result = [];
    // Current starts with empty string
    addFlips(n,'');
    return result;
}
,

如果有兴趣,这里的解决方案不使用递归,而是使用Applicative类型。


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

  • 2 2 →[H,T]×[H,T]→[HH,HT,TH,TT]
  • 2 3 →[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','T','H'); //=> 'HHTH'
//...

可以编写这样的函数来生成 n 掷硬币的结果的列表:

const outcomes = n => Array(n).fill(['H','T']);

outcomes(2); //=> [['H','T'],['H','T']]
outcomes(3); //=> [['H','T']]
// ...

我们现在可以在这里看到一个解决方案:要获取所有可能组合的列表,我们需要在所有列表中应用concat

但是我们不想这样做。相反,我们要使concat使用值的容器而不是单个值。

因此:

concat(['H','T']);

产生与以下相同的结果

[ concat('H','H'),concat('H','T'),concat('T','T')
]

在函数式编程中,我们说我们要lift concat。在此示例中,我将使用Ramda的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>

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-