「笔记」Introduction-下

想清楚了再写代码!想清楚了再写代码!想清楚了再写代码!
问:如何提升动态规划能力?
1.多刷题,锻炼用状态描述问题,转移解决问题的思维
2.了解经典动态的规划类型,积累状态标识与转移的经验。
问:边界情况弄不清楚怎么办?
1.想清楚了再写!
2.记忆化搜索实现
3.在纸上写思路内容等,强制整理思维。

ps:喂你脚下有坑老师讲的真滴好,刚开始写有关的题,脑子一片浆糊,看答案虽然会有马上看懂的感觉,但是提升却并不多。提升最多的是自己手写在纸上从头到尾推出来的题!不过一开始开始要看答案...积累些经验思维后再推效率可能会高些!

动态规划题目特点

动态规划原本是用来解决最值这类的最优化问题,应用于计数、存在判定(本质依旧是计数)的问题也能工作,所以看到最值、计数、存在性判定这三类问题,不妨思考下实用动态规划来解决。
最值、计数、存在性判定三类问题对应在数塔问题中分别为一条路径的最大数字和是多少、求走出一条路径数字和不超过 55 的方案数、求是否存在一条路径的数字和等于 55。


状态表示图解?

总的来说动态规划的核心就是状态表示。好的状态表示,动态规划就完成了大半!我们要确定一个既不是那么复杂导致超时,又不过于简单产生后效性的合理状态表示。有了一个合理的状态表示之后,转移方程、决策边界和代码实现都是稍加思考就能得到了。
个人认为最好的动态规划就是无限接近没有后效性的点,也许是错的。

解题顺序:

Step 1 确定状态表示,包含阶段划分与状态表示
Step 2 写出转移方程:帮助你想清楚状态之间到底是如何转移的
Step 3 确定边界:初始 Cases!
Step 4 如果使用递推,考虑一下子状态枚举的顺序。

动态规划代码实现

两种写代码的方式:记忆化搜索与递推。我认为记忆化搜索是一种相对容易理解好上手的代码方式,递推需要考虑更多的东西,优点是支持加入各种高级优化。

  1. 记忆化搜索
    记忆化搜索可以理解为在我们确定的状态上进行搜索,然后通过一个额外的数组保存每一个状态的答案,搜索某一状态时,如果有答案就直接返回,否则继续向下搜索并保存计算过的答案!
    好处有以下几点:
    1. 避免计算一些用不到的状态。(能跑到的都是有可能的状态)
    2. 决策边界容易考虑。(原因同1)
    3. 减少思考量,不用考虑状态之间计算顺序,程序只需要立足当前状态与当前状态的子状态。
    4. 实现容易,写出搜索加记忆化就完成了。
    • 5. 可以使用搜索的奇技淫巧优化。(?不理解)
      写一个比较抽象的模板:
int dp[状态表示];
int dfs(状态表示) {
    if (决策边界) return 决策边界答案; // 决策边界
    if (dp[状态表示] != 无效数值) return dp[状态表示]; // 记忆化
    for (当前状态表示的 子状态) dfs(子状态) 更新 dp[状态表示]; // 状态转移
    return dp[状态表示];
}
int solve() {
    memset(dp, 无效数值, sizeof(dp));
    return dfs(原问题状态);
}

  1. 递推
    简单明了用For循环嵌套对所有状态进行枚举,即计算所有状态,然后用转移方程更新状态。
    1. 可以加入各种动态规划优化
    2. 没有记忆化搜索中的系统栈的开销,速度较快



学了那么多终于要实战了!

例题0

64.最小路径和
题目:给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
输入:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。

先确定状态表示,从右上到左下每一斜排为一个阶段。
我们回忆一下上文中题目特点的解题顺序:

  1. 确定状态表示,包含阶段划分与状态表示。
  • 阶段划分:我们把每个位置上的数设定为dp[i][j],阶段就可以简单划分为不同的第i+j阶段。
  • 状态划分:两个参数i和j共同实现了状态的划分,在同一阶段下(i与j之和相等),参数不同就对应了了处于同一阶段不同的状态。
  1. 写出转移方程:帮助你想清楚状态之间到底是如何转移的。
  • 写出转移方程:我们根据题目中的描述来看,题目中有“每次只能向下或者向右移动一步”,这个动态的描述就对应了我们要求得的转移方程dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j]。个人认为转移方程是题目中动态描述和状态表示相结合用代码表示出来用来描述题目中动态的方程。
  1. 确定边界:初始 Cases!
  • 确定边界:键盘只有一个起点,所以dp[0][0]=grid[0][0],超出边界是不行的,记忆化搜索没有什么
