不使用 for , while ,递归,如何解决一道简单的算法题?

今天碰到一道关于的斐波那契数列的题
首先还是要认识一下这个「 斐波那契数列 」,我最早接触都这个数列是在高中的时候。当时在学校,我属于喜欢看课外书的学渣,不知道哪一天就接触到一本 C++ 编程入门书,然后里面就有一节就是如何实现斐波那契数列……还是先看题吧:

题目

给定一个参数数组 arr 和一个要返回的数组的长度 n, 使用一个函数来求出类似斐波那契数列所组成的数组

例子:

tribonacci([1, 1, 1] , 5)  // [1,1,1,3,5]
tribonacci([1, 1, 1], 10)  // [1,1,1,3,5,9,17,31,57,105]

原题链接:https://www.codewars.com/kata/tribonacci-sequence/train/javascript


思路

题目的关键:根据例子可以得知,数组中三个相邻的元素的和是数组中新的元素,这个新元素的位置是三个求和元素中的最后一个元素的索引 + 1, 问题来了如何获得数组中对应的相邻的三个元素?

这种类似的题其实解法挺多的,编程嘛,就要挑战一下新思路,若不使用 for , while 以及 递归的手法,如何实现遍历,并解决这个问题。我使用的手法:

在贴代码之前我先复习一下在 JavaScript 中可以用哪些方法实现数组的遍历

  1. 使用 for 或 while
  2. 数组提供的遍历的函数
  3. 还有就是递归

解法

个人比较喜欢函数所以这次就使用第二种方法:使用数组中的方法

function tribonacci(signature,n){

  // 处理遍历的元素,得到一个新的元素
  let handleThree = (index) => {

    // 前数组三项永远都是不改变,是传进来的三项
    if (index < signature.length) {
      return signature[index];
    } else {

      // 过滤出数组中对应的三项元素,求和并更新传进来的数组,
      let addItem = signature
                 .filter((item, signIndex) => (index - 3) <= signIndex)
                 .reduce((itemB, itemA) => itemB + itemA)

      signature.push(addItem);
      return addItem;
    }
  }

  // 得到最后的结果
  return Array.apply(null, {length: Number(n)})
         .map((item, index) => handleThree(index));
}

具体的代码实现感觉写的有点冗长,不过逻辑还是很明了

  • 整个大的函数返回值是一个数组。一个用 Array 创建的新数组使用 apply 把数组的长度确定,然后用 map 进行遍历,遍历的时候对在 map 中对每一项元素进行一些操作:

    1. 首先是判断是不是前三项

      • 是前三项直接出来返回对应的值

      • 若不是前三项则开始获取到需要相加的三项元素,我用了 filter 来拿到,之后用 reduce 来进行求和操作(这一步很关键,此时我已经拿到来一项新的值),接着更新传进来的数组,之后返回到 map 中进行下一个操作;直到遍历完整个数组。


在这个题中使用 apply 的一个特殊用法,把一个对象传到 Array 构造函数中,这个对象就包含这这个数组的长度,这个用法并不常见。

apply

很多人可能知道 apply , call 在扩充函数作用域的情况之下他们的异同之处。其实 apply 还有一个特殊的用处,就是用来传递特殊的值

例子一:求数组中最大值
let arr = [1, 2, 3, 4, 56];

Math.max(num); // NaN

// 方法一 es6之后的写法
Math.max(...num); // 6

// 方法二 es6之前的写法
Math.max.apply(null, num); // 6

方法一属于 es6 的 「 扩展语法 」的用法
这个例子中很清楚得知 Math.max() 里面的参数为数组的时候,并且不做处理,得到不是我们想要的结果

例子二:使用 apply 填充数组
// 方法一
Array.apply(null, ['5']); // ['5']

// 方法二
Array.apply(null, [5]); // [undefined x 5]

// 方法三 得到的结果等价于 方法二
Array(5); // [undefined x 5] 

// 方法四
Array.apply(null, {length: 5}); // [undefined, undefined, undefined, undefined, undefined]

上面的写法不一样,但是做的事情是一样的:通过构造函数 Array 创建了一个数组,其中有些写法很糟糕,比如:方法一,方法二,方法三(没有人会这么创建一个数组,更多是采用字面量的方式)。值的注意的地方是方法二中的写法,创建的其实是这样的数组:

// 方法二
Array.apply(null, [5]); // [, , , , ,] 或 [, , , , , ,]

并且有这样的问题,调用 map 或 forEach 的时候不会执行:

let arr = Array.apply(null, [5]);
arr.forEach((index) => index); //不会执行

所以方法四的优势就显现出来,可以通过 map 或 forEach 进行遍历整个数组

let arr = Array.apply(null, {length: 5}); // [undefined, undefined, undefined, undefined, undefined]
arr.map((el, index) => index); //[0, 1, 2, 3, 4]

省略 new 的写法参考 《 JavaScript 高级程序设计(第3版) 》第5章-引用类型 5.2-Array 类型(86页)

这也就是我的解答中采用这个方法的原因:能够确定数组的长度,并且内部的元素不确定;但想使用函数的方式进行遍历,恰好有这样的一种方式可以实现。

欢迎交流新的解题思路

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值