剑指 offer——贪心、动态规划篇

10-I. 斐波拉契数列

题意:面试题10- I. 斐波那契数列
思路:最基础的动态规划题。数据量比较大的时候不能使用递归,会报StackOverFlow Exception,最优的方式是迭代计算。

class Solution {
    public int fib(int n) {
        if (n <= 1) {
            return n;
        }
        int a = 0;
        int b = 1;
        int index = 2;
        int tmp;
        while (index <= n) {
            tmp = (a + b) % 1000000007;
            a = b;
            b = tmp;
            index ++;
        }
        return b;
    }
}

10-II. 青蛙跳台阶

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

思路:同斐波拉契数列问题,最基础的动态规划问题。

class Solution {
    public int numWays(int n) {
        if (n == 0 || n == 1) {
            return 1;
        }
        int a = 1;
        int b = 1;
        int c = 0;
        int i = 2;
        while (i <= n) {
            c = (a + b) % 1000000007;
            a = b;
            b = c;
            i++;
        }
        return c;
    }
}

14-I. 剪绳子

题意:面试题14- I. 剪绳子
思路1:dp[i]表示长度为i的绳子,能够得到的最大乘积数。dp[i+1]的计算方式为,每次可以剪掉1~i的长度j,其余的绳子可以剪(即dp[i+1-j]),也可以不剪(即i+1-j),即
dp[i+1] = Math.max(dp[i+1-j] * j, (i+1-j) * j), 其中j \(\in\) [1, i]

class Solution {
    public int cuttingRope(int n) {
        if (n == 2) {
            return 1;
        }
        int[] dp = new int[n + 1];
        dp[2] = 1;
        for (int i = 3; i < n + 1; i ++) {
            for (int j = 1; j < i;j ++) {
                dp[i] = Math.max(dp[i-j] * j, (i-j)*j);
            }
        }
        return dp[n];
    }
}

思路2:将绳子长度先尽可能的划分为3的倍数,如果最后剩余1,则拿出一个已经分配的3来凑成4。
例如:5 = 3 + 2
6 = 3 + 3 -> 3 * 3 = 9
7 = 3 + 3 + 1 = 3 + 4 -> 3 * 4 = 12
……

class Solution {
    public int cuttingRope(int n) {
        if (n <= 3) {
            return n - 1;
        }
        int remind = n;
        int multi = 1;
        while (remind > 4) {
            multi = multi * 3;
            remind -= 3;
        }
        return multi * remind;
    }
}

14-II. 剪绳子 II

题意:试题14- II. 剪绳子 II
思路:同剪绳子I

class Solution {
    public int cuttingRope(int n) {
        if (n <= 3) {
            return n - 1;
        }
        int remind = n;
        long multi = 1;
        while (remind > 4) {
            multi = (multi * 3) % 1000000007;
            remind -= 3;
        }
        return (int)((multi * remind) % 1000000007);
    }
}

19. 正则表达式匹配

题意:面试题19. 正则表达式匹配
思路:用dp[i][j]表示s字符串前i个字符串是否与p的前j个字符串匹配,那么所要求的结果就是dp[s.length()][p.length()]的值。
递推式:对于某一个位置dp[i][j],是否匹配可以分以下两种情况:
(1)当s[i] == p[j]时,如果s[0...i-1]与p[0...j-1]匹配,那么s[0...i]与p[0...j]匹配,反之不匹配。即dp[i][j] = dp[i-1][j-1];
(2)当s[i] != p[j]时。如果p[j] != ‘*’,那么s[0...i]与p[0...j]一定不匹配。如果p[j] == '*',代表p[j-1]位置的字符可以出现0次或多次
(a)当p[j-1]位置的字符不出现时,判断s[0...i]与p[0...j-2]是否匹配即可,即dp[i][j] = dp[i][j-2]
(b)当p[j-1]位置的字符出现时,需要判断s[i]位置上的字符是否与p[j-1] (注意可以是'.')相同,相同则dp[i][j] = dp[i-1][j]

class Solution {
    public boolean isMatch(String s, String p) {
        int sLen = s.length();
        int pLen = p.length();
        char[] sArr = s.toCharArray();
        char[] pArr = p.toCharArray();
        boolean[][] dp = new boolean[sLen + 1][pLen + 1];
        dp[0][0] = true;
        for (int i = 0; i < sLen + 1; i ++) {
            for (int j = 1; j < pLen + 1; j ++) {
                if (i > 0 && (sArr[i - 1] == pArr[j - 1] || pArr[j - 1] == '.')) {
                    dp[i][j] = dp[i - 1][j - 1];
                } else if (pArr[j - 1] == '*'){
                    if (i > 0 && j > 1
                    && (sArr[i - 1] == pArr[j - 2] || pArr[j - 2] == '.')) {
                        dp[i][j] |= dp[i - 1][j];
                    }
                    if (j > 1){
                        dp[i][j] |= dp[i][j - 2];
                    }
                }
            }
        }
        return dp[sLen][pLen];
    }
}

