剑指offer--javascript版每日一题

266 阅读4分钟

1.用两个栈实现队列

题目描述

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

解题思路

在新建类的时候,给对象初始化两个数组:stack1、stack2,作为栈。(我们知道,栈是先进后出,只有栈顶可以进出元素,这里我们可以假定数组的开头为栈顶,所以我们只能对这两个数组的开头进行元素增、减操作)

要实现的功能:先进先出

在执行函数appendTail时向stack1的开头插入要添加的值;在执行deleteHead函数时,先判断stack2是否为空,若为空,则从stack1数组中,从开头取值放入stack2的开头,再从stack1开头取值放入stack2的开头...直至将stack1中元素取完。

代码实现

class Quene {
            constructor () {
                this.stack1 = []
                this.stack2 = []
            }
            appendTail (value) {
                // 新插入的元素都放在stack1中
                this.stack1.unshift(value)
            }
            deleteHead () {
                // 删除的元素从stack2中删除,如果stack2空,就从stack1的末尾取
                if ( !this.stack2.length ) {
                    for (let i=0; i<this.stack1.length; i++) {
                        this.stack2.unshift(this.stack1.pop())
                    }   
                }
                return this.stack2.length ? this.stack2.shift() : -1
            }
        }
        let quene = new Quene()
        quene.appendTail(1)
        quene.appendTail(2)
        console.log(quene.deleteHead())
        quene.appendTail(3)
        quene.appendTail(4)
        console.log(quene.deleteHead());
        console.log(quene.deleteHead());

图解

let quene = new Quene()
quene.appendTail(1)

image.png

let quene = new Quene()
quene.appendTail(1)

quene.appendTail(2)

image.png

let quene = new Quene()
quene.appendTail(1)
quene.appendTail(2)

console.log(quene.deleteHead());

执行deleteHead删除一个元素操作时,会先判断stack2是否为空,此时为空;则从stack1数组中,从开头取值放入stack2的开头,再从stack1开头取值放入stack2的开头...直至将stack1中元素取完。

image.png

image.png 接着再执行stack2开头处的删除操作:

image.png

let quene = new Quene()
quene.appendTail(1)
quene.appendTail(2)
console.log(quene.deleteHead());

quene.appendTail(3)
quene.appendTail(4)

image.png

let quene = new Quene()
quene.appendTail(1)
quene.appendTail(2)
console.log(quene.deleteHead());
quene.appendTail(3)
quene.appendTail(4)

console.log(quene.deleteHead());

执行deleteHead删除一个元素操作时,会先判断stack2是否为空,此时不为空;则直接执行stack2开头处的删除操作:

image.png

let quene = new Quene()
quene.appendTail(1)
quene.appendTail(2)
console.log(quene.deleteHead());
quene.appendTail(3)
quene.appendTail(4)
console.log(quene.deleteHead());

console.log(quene.deleteHead());
console.log(quene.deleteHead());

执行deleteHead删除一个元素操作时,会先判断stack2是否为空,此时为空;则从stack1数组中,从开头取值放入stack2的开头,再从stack1开头取值放入stack2的开头...直至将stack1中元素取完。

image.png 接着再执行stack2开头处的删除操作:

image.png

2. 查询斐波那契数列的项

题目描述

斐波那契数列:第0项是0,第一项是1,第二项是1,后面第n项是第n-1项与第n-2项的和。

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:

F(0) = 0,   F(1) = 1 F(N) = F(N - 1) + F(N - 2), 其中 N > 1. 斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

解题思路

尾调用优化:函数return了另一个函数。

思路是从前两项0、1开始,执行n次两项相加:

比如当n=3,求Fn(3):

第一次: 0 + 1 = 1

第二次: 1 + 1 = 2

第三次: 2 + 1 = 3

代码实现

var fib = function(n) {
    return fibImpl(0, 1, n);
};

function fibImpl(a, b, n) {//a,b 分别对应 F(0), F(1)
    if (n == 0) {
        return a;
    }
    //结果要取模
    return fibImpl(b, (a + b)%1000000007 , n - 1);
}

