专题复习:动态规划(1)

(1) NC127 最长公共子串

https://www.nowcoder.com/practice/f33f5adc55f444baa0e0ca87ad8a6aac?tpId=188&&tqId=38644&rp=1&ru=/activity/oj&qru=/ta/job-code-high-week/question-ranking

定义dp[i][j] 表 示 字 符 串 str1 中 第 i 个 字 符 和 str2 种 第 j 个 字 符 为 最 后 一 个 元 素 所 构 成 的 最长公共子串。 如 果 要 求 dp[i][j] , 也 就 是 str1 的 第 i 个 字 符 和 str2 的 第 j 个 字 符 为 最 后 一 个元素所构成的最长公共子串,我们首先需要判断这两个字符是否相等。
如果不相等,那么他们就不能构成公共子串,也就是

dp[i][j]=0;
如 果 相 等 , 我 们 还 需 要 计 算 前 面 相 等 字 符 的 个 数 , 其 实 就 是 dp[i-1][j-1] , 所 以 dp[i][j]=dp[i-1][j-1]+1;

在代码中,我们用dp[1][1]表示str1中的第1个元素和str2中的第1个元素所构成的最长公共子串。
 

class Solution {
public:
    /**
     * longest common substring
     * @param str1 string字符串 the string
     * @param str2 string字符串 the string
     * @return string字符串
     */
    string LCS(string str1, string str2) {
        // write code here
        vector<vector<int>> res(str1.length()+1, vector<int>(str2.length()+1, 0));
        int maxLen = 0;//表示最长公共子串的长度
        int LastIndex = 0;//表示最长公共子串最后一个元素在str1中是第几个元素
        //i表示 str1中的第i个元素
        for(int i = 0; i < str1.length(); i++)
        {
            for(int j = 0; j < str2.length();j++)
            {
                if(str1[i] == str2[j])
                {
                    res[i+1][j+1] = res[i][j] + 1;
                    if(res[i+1][j+1] > maxLen)
                    {
                        maxLen = res[i+1][j+1];
                        LastIndex = i;
                    }
                }
                else
                {
                    res[i+1][j+1] = 0;
                }
            }
        }
        string str;
        if(maxLen == 0) return "-1";
        else
        {
            for(int i = maxLen - 1; i>=0; i--)
            {
                str += str1[LastIndex - i];
            }
        }
        return str;
    }
};

进行空间优化,采用滚动数组,j要逆序遍历,为什么逆序,参考上一篇博客背包问题。

class Solution {
public:
    /**
     * longest common substring
     * @param str1 string字符串 the string
     * @param str2 string字符串 the string
     * @return string字符串
     */
    string LCS(string str1, string str2) {
        // write code here
        vector<int> res(str2.length()+1, 0);
        int maxLen = 0;//表示最长公共子串的长度
        int LastIndex = 0;//表示最长公共子串最后一个元素在str1中是第几个元素
        //i表示 str1中的第i个元素
        for(int i = 0; i < str1.length(); i++)
        {
            for(int j = str2.length() -1; j >= 0;j--)
            {
                if(str1[i] == str2[j])
                {
                    res[j+1] = res[j] + 1;
                    if(res[j+1] > maxLen)
                    {
                        maxLen = res[j+1];
                        LastIndex = i;
                    }
                }
                else
                {
                    res[j+1] = 0;
                }
            }
        }
        string str;
        if(maxLen == 0) return "-1";
        else
        {
            for(int i = maxLen - 1; i>=0; i--)
            {
                str += str1[LastIndex - i];
            }
        }
        return str;
    }
};

(2) leeetcode139. 单词拆分 https://leetcode-cn.com/problems/word-break/

dp[i] 表 示 字 符 串 的 前 i 个 字 符 经 过 拆 分 是 否 都 存 在 于 字 典 wordDict 中 。 如 果 要
求 dp[i] , 我 们 需 要 往 前 截 取 k 个 字 符 , 判 断 子 串 [i-k+1 , i] 是 否 存 在 于 字 典 wordDict中,并且前面[0,i-k]子串拆分的子串也是否都存在于wordDict中。

