动态规划举例(决策+递推关系)

什么时候用动态规划:

(1)起步情况未知、结束情况未知,到达本一步时,不知道上一步从哪里来或者上一步取还是不取的时候

(2)可以利用到上一步的最优解,如果不能利用上一步的最优解没就用暴力循环解法,例如数组中的最大矩形(tiger基金的笔试题)_xuehuagongzi000的博客-CSDN博客

(3)并且求最大或者最小值的情形

怎么用动态规划:

保存当前最优值状态:保存以当前为结尾的最值(根据题意获得,例如连续子数组最大和的状态里保存以当前数据为结尾的连续子数组最大和,最长递增子序列的状态是以当前为结尾的最长递增子序列​​​​​​​长度)。其中必须包含当前为结尾的数据,如果包含当前为结尾的数据此时不是最优解,真正的最优解也可能会用到。当前最值的数据结果可能是一个值、一维数组、二维数组。

决策:不同的决策,递推关系不同

递推关系:这一步的最大值和前一步或者前两步的最大值的关系,每走一步都是求以当前为结尾的最值并且利用到上一步最值

状态转移过程:本一步的由来可能是从上一步线性到达的(单层循环),也可能是非线性到达的(双层或者三层循环)。线性的决策有不知道上一步从哪里来或者上一步取还是不取的时候

1、连续子数组的最大和,并返回子数组下标(趋势科技的笔试题)

(1)、赖以决策的策略是或者说什么时候进行决策sum[i-1]<=0和sum[i-1]>0。

(2)、利用前面的状态值的递推关系是sum[i]=v[i]和sum[i]=v[i]+sum[i-1]。

[cpp]  view plain  copy
  1. vector<int> continuousSubarraySum(vector<int>& v){     
  2.     const int len=v.size();  
  3.     vector<int> sum(len,0 );  
  4.     int start=0,end=0;  
  5.     int curStart=0;  
  6.     sum[0]=v[0];// 为了DP有i-1项。  
  7.     int maxSum=sum[0];  
  8.     for(int i=1;i<len;i++){  
  9.         if(sum[i-1]<=0){// sum[i-1]+v[i]一定小于v[i].  
  10.             sum[i]=v[i];  
  11.             curStart=i;  
  12.         }else{//sum[i-1]>0的同时(v[i]有可能小于0),但v[i]可能在返回的向量中. 
  13.             sum[i]=v[i]+sum[i-1];  
  14.         }  
  15.               
  16.         if(sum[i]>maxSum){  
  17.             maxSum=sum[i];  
  18.             start=curStart;  
  19.             end=i;  
  20.         }  
  21.     }  
  22.     cout<<"最大子序列下标:"<<start<<"--"<<end<<endl;  
  23.     vector<int> resV(2,0);  
  24.     resV[0]=start;   
  25.     resV[1]=end;  
  26.     return resV;  
  27. }  

2、最大连续子数组之积

(1).理解题意,列出所有可能的情况

f[i]存放当前的最大值

g[i]存放当前的最小值

public class Maximum_Product_Subarray {
 public static void main(String[] args) {
 int[] array={-4,-3,-2};
 int result=get_Maximum_Product_Subarray(array);
 System.out.println(result);
 } 
 //这个是求最大连续子数组之积 
 //int[] array={-2,3,2,-4};  
private static int get_Maximum_Product_Subarray(int[] A) { 
    if (A == null || A.length == 0) { return 0; } 
    int[] f = new int[A.length]; 
    int[] g = new int[A.length]; 
    f[0] = A[0]; g[0] = A[0]; 
    int res = A[0];
     for (int i = 1; i < A.length; i++) 
    { //三者中取一个
     f[i] = Math.max(Math.max(f[i - 1] * A[i], g[i - 1] * A[i]), A[i]);
     g[i] = Math.min(Math.min(f[i - 1] * A[i], g[i - 1] * A[i]), A[i]);
     res = Math.max(res, f[i]); 
    } 
return res;
 }
 }



或者写成
if(A[i]>0){
f[i] = Math.max(f[i - 1] * A[i], A[i]);
 g[i] = Math.min(g[i - 1] * A[i], A[i]);
} else {
f[i] = Math.max(g[i - 1] * A[i]), A[i]);
 g[i] = Math.min(f[i - 1] * A[i], A[i]);
}


(2).分析出赖以决策的策略或目标A[i]>0或者A[i]<0


(3).找到递推关系(一定要利用到前面的状态值)

找递推关系应多考虑和前一个状态或者前两个状态的关系。

例如:使用到前一个状态的结果,

或者写成
if(A[i]>0){
f[i] = Math.max(f[i - 1] * A[i], A[i]);
 g[i] = Math.min(g[i - 1] * A[i], A[i]);
} else {
f[i] = Math.max(g[i - 1] * A[i]), A[i]);
 g[i] = Math.min(f[i - 1] * A[i], A[i]);
}

