算法 | Leetcode 剑指offer Javascript 刷题

Leetcode终于上线剑指offer的题目了 记下刷题记录

面试题03. 数组中重复的数字

简单题
空间复杂度:O(N)
时间复杂度:O(N)

var findRepeatNumber = function(nums) {
    const arr = new Array(nums.length)
    for(let n of nums) {
        if(arr[n]) {
            return n
        }
        arr[n] = true
    }
    return false
};

面试题04. 二维数组中的查找

简单题
空间复杂度:O(1) 辅助数据也就是坐标i,j而已。
时间复杂度:O(N+M) 最复杂的情况就是从上到下,从右到左。

const findNumberIn2DArray = function(matrix, target) {
    // 左到右递增
    // 上到下递增
    // 从左上角开始,item > target j--, item < target i++
    const n = matrix.length
    if(n === 0) return false
    const m = matrix[0].length
    if(m === 0) return false
    if(target < matrix[0][0]) return false
    if(target > matrix[n-1][m-1]) return false
    let i = 0, j = m-1
    while(i=0 && j=0) {
        if(matrix[i][j] === target) {
            return true
        }
        if(matrix[i][j] > target) {
            j--
            continue
        }
        if(matrix[i][j] < target) {
            i++
            continue
        }
    }
    return false
};

面试题05. 替换空格

简单题
时间复杂度:O(N)
空间复杂度:O(N)

// 先复习下字符串操作API吧
// replace实现
const replaceSpace = function(s) {
    return s.replace(/ /g, '%20')
};
// split + join
const replaceSpace = function(s) {
    return s.split(' ').join('%20')
};
// 数组 + join做法
const replaceSpace = function(s) {
    const arr = []
    for(const c of s) {
        c === ' ' ? arr.push('%20') : arr.push(c)
    }
    return arr.join('')
};

面试题06. 从尾到头打印链表

简单题
递归法和循环压栈逆序输出法,都是O(N)

const reversePrint = function(head) {
    // 用递归法
    const res = []
    const reverse = (node) => {
        if(!node) {
            return
        } else {
            reverse(node.next)
            res.push(node.val)
        }
    }
    reverse(head)
    return res
};
const reversePrint = function(head) {
    // 用循环法
    const res = []
    while(head) {
        res.push(head.val)
        head = head.next
    }
    return res.reverse()
};

面试题07. 重建二叉树

中等题
时间复杂度O(N)
空间复杂度O(N) 函数调用栈空间浪费。

var buildTree = function(preorder, inorder) {
    // 重建二叉树
    // 前序遍历:先中后左最后右,因此第一个是中节点
    // 中序遍历:先左后中最后右,因此第一个是左树
    const n = preorder.length
    if(n === 0) return null
    const build = (ps, pe, is, ie) => {
        const mid = preorder[ps]
        const midNode = new TreeNode(mid)
        if(ps === pe || is === ie) {
            return midNode
        }
        // im是中节点的inorder坐标,它同时代表:左子树的长度=im-is,右子树的长度是ie-im
        let im
        for(let i=is;i<=ie;i++) {
            if(inorder[i] === mid) {
                im = i
                break
            }
        }
        let leftLen = im-is, rightLen = ie-im
        // 1,1,0,0
        midNode.left = leftLen > 0 ? build(ps+1, ps+1+leftLen-1, is, is+leftLen-1) : null
        midNode.right = rightLen > 0 ? build(ps+1+leftLen, ps+1+leftLen+rightLen-1, im+1, im+1+rightLen-1) : null
        return midNode
    }
    return build(0, n-1, 0, n-1)
};

翻了一下过去的提交,发现自己又踩坑了,居然没记住这个结果:就是构建树的过程本来就是一个先序遍历,所以每次只要考虑inorderIndex就行了。