s.substr(j, i - j)  j表示子串的起始索引位置  i - j为子串的长度

即要判断前i个字符是否可以拆分成几个字典中存在的单词:转化为 判断前j个字符是否可以拆分成字典中存在的单词&&   (j 到i 的子串是否存在与字典中)

代码如下:
 

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        set<string> a;//将字典中的单词存入set中便于查找
        for(int i = 0; i < wordDict.size(); i++)
        {
            a.insert(wordDict[i]);
        }
        int len = s.length();
        vector<int> res(len+1, 0);
        res[0] = 1;
        for(int i = 1; i <= len; i++)
        {
            for(int j = 0; j < i; j++)
            {
                //这里的res[j]表示前j个字符, 正好对应索引从0到j-1
                if(res[j]==1 && a.find(s.substr(j, i - j))!= a.end())//
                {
                    res[i] = 1;
                    break;//直接跳出内层的for循环
                }
            }
        }
        return res[len];

    }
};

(3) leetcode 1049. 最后一块石头的重量 II

https://leetcode-cn.com/problems/last-stone-weight-ii/  (转化为0-1背包问题)

这题是让每次从数组中任意选择两个石头,让他们相互销毁,求最终剩下的一块石头最小
可能重量,如果没有剩下,则返回0。直接计算可能不太好计算,我们这样来思考一下
这题相当于一群战斗力各不相同的人相互厮杀,如果战斗力相同他们就同归于尽,如果战
斗力不同,战斗力小的死去,战斗力高的活下来,但战斗力高的战斗值要减去战斗力小的
值。最后如果他们都能同归于尽最好不过了,如果不能都同归于尽,最后会剩下一个人,
他的战斗值越小越好。
所以这题相当于把一群战斗力各不相同的人分为两组,每组战斗力总和相差越小越好,也
就是把数组分为两部分,这两部分的差值尽可能小。

解题思路如下
我 们 首 先 计 算 数 组 中 所 有 元 素 的 和 , 比 如 是 sum , 然 后 把 它 分 为 两 部 分 , 如 果 sum 是 偶数, 那 么 这 两 部 分 的 值 都 是 sum/2 , 如 果 sum 是 奇数, 则 一 部 分 是 sum/2 , 另 一 部 分 是sum/2+1;我们取较小的sum/2。
解 题 思 路 就 变 成 了 从 数 组 中 选 择 一 些 元 素 , 让 他 们 的 和 尽 可 能 的 接 近 sum/2 , 经 过 我 们的不断分析,所以这题就变成了经典的基础背包问题,也就是说背包的容量是sum/2 ,让我们从数组选选择一些元素放到背包中,求背包所能容纳的最大价值。
定义dp[i][j] 表 示 前 i 个 元 素 放 到 背 包 容 量 为 j 的 背 包 中 , 所 能 获 取 的 最 大 价 值 , 当 我 们 选择第i个元素stones[i-1]的时候。
如果stones[i-1]<=j,说明第i个元素能放到背包中,我们可以选择放也可以选择不放,
如果选择放那么

class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        
        int sum = 0;
        for(int i = 0; i < stones.size(); i++)
        {
            sum += stones[i];
        }
        int capability = sum>>1;
        vector<vector<int>> dp(stones.size()+1, vector<int>(capability + 1, 0));

        for(int i = 1; i <= stones.size(); i++)
        {
            for(int j = 1; j <= capability; j++)
            {
                if(j - stones[i - 1] >= 0)
                {
                    dp[i][j] = max(dp[i-1][j], dp[i-1][j - stones[i - 1]] + stones[i - 1]);
                }
                else
                {
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        return (sum - dp[stones.size()][capability] * 2);
    }
};

进行空间优化

class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        
        int sum = 0;
        for(int i = 0; i < stones.size(); i++)
        {
            sum += stones[i];
        }
        int capability = sum>>1;
        vector<int> dp(capability + 1, 0);

        for(int i = 1; i <= stones.size(); i++)
        {
            for(int j = capability; j >= 1; j--)
            {
                if(j - stones[i - 1] >= 0)
                {
                    dp[j] = max(dp[j], dp[j - stones[i - 1]] + stones[i - 1]);
                }
                else
                {
                    dp[j] = dp[j];
                }
            }
        }
        return (sum - dp[capability] * 2);
    }
};

