动态规划总结一

LeetCode53.最大子序列和

https://leetcode-cn.com/problems/maximum-subarray/

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

动态规划:
1.递推关系

(1).明确dp[i]表示什么:

dp[i] 表示以nums[i]为结尾的最大子序列和

假设nums数组长度为size,最终结果为:max{ dp[i] },其中0 <= i <= (size - 1)

(2).根据题意导出递推关系:

if (dp[i - 1] > 0): dp[i] = d[i - 1] + nums[i]

if (dp[i - 1] <= 0): dp[i] = nums[i]

(3).确认初始条件:

dp[0] = nums[0]

2.题解
class Solution
{
public:
    //动态规划
    int maxSubArray(vector<int>& nums)
    {
        int size = nums.size();
        if (size == 0) return 0;

        vector<int> dp(size);
        dp[0] = nums[0];

        for (int i = 1;i < size;++i)
        {
            if (dp[i - 1] > 0)
            {
                dp[i] = dp[i - 1] + nums[i];
            }
            else
            {
                dp[i] = nums[i];
            }
        }

        return *max_element(dp.begin(), dp.end());
    }
};
3.时间复杂度

假设数组nums长度为N,则时间复杂度为O(N)

4.空间复杂度

假设数组nums长度为N,则空间复杂度为O(N)

优化:
1.基本思想

因为只需要找子序列和中最大的那个值,所以我们可以在计算以每一个数组元素为结尾的子序列和的过程中,不断去更新最大的那个值。

2.题解
class Solution
{
public:
    //优化
    int maxSubArray(vector<int>& nums)
    {
        int size = nums.size();
        if (size == 0) return 0;

        int pre = nums[0];
        int result = nums[0];
        for (int i = 1;i < size;++i)
        {
            if (pre > 0)
            {
                pre = pre + nums[i];
            }
            else
            {
                pre = nums[i];
            }

            result = max(result, pre);
        }

        return result;
    }
};
3.时间复杂度

假设数组nums长度为N,则时间复杂度为O(N)

4.空间复杂度

空间复杂度为O(1)

Leetcode1143.最长公共子序列

https://leetcode-cn.com/problems/longest-common-subsequence/

定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。

若这两个字符串没有公共子序列,则返回 0。

输入:text1 = “abcbdab”, text2 = “bdcaba”
输出:4
解释:最长公共子序列是 “bcba”,它的长度为 4。

动态规划:
1.递推关系

(1).明确dp[i] [j]表示什么:

dp[i] [j]表示长度为i的字符串1和长度为j的字符串2的最长公共子序列的长度

假设text1的长度为size1,text2的长度为size2,最终结果为dp[size1] [size2]

(2).根据题意导出递推关系:

当text1[i - 1] == text2[j - 1]时,说明该字符是公共子序列的一部分。

而当text1[i - 1] != text2[j - 1]时,说明这两个字符中至少有一个字符不是公共子序列的一部分

所以:

if (text1[i - 1] == text2[j - 1]):dp[i] [j] = dp[i - 1] [j - 1] + 1

if (text1[i - 1] != text2[j - 1]):dp[i] [j] = max{ dp[i - 1] [j], dp[i] [j - 1] }

(3).确认初始条件:

dp[0] [j] = 0,其中1 <= j <= size2

dp[i] [0] = 0,其中1 <= i <= size1