var buildTree = function(preorder, inorder) {
    /**
     * 其实 这个build的过程本身就是一个先序遍历,因此只需要设置一个全局自增的Index就能走先序遍历的过程了
     * 所以 目前需要的下标就是中序遍历的left和right部分
     */
    let index = 0
    const inorderMap = new Map()
    for(let i=0;i {
        if(inLeft === inRight) {
            return null
        }
        const rootVal = preorder[index]
        const root = new TreeNode(rootVal)
        const inorderIndex = inorderMap.get(rootVal)
        index++
        root.left = recBuild(inLeft, inorderIndex)
        root.right = recBuild(inorderIndex+1, inRight)
        return root
    }
    return recBuild(0, inorder.length)
};

面试题09. 用两个栈实现队列

简单题

var CQueue = function() {
    this.s1 = []
    this.s2 = []
};

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

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

面试题10- I. 斐波那契数列

简单题

var fib = function(n) {
    const cache = {
        0: 0n,
        1: 1n
    };
    return Fibonacci(n) % 1000000007n;

    /**
     * @param {number} n
     * @return {number}
     */
    function Fibonacci(n) {
        if (cache[n] !== undefined) {
            return cache[n];
        }

        cache[n] = Fibonacci(n - 1) + Fibonacci(n - 2);
        return cache[n];
    }
};

面试题10- II. 青蛙跳台阶问题

同上

面试题11. 旋转数组的最小数字

简单题
简单题的简单思路

var minArray = function(numbers) {
    let i
    for(i=numbers.length-1;i>0;i--) {
        if(numbers[i]

但是这道题在leetcode原本的题库是困难题,因为它可以花样百出。

var minArray = function(numbers) {
    let left = 0, right = numbers.length-1
    while(left < right) {
        let mid = Math.floor((left+right)/2)
        // mid一定在数组的右边
        if(numbers[mid]>numbers[right]) {
            left = mid+1
        // mid一定在数组的左边
        } else if (numbers[mid]

面试题12. 矩阵中的路径

中等题
极速地写了一个,没有优化过,但是思路很明显,就是dfs。

const exist = function(board, word) {
    const n = board.length
    const m = board[0].length
    const len = word.length
    // 如果字母a,上下左右都有下一个字母,那就得全部都试一试啦,有一个达成和word一样就立马推出。
    // travel 目前的位置i,j和目前走到word第几个w
    const travel = (i,j,w, visit) => {
        if(w===len-1) {
            return true
        }
        // 看看左边?
        if(j-1>=0 && board[i][j-1]===word[w+1]) {
            let pos = `${i}-${j-1}`
            if(!visit[pos]) {
                if(travel(i,j-1,w+1,{[pos]:true,...visit})) return true
            }
        }
        // 看看右边?
        if(j+1=0 && board[i-1][j]===word[w+1]) {
            let pos = `${i-1}-${j}`
            if(!visit[pos]) {
                if(travel(i-1,j,w+1,{[pos]:true,...visit})) return true
            }
        }
        // 看看下边?
        if(i+1

面试题13. 机器人的运动范围

中等题
Leetcode题解里的BFS,比我使用DFS复杂度低了。
空间复杂度O(N),时间复杂度O(N)

var movingCount = function(m, n, k) {
    let res = 0;
    const directions = [
        [1, 0],
        [0, 1]
    ];
    const queue = [[0, 0]];
    const visited = {
        "0-0": true
    }; // 标记 (x,y) 是否被访问过

    while (queue.length) {
        const [x, y] = queue.shift();
        //  (x, y) 的数位之和不符合要求
        // 题目要求节点每次只能走1格,所以无法从当前坐标继续出发
        if (bitSum(x) + bitSum(y) > k) {
            continue;
        }
        ++res;

        for (const direction of directions) {
            const newx = direction[0] + x;
            const newy = direction[1] + y;
            if (
                !visited[`${newx}-${newy}`] &&
                newx >= 0 &&
                newy >= 0 &&
                newx < m &&
                newy < n
            ) {
                queue.push([newx, newy]);
                visited[`${newx}-${newy}`] = true;
            }
        }
    }

    return res;
};

function bitSum (n) {
    let res = 0
    while(n) {
        res += n%10
        n = Math.floor(n/10)
    }
    return res
}

面试题16. 数值的整数次方

中等题
先写个普通循环版本,果不其然超时了。

var myPow = function(x, n) {
    let res = 1
    if(n >= 0) {
        while(n--) {
            res *= x
        }
    } else {
        while( (n++)!=0 ) {
            res /= x
        }
    }
    return res
};

想了下可以用递归来实现二分思想。
如果指数是偶数的情况下:Pow(base,exponent) = Pow(base,exponent/2)*Pow(base,exponent)
指数是奇数的情况下:Pow(base,exponent) = base * Pow(base,exponent/2) * Pow(base,exponent/2)
再区分下奇数偶数的情况,就能成了:

var myPow = function(x, n) {
    const pow = (x,n) => {
        if(n===0) {
            return 1
        }
        if(n===1) {
            return x
        }
        const exp = Math.floor(n/2)
        return n % 2 === 1 ? x*pow(x,exp)*pow(x,exp):pow(x,exp)*pow(x,exp)
    }
    return n >= 0 ? pow(x,n) : 1/pow(x,-n)
};

突然想到之前学计算机安全导论的时候学过一个叫快速幂的算法,这里用来试试。

var myPow = function(x, n) {
    const pow = (x,n) => {
        let ans = 1
        while(n) {
            if(n & 1) {
                ans *= x
            }
            x *= x
            n = Math.floor(n/2)
        }
        return ans
    }
    return n >= 0 ? pow(x,n) : 1/pow(x,-n)
};

快速幂的思想是:


摘自百度百科

面试题17. 打印从1到最大的n位数

简单题
直接调用API辣,其实也可以用上面的快速幂计算10的n次幂

var printNumbers = function(n) {
    const res = []
    const max = 10 ** n - 1
    for(let i=1;i<=max;i++) {
        res.push(i)
    }
    return res
};

面试题18. 删除链表的节点

var deleteNode = function(head, val) {
    if(head.val === val) {
        head = head.next
        return head
    }
    let node = head
    while(node.next) {
        if(node.next.val === val) {
            node.next = node.next.next
            break
        }
        node = node.next
    }
    return head
};

面试题19. 正则表达式匹配

简单题
回溯法

var isMatch = function(s, p) {
    const _isMatch = (i, j) => {
        if(i === s.length && j===p.length) {
            return true
        }
        if(i !== s.length && j === p.length) {
            return false
        }
        if(p[j+1] === '*') {
            if(s[i]===p[j] || (p[j]==='.' && i !== s.length)) {
                return _isMatch(i+1,j) || _isMatch(i,j+2)
            } else {
                return _isMatch(i,j+2)
            }
        }
        if (s[i]===p[j]  || (p[j]==='.' && i !== s.length)){
            return _isMatch(i+1, j+1)
        } else {
            return false
        }
    }
    return _isMatch(0,0)
};

322. 零钱兑换

回溯法剪枝依然超时,所以应该用动态规划

/**
 * @param {number[]} coins
 * @param {number} amount
 * @return {number}
 */
var coinChange = function(coins, amount) {
    /**
     * 最优化问题
     * 可以用回溯法和动态规划法解决
     * 优化目标:硬币数量最少
     * 先用回溯法莽一波
     * 动态规划也可以减少解空间
     */
    /**
     * rest: 剩余金额
     * count: 使用硬币数
     */
    let res = Infinity
    const sub = (rest, count) => {
        // 剪枝
        if(count > res) {
            return
        }

        if(rest === 0) {
            res = Math.min(count, res)
            return
        } else if(rest < Math.min(coins)) {
            return
        }

        // 剩余情况
        for(const coin of coins) {
            sub(rest-coin, count+1)
        }
    }
    sub(amount, 0)
    return res === Infinity ? -1 : res
};
var coinChange = function(coins, amount) {
    /**
     * 最优化问题
     * 可以用回溯法和动态规划法解决
     * 优化目标:硬币数量最少
     * 先用回溯法莽一波
     * 动态规划也可以减少解空间
     */
    // dp是此金额花费的硬币数
    const dp = Array(amount+1).fill(Infinity)
    dp[0] = 0
    for(let i=1;i<=amount;i++) {
        for(let coin of coins) {
            if(i-coin>=0) {
                dp[i] = Math.min(dp[i], dp[i-coin]+1)
            }
        }
    }
    return dp[amount] === Infinity ? -1 : dp[amount]
};

面试题37. 序列化二叉树

var serialize = function(root) {
    // 用层次遍历来读入
    if(!root) return '[]'
    let res = '['
    const queue = [root]
    while(queue.length) {
        const t = queue.shift()
        if(t === null) {
            res+='null,'
        }else {
            res+=`${t.val},`
        }
        // null节点不会再加入它们的子节点而导致出错
        if(!t) continue
        queue.push(t.left)
        queue.push(t.right)
    }
    return res.slice(0, res.length-1) + ']'
};

/**
 * Decodes your encoded data to tree.
 *
 * @param {string} data
 * @return {TreeNode}
 */
var deserialize = function(data) {
    if(data === '[]' || !data) return null
    data = data.slice(1,data.length-1).split(',')
    let index = 0
    const head = getTreeNode(data[index++])
    const queue = [head]
    while(queue.length) {
        let node = queue.shift()
        node.left = getTreeNode(data[index++])
        node.right = getTreeNode(data[index++])
        node.left && queue.push(node.left)
        node.right && queue.push(node.right)
    }
    return head
};

const getTreeNode = (value) => {
    if(value === 'null') return null
    else return new TreeNode(Number(value))
}

/**
 * Your functions will be called as such:
 * deserialize(serialize(root));
 */

面试题38. 字符串的排列

/**
 * @param {string} s
 * @return {string[]}
 */
var permutation = function(s) {
    if (!s.length) {
        return [];
    }
    const result = [];
    _permutation(s.split(""), 0, result);
    return result
};

/**
 * 回溯遍历得到所有的结果
 *
 * @param {string[]} strs
 * @param {number} start
 * @param {string[]} result
 */
function _permutation(strs, start, result) {
    if (start === strs.length) {
        result.push(strs.join(""));
        return;
    }
    const map = {}
    for (let i = start; i < strs.length; ++i) {
        if (map[strs[i]]) {
            // 进行剪枝
            continue;
        }
        map[strs[i]] = true;
        [strs[i], strs[start]] = [strs[start], strs[i]];
        _permutation(strs, start + 1, result);
        [strs[i], strs[start]] = [strs[start], strs[i]];
    }
}

面试题39. 数组中出现次数超过一半的数字

var majorityElement = function(nums) {
    /**
     * 摩尔投票法
     */
    let count = 1
    let res = nums[0]
    for(let n of nums) {
        if(n !== res) {
            count--
            if(count === 0) {
                res = n
                count = 1
            }
        } else {
            count++
        }
    }
    return res
};

你可能感兴趣的:(算法 | Leetcode 剑指offer Javascript 刷题)