抱佛脚-刷题系列之动态规划

抱佛脚一时爽,一直抱佛脚一直爽!这篇文章总结常见的动态规划问题~
参考链接:leetcode 剑指offer

概述


  • 如果动态规划方程只需用到有限个变量,则可以不用动态规划数组,而是用几个变量记录它们
  • 动态规划和递归+记忆数组本质上是一样的

跳格子问题


不记录体力值;返回是否能到最后一格(lc55)

  • 原始dp是f[i]表示能否跳到i;f[i+1]=for j in 0~i: f[j] && nums[j] >= i - j;现在优化为用curMax记录最大的nums[j] + j
  • [0] 返回 true
bool canJump(vector<int>& nums){
        int n=nums.size();
        if(!n)return true;
        int curMax=nums[0];
        bool result=false;
        for(int i=1;i<n; ++i){
        if(curMax>=i){
        curMax=max(curMax,nums[i]+i);
        }else{
        nums[i]=-i; // 处理第i位置无法到达的情况
        }
        }
        return curMax>=n-1;
}    

记录体力值;返回最大剩余体力

  • dp[i]表示跳到格子i最大剩余体力

不记录体力值;返回最少需要跳跃的次数(lc45)

    int jump(vector<int>& nums) {
        int res = 0, n = nums.size(), i = 0, cur = 0;
        while (cur < n - 1) {
            ++res;
            int pre = cur;
            for (; i <= pre; ++i) {
                cur = max(cur, i + nums[i]);
            }
            if (pre == cur) return -1; // May not need this,因为题目已知肯定能跳到最后
        }
        return res;
    }

其他题目


凑到amount最少需要的硬币数(lc322)

  • dp[i]记录凑到i最少需要的硬币数,dp[i] = min(dp[i - coin] + 1);

剪绳子(jz68)

class Solution {
    public int cutRope(int number) {
        if (number <= 1) return -1;
        if (number == 2) return 1;
        if (number == 3) return 2;
        int[] res = new int[number + 1];
        res[0] = 0;
        res[1] = 1;
        res[2] = 2;
        res[3] = 3;
        for (int i = 4; i <= number; ++i) {
            int max = -1;
            for (int j = 1; j <= i / 2; ++j) {
                max = Math.max(max, res[i - j] * res[j]);
            }
            res[i] = max;
        }
        return res[number];
    }
};

编辑距离(lc72)

  • 思路:当 word1[i] == word2[j] 时,dp[i][j] = dp[i - 1][j - 1],其他情况时,dp[i][j] 是其左,左上,上的三个值中的最小值加1,其实这里的左,上,和左上,分别对应的增加,删除,修改操作