(4) leetcode第1035题 不想交的线

思路:设dp[i][j] 表示nums1 中前i个元素和nums2中前j个元素构成的最多的不相交的线的数量。

如 果 要 求 dp[i][j] , 我 们 首 先 判 断 nums1 的 第 i 个 元 素 和 nums2 的 第 j 个 元 素 是 否 相
等 如果 相 等 , 说 明 nums1 的 第 i 个 元 素 可 以 和 nums2 的 第 j 个 元 素 可 以 连 成 一 条 线 , 这 个 时候 nums1 的 前 i 个 元 素 和 nums2 的 前 j 个 元 素 所 能 绘 制 的 最 大 连 接 数 就 是 nums1 的 前 i-1个 元 素 和 nums2 的 前 j-1 个 元 素 所 能 绘 制 的 最 大 连 接 数 加1 , 也 就 是 dp[i][j]=dp[i-1][j-1]+1; 如果不相等,我们就把nums1去掉一个元素,计算nums1的前i-1个元素和nums2的前j 个 元 素 能 绘 制 的 最 大 连 接 数 , 也 就 是 dp[i-1][j] , 或 者 把 nums2 去 掉 一 个 元 素 , 计 算nums2的前j-1个元素和nums1的前i个元素能绘制的最大连接数,也就是dp[i][j-1] ,
这两种情况我们取最大的即可,所以我们可以找出递推公式

