最小 最长 max_min系列 DP

300. 最长上升子序列

[10,9,2,5,3,7,101,18] --> [2,3,7,101]

暴力解法

  1. 对于每个元素,都有选 不选 两个选项,构成一个 状态树,
  2. 找状态树中 从根节点到叶子节点中 包含元素最长的路径,即为最长上升序列

在这里插入图片描述

void dfs(const vector<int>& nums,vector<int>& dp,int index,int& res){
    if(index==nums.size()){
        res = max(res,int(dp.size()));
        return ;
    }
    for(int i=0;i<2;i++){
        if(i==0){
            if(dp.empty() || nums[index] > dp.back()){
                dp.push_back(nums[index]);
                dfs(nums,dp,index+1,res);
                dp.pop_back();
            }
        }else dfs(nums,dp,index+1,res);
    }
}

int lengthOfLIS(vector<int>& nums) {
   int len = nums.size();
   if(len<=1) return len;
   int res = 0;
   vector<int> dp;
   dfs(nums,dp,0,res);
   return res;
}

动规 贪心



120. 三角形最小路径和

给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。

例如,给定三角形:

[
     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]
]
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。

如果你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题,那么你的算法会很加分。

递归 记忆化搜索

自底向上搜索,当到达叶子节点时,直接返回数值,非叶子节点可以有下一层临近节点返回
在这里插入图片描述

class Solution {
private:
    vector<vector<int>> memo;

public:
    int minimumTotal(vector<vector<int>>& triangle) {
        memo = vector<vector<int>>(triangle.size(), vector<int>(triangle.size(), -1));
        return recursion(triangle, 0, 0);
    }
    int recursion(vector<vector<int>>& triangle, int i, int j) {
        if (i == triangle.size() - 1) return triangle[i][j];
        if (memo[i][j] != -1) return memo[i][j];
        
        int a = recursion(triangle, i + 1, j);
        int b = recursion(triangle, i + 1, j + 1);
        memo[i][j] = min(a, b) + triangle[i][j];
        
        return memo[i][j];
    }
};

使用二维数组的dp动态规划
dp[i][j]表示走到第i行j列的最小路径和
则转移方程 dp[i][j] = min(dp[i-1][j-1],dp[i-1][j])+triangle[i][j]; 因为只能上一层的左和右两个元素可以走到[i][j]

需要考虑左右两侧的特俗情况 左右两侧只有固定的路径可达

// 216 三角形最小路径和
int minimumTotal(vector<vector<int>>& triangle) {
    int row = triangle.size();
    if(row==0) return 0;
    int col = triangle[row-1].size();

    vector<vector<int>> dp(row+1,vector<int> (col+1,INT32_MAX));
    dp[0][0] = triangle[0][0];

    int min_val = INT32_MAX;
    for(int i=1;i<row;i++){
        int col_ = triangle[i].size();
        for(int j=0;j<col_;j++){
            
                dp[i][j] = min(dp[i-1][j-1],dp[i-1][j])+triangle[i][j];
           
        }
    }
    for(int j=0;j<col;j++){
        min_val = min(min_val,dp[row-1][j]);
    }
    return min_val;
}


压缩状态方程空间O(n)

其实我们dp时候每次只用到上一层数据,如果我们倒着,从底向上可以优化成O(n)空间的

           |= triangle[n-1,c]       if n-1 is the last row. 
f(n-1, c) -|
           |= min{f(n,c) + triangle[n-1,c], f(n,c+1) + triangle[n-1,c]}


/*input
     *  {{2},
        {3,4},
       {6,5,7},
      {4,1,8,3}};
如果正序遍历的话, dp[j] = dp[j],dp[j-1] , 因为计算当前层 dp[j]需要用到 上一层的dp[j-1] 和 上一层的dp[j], 但是dp[j-1]已经更新为当前层的值, 
 而在计算当前层的dp[j]时,需要用到上一层的dp[j-1],但是计算当前层dp[j]前,已经更新过dp[j-1],可以从下面结果中看出

 * 2  \  \  \
 * 5  9  \  \
 * 11 14 21 \
 * 15 15 23 26
 * 
 * 倒序的话,dp[j] = min(dp[j],dp[j+1]), dp[j](当前层,更新) = dp[j](上一层.未更新),dp[j+1](上一层,未更新)
 * 计算当前层dp[j] 需要用到 上一层的dp[j] 和 dp[j+1], 相当于dp[j] 更新为当前层值,dp[j+1] 等待下一个迭代更新
 * 
 * */
int minimumTotal(vector<vector<int>>& triangle) {
    int row = triangle.size();
    if(row==0) return 0;

    vector<int> dp(row,0);
    for(int i=0;i<row;i++)
        dp[i] = triangle[row-1][i];

    for(int i=row-2;i>=0;i--){
    //
        for(int j=0;j<i+1;j++){
            dp[j] = min(dp[j],dp[j+1]) + triangle[i][j];
        }
    }
    return dp[0];
}


72. 编辑距离

在这里插入图片描述

动规

  1. 状态定义: dp[i][j]表示word1的前i个字母转换成word2的前j个字母所使用的最少操作, i指向word1,j指向word2

  2. 状态转移:

  • 如果当前字母相同,则dp[i][j] = dp[i - 1][j - 1];
  • 否则取增删替三个操作的最小值+1, 即 dp[i][j] = min(dp[i][j - 1], dp[i - 1][j], dp[i][j])

解析

  • 问题1:如果 word1[0…i-1] 到 word2[0…j-1] 的变换需要消耗 k 步,那 word1[0…i] 到 word2[0…j] 的变换需要几步呢?

  • 答:先使用 k 步,把 word1[0…i-1] 变换到 word2[0…j-1],消耗 k 步。再把 word1[i] 改成 word2[j],就行了。如果 word1[i] == word2[j],什么也不用做,一共消耗 k 步,否则需要修改,一共消耗 k + 1 步。

  • 问题2:如果 word1[0…i-1] 到 word2[0…j] 的变换需要消耗 k 步,那 word1[0…i] 到 word2[0…j] 的变换需要消耗几步呢?

  • 答:先经过 k 步,把 word1[0…i-1] 变换到 word2[0…j],消耗掉 k 步,再把 word1[i] 删除,这样,word1[0…i] 就完全变成了 word2[0…j] 了。一共 k + 1 步。

  • 问题3:如果 word1[0…i] 到 word2[0…j-1] 的变换需要消耗 k 步,那 word1[0…i] 到 word2[0…j] 的变换需要消耗几步呢?

  • 答:先经过 k 步,把 word1[0…i] 变换成 word2[0…j-1],消耗掉 k 步,接下来,再插入一个字符 word2[j], word1[0…i] 就完全变成了 word2[0…j] 了。

  • 从上面三个问题来看,word1[0…i] 变换成 word2[0…j] 主要有三种手段,用哪个消耗少,就用哪个。

class Solution {
public:
    int minDistance(string word1, string word2) {
        int l1=word1.size(),l2=word2.size();
        vector<vector<int>> dp(l1+1,vector<int>(l2+1,0));

        for(int i=0;i<=l1;i++)
            dp[i][0] = i;
        for(int i=0;i<=l2;i++)
            dp[0][i] = i;

        for(int i=1;i<=l1;i++){
            for(int j=1;j<=l2;j++){
                if(word1[i-1] == word2[j-1]){
                    dp[i][j] = dp[i-1][j-1];
                }else{
                    dp[i][j] = 1+min(dp[i-1][j],min(dp[i][j-1],dp[i-1][j-1]));
                }
            }
        } 

        return dp[l1][l2];
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值