datawhale组队训练——LeetCode分类练习——动态规划

题目链接:
第一题:5. 最长回文子串
第二题:72. 编辑距离
第三题:198. 打家劫舍
第四题:213. 打家劫舍II
第五题:516. 最长回文子序列
第六题:674. 最长连续递增序列

目录

      • 5. 最长回文子串
      • 72. 编辑距离
      • 198. 打家劫舍
      • 213. 打家劫舍II
      • 516. 最长回文子序列
      • 674. 最长连续递增序列

5. 最长回文子串

题目描述
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。

示例 2:
输入: “cbbd”
输出: “bb”
思路分析

暴力枚举法:
先枚举子串的端点 i 和 j ,然后再判断i~j的子串是否是回文串,如果是的话并且比原答案要长,就更新答案。此算法的复杂度为

中心扩展法思路分析:对该字符串的每一个位置都枚举一次,并以当前位置 i 扩展开,如果符合回文串的规定,并且比答案要长,我们就更新答案。粗略计算的该算法理论复杂度为O(N*N),但实际上不用花这么多时间。

DP版思路分析:
第一种:将字符串翻转形成一个新的字符串,这时两个字符串的最长公共子串就是最长回文子串,因为如果翻转过后还是有一样的子串,就说明这个子串是个回文串。
第二种:

马拉车算法思路分析:

程序代码
c++dp版

class Solution {
public:
    string longestPalindrome(string s){
        int maxx=0,end;int store[1005][1005];
	    memset(store,0,sizeof(store));
	    if(s=="") return "";
	    string result("");
	    int n=s.size();
	    string reverseS=s;
	    reverse(reverseS.begin(),reverseS.end());
	    for(int i=0;i<n;++i)
		    for(int j=0;j<n;++j) 
			    if(s[i]==reverseS[j]){//这里利用查找公共子串的dp算法
				    if(i==0||j==0) store[i][j]=1;
				    else store[i][j]=store[i-1][j-1]+1;
				    if(store[i][j]>maxx) {
					//利用第二个reverseS字符串是由第一个翻转形成的特点,prej其实就是s字符里面回文串的起点 
					    int prej = n-1-j;
					    int nowj = prej+store[i][j]-1;
					    if(nowj==i) {
					    	end=i;
					    	maxx=store[i][j];
					    }	
				    }
			    }
		    result = s.substr(end+1-maxx,maxx);
		    return result;
        }
};

c++ 中心扩展版

class Solution {
public:
    string longestPalindrome(string s){
        //判断空字符串的情况
        if (s == ""){return "";}
        string result("");
        int sSize = int(s.size()); //选择一个中心点,向两侧扩展
        for (int i = 0; i < sSize; i++) {
            string tmpStr = expandHelper(s, i, i);//奇数组情况
            string tmpStr2 = expandHelper(s, i, i + 1);//偶数组情况
            if (int(tmpStr.size()) > int(result.size()))
                result = tmpStr;
            if (int(tmpStr2.size()) > int(result.size()))
                result = tmpStr2;
        }
        return result;
    }
    string expandHelper(string &s, int left, int right) {
        int sSize = int(s.size());
        while (left >= 0 && right < sSize && s[left] == s[right]) {left--; right++;}
        return (s.substr(left + 1, right - left - 1));
    }
};

Python


参考博客:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/5-zui-chang-hui-wen-zi-chuan-cshi-xian-wu-chong-ji/
参考博客:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zhong-xin-kuo-san-dong-tai-gui-hua-by-liweiwei1419/(这一篇超详细)

72. 编辑距离

题目描述
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

插入一个字符
删除一个字符
替换一个字符

示例 1:
输入:word1 = “horse”, word2 = “ros”
输出:3
解释:
horse -> rorse (将 ‘h’ 替换为 ‘r’)
rorse -> rose (删除 ‘r’)
rose -> ros (删除 ‘e’)

示例 2:
输入:word1 = “intention”, word2 = “execution”
输出:5
解释:
intention -> inention (删除 ‘t’)
inention -> enention (将 ‘i’ 替换为 ‘e’)
enention -> exention (将 ‘n’ 替换为 ‘x’)
exention -> exection (将 ‘n’ 替换为 ‘c’)
exection -> execution (插入 ‘u’)
思路分析
当 word1[i] == word2[j],dp[i][j] = dp[i-1][j-1];

当 word1[i] != word2[j],dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1

其中,dp[i-1][j-1] 表示替换操作,dp[i-1][j] 表示删除操作,dp[i][j-1] 表示插入操作。

作者:powcai
链接:https://leetcode-cn.com/problems/edit-distance/solution/zi-di-xiang-shang-he-zi-ding-xiang-xia-by-powcai-3/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

程序代码

class Solution {
public:
    int minDistance(string word1, string word2) {
    int n=word1.size(),m=word2.size();
	int dp[n+1][m+1];
	word1=" "+word1;
	word2=" "+word2;
	//行(i)表示通过变换后要形成的字符串,列(j)表示 
    dp[0][0]=0;
	for(int i=1;i<=n;++i) dp[i][0]=i;
	for(int j=1;j<=m;++j) dp[0][j]=j;
	for(int i=1;i<=n;++i) 
		for(int j=1;j<=m;++j) 
			if(word1[i]==word2[j]) dp[i][j]=dp[i-1][j-1];
			else {
				dp[i][j]=1+min(dp[i-1][j],min(dp[i-1][j-1],dp[i][j-1]));
			}
			//dp[i-1][j-1]表示将当前位置的替换掉 
			//dp[i-1][j]表示 
			//dp[i][j-1] 
	return dp[n][m];
}
};

参考博客:
一次由爬楼梯和零钱兑换II引起的DP子问题定义思考