class Solution {
public:
    int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {

        vector<vector<int>> dp(nums1.size()+1, vector<int>(nums2.size()+1, 0));
        for(int i = 1; i <= nums1.size(); i++)
        {
            for(int j = 1; j <= nums2.size(); j++)
            {
                if(nums1[i - 1] == nums2[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[nums1.size()][nums2.size()];
    }
};

(5) leetcode62. 不同路径 https://leetcode-cn.com/problems/unique-paths/

思路:定义dp[i][j]表示机器人到达网格中(i, j)位置,总共的路径数。

注 意: 这 里 机 器 人 只 能 向 下 和 向 右 移 动 , 不 能 往 其 他 方 向 移 动 , 我 们 用 dp[i][j] 表 示 到 坐标(i,j)这个格内有多少条不同的路径,所以最终的答案就是求dp[m-1][n-1]。
因为只能从上面或左边走过来,所以递推公式是
dp[i][j] = dp[i-1][j] + dp[i][j-1]。
dp[i-1][j]表示的是从上面走过来的路径条数。
dp[i][j-1]表示的是从左边走过来的路径条数。

初始化:第一行和第一列中的位置的路径数都是1.

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> dp(m, vector<int>(n, 0));
        //初始化第一行和第一列的值
        for(int i = 0; i < m; i++) dp[i][0] = 1;
        for(int i = 0; i < n; i++) dp[0][i] = 1;
        for(int i = 1;i < m; i++)
        {
            for(int j = 1; j < n; j++)
            {
                dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
        return dp[m-1][n-1];
    }
};

空间优化:

由dp[i][j] = dp[i-1][j] + dp[i][j-1]可知,dp[i][j]依赖同行前面的值,并且依赖上一行同列的值。因此,j不能采用逆序来遍历,应该采用正序来遍历。

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<int> dp(n, 0);
        //初始化第一行和第一列的值
        for(int i = 0; i < n; i++) dp[i] = 1;
        for(int i = 1;i < m; i++)
        {
            for(int j = 1; j < n; j++)
            {
                dp[j] = dp[j] + dp[j-1];
            }
        }
        return dp[n-1];
    }
};

(6) leetcode 63. 不同路径 II  在上一题的基础上,加上了障碍物。https://leetcode-cn.com/problems/unique-paths-ii/

思路:在初始化第一行和第一列的值时考虑障碍物,另外,在计算其他值时同样考虑障碍物的影响

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        
       

        vector<vector<int>> dp(obstacleGrid.size(), vector<int>(obstacleGrid[0].size(), 0));
        //初始化第一行的值 初始化时要考虑障碍物
        int flag1 = 0; //表示有没有障碍物出现
        for(int i = 0; i < obstacleGrid.size(); i++) 
        {
            if(flag1 == 0 && obstacleGrid[i][0] == 0)
            {
                dp[i][0] = 1;
            }
            else if(flag1 == 0 && obstacleGrid[i][0] == 1)
            {
                dp[i][0] = 0;
                flag1 = 1;
            }
            else dp[i][0] = 0;
        }
        //初始化第一列的值 初始化时要考虑障碍物
        int flag2 = 0;
        for(int i = 0; i < obstacleGrid[0].size(); i++)
        {
            if(flag2 == 0 && obstacleGrid[0][i] == 0)
            {
                dp[0][i] = 1;
            }
            else if(flag2 == 0 && obstacleGrid[0][i] == 1)
            {
                dp[0][i] = 0;
                flag2 = 1;
            }
            else dp[0][i] = 0;
        } 
        //计算其他位置的值
        for(int i = 1;i < obstacleGrid.size(); i++)
        {
            for(int j = 1; j < obstacleGrid[0].size(); j++)
            {
                if(obstacleGrid[i][j] == 1) //考虑障碍物
                {
                    dp[i][j] = 0;
                }
                else dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
        return dp[obstacleGrid.size()-1][obstacleGrid[0].size()-1];
    }
};

(7) 剑指offer剑指 Offer 47. 礼物的最大价值

https://leetcode-cn.com/problems/li-wu-de-zui-da-jie-zhi-lcof/

class Solution {
public:
    int maxValue(vector<vector<int>>& grid) {
        //行数
        int m = grid.size();
        //列数
        int n = grid[0].size();
        vector<vector<int>> dp(m, vector<int>(n, 0));
        dp[0][0] = grid[0][0];
        //初始化第1列的价值
        for(int i = 1; i < m; i++) dp[i][0] = dp[i -1][0] + grid[i][0];
        //初始化第1行的价值
        for(int i = 1; i < n; i++) dp[0][i] = dp[0][i-1] + grid[0][i];

        for(int i = 1;i < m; i++)
        {
            for(int j = 1; j < n; j++)
            {
                dp[i][j] = ((dp[i-1][j] > dp[i][j-1])? dp[i-1][j] : dp[i][j-1]) + grid[i][j];
            }
        }
        return dp[m-1][n -1];
    }
};

(8) leetcode516. 最长回文子序列

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

求的是最长回文子序列

我们定义dp[i][j]表示字符串中从i到j之间的最长回文子序列。
1,如果s.charAt(i) == s.charAt(j),也就是说两头的字符是一样的,他们可以和中间
的最长回文子序列构成一个更长的回文子序列,即 dp[i][j] = dp[i+1][j-1] + 2

 如 果 s.charAt(i) != s.charAt(j) , 说 明 i 和 j 指 向 的 字 符 是 不 相 等 的 , 我 们 可 以 截
取,要么去掉i指向的字符,要么去掉j指向的字符,然后取最大值,即
dp[i][j] = Math.max(dp[i][j-1], dp[i+1][j])

 

 初始化:一个字符也是回文串,即dp[i][i]=1;注意二维数组dp[i][j]的定义,如果i>j是没有意义的。

 从 上 面 图 中 可 以 看 出 如 果 我 们 想 求 dp[i][j] , 那 么 其 他 3 个 必 须 都 是 已 知 的 , 很 明 显 从 上往下遍历是不行的,我们只能让i从最后一个字符往前遍历,j从i的下一个开始遍历,最后只需要返回dp[0][length - 1]即可。

class Solution {
public:
    int longestPalindromeSubseq(string s) {
        int len = s.length();
        vector<vector<int>> dp(len+1, vector<int>(len+1, 0));

        for(int i = len - 1; i >=0; i-- )
        {
            dp[i][i] = 1;
            for(int j = i+1; j < len; j++)
            {
                if(s[i] == s[j])
                {
                    dp[i][j] = dp[i+1][j-1] + 2; 
                } 
                else
                {
                    dp[i][j] = max(dp[i +1][j], dp[i][j -1]);
                }
            }
        }

        return dp[0][len - 1]; 
    }
};

(9) leetcode5 最长回文子序列 https://leetcode-cn.com/problems/longest-palindromic-substring/

中心扩展法+ 动态规划

方法一:中心扩展法

本题最容易想到的一种方法应该就是 中心扩散法。
中心扩散法怎么去找回文串?
从每一个位置出发,向两边扩散即可。遇到不是回文的时候结束。举个例子,str = acdbbdaastr=acdbbdaa 我们需要寻找从第一个 b(位置为 33)出发最长回文串为多少。怎么寻找?
首先往左寻找与当期位置相同的字符,直到遇到不相等为止。
然后往右寻找与当期位置相同的字符,直到遇到不相等为止。
最后左右双向扩散,直到左和右不相等。如下图所示:

class Solution {
public:
    string longestPalindrome(string s) {
        //中心扩展法
        int len = s.length();
        if(len < 2) return s;
        
        int maxLen = 1; //回文字符串的最长长度
        int start = 0;  //最长回文字符串的起始位置

        int left = 0, right = 0, tempLen = 1; //临时变量用来记录不同中心字符得到的最长回文子串的信息

        for(int i = 0; i < len; i++)
        {
            left = i - 1;
            right = i + 1;

            //找到左边与s[i]相同的 并将left往左移
            while(left>=0&& s[left] == s[i]) left--,tempLen++;
            while(right < len && s[right] == s[i]) right++, tempLen++;
            while(left>=0 && right < len && s[left] == s[right])
            {
                left--;
                right++;
                tempLen+=2;
            }
            if(tempLen > maxLen)
            {
                maxLen = tempLen;
                start = left + 1;//因为left多减了1 因此要加上
            }
            tempLen = 1; //注意注意::: 因为tempLen用来记录不同中心字符的最长回文子串长度,
                         //  因此,每计算完一个字符,其值要恢复初始值
        }
        return s.substr(start, maxLen);

    }
};

方法二:中心扩展法+动态规划

当利用动态规划时,dp[left][right]表示从  left 到 right组成的子串是否是回文字符串。

如果s[left] == s[right] 并且 ( right - left <=2 || dp[left+1][right -1] 为回文字符串时 ),

dp[left][right] = true;

这里 s[left] == s[right]还不够判断dp[left][right] 为回文字符串,还需要判断 left+1 到right - 1 的字符串。 既取决于dp[left+1][right -1], 另外当right - left <= 2时,也可以判定其为回文字符串。

class Solution {
public:
    string longestPalindrome(string s) {

        int len = s.length();
        if(len < 2) return s;

        int maxLen = 1; //此时,一定要初始化为1 表示回文子串的长度
        int start = 0;  //表示回文子串的起始位置对应的索引
        
        //定义状态数组
        vector<vector<bool>> dp(len + 1, vector<bool>(len + 1, false));

        for(int right = 1; right < len; right++)
        {
            for(int left = right - 1; left >= 0; left--)
            {
                //此处的判定条件比较关键
                if((s[left] == s[right]) && ((right - left <=2 || dp[left + 1][right - 1] == true)))
                {
                    dp[left][right] = true;
                    //此时,比较回文子串是否是最长的。
                    if(right - left + 1 > maxLen)
                    {
                        maxLen = right - left + 1;
                        start = left;
                    }
                }
            }
        }
        return s.substr(start, maxLen);
    }
};

(10)  leetcode122. 买卖股票的最佳时机 II

定义状态:

dp[i][0] :表示第i天手上没有股票时的最大利润。

第i天手上没有股票,可能是以下两种情况:

(1) 第i-1 天手上没有股票, 第i天也没有参与任何交易 即dp[i-1][0]

(2) 第i-1天 手上有股票,在第i天把股票都卖出去了即 dp[i-1][1] + price[i]

取两种情况的最大值。

dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i]);
 

