LeetCode动态规划题目总结(持续更新中)

Climbing Stairs

原文:https://blog.csdn.net/xpy870663266/article/details/104665131

LC70. Climbing Stairs

题目:https://leetcode.com/problems/climbing-stairs/submissions/

You are climbing a stair case. It takes n steps to reach to the top.
Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?

首先将问题形式化,设有 n n 级台阶,位置分别标号 [ 1 , 2 , . . . , n ] [1,2,...,n] ,我们一开始处于位置 0 0 ,最后要到达位置 n n .

dp[i]表示到达位置 i i 的方法数.

状态转移方程

到达位置i有两种情况(状态转移来源):从位置i-1走一步台阶到达i;从位置i-2走两步台阶到达位置i,故状态转移方程:dp[i]=dp[i-1]+dp[i-2]

初始状态

从位置0出发,到达位置1只有1种可能,到达位置2有2种可能.

代码如下

未经过空间优化:

class Solution {
public:
    int climbStairs(int n) {
        if(n==1)return 1;
        if(n==2)return 2;
        vector<int> dp(n+1,0);
        dp[1]=1;
        dp[2]=2;
        for(int i=3;i<=n;i++){
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
};

经过空间优化:

class Solution {
public:
    int climbStairs(int n) {
        if(n==1)return 1;
        if(n==2)return 2;
        int f_n_1=1;
        int f_n=2;
        for(int i=3;i<=n;i++){
            int tmp=f_n+f_n_1;
            f_n_1=f_n;
            f_n=tmp;
        }
        return f_n;
    }
};

LC746. Min Cost Climbing Stairs

题目:https://leetcode.com/problems/min-cost-climbing-stairs/

On a staircase, the i-th step has some non-negative cost cost[i]
assigned (0 indexed).

Once you pay the cost, you can either climb one or two steps. You need
to find minimum cost to reach the top of the floor, and you can either
start from the step with index 0, or the step with index 1.

思路:todo

代码:


class Solution {
public:
	// 未经过空间优化
    // int minCostClimbingStairs(vector<int>& cost) {
    //     vector<int> dp(cost.size()+1);
    //     if(cost.size()<=2)return 0;
    //     dp[0]=0;
    //     dp[1]=0;
    //     for(int i=2;i<dp.size();i++){
    //         dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
    //     }
    //     return dp.back();
    // }
    int minCostClimbingStairs(vector<int>& cost) {
        if(cost.size()<=2)return 0;
        int dpi_2=0;
        int dpi_1=0;
        int dpi;
        for(int i=2;i<cost.size()+1;i++){
            dpi=min(dpi_1+cost[i-1],dpi_2+cost[i-2]);
            dpi_2=dpi_1;
            dpi_1=dpi;
        }
        return dpi;
    }
};

Subarray and Substring

注意:subarray 的题目中所说的 contiguous subarray 其实就是substring的意思,所以放到一起.

LC53. Maximum Subarray

题目:https://leetcode.com/problems/maximum-subarray/

Given an integer array nums, find the contiguous subarray (containing
at least one number) which has the largest sum and return its sum.

dp[i]表示以nums[i]结尾的子数组的元素求和最大值,则有dp[i]=max(nums[i],dp[i-1]+nums[i]),由于dp[i]只依赖前一个状态dp[i-1],故可以优化为只使用一个变量来保存dp[i].

经过空间优化的代码如下

class Solution {
public:
    int maxSubArray(vector<int>& n) {
        if(n.size()==0)return 0;
        int res=n[0];  //记录dp[0]到dp[i]的最大值,作为最终结果
        int smax=n[0]; //记录dp[i]的值
        for(int i=1;i<n.size();i++){
            smax=max(n[i],n[i]+smax);
            if(smax>res)res=smax;
        }
        return res;
    }
};

LC152. Maximum Product Subarray

题目:https://leetcode.com/problems/maximum-product-subarray/

Given an integer array nums, find the contiguous subarray within an array (containing at least one number) which has the largest product.

dp[i]表示以n[i]结尾的子数组的元素求积值,本题与上一题LeetCode53(简写为LC53,下同)的区别在于,dp[i]无法仅由n[i]dp[i-1]转移得到。例如对于数组n:[-2, 3, -4]dp[1]=3max(n[2],dp[1])=3,而实际上dp[2]=n[0]*n[1]*n[2]=24≠max(n[2],dp[1])。实际上本题需要构造两个dp数组,一个是前面所说的,另一个则是表示以n[i]结尾的子数组的元素求积值。这种构造两个dp数组的问题在后面的LC309、LC714中也有用到。

设两个dp数组分别为maxdp[i]mindp[i],分别表示以n[i]结尾的子数组的元素求积最大/小值。则maxdp[i]的转移方程为:

如果n[i]大于等于0:
	maxdp[i]=max(n[i], n[i]*maxdp[i-1])
否则n[i]小于0:
	maxdp[i]=max(n[i], n[i]*mindp[i-1])

mindp[i]的转移方程为:

如果n[i]大于等于0:
	mindp=min(n[i], n[i]*mindp[i-1])
否则:
	mindp=min(n[i], n[i]*maxdp[i-1])

另外,由于两个dp数组的第i个状态都只依赖第i-1个状态,所以可以在空间上优化,分别用smaxsmin表示maxdp[i]mindp[i]

代码如下

class Solution {
public:
    int maxProduct(vector<int>& n) {
        if(n.size()==0)return 0;
        int res=n[0];
        int smin=res,smax=res; //min value and max value for current state
        for(int i=1;i<n.size();i++){
            if(n[i]>=0){
                smax=max(n[i],n[i]*smax);
                smin=min(n[i],n[i]*smin);
            }else{
            	//先用tmp保存samx(即maxdp[i-1])是因为:
            	//smax在下一行将被赋值改变,表示得到maxdp[i],但下下行的求smin,即mindp[i]时又要用到maxdp[i-1]
                int tmp=smax; 
                smax=max(n[i],n[i]*smin); 
                smin=min(n[i],n[i]*tmp);
            }
            if(smax>res)res=smax;
        }
        return res;
    }
};

LC674. Longest Continuous Increasing Subsequence —— 最长公共子串

这个很少考,因为简单,更常考的最长递增子序列。

https://leetcode.com/problems/longest-continuous-increasing-subsequence/

//这是未经过空间优化的实现
class Solution {
public:
    int findLengthOfLCIS(vector<int>& nums) {
        int n=nums.size();
        if(n==0)return 0;
        vector<int> dp(n,1);
        int max_len=1;
        for(int i=1;i<n;i++){
            if(nums[i]>nums[i-1]){
                dp[i]=dp[i-1]+1;
                if(dp[i]>max_len){
                    max_len=dp[i];
                }
            }
        }
        return max_len;
    }
};

LC5. Longest Palindromic Substring —— 最长回文子串

题目:https://leetcode.com/problems/longest-palindromic-substring/

即求最长回文子串.

Given a string s, find the longest palindromic substring in s. You may
assume that the maximum length of s is 1000.

下面代码中,dp[i][j]表示的是长度为 i 起点为 j(包括j)的子串 是否为回文子串.

class Solution:
    def longestPalindrome(self, s: str) -> str:
        n=len(s)
        if n<=1:return s
        dp=[[False for _ in range(n)] for _ in range(n+1)]
        for i in range(n):  # 长度为0或1的子串都是回文的
            dp[0][i]=True
            dp[1][i]=True
        # 目前的最长回文子串起点在0,长度为1
        mlen=1
        start=0
        for i in range(2,n+1):  # 从2开始遍历所有可能的长度
            for j in range(n-i+1):  # 遍历所有可能的起点
                if s[j]==s[j+i-1]:  # 头与尾相等才需要继续判断,不等的话保留为false,即非回文
                    dp[i][j]=dp[i-2][j+1]  # 看去掉头尾后的子串是不是回文的
                    if dp[i][j] and i > mlen:
                        mlen=i
                        start=j
        return s[start:start+mlen]

LC647. Palindromic Substrings —— 回文子串的数量

题目:https://leetcode.com/problems/palindromic-substrings/

Given a string, your task is to count how many palindromic substrings
in this string.

The substrings with different start indexes or end indexes are counted
as different substrings even they consist of same characters.

代码如下

class Solution:
    def countSubstrings(self, s: str) -> int:
        n=len(s)
        if n<=1:return n
        dp=[[False for _ in range(n)] for _ in range(n+1)]
        for i in range(n):  # 长度为0或1的子串都是回文的
            dp[0][i]=True
            dp[1][i]=True
        # 目前的(长度大于0的)回文子串数量为n
        cnt=n
        for i in range(2,n+1):  # 从2开始遍历所有可能的长度
            for j in range(n-i+1):  # 遍历所有可能的起点
                if s[j]==s[j+i-1]:  # 头与尾相等才需要继续判断,不等的话保留为false,即非回文
                    dp[i][j]=dp[i-2][j+1]  # 看去掉头尾后的子串是不是回文的
                    if dp[i][j]:
                        cnt+=1
        return cnt

Subsequence

LC300. Longest Increasing Subsequence —— 最长递增子序列 LIS

LIS问题的两个类似问题,见LC376和LC368.

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n=nums.size();
        if(n<=1)return n;
        
        int dp[n]={1};  //以nums[i]结尾的最长递增子序列的长度
        int max=1;
        for(int i=1;i<n;i++){
            dp[i]=1;
            for(int j=i-1;j>=0;j--){ //依次将nums[i]接到以nums[j](j<i)结尾的递增子序列后
                if(nums[j] < nums[i]){ //如果接上去仍然符合递增子序列
                    dp[i]=dp[j]+1>dp[i]?dp[j]+1:dp[i]; //选取其中最长的递增子序列的长度作为dp[i]
                }
            }
            if(dp[i]>max)max=dp[i];
        }
            
        return max;
    }
};