https://leetcode-cn.com/problems/edit-distance/solution/bian-ji-ju-chi-dong-tai-dpijguan-xi-shi-jie-shi-by/

198. 打家劫舍

题目描述
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 1:
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

示例 2:
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。

提示:
0 <= nums.length <= 100
0 <= nums[i] <= 400
思路分析

本题用一个简单的dp就可以表示出来。
dp[i][0]表示第i座房子选择不偷,
dp[i][1]表示第i座房子选择偷,
状态转移方程:
①dp[i][0]=max(dp[i-1][0],dp[i-1][1]);//当本次第i座房子不偷的话,可以去前一次头或者不偷的最大值来更新当前状态
②dp[i][1]=max(dp[i-1][0]+nums[i],dp[i][1]);//如果本次第i座房子要偷的话,那么选择上一次不偷加上本次偷的数量与本次的相比较,取最大值。

程序代码
c++版

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

c++优化版1

int rob(vector<int>& nums) {
    if (nums.size() == 0) {
        return 0;
    }
    // 子问题:
    // f(k) = 偷 [0..k) 房间中的最大金额

    // f(0) = 0
    // f(1) = nums[0]
    // f(k) = max{ rob(k-1), nums[k-1] + rob(k-2) }

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

c++优化版2

int rob(vector<int>& nums) {
    int prev = 0;
    int curr = 0;

    // 每次循环,计算“偷到当前房子为止的最大金额”
    for (int i : nums) {
        // 循环开始时,curr 表示 dp[k-1],prev 表示 dp[k-2]
        // dp[k] = max{ dp[k-1], dp[k-2] + i }
        int temp = max(curr, prev + i);
        prev = curr;
        curr = temp;
        // 循环结束时,curr 表示 dp[k],prev 表示 dp[k-1]
    }

    return curr;
}

空间优化参考博客:https://leetcode-cn.com/problems/house-robber/solution/dong-tai-gui-hua-jie-ti-si-bu-zou-xiang-jie-cjavap/
(因为这题比较简单,不想再去写优化后的代码,就直接看了一下别人的博客)

213. 打家劫舍II

题目描述
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

示例 1:
输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

示例 2:
输入: [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
思路分析

与上面那题不同的是,这题的房子是形成一个圆圈式排列的,所以偷了第一家就不能偷第n家,偷了第n家就不能偷第一家,在这里把边界状态要处理一下。详情请见程序代码。

程序代码

class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        if(n == 0){
            return 0;
        }else if(n < 4){
            return (*max_element(nums.begin(),nums.end()));
        }
        int dp[n][2];               //边界情况:
        dp[0][0] = 0;               //没偷第0家时,偷到前0家的最大金额为0
        dp[0][1] = nums[0];         //有偷第0家时,偷到前0家的最大金额为nums[0]
        dp[1][1] = nums[0];         //有偷第0家时,偷到前1家的最大金额为nums[0]
        dp[1][0] = nums[1];         //没偷第0家时,偷到前1家的最大金额为nums[1]
        for(int i = 2; i < n; i++){
            if(i != n - 1){
                //如果不是偷最后一家,分有偷第0家和不偷第0家两条路线进行状态转移
                dp[i][0] = max(dp[i-1][0],dp[i-2][0] + nums[i]);
                dp[i][1] = max(dp[i-1][1],dp[i-2][1] + nums[i]);
            }else{
                //偷最后一家时,如果没偷过第0家,可以直接选择偷或者不偷
                dp[i][0] = max(dp[i-1][0],dp[i-2][0] + nums[i]);
                //如果已经偷过第0家,只能选择不偷
                dp[i][1] = dp[i-1][1];
            }
        }
        //返回两种偷法中金额较大的一种
        return max(dp[n-1][0],dp[n-1][1]);
    }
};

参考博客:https://leetcode-cn.com/problems/house-robber-ii/solution/dong-tai-gui-hua-by-hardcoding-10/

516. 最长回文子序列

题目描述
给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000 。

示例 1:
输入:
“bbbab”
输出:
4
一个可能的最长回文子序列为 “bbbb”。

示例 2:
输入:
“cbbd”
输出:
2
一个可能的最长回文子序列为 “bb”。

提示:
1 <= s.length <= 1000
s 只包含小写英文字母
思路分析
程序代码

674. 最长连续递增序列

题目描述
给定一个未经排序的整数数组,找到最长且连续的的递增序列,并返回该序列的长度。

示例 1:
输入: [1,3,5,4,7]
输出: 3
解释: 最长连续递增序列是 [1,3,5], 长度为3。
尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为5和7在原数组里被4隔开。

示例 2:
输入: [2,2,2,2,2]
输出: 1
解释: 最长连续递增序列是 [2], 长度为1。

注意:数组长度不会超过10000。
思路分析

下面的c++版1,我感觉我只是用了贪心,并没有用到dp的思想。如果nums[i]>nums[i-1],就更新答案,并且ans取最大值。
当然还可以优化一下,把空间优化为O(1)

程序代码
c++版1

class Solution {
public:
    int findLengthOfLCIS(vector<int>& nums) {
        int dp[10005],ans=1; 
        int n=nums.size();
        if(n==0) return 0;
        //特殊值判断一下
	    for(int i=0;i<n;++i) dp[i]=1;
	    for(int i=1;i<n;++i) 
	    	if(nums[i]>nums[i-1]) {dp[i]=dp[i-1]+1;ans=max(ans,dp[i]);}
	    //如果后面的数大于前面的数,dp[i-1]+1,并且比较一下答案,取更大的一个。
	    	else dp[i]=1;
	    return ans;
    }
};

你可能感兴趣的:(LeetCode,区间DP,线性DP)