动态规划学习之LeetCode分割整数相关题目(第343、279、91题)

题目:给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。

示例:

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

说明: 你可以假设 不小于 2 且不大于 58。

分析:

  1.定义一个状态转移数组dp,dp[i]表示数字i拆分为至少两项之和后,拆分后的数字之间的最大乘积。

  2.寻找关系式,那么如何确定dp[i]的最大值呢?首先我们要确定它有哪些取值的可能,对于拆分之和的数字的乘积,这个乘积可能是两个数字的乘积,也可能是多个数字之间的乘积,那么如何表示这两种乘积呢?

    1)对于两个数的乘积,就是一个小于i的数j,和i-j之间的乘积,即 (i - j)* j;

    2)对于多个数之间的乘积,就是相当于一个小于i的数j,和dp[i-j]的乘积,即 j * dp[i-j] ;

  3.初始条件,根据题目,n从2开始,多以dp[2] = 1;

  4.需要借助于双层循环,外层循环从3开始,到n结束,因此状态转移数组的空间大小是n+1,内层循环是为了寻找当前i的最大拆分数字之积

首先的代码如下:

    public int integerBreak(int n) {
        if (n==2)
            return 1;
        int[] dp = new int[n+1];
        dp[2] = 1;

        for (int i = 3; i <= n ; i++) {
            for (int j = 1; j < i; j++) {
                dp[i] = Math.max(Math.max(dp[i],j*(i-j)),j*dp[i-j]);
            }
        }
        return dp[n];
    }

 

题目:给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

示例:

输入: n = 12
输出: 3 
解释: 12 = 4 + 4 + 4.

分析:这个题的解决思路和上面那个题的解决思路相似,具体流程如下:

  1.定义一个状态转移数组dp,dp[i]表示数字i由完全平方数组成的最少个数;

  2.寻找关系式,如果i本身就是一个完全平方数,那么这就是最理想的情况了,dp[i]的值直接为1;如果不是,那么就需要从小于1~i-1的数字中去寻找了,那么此时dp[i] = dp[j] + dp[i-j],即i拆分为j和i-j,二者的最少个数,就是i的最少个数。

  3.初始值,外层循环是从1开始遍历,到n结束,因此dp[1] = 1,内层循环用于寻找当前遍历的数字i的拥有完全平方数的最少个数,如果i不是完全平方数字,那么在i自己寻找之前,dp[i]设置为一个最大值。

实现代码:

    public int numSquares(int n) {
        if ((int) Math.sqrt(n) - Math.sqrt(n) == 0){
            return 1;
        }
        int[] dp = new int[n+1];
        dp[1] = 1;

        for (int i = 2; i <= n; i++) {
            if ((int) Math.sqrt(i) - Math.sqrt(i) == 0){
                dp[i] = 1;
            }else {
                dp[i] = Integer.MAX_VALUE;
                for (int j = 1; j < i; j++) {
                    dp[i] = Math.min(dp[i],dp[j] + dp[i-j]);
                }
            }
        }
        return dp[n];
    }

 

 

题目:一条包含字母 A-Z 的消息通过以下方式进行了编码:

'A' -> 1
'B' -> 2
...
'Z' -> 26

给定一个只包含数字的非空字符串,请计算解码方法的总数。

示例:

输入: "12"
输出: 2
解释: 它可以解码为 "AB"(1 2)或者 "L"(12)

分析:对于给定的一串数字,能够正确的解码的前提条件是,这个数字或者它和它前面的一个数字组成的两位数字位于1~26之间,否则都是不能被正确解码的,具体的过程如下:

  1.定义一个数组dp,dp[i]表示前i个字符构成的子串所拥有的解码方法的总数;

  2.寻找关系式:对于能够解码的情况有两种:

    一种是,对于当前遍历的这个字符,被用于单独解码,它只要大于'0',那么就能够被解码,在它被用来单独解码的时候,它的前i-1个字符构成的子串的解码方法总数是不变的,也就是相当于在前i-1个字符构成的子串末尾增加一个单独编码的字符,并不会影响整体编码,它们的解码方法总数一致;

    另一种是,对于当前遍历的这个字符,不是被用于单独编码的,它被用来和它的前一个字符组成一个二位数,如果这个二位数在10~26之间,对于这种的组合编码的情况下,前i个字符构成的子串含有的解码方法总数是和它的前i-2个字符构成的子串的解码方法总数是一致的,即在前i-2个字符构成的子串的末尾加上一个组合编码数,并不会影响前i-2字符的解码情况。

    这两个情况需要逐一判断,才不会漏掉所有的解码方法

  3.初始值,dp[0] = 1,dp[1] = 1,这里d[1]代表字符串的第一个字符,dp[0]没有实际的含义,只是充当一个数字1存在,此处设置dp[0]为1,纯粹是为了i=1时:

  第i个字符和第i-1个字符能够构成合法解码数字的时候,使解码方法总数加1。因为此时子串的总个数是2,比如226,子串22解码个数包括2、2和22。为22的时候,相对于dp[2]的值,应该是dp[1]的值再加上1,因为22也构成了一个解码方法,而dp[0]并没有对应的子字符串,所以用来当做一个1。

实现代码:

    public int numDecodings(String s) {
        if (s.charAt(0)=='0')
            return 0;
        if (s.length()==1&&s.charAt(0) > '0')
            return 1;
        int n = s.length();
        int[] dp = new int[s.length()+1];
        dp[0] = 1;
        dp[1] = 1;
        for (int i = 1; i < n; i++) {
            int two_nums = s.charAt(i) - '0' + (s.charAt(i-1) -'0')*10;
            if (s.charAt(i) > '0')
                dp[i+1] = dp[i];
            if (two_nums > 9 && two_nums <=26)
                dp[i+1] += dp[i-1];
            if (dp[i+1]==0)
                return 0;
        }
        return dp[n];
    }

一定要根据题目的要求找清楚数组元素之间的推导关系!!!

posted @ 2020-04-06 12:20  有心有梦  阅读(193)  评论(0编辑  收藏  举报