LC673. Number of Longest Increasing Subsequence —— LIS的数量

题目:https://leetcode.com/problems/number-of-longest-increasing-subsequence/

代码:

class Solution {
public:
    int findNumberOfLIS(vector<int>& nums) {
        
        int n=nums.size();
        if(n<=1)return n;
        
        vector<int> dp(n,1);
        dp[0]=1;
        vector<int> cnt(n,1); //用于记录以nums[i]结尾的LIS的个数
        int max_len=1; //记录最长的LSI的长度
        for(int i=1;i<n;i++){
            for(int j=i-1;j>=0;j--){
                if(nums[j]<nums[i]){//如果递增
                    if(dp[j]+1>dp[i]){
                        dp[i]=dp[j]+1; 
                        cnt[i]=cnt[j]; // 以nums[i]结尾的LIS数量等于以nums[j]结尾的LIS数量
                    }else if(dp[j]+1==dp[i]){
                        //如果子序列"xxx..nums[j]nums[i]"和前面的j就得到的LSI长度是一样的
                        //说明nums[i]既可以拼接到当前nums[j]之后,也可以拼接到其他nums[j]之后,所以加起来
                        //例如[1,3,5,4,7]中最长子序列是[1, 3, 4, 7]和[1, 3, 5, 7],结尾是7,而7前面既可以是4也可以是5
                        cnt[i]+=cnt[j];  
                    }
                }
            }
            if(dp[i]>max_len)max_len=dp[i]; //更新LSI长度
        }
        //记录最长LSI的个数,由于以不同nums[i]结尾的LSI可能有相同的长度,所以需要遍历求和
        int sum_max_cnt=0; 
        for(int i=0;i<n;i++)sum_max_cnt+=dp[i]==max_len?cnt[i]:0;
        return sum_max_cnt;
    }
};

