当前位置:主页 > 查看内容

Java 与 动态规划

发布时间:2021-07-18 00:00| 位朋友查看

简介:动态规划: 动态规划Dynamic ProgrammingDP是运筹学的一个分支是求解决策过程最优化的过程。适用动态规划的问题必须满足 最优化原理 最优子结构每个阶段的最优状态可以从之前某个阶段的 某个 或 某些 状态直接得到 无后效性 不需要管之前这个状态是如何得到的……

动态规划:

动态规划(Dynamic Programming,DP)是运筹学的一个分支,是求解决策过程最优化的过程。适用动态规划的问题必须满足:

  • 最优化原理(最优子结构):每个阶段的最优状态可以从之前某个阶段的某个某些状态直接得到;
  • 无后效性:不需要管之前这个状态是如何得到的。
  • 子问题重叠:动态规划算法的关键在于解决冗余,这是动态规划算法的根本目的。动态规划实质上是一种以空间换时间的技术。

DP步骤:

  1. 确定状态:两个意识,最后一步和子问题。
  2. 转移方程:
  3. 初始条件和边界情况:
  4. 计算顺序:
/**
* DP Step:
*      1)确定状态:
*          i)简单说,即开辟一个数组,数组中元素代表什么?
*          ii)两个意识:最后一步和子问题
*      2)转移方程:
*      3)初始条件和边界情况:
*          i)初始条件:转移方程无法计算,但是实际又需要的
*      4)计算顺序:
*          一般是从左往右,从上往下。
*
*/ 

题目特点:

  • 计数:基本操作:加法。
  • 求最大最小值:基本操作:min()/max()
  • 求存在性:基本操作:AND/OR
/**
* Analyze: 涉及到(1)计数,e.g.,有多少种方式/方法
*               (2)求最大最小问题,e.g.,最长上升子序列、最大数字和
*               (3)求存在性:e.g.,是否、能不能
*          一般用动态规划解决。
*/

例题1,求最大最小问题:找零。

package DynamicProgramming;

/**
 * Question: 需要付27元钱的账单,你有2,5,7,三种零钱若干(假使无穷多)。
 * 问:如何用数目最少的零钱组合就可以支付账单?
 * Analyze: 涉及到 求最大最小问题,基本操作是 min/max
 * DP Step:
 *      设F(27)表示拼出27的最优组合数目,F(X)表示拼出X的最少硬币数。
 *      1)最后一步:
 *          假设需要K枚硬币,最后一枚是ak,则,前K-1枚拼出了(27-ak).
 *          我们知道这(K-1)枚拼出(27-ak)是最优的。
 *          但是,我们不知道K是多少,ak是多少?
 *      2)子问题:
 *          子问题:如何用最少硬币拼出(27-ak)?
 *          原问题:如何用最少硬币拼出27?
 *          将原问题转化成了规模更小的子问题。
 *          Wait!!!
 *          K是多少?ak是多少?
 *          虽然我们不知道ak是多少,但是,我们知道ak的取值范围:2 5 7
 *          所以!
 *              F(27) = min{F(27-2)+1,F(27-5)+1,F(27-7)+1}
 *      3)状态转移方程:
 *          F[x] = min{F[x-2]+1,F[x-5]+1,F[x-7]+1}
 *      4)边界情况:
 *          x-2、x-5、x-7小于0怎么办?置为无穷大
 *      5)初始值:
 *          F[负数] = INF,F[0] = 0  1->INF:皆可计算
 *      6)什么时候停下来?
 *          找到27元的最优组合数
 *      7)计算顺序?
 *          因为F[x]依赖于F[x-2],F[x-5],F[x-7],所以,从左往右计算。
 *
 */

public class Coin {
    public static final int INF = 1000000007;
    public static void main(String[] args){
        int total = 27;
//        int count = Rec(total);       // 递归版
        int count = DP(total);          // DP版
        System.out.println(count);
    }
    public static int DP(int money){
        // 开辟数组大小 +1
        // 含义:dp[i] 表示凑够零钱i所需的最少组合数。
        int[] dp = new int[money+1];
        // 初始化
        dp[0] = 0;
        // 状态转移
        for (int i=1;i<=money;i++){
            dp[i] = min(dp,i-2,i-5,i-7);        // 状态转移方程
        }
        return dp[money];
    }

    public static int min(int[] a, int i,int j,int k){
        int res1 = i<0?INF:(a[i]+1);
        int res2 = j<0?INF:(a[j]+1);
        int res3 = k<0?INF:(a[k]+1);
        return (res1<res2?res1:res2)<res3?(res1<res2?res1:res2):res3;
    }

    public static int Rec(int money){
        /**
         * 递归的过程,可以用递归树来模拟:从上往下,从左往右。
         */
        if (money==0)
            return 0;
        if (money==1)
            return INF;           // 此处为什么不用Integer.MAX_VALUE?因为溢出问题!变成负数。
        int res = Integer.MAX_VALUE;
        if (money>=2){
            res = Math.min(Rec(money-2)+1,res);
        }
        if (money>=5) {
            res = Math.min(Rec(money - 5) + 1, res);
        }
        if (money>=7) {
            res = Math.min(Rec(money - 7) + 1, res);
        }
        return res;
    }
}

例题2,计数问题:走格子。

package DynamicProgramming;