图解

到这里根据终于理清楚思路了,开始写代码!
因为递推要多考虑枚举顺序,我们先写记忆化搜索的代码。

class Solution {
public:
    int dp[550][550];
    vector<vector<int>> grid;

    int dfs(int x, int y) {
        // 搜不下去就是边界
        if (x == 0 && y == 0) return grid[0][0];
        //设定初始化
        if (x < 0 || y < 0) return 2100000000;
        //给不存在的数字设定一个特大的值,实现边界设定。

        // 记忆化,当读到的数字不是我们设定的特殊值,即更新过时我们直接使用已知的值。
        if (dp[x][y] != -1) return dp[x][y];

        // 状态转移
        dp[x][y] = min(dfs(x - 1, y), dfs(x, y - 1)) + grid[x][y];
        return dp[x][y];
    }
    int minPathSum(vector<vector<int>>& grid) {
        int n = grid.size(), m = grid[0].size();
        this->grid = grid;

        memset(dp, -1, sizeof(dp));
        return dfs(n - 1, m - 1);
    }
};


  • 说实话,记忆化的实现并不是特别清楚,之后会在扩展里进行细节到步骤的说明。(没打对勾说明还没做这件事,做完了的事情会打对勾)。


    接下来说递推
class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int n = grid.size(), m = grid[0].size();
        int dp[n][m] = {0};

        // 决策边界
        dp[0][0] = grid[0][0];
        for (int i = 1; i < n; i++) dp[i][0] = dp[i - 1][0] + grid[i][0];
        for (int j = 1; j < m; j++) dp[0][j] = dp[0][j - 1] + grid[0][j];

        // 状态转移
        for (int i = 1; i < n; i++){
            for (int j = 1; j < m; j++){
                dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
            }
        }

        return dp[n - 1][m - 1];
    }
};

递推需要考虑枚举顺序,写出状态转移第一步可得:
dp[1][1]=min(dp[0][1],dp[1][0])+grid[i][j]
这里我们需要等号右侧都是已知的,所以我们要先对边界进行计算

即,

//边界决策
dp[0][0] = grid[0][0];
        for (int i = 1; i < n; i++) dp[i][0] = dp[i - 1][0] + grid[i][0];
        for (int j = 1; j < m; j++) dp[0][j] = dp[0][j - 1] + grid[0][j];

通过以上边界的决策,再加上我们的状态转移最内层第一次循环,我们就得到了dp[1][1]的值
然后for循环中接下来我们会求dp[1][2],需要的值就是dp[1][1]和dp[0][2]...
以此类推,我们就能获得我们的所有的值。

之所以我们没有和记忆化搜索一样用到实现边界设定的代码。
if (x < 0 || y < 0) return 2100000000;
是因为我们用for已经对范围进行了限定。

总结

  1. 动态规划以状态表示为核心,需要确定转移方程与边界情况
  2. 满足三条基本性质:最优子结构、无后效性、子问题重叠
  3. 解决最值、计数、存在性判定三类问题
  4. 使用记忆化搜索或递推地方式实现

以上就是Introduction这节课中所学到的全部知识,后期我还会在扩展中对我写过的题目连接到个人思考里,大家感兴趣可以看看。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容

  • 前言 看大神推荐的书单中入门有这么一本书,所以决定把这本书的精华(自认为很有用的点),或许是我自己现在能用到的点都...
    我没有三颗心脏阅读 2,184评论 0 6
  • 第6章 继承与多态 学习目标 了解继承的目的 了解继承与多态的关系 知道如何重写方法 认识java.lang.Ob...
    默然说话_牟勇阅读 606评论 0 0
  • 第一部分 打好基础 Laying the Foundation 第一章 欢迎进入软件构建的世界 Welcome t...
    白桦叶阅读 4,623评论 0 17
  • 今天晚上对对联, 用上“雾霾”一词 1.诗心如兰 雾霾暗日月 建安出诗才 雾霾冬逞狂 李杜春开花(李子、杜梨树) ...
    行者顺达阅读 354评论 0 0
  • 槐花下,你轻轻的舞 很阳光,带着憧憬 一路浅吟轻唱 山花,在时光无情的流转中 一生都在托梦 圆了思念的孔 低下的头...
    江城妖怪阅读 252评论 0 1