LC376. Wiggle Subsequence —— 最长摆动子序列

https://leetcode.com/problems/wiggle-subsequence/

class Solution {
public:
    bool differ(int a,int b,int c){ //判断3个数 a,b,c是否符合“摆动”的定义,即c-b与b-a异号
        return (c-b)*(b-a)<0;
    }
    int wiggleMaxLength(vector<int>& nums) {
        int n=nums.size();
        if(n<2)return n;
        
        vector<int> dp(n,1);  //以nums[i]结尾的最长摆动子序列的长度
        vector<int> pre(n,-1); //记录以nums[i]结尾的最长摆动子序列的倒数第二个数字的位置
        int max_len=1;
        for(int i=1;i<n;i++){
            for(int j=i-1;j>=0;j--){
            	/*依次判断将nums[i]接到以nums[j]结尾的最长摆动子序列后面是否能构成摆动子序列
            	如果以nums[j]结尾的最长摆动子序列长度为1,则只需要nums[i]!=nums[j]就能构成2个数字的摆动子序列
            	否则需要新接上的nums[i]与以nums[j]结尾的最长摆动子序列的最后2个数字满足摆动定义*/
                if((dp[j]==1&&nums[i]!=nums[j]) 
                   ||(dp[j]>1&&differ(nums[pre[j]],nums[j],nums[i]))){
                    if(dp[j]+1>dp[i]){ //如果接到nums[j]之后是更长的摆动子序列,更新dp[i]
                        dp[i]=dp[j]+1;
                        pre[i]=j;
                    }
                }
            }
            if(dp[i]>max_len){
                max_len=dp[i];
            }
        }
        return max_len;
    }
};

