算法学习——线性DP

连续子数组的最大和

在这里插入图片描述
采用动态规划求解,只需要O(n)时间复杂度即可求出。定义dp[i]为末尾下标为i的子数组的最大值,它只能通过原数组对应的值和dp数组前一项(末尾下标为i-1的子数组最大值)与原数组对应的值之和转移而来:
dp[i]=max(nums[i],nums[i]+dp[i-1])

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n=nums.size(),res=-1e8;
        vector<int>dp(n+1);
        for(int i=0;i<n;i++){
            dp[i+1]=max(nums[i],nums[i]+dp[i]);
            res=max(res,dp[i+1]);
        }
        return res;
    }
};

最长递增子序列

在这里插入图片描述
定义dp[i]为子序列末尾下标为i的最长递增子序列长度,对于原数组中每个数本身可以为一个子序列,所以dp[i]初值为1,然后在下标i的前面找到下标j满足nums[j] < nums[i],就可以尝试转移,转移方程如下:
dp[i]=max(dp[i],dp[j]+1) (0<=j<i)
朴素版本的求解代码如下:(时间复杂度为O(n^2))

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

当然还可以采用二分查找的方法对其进一步优化,即定义一个数组d,该数组表示所求的最长递增子序列,当nums[i]>d[len-1]时显然可以直接将nums[i]插入到最长递增子序列中,否则就在d数组中二分查找,找到第一个比nums[i]大的数将其赋值为nums[i],数组遍历一遍之后就得到了最长递增子序列d以及它的长度,时间复杂度为O(nlogn),代码如下:

class Solution {
public:
int d[3000];
    int lengthOfLIS(vector<int>& nums) {
        int n=nums.size(),res=0,len=0;
        for(int i=0;i<n;i++){
            if(len==0||nums[i]>d[len-1]){
                d[len]=nums[i];
                len++;
            }
            else{
                int pos=lower_bound(d,d+len,nums[i])-d;
                d[pos]=nums[i];
            }
        }
        return len;
    }
};

统计放置房子的方式数

在这里插入图片描述
在这里插入图片描述
还是采用动态规划法求解,定义方程为dp[i][4]表示当前地块编号为i的放置房子的总方案统计,第二维是放置房子的4种状态,即:两边都不放、放在上方、放在下方、两边都放,同时注意取模,对应的转移方程有:
dp[i][0]=(dp[i-1][0]+dp[i-1][1]+dp[i-1][2]+dp[i-1][3])%mod;
dp[i][1]=(dp[i-1][0]+dp[i-1][2])%mod;
dp[i][2]=(dp[i-1][0]+dp[i-1][1])%mod;
dp[i][3]=dp[i-1][0];

AC代码:

class Solution {
public:
long long dp[10010][4],mod=1e9+7;
    int countHousePlacements(int n) {
        int res=0;
        for(int i=0;i<4;i++)
        dp[1][i]=1;
        for(int i=2;i<=n;i++){
            dp[i][0]=(dp[i-1][0]+dp[i-1][1]+dp[i-1][2]+dp[i-1][3])%mod;
            dp[i][1]=(dp[i-1][0]+dp[i-1][2])%mod;
            dp[i][2]=(dp[i-1][0]+dp[i-1][1])%mod;
            dp[i][3]=dp[i-1][0];
        }
        for(int i=0;i<=3;i++)
        res=(res+dp[n][i])%mod;
        return res;
    }
};

Computer Game

在这里插入图片描述
在这里插入图片描述
根据样例解释,我们不难发现这个递推关系式。首先定义字符数组g[110][2]将两行01字符串存入数组中,再定义一个同样大小的布尔类型数组vis表示每个位置是否可以到达(0表示可达,1表示不可达)。在0的位置是否可以到达根据前一步的状态进行判断,在1的位置根本无法到达,转移方程为:
if(g[0][i] == ‘0’)
vis[0][i]=vis[0][i-1]|vis[1][i-1];
if(g[1][i] == ‘0’)
vis[0][i]=vis[0][i-1]|vis[1][i-1];

AC代码:

#include<bits/stdc++.h>
using namespace std;
int t;
int n;
char g[2][110];
bool vis[2][110];
int main(){
	cin>>t;
	while(t--){
		memset(vis,0,sizeof(vis));
		cin>>n;
		cin>>g[0]>>g[1];
		vis[0][0]=!(g[0][0]-'0');
		vis[1][0]=!(g[0][1]-'0');
		bool flag=true;
		for(int i=1;i<n;i++){
		if(g[0][i]=='0')
		vis[0][i]=vis[0][i-1]|vis[1][i-1];
		if(g[1][i]=='0')
		vis[0][i]=vis[0][i-1]|vis[1][i-1];
		if(vis[0][i]==0&&vis[1][i]==0){
				flag=false;
				break;
			}
		} 
		if(flag)
		cout<<"YES"<<'\n';
		else
		cout<<"NO"<<'\n';
	}
} 

像这种动态规划求解的题目,种类特别多,这里列出的只是简单的线性dp,而要真正练好dp需要不断总结经验和熟悉它的各种变形。仅仅背代码模板是不管用的,关键在于理解它的计算过程和状态定义。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值