刷剑指offer 2

143 阅读4分钟

1.剑指 Offer 11. 旋转数组的最小数字

输入: numbers = [3,4,5,1,2]
输出: 1
输入: numbers = [2,2,2,0,1]
输出: 0

可直接遍历一遍输出其中最小的数字,复杂度是O(n),考虑优化,查找有序数组,用二分查找。思考旋转数组的性质,最小值左边的元素一定是最大值,并且最小值右边的元素一定都小于等于最小值左边的元素。二分是把低位元素设为low,高位为heigh,中间值为mid。 image.png 这样就有三种情况,mid是最小值,mid在最小值右边,mid在最小值左边。逐个分析,如果number[heigh]>number[mid],就说明最小值一定在mid左边或者就是mid,而如果number[heigh]<number[mid],就说明最小值一定在mid的右边,到这可以写出一部分代码:

var minArray = function(numbers) {
    let low = 0
    let heigh = numbers.length
    while(low <=heigh) {
        let mid = Math.floor((heigh-low)/2) + low
        if(numbers[mid] < numbers[heigh]) {
            heigh = mid
        } else if(numbers[mid] > numbers[heigh]) {
            low = mid+1
        }
    }
};

那么还有一种情况,number[heigh]===number[mid]的时候,目标值在mid左边或者右边都有可能

image.png 这时候,就需要将heigh一步一步移动去探测具体目标值在哪,当右指针到了左指针左边,就代表搜索完了,此时numbers[low]就是我们要的位置。

var minArray = function(numbers) {
    let low = 0
    let heigh = numbers.length
    while(low <=heigh) {
        mid = Math.floor((heigh-low)/2) + low
        if(numbers[mid] < numbers[heigh]) {
            heigh = mid
        } else if(numbers[mid] > numbers[heigh]) {
            low = mid+1
        } else {
            heigh -= 1
        }
    }
    return numbers[low]
};

2.剑指 Offer 12. 矩阵中的路径

遇到图搜索,目前已知的就是DFS和BFS。这题的基本思路就是dfs和回溯,每次向外一层搜索,如果遇到对的就继续,如果不对就回溯回来,向下一个方向继续搜索,同时为了优化性能还需要标记走过且不匹配的路径。 首先先按照dfs模板把深度优先搜索给写了,第一步,建立visited表,建立遍历方向表

var exist = function(board, word) {
    const m = board.length
    const n = board[0].length
    const visited = new Array(m).fill(0).map(()=>new Array(n).fill(false))
    const dir = [[0,1],[1,0],[0,-1],[-1,0]]
};

第二步,根据dfs模板,建立dfs函数对图进行搜索,如果遇到匹配的,就继续搜索,否则就回溯,并把visited设置回false。

var exist = function(board, word) {
    const m = board.length
    const n = board[0].length
    const visited = new Array(m).fill(0).map(()=>new Array(n).fill(false))
    const dir = [[0,1],[1,0],[0,-1],[-1,0]]
    const dfs = (x,y,k)=> {
        if(board[x][y] != word[k]) return false
        else if(k === word.length-1) return true
        visited[x][y] = true
        let result = false
        for(const [dx,dy] of dir) {
            let i = x + dx
            let j = y + dy
            if(i>=0 && i<m && j>=0 && j<n) {
                if(!visited[i][j]) {
                    const flag = dfs(i,j,k+1)
                    if(flag) {
                        result = true
                        break
                    }
                }
            }
        }
        visited[x][y] = false
        return result
    }
};

最后运行主函数用两个for循环遍历整张表

var exist = function(board, word) {
    const m = board.length
    const n = board[0].length
    const visited = new Array(m).fill(0).map(()=>new Array(n).fill(false))
    const dir = [[0,1],[1,0],[0,-1],[-1,0]]
    const dfs = (x,y,k)=> {
        if(board[x][y] != word[k]) return false
        else if(k === word.length-1) return true
        visited[x][y] = true
        let result = false
        for(const [dx,dy] of dir) {
            let i = x + dx
            let j = y + dy
            if(i>=0 && i<m && j>=0 && j<n) {
                if(!visited[i][j]) {
                    const flag = dfs(i,j,k+1)
                    if(flag) {
                        result = true
                        break
                    }
                }
            }
        }
        visited[x][y] = false
        return result
    }
    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            const flag = dfs(i, j, 0);
            if (flag) {
                return true;
            }
        }
    }
    return false;

};