LC1143. Longest Common Subsequence —— 最长公共子序列 LCS

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

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int n1=text1.size();
        int n2=text2.size();
        if(n1==0||n2==0)return 0;
        
        //dp[i][j]表示text1的前i个字符与text2的前j个字符的LCS的长度
        vector<vector<int>> dp(n1+1, vector<int>(n2+1,0)); 
        for(int i=1;i<=n1;i++){
            for(int j=1;j<=n2;j++){
                //若当前两个字符相等
                if(text1[i-1]==text2[j-1]){ //注意前i个字符的最后一个字符下标是i-1, 同理有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[n1][n2];
    }
};

LC72. Edit Distance

本题与LCS思路相同,放到一块。

在这里插入图片描述

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        n1,n2=len(word1),len(word2)
        if n1==0 or n2==0:
            return max(n1,n2)
        
        dp=[[0 for _ in range(n2+1)] for _ in range(n1+1)]
        
        for i in range(n1+1):
            dp[i][0]=i
        for j in range(n2+1):
            dp[0][j]=j
        
        for i in range(1,n1+1):
            for j in range(1,n2+1):
                if word1[i-1]==word2[j-1]:
                    dp[i][j]=dp[i-1][j-1]
                else:
                    dp[i][j]=min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1
        return dp[n1][n2]

LC516. Longest Palindromic Subsequence —— 最长回文子序列

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

最长回文子串和最长回文子序列的DP数组定义是类似的:

最长回文子串dp[i][j]表示的是长度为i起点为j子串(即s[j:j+i-1])是否为回文子串。

最长回文子序列dp[i][j]表示的是长度为i起点为j子串(即s[j:j+i-1])的最长回文子序列的长度。

class Solution:
    def longestPalindromeSubseq(self, s: str) -> int:
        n = len(s)
        if n <= 1: return n
        # 行代表长度,范围是[0,n];列代表起点,范围是[0,n-1]
        dp = [[0 for _ in range(n)] for _ in range(n + 1)]
        for i in range(n):
            dp[1][i] = 1
        for i in range(2, n + 1):
            for j in range(n - i + 1):
                if s[j] == s[j + i - 1]:
                    dp[i][j] = dp[i - 2][j + 1] + 2
                else:
                    dp[i][j] = max(dp[i - 1][j], dp[i - 1][j + 1])
        return dp[n][0]
        

Subset

LC368. Largest Divisible Subset —— 最大可除子集

https://leetcode.com/problems/largest-divisible-subset/

本题属于最长递增子序列(LIS)问题的变种,区别在于需要先排序才能方便递归。

如果一个可除集内的元素无序,那么对于一个新来的元素 b b ,需要判断其是否能整除任意一个元素才知道能否加入该可除集。实际上有个技巧是,只需要判断 b b 是否能整除其中最大的元素即可,但怎么快速找到最大元素?可以通过排序来达到目的。

对于有序的、从小到大的“可除集” [ a 1 , a 2 , . . . , a n ] [a_1,a_2,...,a_n] ,若 b b 能整除 a n a_n ,则 b b 一定能整除任意 a i a_i ,故将 b b 加入该可除集仍可构成可除集。这样,只需要判断 b b 是否能整除可除集末尾的元素就能知道 b b 能不能加入后保持其是可除的。

由于需要返回具体的子集(子序列),所以需要记录状态转移信息方便回溯。

class Solution {
public:
    vector<int> largestDivisibleSubset(vector<int>& nums) {
        int len=nums.size();
        vector<int> res;
        if(len==0)return res;
        if(len==1)return nums;
        
        sort(nums.begin(),nums.end());//排序
        
        vector<int> dp(len, 1);
		vector<int> parent(len,-1); //记录状态转移信息方便回溯
        int largest_set_size=1;
        int largest_set_end_index=0;
        
        for(int i=1;i<len;i++){
            for(int j=i-1;j>=0;j--){
                if(nums[i]%nums[j]==0){
                    //与LSI问题中的`dp[i]=max(dp[j]+1,dp[i]);`类似
                    if(dp[j]+1>dp[i]){
                        dp[i]=dp[j]+1;
                        parent[i]=j; //记录从何处转移而来,方便后面的回溯
                    }
                }
            }
            if(dp[i]>largest_set_size){ //更新最大子集大小以及以哪个元素结束
                largest_set_size=dp[i];
                largest_set_end_index=i;
            }
        }
        // 回溯得到最长可除子集
        for(int i=largest_set_end_index;i>=0;){
            res.push_back(nums[i]);
            i=parent[i];
        }
        return res;
    }
};

LC416. Partition Equal Subset Sum

本题是背包问题的变种,见 0-1背包与完全背包模板

LC494. Target Sum

本题是背包问题的变种,见 0-1背包与完全背包模板

Buy and Sell Stock —— “状态机”

原文:https://blog.csdn.net/xpy870663266/article/details/104665131

LC121. Best Time to Buy and Sell Stock

题目:https://leetcode.com/problems/best-time-to-buy-and-sell-stock/

Say you have an array for which the ith element is the price of a
given stock on day i.

If you were only permitted to complete at most one transaction (i.e.,
buy one and sell one share of the stock), design an algorithm to find
the maximum profit.

Note that you cannot sell a stock before you buy one.

对于每一天来说,在这天卖出能得到最高profit,对应的是在之前的几天中最低价的那天买入。所以只需要遍历一次,并用变量维护前面几天的最低价是多少,对于每一天用其价格减去前几天的最低价得到当天能得到的最高profit,如果这个profit是见过的最大profit(也用一个变量来保存)就更新。

代码如下

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int min_value=INT_MAX;
        int max_profit=0;
        for(auto p:prices){
            if(p-min_value>max_profit){
                max_profit=p-min_value;
            }
            if(p<min_value){
                min_value=p;
            }
        }
        return max_profit;
    }
};