int minDistance(string word1, string word2) {
    int m = word1.size(), n = word2.size();
    vector<vector<int>> dp(m + 1, vector<int>(n + 1));
    for (int i = 0; i <= m; ++i) dp[i][0] = i;
    for (int i = 0; i <= n; ++i) dp[0][i] = i;
    for (int i = 1; i <= m; ++i) {
        for (int j = 1; j <= n; ++j) {
            if (word1[i - 1] == word2[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1;
            }
        }
    }
    return dp[m][n];
}

最大正方形(lc221)

  • dp[i][j] 表示到达 (i, j) 位置所能组成的最大正方形的边长
  • 对于任意一点 dp[i][j],若当前 (i, j) 位置为0,则dp[i][j]为0
  • 否则,该点是正方形的右下角;左边,上边,和左上边这三个位置的dp值 suppose 都应该算好的。此时要看 dp[i-1][j-1], dp[i][j-1],和 dp[i-1][j] 这三个位置,我们找其中最小的值,并加上1,就是 dp[i][j] 的当前值了
int maximalSquare(vector<vector<char>>& matrix) {
    if (matrix.empty() || matrix[0].empty()) return 0;
    int m = matrix.size(), n = matrix[0].size(), res = 0;
    vector<vector<int>> dp(m, vector<int>(n, 0));
    for (int i = 0; i < m; ++i) {
        for (int j = 0; j < n; ++j) {
            if (i == 0 || j == 0) dp[i][j] = matrix[i][j] - '0';
            else if (matrix[i][j] == '1') {
                dp[i][j] = min(dp[i - 1][j - 1], min(dp[i][j - 1], dp[i - 1][j])) + 1;
            }
            res = max(res, dp[i][j]);
        }
    }
    return res * res;
}

最小路径和(lc64)

  • 因为到达当前位置 (i, j) 只有两种情况,要么从上方 (i-1, j) 过来,要么从左边 (i, j-1) 过来,我们选择 dp 值较小的那个路径,即比较 dp[i-1][j] 和 dp[i][j-1],将其中的较小值加上当前的数字 grid[i][j],就是当前位置的 dp 值了
  • 因为每次只需要借鉴两个值,所以用两个变量记住它们即可
int minPathSum(vector<vector<int>>& grid) {
    if (grid.empty() || grid[0].empty()) return 0;
    for (int i = 0; i < grid.size(); ++i) {
        for (int j = 0; j < grid[i].size(); ++j) {
            if (i == 0 && j == 0) continue;
            int up = (i == 0) ? INT_MAX : grid[i - 1][j];
            int left = (j == 0) ? INT_MAX : grid[i][j - 1];
            grid[i][j] += min(up, left);
        }
    }
    return grid.back().back();
}

解码方法(lc91)

  • 法一:对已有的结果集,把当前数attach到最后一个结果后面,或者把当前数作为一个新数加入结果集;返回结果集大小
  • 法二:取s的第一位,求后面的数的numDecodings结果;取s的前两位,求后面的数的numDecodings结果
  • 法三:dp;dp[i]表示前i位能解码的数量
int numDecodings(string s) {
    if (s.empty() || s[0] == '0') return 0;
    vector<int> dp(s.size() + 1, 0);
    dp[0] = 1;
    for (int i = 1; i < dp.size(); ++i) {
        dp[i] = (s[i - 1] == '0') ? 0 : dp[i - 1];
        if (i > 1 && (s[i - 2] == '1' || (s[i - 2] == '2' && s[i - 1] <= '6'))) {
            dp[i] += dp[i - 2];
        }
    }
    return dp.back();
}

从矩阵左上角到右下角路径数(lc62)

  • 法一:dp;f[i][j] = f[i - 1][j] + f[i][j - 1]
  • 法二:相当于机器人总共走了 m + n - 2步,其中 m - 1 步向右走,n - 1 步向下走,那么总共不同的方法个数= C(m+n-2 m-1)

三角形路径和(lc120)

  • 法一:深搜,会超时
  • 法二:dp
    • 思路:dp[i][j]表示到达triangle[i][j]的最小路径和:dp[i][j]=min(dp[i-1][j],dp[i-1][j-1])
    • 改进:不用额外的dp数组,直接把和累加到triangle中
int minimumTotal(vector<vector<int>>& triangle) {
    if (!triangle.size() || !triangle[0].size()) return 0;
    for (int i = 1; i < triangle.size(); ++i) {
        for (int j = 0; j < triangle[i].size(); ++j) {
            if (j == 0) triangle[i][j] += triangle[i - 1][j];
            else if (j == triangle[i].size() - 1) triangle[i][j] += triangle[i - 1][j - 1];
            else triangle[i][j] += min(triangle[i - 1][j], triangle[i - 1][j - 1]);
        }
    }
    int res = INT_MAX;
    for (int i = 0; i < triangle.back().size(); ++i) {
        res = min(res, triangle.back()[i]);
    }
    return res;
}
  • 法三:更好的dp
int minimumTotal(vector<vector<int>>& triangle) {
    vector<int> dp(triangle.back());
    for (int i = (int)triangle.size() - 2; i >= 0; --i) {
        for (int j = 0; j <= i; ++j) {
            dp[j] = min(dp[j], dp[j + 1]) + triangle[i][j];
        }
    }
    return dp[0];
}

交叉字符串(lc97)

  • dp[i][j]表示s3的前i+j位是否由s1的前i位和s2的前j位交叉而成
bool isInterleave(string s1, string s2, string s3) {
    int len1 = s1.size(), len2 = s2.size(), len3 = s3.size();
    if (len3 != len1 + len2) return false;
    if (!len1) return s2 == s3;
    if (!len2) return s1 == s3;
    vector<vector<bool>> dp(len1 + 1, vector<bool>(len2 + 1, false));
    dp[0][0] = true;
    for (int j = 1; j <= len2; ++j) dp[0][j] = (s3.substr(0, j) == s2.substr(0, j));
    for (int i = 1; i <= len1; ++i) dp[i][0] = (s3.substr(0, i) == s1.substr(0, i));
    for (int i = 1; i <= len1; ++i) {
        for (int j = 1; j <= len2; ++j) {
            if (s3[i + j - 1] == s1[i - 1]) dp[i][j] = dp[i - 1][j];
            if (s3[i + j - 1] == s2[j - 1]) dp[i][j] = dp[i][j] || dp[i][j - 1];
        }
    }
    return dp[len1][len2];
}

打家劫舍(lc198)

  • dp[i] = max(dp[i - 2], dp[i - 3]) + nums[i]

求两个正整数数组的最长公共子串

  • dp[i][j]为以A[i]和B[j]结尾的最长公共子串

俄罗斯套娃信封问题(lc354)

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