3.剑指 Offer 13. 机器人的运动范围

也是一道dfs搜索题目

var movingCount = function(m, n, k) {
    const visited = Array(m).fill(0).map(() => Array(n).fill(false))
    const digitSum = (n) => {
        let ans = 0
        while(n) {
            ans += n % 10
            n = Math.floor(n/10)
        }
        return ans
    }
    const dfs = (x,y) => {
        if(digitSum(x) + digitSum(y) > k || x >= m || y >= n || visited[x][y]) return 0
        visited[x][y] = true
        return 1 + dfs(x+1,y) + dfs(x,y+1)
    }    
   return dfs(0,0) 
};

4.剑指 Offer 14- I. 剪绳子

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
(2 <= n <= 58)

是一个数学问题,利用“均值不等式”可知尽可能切成长度为3的段是最优解,次优2,最差1(然而真正做题的时候估计只能靠例子来猜到底几段好),那么斗胆将这道题简化为,如何把一段绳子尽可能分成长度3,2,1,3最优,1最次,利用贪心来做。

var cuttingRope = function(n) {
    if(n<=3) return n-1
    let a = Math.floor(n/3)
    let b = n%3
    if(b === 0)return Math.pow(3,a)//3的倍数,直接返回题目要求的乘积,同时可3先3
    if(b === 1) return Math.pow(3, a-1) * 4//先拆3的倍数,剩1个,将3+1替换为2+2,因为4>3
    return Math.pow(3,a)*2//有3先3,剩2就直接乘进去了
};

5.剑指 Offer 14- II. 剪绳子 II

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
(2 <= n <= 1000)

这道题的不同在于存在大数越界问题。

评论区有人贴了一个用bigInt的方法,值得学习,这是我第一次在实际做题中看到bigInt的用处

var cuttingRope = function (n) {
    if (n <= 3) {
        return n - 1;
    }
    const MOD = BigInt(1000000007);
    const a = BigInt(Math.floor(n / 3));
    const b = BigInt(n % 3);
    const get = ((x, a, mod) => {
        let res = 1n;
        while (a > 0n) {
            // console.log(x, a, res, mod)
            if (a % 2n === 1n) {
                res = (res * x) % mod;
            }
            x = (x * x) % mod;
            a = a % 2n === 1n ? (a - 1n) / 2n : a / 2n;
        }
        return res;
    })
    if (b === 0n) {
        return get(3n, a, MOD);
    } else if (b === 1n) {
        return (get(3n, a - 1n, MOD) * 4n) % MOD;
    } else if (b === 2n) {
        return (get(3n, a, MOD) * 2n) % MOD;
    }
};

对待大数可以通过每步取余来做,如果只最后一步取余的话,可能会在中间出现超过范围的问题

var cuttingRope = function(n) {
    if(n === 2) return 1
    if(n === 3) return 2
    let res = 1
    while(n>4) {
        res *= 3
        res %= 1000000007
        n-=3
    }
    return res*n%1000000007
};

6.剑指 Offer 15. 二进制中1的个数

这是一道位运算问题,可以用循环检查二进制位的方法,让n与 2^i进行与运算,只有第i位为1时结果不为0,于是可以判断其中1的个数

var hammingWeight = function(n) {
    let res = 0;
    for (let i = 0; i < 32; i++) {
        if ((n & (1 << i)) !== 0) {
            res++;
        }
    }
    return res;
};

7.剑指 Offer 16. 数值的整数次方

输入: x = 2.00000, n = 10
输出: 1024.00000
输入: x = 2.10000, n = 3
输出: 9.26100
输入: x = 2.00000, n = -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25

不用库函数,实现Math.pow() 使用快速幂方法,什么是快速幂呢?比如我们计算2^20,没必要从从1次方算20次到20次方,可以1-2-4-8-16-20次方的过程,计算快速幂,其实本质是把数字降幂的过程。

var myPow = function(x, n) {
    if (n == 0) return 1;
        if (n == -1) return 1.0 / x;
        //奇数
        if (n & 1) return x * myPow(x*x, n>>1); //右移代替除以2,位与代替求余运算来判断奇偶
        //偶数
        else return myPow(x*x, n>>1)
}