dp[i][0] :表示第i天手上有股票时的最大利润。

第i天手上有股票,可能是以下两种情况:

(1) 第i-1 天手上没有股票, 第i天买入股票 即dp[i-1][0] - price[i]

(2) 第i-1天 手上有股票,在第i天没有交易即 dp[i-1][1] 

取两种情况的最大值。

dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i]);
 

最终的结果,应该为dp[i][0] i为最后一天。

初始化:dp[0][0] = 0  dp[0][1] = -price[0]

class Solution {
public:
    int maxProfit(vector<int>& prices) {

        int size = prices.size();
        if(size < 2) return 0;

        vector<vector<int>> dp(size, vector(2, 0));
        dp[0][0] = 0, dp[0][1] = -prices[0];
        for(int i = 1; i < size; i++)
        {
            dp[i][0] = dp[i-1][0] > (dp[i-1][1] + prices[i])? dp[i-1][0] : (dp[i-1][1] + prices[i]);

            dp[i][1] = dp[i-1][1] > (dp[i-1][0] - prices[i])? dp[i-1][1] : (dp[i-1][0] - prices[i]);

        } 
        return dp[size - 1][0];

    }
};

上 面 计 算 的 时 候 我 们 看 到 当 天 的 利 润 只 和 前 一 天 有 关 , 没 必 要 使 用 一 个 二 维 数 组 , 只 需要 使 用 两 个 变 量 , 一 个 记 录 当 天 交 易 完 之 后 手 里 持 有 股 票 的 最 大 利 润 , 一 个 记 录 当 天 交易完之后手里没有股票的最大利润.
 