3.青蛙跳台阶的可能性有多少种

此类求 多少种可能性 的题目一般都有 递推性质 ,即 f(n)f(n) 和 f(n-1)f(n−1)…f(1)f(1) 之间是有联系的。

设跳上 n 级台阶有 f(n) 种跳法。在所有跳法中,青蛙的最后一步只有两种情况: 跳上 1 级或 2 级台阶。 当为 1 级台阶: 剩 n−1 个台阶,此情况共有 f(n−1) 种跳法; 当为 2 级台阶: 剩 n−2 个台阶,此情况共有 f(n−2) 种跳法。 f(n)为以上两种情况之和,即 f(n)=f(n-1)+f(n-2) ,以上递推性质为斐波那契数列。本题可转化为 求斐波那契数列第 n 项的值 ,与 面试题 斐波那契数列 等价,唯一的不同在于起始数字不同。

青蛙跳台阶问题: f(0)=1f(0)=1 , f(1)=1f(1)=1 , f(2)=2f(2)=2 ;

斐波那契数列问题: f(0)=0f(0)=0 , f(1)=1f(1)=1 , f(2)=1f(2)=1 。

斐波那契数列的定义是 f(n + 1) = f(n) + f(n - 1),生成第 n 项的做法有以下几种:

递归法:

原理: 把 f(n) 问题的计算拆分成 f(n-1) 和 f(n-2) 两个子问题的计算,并递归,以 f(0) 和 f(1) 为终止条件。

缺点: 大量重复的递归计算,例如 f(n)和 f(n - 1) 两者向下递归都需要计算 f(n - 2) 的值。

记忆化递归法:

原理: 在递归法的基础上,新建一个长度为 n 的数组,用于在递归时存储 f(0) 至 f(n)的数字值,重复遇到某数字时则直接从数组取用,避免了重复的递归计算。

缺点: 记忆化存储的数组需要使用 O(N)O 的额外空间。

动态规划:

原理: 以斐波那契数列性质 f(n + 1) = f(n) + f(n - 1) 为转移方程。从计算效率、空间复杂度上看,动态规划是本题的最佳解法。

// 递归--超出时间限制
        function getFn(n) {
            if (n == 0) {
                return 1
            }
            if (n == 1) {
                return 1
            }
            if (n == 2) {
                return 2
            }
            return getFn(n-1) + getFn(n-2)
        }
        console.log(getFn(7));

// 备忘录算法:将重复用到的项存入 --超出时间限制
        // 先判断是否有该项,有就用,没有就存入该项
        function getFnThreen(n) {
            var obj = {}
            if (n == 0) {
                return 1
            }
            if (n == 1) {
                return 1
            }
            if (obj.n) {
                return obj.n
            } else {
                obj.n = getFnThreen(n-1) + getFnThreen(n-2)
                return obj.n
            }

        }
        console.log(getFnThreen(7));
// 尾调用优化 : 一个函数的返回值是另一个函数
        function getFnTwo(n) {
            return fibIm(1,1,n)
        }
        function fibIm(a, b, n) {
            if ( n == 0 ) {
                return a
            }
            return fibIm(b, (a+b), n-1)
        }
        console.log(getFnTwo(4));
// 动态规划
        // 动态规划:边界、状态转换方程、最优子结构
        // 边界:F(0)=0,F(1)=1;状态转换方程:F(n) = F(n-1) + F(n-2);
        // 自底向上,每次只用保存两个变量F(n-1)、F(n-2)

        function getFnFour(n) {
            if (n == 0) {
                return 1
            }
            if (n == 1) {
                return 1
            }
            var a=1,b=2,temp
            for (var i=3; i<=n; i++){
                temp = a+b
                a = b;
                b = temp
            }
            return temp
        }
        console.log(getFnTwo(4));
var numWays = function(n) {
    var arr = [1,1,2]
    for (var i=3; i<=n; i++){
        arr[i] = (arr[i-1] + arr[i-2])% 1000000007
    }
    return arr[n]
};

最后这种性能最优。