剑指offer刷题笔记(二)

剑指offer刷题笔记(二)


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

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

F(0) = 0,   F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.

斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

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

思路:斐波那契数列是很经典的一道题目,通常在学校里是作为递归的一道经典题目来讲解的,但是斐波那契数列在递归中会产生大量的重复计算,
如在计算F(7)时需要计算F(6)F(5),在计算F(6)的时候就需要计算F(5)F(4),这时F(5)就被重复计算了,思考一下如何改善,我们可以从下往上进行计算,而非递归一般从上往下,在计算F(5)
的时候,我们就已经知道F(4)和F(3)的值,这样就可以剔除掉重复计算。

class Solution {
    public int fib(int n) {
        if(n==0||n==1){
            return n;
        }
        long[] dp=new long[n+1];
        dp[0]=0;
        dp[1]=1;
        for(int i=2;i<=n;i++){
            dp[i]=dp[i-1]+dp[i-2];
            dp[i]=dp[i]%1000000007;
        }
        return (int)dp[n];
    }
}


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

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

输入:n = 2
输出:2
输入:n = 7
输出:21
0 <= n <= 100

思路 这时一道变形的斐波那契数列问题,假设青蛙跳2级台阶,他可以跳上一级,也可以二级,如果跳三级台阶,那么就出现了两种可能,一是跳上一级,然后再选择跳一级和二级,二是跳上二级再跳一级
相当于F(3)=F(2)+F(1)
基于这个思路和上一题的解法,我们可以得出这一题的解法。

class Solution {
    public int numWays(int n) {
        if(n==0||n==1){
            return 1;
        }
        long[] dp=new long[n+1];
        dp[0]=1;
        dp[1]=1;
        for(int i=2;i<=n;i++){
            dp[i]=dp[i-1]+dp[i-2];
            dp[i]=dp[i]%1000000007;
        }
        return (int)dp[n];
    }
}


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

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。

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

思路:观察这个数组,我们就可以发现,这个数组中是半有序的,因此我们可以采取二分查找的办法。

class Solution {
    public int minArray(int[] numbers) {
        if(numbers.length==0){
            return -1;
        }
        int left=0;
        int right=numbers.length-1;
        if(numbers[left]numbers[right]){
                left=mid+1;
            }
            else if(numbers[mid]==numbers[right]){
                --right;
            }
            else{
                right=mid;
            }
        }
        return numbers[right];
    }
}