/***
 * Question:有一个二维矩阵,机器人从左上角走到右下角,每次只能向右或者向下走。
 * 问,机器人有多少种路径?
 * Analyze:计数问题:基本操作是 加法+
 * DP Step:
 *      1)确定状态:
 *          dp[i][j] 含义:机器人走到(i,j)的方法;
 *          最后一步:
 *              因为最后一步到达(m-1,n-1),机器人的倒数第二步可能位置:上:(m-2,n-1),左:(m-1,n-2)
 *              则:dp[m-1][n-1] = dp[m-2][n-1]+dp[m-1][n-2]
 *          子问题:
 *              问:机器人走到(m-2,n-1),(m-1,n-2)有多少种方法?
 *     2)状态转移方程:
 *          dp[i][j] = dp[i-1][j]+dp[i][j-1]
 *     3)初始条件和边界情况:
 *          初始条件:dp[0][0] = 1
 *                  i-1、j-1出现负数时,为0.
 *          另一种初始化方法:
 *              直接把边界i=0、j=0的时候初始化为1
 *          则 dp[1][0]=dp[0][0]+dp[1][-1]=1+0=1
 *          机器人走到(m-1,n-1)结束
 *     4)计算顺序:
 *          从左往右,从上往下。
 *
 */
public class UniquePaths {
    public static void main(String[] args) {
        int[][] map = new int[2][2];
        int count = uniquePaths(map);
        System.out.println(count);

    }
    public static int uniquePaths(int[][] map){
        int count = 0;
        map[0][0] = 1;
        for (int i=0;i<map.length;i++){
            for (int j = 0; j < map[i].length; j++) {
                if (i==0&&j==0)
                    continue;
                int up = (i-1<0||j<0)?0:map[i-1][j];
                int left = (i<0||j-1<0)?0:map[i][j-1];
                map[i][j] = up + left;
//                System.out.println(i+","+j+" : "+map[i][j]);
            }
        }
        count = map[map.length-1][map[0].length-1];
        return count;
    }
}

例题3,存在性问题:青蛙跳跃

package DynamicProgramming;

/**
 * Question:跳跃游戏,有n块石头,分别在x轴上从0到n-1.一只青蛙从石头0想跳到石头n-1.
 * 如果,在第i个石头上,它最多可以跳距离为ai。
 * 问:青蛙能否跳到石头n-1?
 * Analyze: 存在性问题。基本操作:OR/AND
 * DP Step:
 *      1)确定状态:
 *          F[i]表示青蛙能否跳到第i个石头上。
 *          i)最后一步:
 *              最后一步在(n-1)位置,那么上一步呢?
 *              假设在第i个位置处,那么能确定吗?
 *              不能。但是i满足两个条件:
 *              condition1:青蛙可以跳到石头i;
 *              condition2:青蛙可以从石头i跳到n-1.
 *          ii)子问题:
 *              青蛙能否跳到石头i?
 *      2)状态转移方程:
 *          F[i] = OR_{0<=j<i}(F[i] AND (F[j]+a[j]>i))
 *      3)初始条件和边界情况
 *          F[0] = true. 可达
 *          i = n-1时停止
 *      4)计算顺序:
 *          从左往右。
 */
public class Jump {
    public static void main(String[] args) {
        int[] ar = new int[]{2,3,1,1,4};
        boolean flag = junmp(ar);
        System.out.println(flag);
        int[] ar1 = new int[]{2,1,1,0,4};
        boolean flag1 = junmp(ar1);
        System.out.println(flag1);
    }
    public static boolean junmp(int[] ar){
        boolean[] flag = new boolean[ar.length];
        flag[0] = true;
        for (int i=1;i<ar.length;i++){
            for (int j=0;j<i;j++){
                if (flag[j] && j+ar[j]>=i){
                    flag[i] = true;
                    break;
                }
            }
        }
        return flag[ar.length-1];
    }
}

练习:HUAWEI 笔试

问题:
有n个中继器,置于长度为n的线路。(每隔单位距离,放置一个中继器),中继器i能传输的信号距离由数组num[i]决定。
问:
从节点0传到节点n-1,需要的最少中转次数?
测试样例:
输入:
4
2 3 1 1
输出:
2
分析:
F[i]:表示信号传到节点i,所需的最少中转次数。
1)最后一步:信号从节点k传到节点n-1.
问题:节点k,如何确定???
显然,节点k要满足一定条件:即信号可以从节点k传到节点n-1。
2)子问题:从节点0传到节点k,需要的最少中转次数?
3)状态转移方程:
F[i] = min{F[k]+1} // k节点 有约束条件。
4)初始化:
F[0] = 1; // 表示从0号节点传出去,算一次中转。
5)边界:信号传到节点n-1结束。
6)计算顺序:从左往右。

package DynamicProgramming;


public class HW03 {
    public static void main(String[] args) {
        int[] nums = new int[]{3,2,1,1,1};
        System.out.println(minNum(nums));

    }
    public static int minNum(int[] nums){
        int[] need = new int[nums.length];  // 表示到达节点i需要的最少中继器数目
        // 初始化
        need[0] = 1;    // 0号节点出发 第一次中转

        // 状态转移
        for (int i=1;i<nums.length;i++){
            need[i] = Integer.MAX_VALUE;
            for (int j = 0; j < i; j++) {
                // 判断中继器j是否可以到达节点i
                if (nums[j]+j>=i){
                    // 到达节点i可以经过中继器j
                    // 比较 是否为最少中转次数
                    need[i] = Math.min(need[i], need[j]+1);
                }
            }
        }
        return need[nums.length-1];
    }
}

;原文链接:https://blog.csdn.net/weixin_44804108/article/details/115751377
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!

推荐图文


随机推荐