LC309. Best Time to Buy and Sell Stock with Cooldown

题目:https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/

Say you have an array for which the ith element is the price of a
given stock on day i.

Design an algorithm to find the maximum profit. You may complete as
many transactions as you like (ie, buy one and sell one share of the
stock multiple times) with the following restrictions:

  • You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).
  • After you sell your stock, you cannot buy stock on next day. (ie, cooldown 1 day)

参考:这篇帖子

三种操作:

  1. buy买入股票,操作之后利益需要减去prices[i];
  2. sell卖出股票,操作之后利益需要加上prices[i];
  3. rest休息,对获得的利益没有影响。

设buy[i]代表第i天buy并在剩下的天数都采取rest操作,这种情况下能得到的最大利益。

设sell[i]代表第i天卖出股票,并在剩下的天数中都采取rest操作,这种情况下能够得到的最大利益。

容易得知,最大利益的操作序列一定是以一个sell和k个(k≥0)rest操作结尾的,不会以一个buy和k个(k≥0)rest操作结尾。因此,sell数组的最大值就是能得到最大利益。

状态转移方程

方程一:sell[i]=max(buy[i-1]+prices[i], sell[i-1]-prices[i-1]+prices[i])

理由:第i天卖出股票则在之前必须先买入股票,上次buy的时间分两种情况:

