zoukankan      html  css  js  c++  java
  • 动态规划之一阳指——转移状态的横向对比

    先粗略介绍解决动态规划的整个过程,这里提供两个解决思路:

    第一种,固定模式:

    1 确定状态

    需要两个意识:

    最后一步:最优策略的最后一步。

    子问题:子问题与原问题类似

    2 设置转移方程 

    3 添加初始条件和边界情况,比如题目的限制条件等等

    4 计算优化,空间、时间上 

    第二种,递归改写,但是对于有些问题递归是不太好写的。

    1 设计暴力算法——递归(递归的思路相当清晰,写出来的代码相当短小精悍)

    2 找到冗余设计并存储状态(-维,二维,三维数组,甚至用Map )

    3 更改递归式(状态转移方程),存储递归返回的值。

    4 自底向上计算最优解(编程方式)

     

    局部上看,本文主要想解决的问题是如何分析动态规划的状态问题,状态是解决转移方程的关键钥匙,不论采用何种方法解决问题,状态是一定要事先确定的!

    设置状态——

    一般是采用数组存储的方式:

    1 一维数组

    每个元素 f[ i ] 的含义

    2 二维数组 

    f[ i ][ j ],i * j 个元素,每个元素的含义

    DP类型

    最大最小值规划:

    LintCode 669: Coin Change

    你有三种硬币,分别面值2元,5元和7元, 每种硬币都有足够多,买一本书需要27元

    如何用最少的硬币组合正好付清,不需要对方找钱。

    输入:
    [1, 2, 5]
    11
    输出: 3
    解释: 11 = 5 + 5 + 1
    输入: 
    [2]
    3
    输出: -1

    分析状态:由于是线性序列,可以采用一维数组 f[ ],f[ ] 存储硬币个数,故设

    状态 f[ X ] = 最少用多少枚硬币拼出 X,X 为数值;

    硬币有三种:2、5、7,

    拼出X所需要的最少硬币数:

      f[ X ] = min{ f[ X - 2 ] + 1, f[ X - 5 ] + 1, f[ X - 7 ] + 1 }

     

    LintCode 114 Unique Paths

    有一个机器人的位于一个 m × n 个网格左上角。

    机器人每一时刻只能向下或者向右移动一步。机器人试图达到网格的右下角。

    问有多少条不同的路径?

    eg1:
    Input: n = 1, m = 3
    Output: 1	
    Explanation: Only one path to target position.
    
    eg2:
    Input:  n = 3, m = 3
    Output: 6	
    Explanation:
    	D : Down
    	R : Right
    	1) DDRR
    	2) DRDR
    	3) DRRD
    	4) RRDD
    	5) RDRD
    	6) RDDR

    分析:网格结构,坐标的移动需坐标 ( x,  y) 的变化,故开辟一个二维数组 valueDp[ ][ ]。但是这个数组如何设置含义呢?

    最后一步:无论机器人用何种方式到达右下角,总有最后挪动的一步;

    每一步向右或者向下,右下角坐标设为 ( m - 1,  n - 1 ),那么前一步机器人一定是在 (m - 2, n - 1 )或者 (m - 1,  n - 2)

    状态:设 f[ i ][ j ]为机器人有多少种方式从左上角走到 (i,  j);

    自然地,f[ i - 1 ][ j ] 表示机器人有多少种方式走到 (i - 1,  j);

    自然地,f[ i ][ j - 1] 表示机器人有多少种方式走到 (i,  j - 1)

    那么,对于任意一个格子( i,  j ),有两个方向可以到达此位置,根据加法组合原理,可以推导出转移方程:

      f[ i ][ j ] = f[ i - 1 ][ j ] + f[ i ][ j - 1]

    存在型DP

    LintCode 116. 跳跃游戏  jump-game

    给出一个非负整数数组,你最初定位在数组的第一个位置;

    数组中的每个元素代表你在那个位置可以跳跃的最大长度。    

    判断你是否能到达数组的最后一个位置。

    e.g.1:
    输入 : [2,3,1,1,4]
    输出 : true
    
     
    e.g.2:
    输入 : [3,2,1,0,4]
    输出 : false

    分析:题目是一维数组,判断结果是否可达,所以采用一维数组状态

    状态:设 f[ j ] 表示青蛙能不能跳到石头 j;

    那么选择上一个石头 i 的条件是什么?为什么上一个石头不是 j - 1 呢?这是设置变量 i 的原因。

    抛出来三个问题:

    怎么选择石头 i;能不能跳到石头 i;最后一步的距离不能超过 ai ;

      OR 0<=i<j   ;     f[ i ]        i + a[ i ] >= j

    就是构成转移方程的元素:

    f[ j ] = OR 0<=i<j (f[ i ] AND i + a[ i ] >= j )

    LintCode 191. 乘积最大子序列  maximum-product-subarray

    给定a[0], ... a[n-1],找出一个序列中乘积最大的连续子序列(至少包含一个数)。

    样例 1:
    输入:[2,3,-2,4]
    输出:6
    
    
    样例 2:
    输入:[-1,2,4,1]
    输出:8

    分析状态:其乘积最大,为一维数组,记为 f[ ],

    乘积的结果和数字的正负号相关,当前值为正数最大值,再乘一个负值,就变成负数最小值了,再乘负数,变成正数最大值;

    考虑到一个数组存储空间和 序列相关的话,不够用,所以选取两个一维数组,f[ ] 和 g[ ]

    f[ j ] = 以 a[ j ]结尾的连续子序列的最大乘积

    g[ j ] =以 a[ j ]结尾的连续子序列的最小乘积

    故状态:

    f[ j ] = 以 a[ j ] 结尾的连续子序列的最大乘积

    情况1 :子序列就是a[ j ]本身,a[ j ];

    情况2 :以a[ j - 1 ]结 尾的连续子序列的最大/最小乘积,乘上 a[ j ],max{ a[ j ] * f[ j - 1 ],  a[ j ] * g[ i - 1 ] }

    转移方程:f[ j ] = max{ a[ j ],max{ a[ j ] * f[ j - 1 ],  a[ j ] * g[ i - 1 ] } |  j > 0 }

    坐标型动态规划:数组下标 [ i ][ j ] 即坐标 (i,  j)

    LintCode 115: Unique Paths II

    给定 m 行 n 列的网格,有一个机器人从左上角(0,0)出发,每一步可以向下或者向右走一步

    网格中有些地方有障碍,机器人不能通过障碍格

    问有多少种不同的方式走到右下角

    Example 1:
    Input: [[0]]
    Output: 1
    
    
    Example 2:
    Input:  [[0,0,0],[0,1,0],[0,0,0]]
    Output: 2
    	
    Explanation:
    Only 2 different path.

    状态分析:假设坐标为 (x,  y),记为 (i,  j)

    最后一步一定是从左边 (i,  j - 1) 或上边 (i - 1,  j) 过来

    设状态 f[ i ][ j ] 表示从左上角有多少种方式走到格子 (i,  j);

    那么 (i,  j - 1)  记为 f[ i ][ j - 1 ] 表示从左边有多少种方式走到格子 (i,  j - 1);

      (i - 1,  j)  记为 f[ i - 1 ][ j ] 表示从上边有多少种方式走到格子 (i - 1,  j)

    所以状态转移方程为:

      f[ i ][ j ] = f[ i - 1 ][ j ] + f[ i ][ j - 1 ]

    但是如果遇到障碍怎么办?则加上限制条件:

      f[ i ][ j ] = 0

    初始条件和边界条件不在本文的讨论范围内。 

     

     

    LintCode 515 Paint House

    这里有 n 个房子在一列直线上,现在我们需要给房屋染色,分别有红色蓝色和绿色。每个房屋染不同的颜色费用也不同,你需要设计一种染色方案使得相邻的房屋颜色不同,并且费用最小,返回最小的费用。

    费用通过一个 n x 3 的矩阵给出,比如 cost[ 0 ][ 0 ] 表示房屋 0 染红色的费用,cost[ 1 ][ 2 ] 表示房屋 1 染绿色的费用。

    样例 1:
    输入: [[14,2,11],[11,14,5],[14,3,10]]
    输出: 10
    解释: 第一个屋子染蓝色,第二个染绿色,第三个染蓝色,最小花费:2 + 5 + 3 = 10.
    
    样例 2:
    输入: [[1,2,3],[1,4,6]]
    输出: 3
    

    显然题目中是二维数组,故状态也要开辟一个二维数组空间 

    当前房子 i 所要涂的颜色与前一个房子 i - 1 涂的颜色相关,

    只有三个颜色,所以二维元素个数为 3.

    即可以设置状态: valueDp[ i ][ j ] 为第 i 个房子油漆颜色 j 所需要的花费;

    当前房子为红色:

    valueDp[ i ][ 0 ] = min{ valueDp[ i - 1 ][ 1 ] + cost[ i - 1 ][ 0 ], valueDp[ i - 1 ][ 2 ] + cost[ i - 1 ][ 0 ] };

    当前房子为蓝色:

    valueDp[ i ][ 1 ] = min{ valueDp[ i - 1 ][ 0 ] + cost[ i - 1 ][ 1 ], valueDp[ i - 1 ][ 2 ] + cost[ i - 1 ][ 1 ] };

    当前房子为绿色:

    valueDp[ i ][ 2 ] = min{ valueDp[ i - 1 ][ 0 ] + cost[ i - 1 ][ 2 ], valueDp[ i - 1 ][ 1 ] + cost[ i - 1 ][ 2 ] };

     

     

    划分型

    LintCode 512 Decode Ways

    有一个消息包含A-Z通过以下规则编码

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

    现在给你一个加密过后的消息,问有几种解码的方式

    样例 1:
    输入: "12"
    输出: 2
    解释: 它可以被解码为 AB (1 2) 或 L (12).
    
    样例 2:
    输入: "10"
    输出: 1
    

    状态分析:字符的数值范围在 1 ~ 26,超过这个范围必然会被截断。

    字符串的长度个数设置为 i ,对应的一维数组状态 f[ i ] = 字符长度为 i 有多少种解密方式

    但是这个动态转移方程有点不太好想,可以假设数字串 S 前 i 个数字解密成字母串有 f[ i ] 种方式

      f[ i ] = f[ i - 1 ] | S[ i - 1 ] 对应一个字母+ f[ i - 2 ] | S[ i - 2 ] S[ i - 1 ] 对应一个字母

     

     

     

    LintCode 667. 最长的回文序列
    给一字符串 s, 找出在 s 中的最长回文子序列的长度. 你可以假设 s 的最大长度不超过 1000.

    样例1
    输入: "bbbab"
    输出: 4
    解释:
    一个可能的最长回文序列为 "bbbb"
    
    样例2
    输入: "bbbbb"
    输出: 5

    分析状态:

    字符串需要转成字符数组便于取出具体的字符元素;

    虽然是一维序列,但是对字符数组的操作是从两边进行的,所以需要设置二维数组来存储状态。

    故设 transferDp[ i ][ j ]为 S[ i...j ] 的最长回文子串的长度,i  j  为字符数组 S[ ] 的起始下标

    当 S[ i ] == S[ j ] 时,字符数组 S,两边同时缩减一个字符,状态数组 + 2   ==>   transferDp[ i + 1 ][ j - 1 ] + 2 ;

    transferDp[ i ][ j ] = max{ transferDp[ i + 1][ j ],  transferDp[ i ][ j - 1 ], transferDp[ i + 1 ][ j - 1 ] + 2 | S[ i ] == S[ j ] }

    遍历过程如下图:

    这里提醒重要的一点:状态数组一定要和实际意义结合起来,数组虽然可以存储值,但是与操作过程相违背,可以不用赋值。

    transferDp[ i ][ j ]  = 字符数组 i  至 j 之间的字符元素;transferDp[ ][ ] 的下三角元素就不存在实际意义,就可以不用赋值,可以写成:

    for (int i = 0 ; i < n ; i++) {
        for (int j = i; j < n; j++) {
            valueDp[i][j] = -1;
            
        }
    }
    

    LintCode 396 Coins In A Line III 

    给定一个序列 a[0], a[1], ... a[N-1]

    两个玩家Alice和Bob轮流取数

    每个人每次只能取第-一个数或最后一个数双方都用最优策略,使得自己的数字和尽量比对手大问先手是否必胜

    如果数字和一样,也算先手胜

    输入:[1,5, 233, 7]
    输出: True (先手取走1, 无论后手取哪个,先手都能取走233) 

    分析状态:设 f[ i ][ j ] 为一方先手在面对 a[ i...j ] 这些数字时,能得到的最大的与对手的数字差

  • 相关阅读:
    二进制回复操作
    日志和备份介绍
    mraiadb查
    mraiadb增三删改
    mardb基本操作
    redis搭建主从和多主
    ldd 查看符号找不到
    一个声明指定了多个类型
    word中为选定文本加边框和底纹
    ue配置lua语法高亮
  • 原文地址:https://www.cnblogs.com/qianyuesheng/p/13913600.html
Copyright © 2011-2022 走看看