连续子数组的最大和
采用动态规划求解,只需要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需要不断总结经验和熟悉它的各种变形。仅仅背代码模板是不管用的,关键在于理解它的计算过程和状态定义。