2.题解
class Solution 
{
public:
    int longestCommonSubsequence(string text1, string text2) 
    {
        int size1 = text1.size();
        int size2 = text2.size();

        vector<vector<int>> dp(size1 + 1, vector<int>(size2 + 1, 0));

        for (int i = 1;i < size1 + 1;++i)
        {
            for (int j = 1;j < size2 + 1;++j)
            {
                if (text1[i - 1] == text2[j - 1])
                {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }
                else
                {
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }

        return dp[size1][size2];
    }
};
3.时间复杂度

假设字符串text1长度为M,字符串text2长度为N,则时间复杂度为O(MN)

4.空间复杂度

假设字符串text1长度为M,字符串text2长度为N,则空间复杂度为O(MN)

LeetCode64.最小(大)路径和

https://leetcode-cn.com/problems/minimum-path-sum/

给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。

动态规划:
1.递推关系

(1).明确dp[i] [j]表示什么:

dp[i] [j]表示走到网格的第i行,第j列的时候,路径和的最小值

假设网格有m行,n列,最终结果为dp[m - 1] [n - 1]

(2).根据题意导出递推关系:

因为在仅在第一行或第一列移动时,路径是唯一的,所以初始条件为:

dp[0] [j] = dp[0] [j - 1] + grid[0] [j],其中0 <= j <= (n - 1)

dp[i] [0] = dp[i - 1] [0] +grid[i] [0] ,其中0 <= i <= (m - 1)

而不在第一行和第一列移动时,移动到一个格子可能是从上面来的,也可能是从左边来的,这时就需判断上面来的和左面来的那个路径和比较小,所以:

dp[i] [j] = min{ dp[i - 1] [j], dp[i] [j - 1]} + grid[i] [j]

2.题解
class Solution 
{
public:
    int minPathSum(vector<vector<int>>& grid) 
    {
        if (grid.size() == 0 || grid[0].size() == 0)
        {
            return 0;
        }

        int m = grid.size();
        int n = grid[0].size();

        int dp[m][n];

        dp[0][0] = grid[0][0];

        for (int i = 1;i < m;++i)
        {
            dp[i][0] = dp[i - 1][0] + grid[i][0];
        }

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

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

        return dp[m - 1][n - 1];
    }
};
3.时间复杂度

假设网格行数为M,列数为N,则时间复杂度为O(MN)

4.空间复杂度

假设网格行数为M,列数为N,则空间复杂度为O(MN)

扩展.最小(大)路径和2

给定一个m * n的二维数组。现在从左上走到右下再回到左上,中间不走重复的点,求所走单元的和的最大值。

动态规划:
1.思路

(1).分析问题:

设二维数组grid行数为M,列数为N。

首先,将问题转换为求两个对象同时从(0, 0)出发到(M - 1, N - 1),途中两者不能相遇。

使用一个三维数组dp[step] [row1] [row2],step代表两者当前步数,row1,row2分别代表两者当前所在的行,相应的两者当前所在的列可以通过step - row1 -1, step - row2 -1计算得到。两者同时从起点出发,两者走到同一位置也肯定是同时到达:如果一起走到了终点,则返回终点的值;如果一起走到了除终点外的同一位置,则返回0 。

(2)递推公式:

dp [step] [row1] [row2] =

max(两者同时向下,一个向下,另一个向右,一个向右,另一个向下,两者同时向右) +

grid[row1] [step - row1 -1] + grid[row2] [step - row2 -1]

2.源码
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;


int maxPathSum2(vector<vector<vector<int>>>& dp, 
                const vector<vector<int>>& grid,
                int step, int row1, int row2)
{
    int m = grid.size();
    int n = grid[0].size();

    //两者同时走到右下
    if (step == m + n -1)
    {
        return grid[row1][step - row1 - 1];
    }

    //两者在非起点非终点位置相遇
    if (row1 == row2 && step != 1)
    {
        return 0;
    }

    //使用dp[step][row1][row2]存储记忆化搜索
    if (dp[step][row1][row2] != -1)
    {
        return dp[step][row1][row2];
    }

    int result = 0;

    //两者同时向下
    if (row1 < m - 1 && row2 < m - 1)
    {
        result = 
        max(result, maxPathSum2(dp, grid, step + 1, row1 + 1, row2 + 1));
    }

    //一个向下,另一个向右
    if (row1 < m - 1 && step - row2 - 1 < n - 1)
    {
        result = 
        max(result, maxPathSum2(dp, grid, step + 1, row1 + 1, row2));
    }

    //一个向右,另一个向下
    if (step - row1 - 1 < n - 1 && row2 < m - 1)
    {
        result = 
        max(result, maxPathSum2(dp, grid, step + 1, row1, row2 + 1));
    }

    //两者同时向右
    if (step - row1 - 1 < n - 1 && step - row2 - 1 < n - 1)
    {
        result = 
        max(result, maxPathSum2(dp, grid, step + 1, row1, row2));
    }

    result = 
    result + grid[row1][step - row1 -1] + grid[row2][step - row2 -1];
    dp[step][row1][row2] = result;
    return result;
}

int main()
{
    vector<vector<int>> grid = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    vector<vector<vector<int>>> dp
    (5, vector<vector<int>>(3, vector<int>(3, -1)));

    cout << "二维数组为: " << endl;
    for (int i = 0;i < grid.size();++i)
    {
        for (int j = 0;j < grid[0].size();++j)
        {
            cout << grid[i][j] << " ";
        }
        cout << endl;
    }

    cout << "所走单元之和最大值为: " << maxPathSum2(dp, grid, 1, 0, 0) - grid[0][0] <<endl;
    return 0;
}

扩展.ski

Michael 想知道在一个区域中最长的滑坡。 区域由一个二维数组给出,数组的每个数字代表点的高度。当且仅当高度减小,一个人可以从某个点滑向上下左右相邻四个点之一。

动态规划:
1.递推关系

(1).明确dp[i] [j]表示什么:

dp[i] [j]表示从二维数组height的第i行,第j列那个点出发的最长滑波的长度

假设二维数组height有m行,n列,最终结果为:

max{ dp[i] [j] } + 1,其中0 <= i <= m - 1, 0 <= j <= n - 1

(2).根据题意导出递推关系:

只要一个点的高度大于四周任意一点的高度,那么就能从这个点向四周滑行一个单位,所以:

dp[i] [j] = max{ dp[i-1] [j], dp[i] [j-1], dp[i+1] [j], dp[i] [j+1] } + 1

2.源码
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

//滑行数组
vector<int> dx = {1, -1, 0, 0};
vector<int> dy = {0, 0, 1, -1};

//辅助函数,判断是否越界
bool inBound(int rows, int columns, int i,int j)
{
    return (i >= 0 && i < rows && j >=0 && j <columns);
}

int dpSki(const vector<vector<int>>& height, 
          vector<vector<int>>& dp, int i, int j)
{
    int rows = height.size();
    int columns = height[0].size();

    //使用dp[i][j]存储记忆化搜索
    if (dp[i][j] != 0)
    {
        return dp[i][j];
    }

    for (int k = 0;k < 4;++k)
    {

        //未越界
        if (inBound(rows, columns, i + dx[k], j + dy[k]))
        {
            //四周比中间低
            if (height[i + dx[k]][j + dy[k]] < height[i][j])
            {
                int temp = dpSki(height, dp, i + dx[k], j + dy[k]);
                dp[i][j] = max(dp[i][j], temp + 1);
            }
        }
    }

    return dp[i][j];
}

int main()
{
    int rows;
    int columns;
    cout << "请输入行数和列数: " << endl;
    cin >> rows >> columns;

    cout << "请输入高度矩阵: " << endl;
    vector<vector<int>> height(rows, vector<int>(columns, 0));
    for (int i = 0;i < rows;++i)
    {
        for (int j = 0;j <columns;++j)
        {
            cin >> height[i][j];
        }
    }

    vector<vector<int>> dp(rows, vector<int>(columns, 0));

    int maxDist = 0;
    for (int i = 0;i < rows;++i)
    {
        for (int j = 0;j <columns;++j)
        {
            int temp = dpSki(height, dp, i, j);
            maxDist = max(maxDist, temp);
        }
    }

    cout << "最长滑行距离为:" << maxDist + 1 << endl;

    return 0;
}
3.时间复杂度

假设二维数组height行数为M,列数为N,则时间复杂度为O(MN)

4.空间复杂度

假设二维数组height行数为M,列数为N,则空间复杂度为O(MN)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值