3、最长递增子序列

示例0: [0,3,1,6,2,1,2,3,4,7] 。

 
示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4
示例 3:

输入:nums = [7,7,7,7,7,7,7]
输出:1



dp[i]是以num[i]为结尾的的最长递增子序列

rclass Solution {
    public int lengthOfLIS(int[] nums) {
        int len = nums.length;
        if (len < 2) {
            return len;
        }
        int[] dp = new int[len];
        Arrays.fill(dp, 1);
        
        int res = dp[0];
        for (int i = 1; i < len; i++) {

            for (int j = 0; j < i; j++) {
                if (nums[j] < nums[i]) {
                    dp[i] = Math.max(dp[j] + 1, dp[i]);
                }
            }
            
            // 在遍历的时候同时找 dp 数组的最大值
            res = Math.max(res, dp[i]);
        }
        return res;
    }
}

5、N*N矩阵从左上角到右下角路径和的最大值(tiger基金的笔试题)

2016年京东编程题

小东所在公司要发年终奖,而小东恰好获得了最高福利,他要在公司年会上参与一个抽奖游戏,游戏在一个6*6的棋盘上进行,上面放着36个价值不等的礼物,每个小的棋盘上面放置着一个礼物,他需要从左上角开始游戏,每次只能向下或者向右移动一步,到达右下角停止,一路上的格子里的礼物小东都能拿到,请设计一个算法使小东拿到价值最高的礼物。
给定一个6*6的矩阵board,其中每个元素为对应格子的礼物价值,左上角为[0,0],请返回能获得的最大价值,保证每个礼物价值大于100小于1000。

随机产生一个n行m列的整数矩阵,如图所示即随机产生的一个7行5列的数值矩阵,在整数矩阵中寻找从左上角至右下角,每步可向下(D)或向右(R)或斜向右下(O)的一条数值和最小的路径。

(1)、赖以决策的策略是Math.max(arr[i][j-1], arr[i-1][j])+b>Math.max(arr[i][j-1], arr[i-1][j])。

(2)、利用前面的状态值的递推关系是arr[i][j] = Math.max(arr[i][j-1], arr[i-1][j])+board[i][j];  。

[java]  view plain  copy
  1. import java.util.*;  
  2.   
  3. public class Bonus {  
  4.     public int getMost(int[][] board) {  
  5.         // write code here  
  6.         int[][] arr = new int[6][6];  
  7.         for(int i = 0;i<6;i++){  
  8.             for(int j = 0;j<6;j++){  
  9.                 int b = board[i][j];  
  10.                 if(i==0&&j==0)  
  11.                     arr[i][j] = b;  
  12.                 else if(i==0){  
  13.                     arr[i][j] = arr[i][j-1]+b;  
  14.                 }else if (j==0) {  
  15.                     arr[i][j] = arr[i-1][j]+b;  
  16.                 }else{  
  17.                     arr[i][j] = Math.max(arr[i][j-1], arr[i-1][j])+b;  
  18.                 }  
  19.             }  
  20.         }  
  21.         return arr[5][5];  
  22.     }  
  23. }  

6、 最大子矩阵和

给定一个正整数、负整数和 0 组成的 N × M 矩阵,编写代码找出元素总和最大的子矩阵。

返回一个数组 [r1, c1, r2, c2],其中 r1, c1 分别代表子矩阵左上角的行号和列号,r2, c2 分别代表右下角的行号和列号。若有多个满足条件的子矩阵,返回任意一个均可。

注意:本题相对书上原题稍作改动

示例:

输入:
[
   [-1,0],
   [0,-1]
]
输出:[0,1,0,1]
解释:输入中标粗的元素即为输出所表示的矩阵
 

        int maxSum=Integer.MIN_VALUE;
        int r1=0,r2=0;//行范围
        int c1=0,c2=0;//列范围

        //把二维数组看成一维数组,每列和构成新数组
        for(r1=0; r1<m; r1++){
            int[] dp = new int[n];//每列和
          
            for(r2=r1; r2<m; r2++){ 

                //对任意两行之间的元素
                int sum=0;
                //遍历列之前要sum=0
                for(int c=0; c<n; c++){
                    dp[c]+=matrix[r2][c];//遍历过程中要把二维数组看成一维数组,看一列的总值,在最上面行变化的时候进行重新初始化

                    //类似一维数组求最大子数组和
                    if(sum>0) {
                        sum+=dp[c];
                        c2=c;  
                    }
                    else {
                        sum=dp[c];
                        c1=c;
                        c2=c;
                    }
                    if(sum>maxSum){//更新坐标
                        maxSum=sum;
                        res[0]=r1;
                        res[1]=c1;
                        res[2]=r2;
                        res[3]=c2;
                    }
                }

            }
        }
        return res;

    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值