class Solution {
public:
    int maxProfit(vector<int>& prices) {

        int size = prices.size();
        if(size < 2) return 0;


        int noGu = 0, youGu = -prices[0];
        for(int i = 1; i < size; i++)
        {
            noGu = noGu > (youGu + prices[i])? noGu : (youGu + prices[i]);
            youGu = youGu > (noGu - prices[i])? youGu : (noGu - prices[i]);

        } 
        return noGu;
    }
};

11. leetcode 121. 买卖股票的最佳时机

方法一:采用双指针left right    当prices[left] > prices[right]时, left = right;  right++;

否则,更新最大收益,right++;

class Solution {
public:
    int maxProfit(vector<int>& prices) {

        int left = 0, right = 1;
        int res = 0;
        int size = prices.size();
        while(right < size)
        {
            if(prices[left] > prices[right])//说明此时肯定会赔钱
            {
                left = right;
                right++;
            }
            else
            {
                res = (res > (prices[right] - prices[left]))? res: (prices[right] - prices[left]);
                right++;
            }
        }
        return res;
    }
};

12. 买卖股票的最佳时机含手续费

与10题基本一样,只不过在卖出股票时要收取手续费

状态转移方程如下:

dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee);
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);

状态数组的初始化不变。

(13) leetcode 198. 打家劫舍

定义dp[ i][0] 表示 第i家不被打劫 可以获得最大利益。此时,第i-1家 可以被打劫也可以不被打劫

dp[i][0] = max(dp[i-1][1],  dp[i-1][0] ) 

定义dp[ i][1] 表示 第i家被打劫 可以获得最大利益。此时,第i-1家 不可以被打劫。

dp[i][1] = dp[i-1][0] + nums[i]

初始化: 第一家没被偷:dp[0][0] = 0;  dp[0][1] = nums[0]

public:
    int rob(vector<int>& nums) {
        int size = nums.size();
        if(size == 0) return 0;
        if(size == 1) return nums[0];
        vector<vector<int>> dp(size, vector<int>(2, 0));

        dp[0][0] = 0;
        dp[0][1] = nums[0];
        for(int i = 1; i < size; i++)
        {
            dp[i][0] = max(dp[i-1][0], dp[i-1][1]);
            dp[i][1] = dp[i-1][0] + nums[i];
        }
        return max(dp[size - 1][1], dp[size - 1][0]);
    }
};