(1)在第i-1天买入;(2)在第k天(k ≤ i-2)天买入。

第(1)种情况可以用buy[i-1]+prices[i]来表示;第(2)种情况可以利用sell[i-1]来得出,这是因为sell[i-1]就包括了在第k(k ≤ i-2)天买入的所有情况。所以第(2)种情况可以用sell[i-1]-prices[i-1]+prices[i]来得到,表示“将在第i-1天卖出修改为在第i天卖出

方程二:buy[i]=max(sell[i-2]-prices[i], buy[i-1]+prices[i-1]-prices[i])

类似方程一的分析思路。根据规则,第i天买入则第i-1天必须是rest,所以上次sell的时间只能有两种情况:

(1)在i-2天卖出;(2)在第k天(k≤i-3)天卖出

两种情况分别对应sell[i-2]-prices[i]buy[i-1]+prices[i-1]-prices[i]buy[i-1]包含了所有在第k天卖出的情况,其中k≤i-3;注意对buy[i-1]来说,第i-2天肯定在rest)

代码如下

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len=prices.size();
        
        //if len==1, just REST and get max profit 0
        if(len<=1)return 0;
        //REST for 2 days Or buy in day0 sell in day1
        if(len==2)return max(0,prices[1]-prices[0]);
        
        // if at least 3 days
        vector<int> buy(len,0);
        vector<int> sell(len,0);
        
        // 初始状态的代码
        //sell[0]其实不存在,但是buy[2]肯定是等于-prices[2],为了递归方程可以算出buy[2],需要sell[0]有个合理的值
        sell[0]=0;
        buy[0]=-prices[0];  //第0天买入,亏损prices[0],注意负号
        sell[1]=buy[0]+prices[1];  //要想在第1天卖出,只可能在第0天买入
        buy[1]=-prices[1];  //要想在第1天买入,第0天只能rest(第0天buy了第1天不能buy)
        
        int max_profit=max(sell[1],0); //0 means rest for 2 days

        for(int i=2;i<len;i++){
            sell[i]=max(buy[i-1]+prices[i],sell[i-1]-prices[i-1]+prices[i]);
            buy[i]=max(sell[i-2]-prices[i],buy[i-1]+prices[i-1]-prices[i]);
            if(sell[i]>max_profit)max_profit=sell[i];
        }
        return max_profit;
    }
};

LC714. Best Time to Buy and Sell Stock with Transaction Fee

题目:https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/

Your are given an array of integers prices, for which the i-th element
is the price of a given stock on day i; and a non-negative integer fee
representing a transaction fee.

You may complete as many transactions as you like, but you need to pay
the transaction fee for each transaction. You may not buy more than 1
share of a stock at a time (ie. you must sell the stock share before
you buy again.)

Return the maximum profit you can make.

和前面的第309题思路一样,代码也几乎完全一样,区别仅在于第i天卖出时还要亏损一笔fee,所以需要修改第一条转移方程,由

sell[i]=max(buy[i-1]+prices[i], sell[i-1]-prices[i-1]+prices[i])