面试题12. 矩阵中的路径

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[["a","b","c","e"],
["s","f","c","s"],
["a","d","e","e"]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。

输入:board =
[["A","B","C","E"], 
["S","F","C","S"],
["A","D","E","E"]],
word = "ABCCED"
输出:true

思路
这题可以考虑使用回溯法,注意一下对边界值的判断,同时使用了一个辅助数组来存储对矩阵的访问状态,如搜索ABCCED,访问矩阵[0][0],符合要求,置辅助数组[0]为ture,代表已访问,然后访问[1][0]不符合,退回[0][0],继续[0][1]符合,依次类推。

class Solution {
    public boolean exist(char[][] board, String word) {
        if(board.length==0){
            return false;
        }

        int rows=board.length;
        int cols=board[0].length;
        int pathLength=0;
        if(word.length()>rows*cols){
            return false;
        }
        boolean[] visted=new boolean[rows*cols];
        for(int row=0;rowword.length()-1){
            return true;
        }
        if(word.charAt(pathLength)=='\0'){
            return true;
        }
        if(row>=rows||col>=cols){
            return  false;
        }
        if(row>=0&&row=0&&col


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

地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?

输入:m = 2, n = 3, k = 1
输出:3
输入:m = 3, n = 1, k = 0
输出:1
  • 1 <= n,m <= 100
  • 0 <= k <= 20

思路
这题与上一题的矩阵有相似之处,都是通过回溯法来解决。

class Solution {
    public int movingCount(int m, int n, int k) {
        if(m<=0||n<=0||k<0){
            return -1;
        }
        boolean[] visted=new boolean[m*n];
        int count=0;
        count=countNumbers(m,n,k,0,0,visted);
        return count;
    }
    public int countNumbers(int rows,int cols,int k,int row,int col,boolean[] visted){
        if(col<0||col>=cols||row<0||row>=rows||visted[row*cols+col]){
            return 0;
        }
        int count=0;
        if(row>=0&&row=0&&col0){
            sum+=numbers%10;
            numbers/=10;
        }
        return sum;
    }
}


面试题14- I. 剪绳子

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m] 。请问 k[0]k[1]...*k[m] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36

  • 2 <= n <= 58
    思路:
    动态规划,这个问题的目标是求剪出的各段绳子乘积的最大值,如我们可以将长度为4的绳子分割为3和1,也可以是2和2,这时一个大问题就被我们分解成了两个子问题,2或者3和1的在分割,那么我们就将一个大问题分解为了两个子问题的最优解,为了避免重复求解子问题,我们就从下往上,从解决最小问题开始。
class Solution {
    public int cuttingRope(int n) {
        if(n<2){
            return 0;
        }
        if(n==2){
            return 1;
        }
        if(n==3){
            return 2;
        }
        int[] length=new int[n+1];
        length[0]=0;
        length[1]=1;
        length[2]=2;
        length[3]=3;
        int max=0;
        for(int i=4;i<=n;i++){
            max=0;
            for(int j=1;j<=i/2;j++){
                max=max>length[j]*length[i-j]?max:length[j]*length[i-j];
                length[i]=max;
            }
        }
        max=length[n];
        return max;
    }
}

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

限制:
2 <= n <= 1000

这一题我们可以看到n的范围明显变大了,因此牵涉到了两个问题,一个是结果的存储,一个是耗时的变大。
结果的存储我们可以考虑用long来存储,并时时将其取模运算,而耗时,我们可以考虑一下,在动态规划的过程中,若是我们每一步都取最优结果,那么组合起来会是一个最优结果吗,这就是贪心法。
我们可以从数学角度上进行证明,在n>=5时,我们要尽量多取长度为3的绳子,3(n-3)>n,显而易见,同时,剩余长度只有4米时,我们要选择将4米绳子分为2米和2米,2*2>1*3,同时在n>=5时,3(n-3)>2(n-2)。

class Solution {
    public int cuttingRope(int n) {
        if (n < 2) {
            return 0;
        }
        if (n == 2) {
            return 1;
        }
        if (n == 3) {
            return 2;
        }
        int length3 = n / 3;
        if (n % 3 == 1) {
            length3--;
        }
        long sum = 1;
        int length2 = (n - length3 * 3) / 2;
        for (int i = 0; i < length3; i++) {
            sum *= 3;
            sum %= 1000000007;
        }
        for (int i = 0; i < length2;i++){
            sum *= 2;
            sum %= 1000000007;
        }
        return (int)sum;
    }
}


面试题15. 二进制中1的个数

请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。

输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。
输入:00000000000000000000000010000000
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。

思路:我们将输入字符与1进行与运算,就可以得到输入字符二进制的最右边的字符是否是1,同时,我们将1左移1位,这样就可以得到输入字符倒数第二位,依次类推,进行32次。

public class Solution {
    // you need to treat n as an unsigned value
    public int hammingWeight(int n) {
        int flag=1;
        int count=0;
        while(flag!=0){
            if((flag&n)!=0){
                count++;
            }
            flag=flag<<1;
        }
        return count;
    }
}

另一种思路
我们可不可以不进行32次判断,而是输入字符中有多少个1就进行多少次判断呢?我们观察一下9,将其转为2进制为1001,我们将9减去1,得到的二进制就是1000,与原数字进行与操作,还是1000,那么当最后一位不是1呢?10,二进制,1010,我们对其减1,得到1001,再与原数字进行与操作,得到1000,那么一个整数的二进制表示中有多少个1,我们就可以进行多少次操作。

public class Solution {
    // you need to treat n as an unsigned value
    public int hammingWeight(int n) {
        int count=0;
        while(n!=0){
            n=(n-1)&n;
            count++;
        }
        return count;
    }
}

你可能感兴趣的:(剑指offer刷题笔记(二))