动态规划问题

  • 典型动态规划问题
    • 重叠子问题
    • 最优子结构
package dynamic_programming;

import javax.security.auth.login.AccountException;

/**
 * @program: Aglorithm
 * @Date: *****
 * @Author: Kyrie
 * @Description:
 *     以斐波那契数列作为引子,引入动态规划
 *          斐波那契数列:Fib(n) = Fib(n-1) + Fib(n-2); n > 2
 *                       Fib(n) = 1; 0 < n <= 2;
 *
 *           分析:
 *           常规的递归方式:
 *                如Fib(6) = Fib(5) + Fib(4);
 *                Fib(5) = Fib(4) + Fib(3); Fib(4) = Fib(3) + Fib(2);
 *                ....
 *                Fib(3) = Fib(2) + Fib(1);
 *                Fib(2) = 1;
 *                Fib(1) = 1;
 *                这种方式会重复计算子问题,比如Fib(3),一共计算了3次;Fib(2)重复计算了5次,效率低。
 *            动态规划:
 *              1.自顶向下的备忘录法
 *              2.自底向上的动态规划
 */
public class Dp01_Fib {

    //常规方式
    public static int Fib(int n){

        if(n <= 0)
            return 0;
        if(n <= 2)
            return 1;
        else
            return Fib(n-1) + Fib(n-2);
    }

    //改成动态规划
    public static int DpFib(int n){
        //用一个数组记录已经计算的值
        int[] values = new int[n+1];  //初始化为0

        return fib(n, values);
    }

    public static int fib(int n, int[] value){

        if(value[n] != 0)  //value[n]代表第n个数的值,若已经计算过,则直接返会
            return value[n];
        //否则,计算并记录下其值
        if(n <= 2)
            return 1;
        else
            return fib(n-1,value) + fib(n-2,value);
    }

     /*
     求解金矿问题
        动态规划--最优化
 		贪心算法--prim算法
     */

    //递归版本
    public static int cal(int[] p, int len){
        if(len == 0){
            return 0;
        }
        int q = Integer.MIN_VALUE;
        for (int i = 1; i <= len; i++) {
            q = Math.max(q, p[i-1] + cal(p, len-i));    //p[0]表示取了一段,p[n-1]表示取了一整段
        }
        return q;
    }

    //备忘录版本-记录算过的值,已经算过的直接返回

    public static int cal2(int[] p, int len){

        int[] memo = new int[len + 1];
        return cut(p, len, memo);
    }
    public static int cut(int[] p, int len, int[] memo){
        if(memo[len] != 0)
            return memo[len];
        int q = -1;
        if(len == 0){
            q = 0;
        }
        for (int i = 1; i <= len; i++) {
            q = Math.max(q, p[i-1]+cut(p, len-i, memo));  //%计算每次切割的最大收益
        }
        memo[len] = q;

        return q;
    }

    public static int cutMemo(int []p, int len)
    {
        int[] r = new int[len+1];
        for(int i = 0;i <= len; i++)
            r[i] = -1;
        return cut2(p, len, r);
    }
    public static int cut2(int[] p, int n, int[] r)
    {
        int q = -1;
        if(r[n] >= 0)
            return r[n];
        if(n == 0)
            q = 0;  //没有直接返回,而是保存下来
        else {
            //整个for循环就是找出长度为N(1,2,3,4,...)的钢条的最大收益值
            for(int i = 1;i <= n;i++)
                q=Math.max(q, cut2(p, n-i, r) + p[i-1]);
            //System.out.println(q);
        }
        r[n] = q;  //剩余长度的最大值,保存长度为N(1,2,3,4,...)的钢条的最大收益值
        //System.out.println(q);

        //比如,如果要求长度为4的钢条的最大收益,那么,可能被切割成1-3,1-1-2,1-2-1,...,等
        //但是,最大收益却是2-2的切割方式,其中前面1-1-2的切割方式中已经计算了长度为2的最大收益
        //所以遇到了,就直接返回了
        return q;
    }

    //自底向上的动态规划
    public static int dp_cut(int[] p, int len){
        //创建并初始化状态表
        int[] arr = new int[len+1];  //收益的最大值,保存长度为N(1,2,3,4,...)的钢条的最大收益值
        for (int i = 1; i <= len ; i++) {
            int q = -1;
            for (int j = 1; j <= i; j++) {
                q = Math.max(q, arr[i-j] + p[j-1]);
            }
            arr[i] = q;
        }
        return arr[len];
    }





    public static void main(String[] args) {
        int[] p = {1, 5, 8, 9, 10, 17, 17, 20, 24, 30};  //钢条价格,len为钢条的长度
        //System.out.println(cal(p,4));  //求最大收益
        System.out.println(cutMemo(p, 4));
        System.out.println(cal2(p, 4));
        System.out.println(dp_cut(p, 4));
    }
}

  • 最大子序和
    给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

    • 示例:

      输入: [-2,1,-3,4,-1,2,1,-5,4],
      输出: 6
      解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

class Solution {
//思路:若sum小于0则舍弃,保留当前数为sum,反之加上当前数,
//判断sum与之前保存的结果比较,保留大的一方。最终返回结果
    public int maxSubArray(int[] nums) {
        int sum = 0;
        int res = nums[0];
        for(int num:nums){
            sum = sum > 0 ? sum + num : num;
            if(res < sum){
                res = sum;
            }
        }
        return res;
    }
}
  • 改成动态规划
package dynamic_programming;

/**
 * @program: Aglorithm
 * @Date: ****
 * @Author: Kyrie
 * @Description:
 * [-2,1,-3,4,-1,2,1,-5,4]
 * 求最大子序和
 * 6 ->[4,-1,2,1]
 *      动态规划求解:
 *           分析:
 *            定义数组dp[i]为当前状态的最优的值,定义head指针指向子序列数组的头部,
 *            其中dp[i]中的最大值对应的索引为子序列数组的尾部。
 *            若当前数与上一次的最优解之和大于当前数,即dp[i-1]+arr[i] > arr[i],则arr[i]保留,head指针不变
 *            或者 dp[i-1] > 0, head不移动;
 *            dp[i] = max(dp[i-1] + arr[i], arr[i]),若dp[i] = arr[i],则head = i;
 *            i   arr[i]   dp[i]       head
 *            0      -2      -2         0
 *            1      1        1         1
 *            2     -3     max(-2,-3)   1
 *            3     4      max(-2,4)    3
 *            4    -1      max(4-1,-1)  3
 *            ...
 *            6     1         6         3   ->  最大子序:head -> i
 *            7    -5         1         3
 *            8    4          5         3
 *
 */
public class Dp02_SumSeq {
    
    public static int maxSubArray(int[] arr) {
        int[] dp = new int[arr.length];
        dp[0] = arr[0];
        //int head = 0;
        //int tail = 0;
        for (int i = 1; i < arr.length; i++) {
            dp[i] = Math.max(dp[i-1]+arr[i], arr[i]);
        }
        int max = dp[0];
        for (int i = 0; i < dp.length; i++) {
            max = dp[i] > max ? dp[i] : max;
            //tail = i;
        }
        return max;
    }

    public static void main(String[] args) {
        int[] arr = {-2,1,-3,4,-1,2,1,-5,4};
        System.out.println(maxSubArray(arr));
    }
}

执行用时 : 3 ms, 在Maximum Subarray的Java提交中击败了66.01% 的用户
内存消耗 : 38.5 MB, 在Maximum Subarray的Java提交中击败了84.41% 的用户

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值