改为:

sell[i]=max(buy[i-1]+prices[i]-fee, sell[i-1]-prices[i-1]+prices[i])

另外,初始化sell[1]时也要记得减去fee.

代码如下

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int len=prices.size();
        //处理特殊情况,即len≤2的情况

        if(len<=1)return 0;//空序列最大利益是0,只有一个时最大利益就是rest

        //0代表两天都是rest,prices[1]-prices[0]代表第0天买入,第1天卖出
        if(len==2)return max(0,prices[1]-prices[0]);
        
        // 如果len至少为3
        vector<int> buy(len,0);
        vector<int> sell(len,0);

        //sell[0]其实不存在,但是buy[2]肯定是等于-prices[2],为了递归方程可以算出buy[2],需要sell[0]有个合理的值
        sell[0]=0;
        buy[0]=-prices[0];
        sell[1]=buy[0]+prices[1]-fee;
        buy[1]=-prices[1];

        int max_profit=max(sell[1],0); //0代表前两天全为rest,不买也不卖

        for(int i=2;i<len;i++){
            sell[i]=max(buy[i-1]+prices[i]-fee,sell[i-1]-prices[i-1]+prices[i]);
            buy[i]=max(sell[i-1]-prices[i],buy[i-1]+prices[i-1]-prices[i]);
            if(sell[i]>max_profit)max_profit=sell[i];
        }

        return max_profit;
    }
};

总结与类题

题目如果可以用状态机来描述,则可以考虑使用 S 个dp数组来求解,其中S为状态数量。每个dp[i]数组分别表示“如果第i天处于状态 S[i] ,能获得的最大/最小值”

腾讯2020校招题目:假期的最少休息天数

在这里插入图片描述

定义3个一维dp数组(放到二维数组里面),分别记录3种状态下有事可做的最多天数,休息(dp[0][i])、健身(dp[1][i])、工作(dp[2][i])。

若第 i 天休息,则无事可做,值为前一天3种状态下的最大值。
dp[0][i] = max(dp[0][i - 1], max(dp[1][i - 1], dp[2][i - 1]));

若第 i 天健身,前一天不可能健身,故值为前一天休息和工作的最大值+1。
dp[1][i] = max(dp[2][i - 1], dp[0][i - 1]) + 1;

若第 i 天工作,前一天不可能工作,故值为前一天休息和健身的最大值+1。
dp[2][i] = max(dp[1][i - 1], dp[0][i - 1]) + 1;

最后输出 n 减去最后一天三种状态下的最大值。

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
// 来自评论区的答案
int main()
{
    int n;
    cin >> n;
    vector<int> work(n), slp(n);
    for (int i = 0; i < n; ++i)
        cin >> work[i];
    for (int i = 0; i < n; ++i)
        cin >> slp[i];
    vector<vector<int>> dp(3, vector<int>(n + 1));
    dp[0][0] = dp[1][0] = dp[2][0] = 0;
    for (int i = 1; i <= n; ++i) {
        dp[0][i] = max(dp[0][i - 1], max(dp[1][i - 1], dp[2][i - 1]));
        if (slp[i - 1]) {
            dp[1][i] = max(dp[2][i - 1], dp[0][i - 1]) + 1;
        }
        if (work[i - 1]) {
            dp[2][i] = max(dp[1][i - 1], dp[0][i - 1]) + 1;
        }
    }
    cout << n - max(dp[0][n], max(dp[1][n], dp[2][n])) << endl;
 
    return 0;
}

Unique Binary Search Trees

LC96. Unique Binary Search Trees

面试题中有类似的题目:一个排序数组能够构成多少个二叉搜索树?事实上结果只和数组的元素个数有关而与元素的具体值无关,所以leetcode的题目是直接问“将1到n来排序能得到多少个二叉搜索树”。

以下思路来自:这个帖子

