动态规划:最大连续子序列乘积
题目描写叙述:
设data[i]:第i个数据,dp[i]:以第i个数结尾的连续子序列最大乘积,
若题目要求的是最大连续子序列和,则易确定状态转移方程为:
dp[i]=max(data[i],dp[i-1]+data[i])(dp[i]为以第i个数结尾的连续子序列最大和)
dp2[i]:以第i个数结尾的连续子序列最小乘积
转移方程:
dp1[i]=max(data[i],dp1[i-1]*data[i],dp2[i-1]*data[i]);
给定一个浮点数序列(可能有正数、0和负数),求出一个最大的连续子序列乘积。
分析:若暴力求解,须要O(n^3)时间,太低效,故使用动态规划。设data[i]:第i个数据,dp[i]:以第i个数结尾的连续子序列最大乘积,
若题目要求的是最大连续子序列和,则易确定状态转移方程为:
dp[i]=max(data[i],dp[i-1]+data[i])(dp[i]为以第i个数结尾的连续子序列最大和)
但乘积存在负负得正的问题,即原本非常小的负数成了一个负数反而变大了。(负数逆袭了),
故不能照抄加法的转移方程,为了解决问题。须要定义两个数组:
dp1[i]:以第i个数结尾的连续子序列最大乘积dp2[i]:以第i个数结尾的连续子序列最小乘积
转移方程:
dp1[i]=max(data[i],dp1[i-1]*data[i],dp2[i-1]*data[i]);
dp2[i]=min(data[i],dp1[i-1]*data[i],dp2[i-1]*data[i]);
最后遍历dp1得到最大值即为答案。
代码例如以下:
#include<stdio.h> double max(double a,double b){return a>b?a:b;} double min(double a,double b){return a<b?a:b;} double dp1[100001]; double dp2[100001]; double data[100001]; double helper(double data[],int n) { dp1[0]=data[0]; dp2[0]=data[0]; for(int i=1;i<n;i++) { dp1[i]=max(data[i],max(dp1[i-1]*data[i],dp2[i-1]*data[i])); dp2[i]=min(data[i],min(dp1[i-1]*data[i],dp2[i-1]*data[i])); } double ans=dp1[0]; for(int i=1;i<n;i++) { ans=max(ans,dp1[i]); } return ans; } int main(void) { int i,n; while(scanf("%d",&n)!=EOF) { for(i=0; i<n; ++i) { scanf("%lf", &data[i]); } double ans = helper(data, n); printf("%lf\n",ans); } return 0; }事实上还能够对空间复杂度进行化简,代码例如以下:
double helper(double data[], int n) { double ans = data[0]; double localMax = data[0]; double localMin = data[0]; for(int i=1; i<n; ++i) { double t1 = max(data[i], max(localMax*data[i], localMin*data[i]) ); double t2 = min(data[i], min(localMax*data[i], localMin*data[i]) ); localMax = t1; localMin = t2; ans = localMax > ans ?localMax : ans; } return ans; }