空间优化:

class Solution {
public:
    int rob(vector<int>& nums) {
        int size = nums.size();
        if(size == 0) return 0;
        if(size == 1) return nums[0];
        
        //nums中最左边的元素
        int noJie = 0;
        int Jie = nums[0];
        //数组中剩余元素
        for(int i = 1; i < size; i++)
        {
            int temp = Jie;
            Jie = noJie + nums[i];
            noJie = max(noJie, temp);
        }
        return max(noJie, Jie);
    }
};

一维数组:dp[i] 表示前i家 能获得的最大收益

状态转移方程: 如果第i家不打劫,dp[i] = dp[i-1]

                         如果第i家被打劫, 由于被打劫的家庭不能连续,因此,此时 第i - 1家不能被打劫。

                         dp[i] = dp[i - 2] + nums[ i - 1]

                         nums[i -1] 是打劫第i家获得的收益

初始化: dp[0] = 0 dp[1] = nums[0]

class Solution {
public:
    int rob(vector<int>& nums) {
        int size = nums.size();
        if(size == 0) return 0;
        if(size == 1) return nums[0];

        vector<int> dp(size + 1, 0);
        dp[0] = 0;
        dp[1] = nums[0];
        for(int i = 2; i <= size; i++)
        {
            dp[i] = max(dp[i-1], dp[i - 2] + nums[i -1]);
        }
        return dp[size];
    }
};

利用滚动数组进行优化

class Solution {
public:
    int rob(vector<int>& nums) {
        int size = nums.size();
        if(size == 0) return 0;
        if(size == 1) return nums[0];
        
        int pre = 0;
        int cur = nums[0];
        int next = 0;
        for(int i = 2; i <= size; i++)
        {
            next = max(cur, pre + nums[i -1]);
            pre = cur;
            cur = next;
        }
        return next;
    }
};

(14) leetcode213. 打家劫舍 II https://leetcode-cn.com/problems/house-robber-ii/

由于数组是环形,打劫第一家,最后一家就不能打劫。 打劫了最后一家,就不能打劫第一家。

根据第一家是否被打劫,试图将数组拆分。

class Solution {
    int helper(vector<int>& nums, int left, int right)
    {
        //数组中最左边的那家 被打劫和不被打劫的收益
        int noJie = 0;
        int Jie = nums[left];
        
        for(int i = left + 1; i < right ; i++)
        {
            int temp = Jie;
            Jie = noJie + nums[i];
            noJie = max(noJie, temp);
        }
        return max(noJie, Jie);
    }

public:
    int rob(vector<int>& nums) {
        int size = nums.size();
        if(size == 0) return 0;
        if(size == 1) return nums[0];
        //第一家被打劫  最后一家不可被打劫  数组拆分为0 到 nums.size() - 1
        int robfirst = helper(nums, 0, nums.size() - 1); 
        //第一家不被打劫 最后一家可以被打劫 数组拆分为 1 到 nums.size()
        int noRobFirst = helper(nums, 1, nums.size());

        return max(robfirst, noRobFirst);
    }
};

(15) leetcode 53. 最大子序和

dp[i] 表示数组nums中前 i+1元素的最大子序列之和

初始化:dp[0] = nums[0]

状态转换方程:对于dp[i]  如果dp[i - 1] > 0   dp[i] = dp[i -1] + nums[i]

                                         如果dp[i - 1] < 0   dp[i] = nums[i]

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int size = nums.size();
        if(size == 0) return 0;
        if(size == 1) return nums[0];

        vector<int> dp(size, 0);
        int maxSum = nums[0];
        dp[0] = nums[0];
        for(int i = 1; i < size; i++)
        {
            dp[i] = (dp[i-1] > 0)? dp[i -1] + nums[i] : nums[i];
            maxSum = maxSum > dp[i] ? maxSum: dp[i];
        }
        return maxSum;
    }
};