G(i) 表示长度为 i 的数组能构成的不同BST数量,并设 F(n,i) 表示长度为 n 的数组以其中的第 i 个数作为根的BST数量(i 从 1 开始)。

则有:

  • G(n)=F(n,1)+F(n,2)+...+F(n,n),因为根据加法定理,G(n)等于以不同元素作为根的BST数量的和
  • F(n,i)=G(i-1)*G(n-i),因为以第 i 个元素作为根时,其左子树使用 1,2,..,i-1i-1 个数来构建BST,即左子树种类等于 G(i-1) ,同理,右子树种类为 G(n-i)。根据乘法定理,结果应等于左子树和右子树的种类树的乘积。
  • G(n)=G(0)*G(n-1)+G(1)*G(n-2)+...+G(n-1)*G(0),这个是由前两条公式推出的。
  • G(0)=1(空树),G(1)=1 (只有根的树)

因此有下面的 O ( n 2 ) O(n^2) 的答案:

class Solution {
public:
	int numTrees(int n) {
		vector<int> G(n + 1, 0);
		G[0] = G[1] = 1;

		for (int i = 2; i <= n; ++i) {
			for (int j = 1; j <= i; ++j) {
				G[i] += G[j - 1] * G[i - j];
			}
		}
		return G[n];
	}
};

House Robber —— 打家劫舍

LC198. House Robber

https://leetcode.com/problems/house-robber/

dp[i]表示前i家能获取的最高金额,在第i家根据偷或不偷有两种状态转移来源。

注意:

  • dp应有n+1个状态,表示前0家、前1家、…、前n
  • i家的末尾一家下标是i-1,所以代码在更新dp[i]时使用的是nums[i-1])。
class Solution {
public:
    // 未经过空间优化的代码
    int rob(vector<int>& nums) {
        int n=nums.size();
        if(n==0)return 0;
        vector<int> dp(n+1,0);
        dp[0]=0;
        dp[1]=nums[0];
        for(int i=2;i<=n;i++){
            dp[i]=max(dp[i-1],nums[i-1]+dp[i-2]);
        }
        return dp.back();
    }
// 空间优化的代码
//     int rob(vector<int>& nums) {
//         int n=nums.size();
//         if(n==0)return 0;

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

LC213. House Robber II

https://leetcode.com/problems/house-robber-ii/

本题和上题基本类似,区别在于:所有的房子围成一个圈,意味着首尾两家也属于相邻。

基本思路当然还是动态规划,可以划分成两种情况来做,以第一家是否被偷为依据分成两个动态规划问题,如果第一家偷,那么从第一家到第n-1家求最大值(因为意味着最后一家一定不能偷);如果第一家不偷,那么从第2家到第n家求最大值。最后再比较两种情况的最大值即可。

class Solution {
public:
    int rob(vector<int>& nums) {
        if(nums.size()==0)return 0; //处理特殊情况
        if(nums.size()==1)return nums[0]; //长度为1则第一个就是最后一个,所以也是特殊情况
        // 偷第一家,即前1家的最大收益是nums[0]
        int dpi_2=nums[0];
        int dpi_1=dpi_2; //nums[1]不能选
        int dpi,max0=dpi_1;
        for(int i=2;i<=nums.size()-2;i++){ //最后一家 nums[n-1] 不能偷
            dpi=max(nums[i]+dpi_2,dpi_1);
            if(dpi>max0)max0=dpi;
            dpi_2=dpi_1;
            dpi_1=dpi;
        }
        // 不偷第一家,即前1家的最大收益是0
        dpi_2=0;
        dpi_1=nums[1];
        int max1=dpi_1;
        for(int i=2;i<nums.size();i++){ //可以考虑投最后一家,即nums[n-1]
            dpi=max(nums[i]+dpi_2,dpi_1);
            if(dpi>max1)max1=dpi;
            dpi_2=dpi_1;
            dpi_1=dpi;
        }
        return max(max0,max1);
    }
};
发布了67 篇原创文章 · 获赞 27 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/xpy870663266/article/details/104665131