刷剑指offer 1

168 阅读4分钟

刷通剑指offer~

1.剑指 Offer 03. 数组中重复的数字

输入:
[2, 3, 1, 0, 2, 5, 3]
输出: 23 

首先思考解题思路,可以暴力,两层for循环,也可以用js的api:nums.indexOf(),但时间复杂度都太高了

image.png 思考优化,想到数组中用空间换时间的方法set和map,最后使用set做,时间复杂度O(n)

image.png

2. 剑指 Offer 04. 二维数组中的查找

[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false

首先思考暴力,二维数组,两个for循环可做,思考优化,因为是有序的,而且是查找,所以想到二分查找,时间复杂度O(log2n)。二分需要找到一个中间值,然后判断这个中间值是大还是小,改变中间值,于是将右上角的值作为中间值,如果target比他大就向左,小就向下。

var findNumberIn2DArray = function(matrix, target) {
    if(!matrix ||!matrix.length || !matrix[0].length) return false
    let m = matrix.length
    let n = matrix[0].length
    let row = 0, col = n-1 
    while(row<m && col>=0) {
        let midVal = matrix[row][col]
        if(midVal === target) return true
        if(midVal < target) row++
        else if(midVal > target) col--
    }
    return false
};

3.剑指 Offer 05. 替换空格

输入: s = "We are happy."
输出: "We%20are%20happy."

这题看上去感觉可以模拟,脑子里是怎么替换空格的:找到空格,把%20插进去。开始动手,如果可以开辟一个额外的空间的话,先拿一个指针指向原数组,然后在新数组里同步添加原数组字符,当遇到空格的时候,就把%20添加进去。

var replaceSpace = function(s) {
    let temp = []
    for(let i=0;i<s.length;i++) {
        if(s[i]===' ') {
            temp.push('%')
            temp.push('2')
            temp.push('0')
            //也可以直接temp.push('%20')
        } else {
            temp.push(s[i])
        }
    }
    return temp.join("")
};

4.剑指 Offer 06. 从尾到头打印链表

输入: head = [1,3,2]
输出: [2,3,1]

顺便复习一下怎么用js创建一个链表:

function ListNode(val) {
    this.val = val
    this.next = null
}
let head = new ListNode(1)
head.next = new ListNode(3)
head.next.next = new ListNode(2)
//目前掌握的方法,以后学到了的话继续补充

考虑用数组记录下每个节点的值,然后逆序返回,时间复杂度O(n)

var reversePrint = function(head) {
    let temp = []
    while(head) {
        temp.push(head.val)
        head = head.next
    }
    return temp.reverse()
}

5.剑指 Offer 07. 重建二叉树(重要,对下标思考很麻烦,得重复做)

Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]
Input: preorder = [-1], inorder = [-1]
Output: [-1]

题目为由前序和中序遍历重新构建二叉树,返回的是一棵二叉树。思路是前序遍历,第一个一定是根节点,然后在中序遍历中找,根节点左边的是左子树,右边的是右子树,分治之后递归的去找。注意最终要构建一棵树,而不是返回一个数组,所以我们在寻找的同时还要把树给构建出来。为了便于查找,先用一个字典把中序遍历的值的下标给记录下来,之后再进行递归。

var buildTree = function(preorder, inorder) {
    const dic = new Map()
    for(let i=0; i<inorder.length;i++) {
        dic.set(inorder[i],i)
    }
    return recur(0,0,inorder.length-1)
    function recur (root, left, right) {
        if(left > right) return null
        const node = new TreeNode(preorder[root])
        let i = dic.get(preorder[root])
        node.left = recur(root+1, left, i-1)
        node.right = recur(root + i - left + 1, i+1,right)
        return node
    }
};

6.剑指 Offer 09. 用两个栈实现队列

输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]
输入:
["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]

栈是给定的,不需要自己去设计,这道题的思路是用两个先进后出的栈实现一个先进先出的队列。想象面前有两个桶,家长为了让你学习,把游戏机放在最底下,然后把语文书,数学书,英语书依次往上放,然后告诉你只允许看最上面的书,要是回来发现里面的东西顺序变了,就说明你玩了游戏,你就完蛋了。那我们该如何玩到游戏机呢?我们从上到下一本一本书的顺序放到另一个桶中,这样另一个桶中的书就是倒序的,直到把游戏机拿出来,然后再一本书一本书的放回第一个桶中,哎,这下我们既玩到了游戏,又没有破坏顺序(然后发现你妈在窗户那看完了全过程,边夸你聪明边拿出了鸡毛掸子)。

var CQueue = function() {
    this.inStack = []
    this.outStack = []
};

/** 
 * @param {number} value
 * @return {void}
 */
CQueue.prototype.appendTail = function(value) {
    this.inStack.push(value)
};

/**
 * @return {number}
 */
CQueue.prototype.deleteHead = function() {
    if(!this.inStack.length && !this.outStack.length) return -1
    if(this.outStack.length) return this.outStack.pop()
    while(this.inStack.length) {
        this.outStack.push(this.inStack.pop())
    }
    return this.outStack.pop()
};

7.剑指 Offer 10- I. 斐波那契数列(重复做,还有矩阵快速幂,打表等多种解法待补充)

F(0) = 0,   F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
输入: n = 2
输出: 1
输入: n = 5
输出: 5

假设没有学过动态规划,第一次看见这个题第一反应应该是递归,因为从小到大学这个都是递归思想,但是会超时,时间复杂度是o(f(n)),会随着规模增大而增大,指数级。

var fib = function(n) {
    if(n === 0) return 0
    if(n === 1) return 1
    return (fib(n-1) + fib(n-2))
}

于是考虑记忆化搜索,用空间来换时间,把每次得到的结果用数组记录下来。

var fib = function(n) {
    const dp = []
    dp[0] = 0
    dp[1] = 1
    for(let i=2; i<=n;i++) {
        dp[i] = (dp[i-1] + dp[i-2])%1000000007
    }
    return (dp[n])
}

不超时了,但空间复杂度太高了,有没有什么优化空间的方法呢?

image.png 想到其实没有必要每次都用数组记录下来,以前的值是可以不要的,只要保留前两个值就可以

var fib = function(n) {
    if(n === 0) return 0
    let a = 1
    let b = 0
    for(let i=1;i<n;i++) {
        a = a+b
        b = a-b
        a %= 1000000007
    }
    return a
}

image.png

8.剑指 Offer 10- II. 青蛙跳台阶问题

输入: n = 2
输出: 2
输入: n = 7
输出: 21

和上一题思路一模一样,每次考虑上两次跳台阶方法之和

var numWays = function(n) {
    if(n === 0 || n === 1) return 1
    let a = 2
    let b = 1
    for(let i=2;i<n;i++) {
        a = a+b
        b = a-b
        a %= 1000000007
    }
    return a
};