42. 连续子数组的最大和

题意:面试题42. 连续子数组的最大和
思路:贪心。状态转移方程式为sum[i] = max(sum[i-1] + nums[i], nums[i]),其中sum[i]表示元素nums[0...i]的和。

class Solution {
    public int maxSubArray(int[] nums) {
        int max = Integer.MIN_VALUE;
        int sum = 0;
        for (int i = 0; i < nums.length; i ++) {
            sum = Math.max(sum + nums[i], nums[i]);
            max = Math.max(max, sum);
        }
        return max;
    }
}

47. 礼物的最大价值

题意:面试题47. 礼物的最大价值
思路:动态规划。递推公式dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + grid[i][j],优化为一维后,第i行礼物最大价值:dp[j] = max(dp[j-1], dp[j]) + grid[i][j]。

class Solution {
    public int maxValue(int[][] grid) {
        int[] dp = new int[grid[0].length];
        dp[0] = grid[0][0];
        for (int j = 1; j < dp.length; j ++) {
            dp[j] = dp[j - 1] + grid[0][j];
        }
        for (int i = 1; i < grid.length; i ++) {
            for (int j = 0; j < grid[0].length; j ++) {
                if (j == 0) {
                    dp[j] += grid[i][j];
                } else {
                    dp[j] = Math.max(dp[j - 1], dp[j]) + grid[i][j];
                }
            }
        }
        return dp[grid[0].length - 1];
    }
}

49. 丑数

题意:面试题49. 丑数
思路:动态规划。丑数是2,3,5的倍数,下一个丑数一定是前面某一个数乘以2或3或5得到的最小数字。使用三个指针two、three、five分别指向2、3、5的倍数,下一个更大的数将由这三个指针指向的数字分别乘2、乘3、乘5取最小值得到。取得最小值之后指针向前走一步。

class Solution {
    public int nthUglyNumber(int n) {
        int two = 0;
        int three = 0;
        int five = 0;
        int[] dp = new int[n];
        dp[0] = 1;
        for (int i = 1; i < n; i ++) {
            dp[i] = Math.min(Math.min(dp[two] * 2, dp[three] * 3), dp[five] * 5);
            if (dp[i] == dp[two] * 2) {
                two ++;
            }
            if (dp[i] == dp[three] * 3) {
                three ++;
            }
            if (dp[i] == dp[five] * 5) {
                five ++;
            }
        }
        return dp[n - 1];
    }
}

60. n个骰子的点数

题意:面试题60. n个骰子的点数
思路:用freq[i][j]表示i个骰子,投出点数为j的次数。那么递推式为:
freq[i][j] = freq[i-1][j-1] + freq[i-1][j-2] + …… + freq[i-1][j-k](其中k<=6 且j-k>0,因为一个骰子只能投出1~6点)

class Solution {
    public double[] twoSum(int n) {
        int[][] freq = new int[n + 1][n * 6 + 1];
        for (int i = 1; i <= 6; i ++) {
            freq[1][i] = 1;
        }
        int k;
        for (int i = 2; i <= n; i ++) {
            for (int j = i; j <= n * 6; j ++) {
                k = 1;
                while (k <= 6 && j - k > 0) {
                    freq[i][j] += freq[i-1][j - k];
                    k ++;
                }
            }
        }
        double sum = Math.pow(6, n);
        double[] res = new double[5 * n + 1];
        for (int i = 0; i < res.length; i ++) {
            res[i] = freq[n][n + i] / sum;
        }
        return res;
    }
}

63. 股票的最大利润

题意:面试题63. 股票的最大利润
思路:动态规划。遍历数组的过程中,记录数组中最小的数字作为股票买入的价格。当前遍历到的数字减去最小的数字就是最大的利润。

class Solution {
    public int maxProfit(int[] prices) {
        int max = 0;
        int min = Integer.MAX_VALUE;
        for (int i = 0; i < prices.length; i ++) {
            min = Math.min(min, prices[i]);
            max = Math.max(max, prices[i] - min);
        }
        return max;
    }
}
posted @ 2020-07-19 19:57  MoonLeo2017  阅读(193)  评论(0编辑  收藏  举报