优化空间,只需要两个变量pre 和cur 来代替 dp数组

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int size = nums.size();
        if(size == 0) return 0;
        if(size == 1) return nums[0];

        //vector<int> dp(size, 0);
        int maxSum = nums[0];
        //dp[0] = nums[0];
        int pre = nums[0];
        int cur = 0;
        for(int i = 1; i < size; i++)
        {
            cur = (pre > 0)? pre + nums[i] : nums[i];
            pre = cur;
            maxSum = maxSum > cur ? maxSum: cur;
        }
        return maxSum;
    }
};

(16)leetcode面试题 17.16. 按摩师 与打家劫舍思路一样

(1)动态规划

class Solution {
public:
    int massage(vector<int>& nums) {
        int size = nums.size();
        if(size == 0) return 0;
        if(size == 1) return nums[0];

        vector<vector<int>> dp(size, vector(2, 0));
        dp[0][0] = 0;
        dp[0][1] = nums[0];
        for(int i = 1; i < size; i++)
        {
            dp[i][0] = max(dp[i - 1][0], dp[i-1][1]);
            dp[i][1] = dp[i-1][0] + nums[i];
        }
        return max(dp[size - 1][0], dp[size - 1][1]);

    }
};

(2)空间优化

class Solution {
public:
    int massage(vector<int>& nums) {
        int size = nums.size();
        if(size == 0) return 0;
        if(size == 1) return nums[0];
        
        int JieShou = nums[0];
        int NoJieShou = 0;
        for(int i = 1; i < size; i++)
        {
            int temp = NoJieShou;
            NoJieShou = max(NoJieShou, JieShou);
            JieShou = temp + nums[i];
        }
        return max(JieShou, NoJieShou);

    }
};

(17) leetcode300. 最长递增子序列用dp[i]表示数组的前i个元素构成的最长上升子序列,如果要求dp[i],我们需要用num[i]和前面的数字一个个比较,如果比前面的任何一个数字大,说明加入到他的后面可 以 构 成 一 个 上 升 子 序 列 , 就 更 新 dp[i] 。 我 们 就 以 [8 , 2 , 3 , 1 , 4] 为 例 来 画 个 图 看一下

 

 

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int size = nums.size();
        if(size <= 1) return size;
        int maxLen = 1;
        vector<int> dp(size, 1);
        for(int i = 1; i < size; i++)
        {
            for(int j = 0; j < i; j++)
            {
                if(nums[i] > nums[j])
                {
                    dp[i] = max(dp[i], dp[j] + 1);
                    maxLen = max(maxLen, dp[i]);
                }
            }
        }
        return maxLen;
    }
};

(18) leetcode 718. 最长重复子数组

dp[i][j] 表示以nums1[i] 和nums2[j] 为结尾的最长重复子数组。

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {

        int size1 = nums1.size();
        int size2 = nums2.size();
        if(size1 == 0||size2 == 0) return 0;
        vector<vector<int>> dp(size1 + 1, vector<int>(size2 + 1, 0));
        int maxlen = 0;
        for(int i = 1; i <= size1; i++)
        {
            for(int j = 1; j <= size2; j++)
            {
                if(nums1[i - 1] == nums2[j -1])
                {
                    dp[i][j] = dp[i -1][j -1]+ 1;
                
                    if(dp[i][j] > maxlen)
                    {
                        maxlen = dp[i][j];
                    }
                }
            }
        }
        return maxlen;
    }
};

优化空间:

由于dp[i][j] = dp[i -1][j -1]+ 1;  由于dp[i][j] 依赖上一行的j-1,因此,空间优化时,j要逆序遍历。

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {

        int size1 = nums1.size();
        int size2 = nums2.size();
        if(size1 == 0||size2 == 0) return 0;
        vector<int> dp(size2 + 1, 0);
        int maxlen = 0;
        for(int i = 1; i <= size1; i++)
        {
            for(int j = size2; j >= 1; j--)
            {
                if(nums1[i - 1] == nums2[j -1])
                {
                    dp[j] = dp[j -1]+ 1;                    
                }
                else//else不能丢
                {
                    dp[j] = 0;
                }
                if(maxlen < dp[j])
                {
                    maxlen = dp[j];
                }
            }
        }
        return maxlen;
    }
};

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值