算法设计-动态规划(1)-子序列和最大问题

这篇博客介绍了动态规划在解决子序列和最大问题中的应用,包括子序列和最大、子序列的绝对值最大、子序列的积最大、两段重合不连续子序列最大和以及子序列和长度受限等场景。通过递推方程和维护最大值、最小值数组来求解,并提到了将二维问题转化为一维问题的技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

算法设计-动态规划(1)

子序列和最大问题
  1. 子序列和最大
    递推方程:d[i] = max{a[i], d[i-1]+a[i]},d[i]表示的是子序列一定会包含第i个元素的最大和,要么是从a[i]开始,要么是在原来的最大序列上再添一个a[i]。最终得到的最大子序列和是max{d[i]}(0<=i<=n)
    代码如下:
//b[i]的状态转移方程:b[i]=max{b[i-1]+a[i],a[i]}。最终我们得到的最大子段和为max{b[i], 0<=i<n}
int findmax(int arr[], int n) {
    int maxvalue = -200, temp = 0;
    for(int i=0; i<n; i++) {
        if(temp > 0) {
            temp += arr[i]; //这里是用前i个数和第i个数进行比较,如果前i-1个数已经大于0,那么肯定是前i个数的和大于第i个数
        }
        else temp = arr[i]; //以上是一定包含第i个数的最大值
        if(temp > maxvalue) {
            maxvalue = temp;
        }
    }
    return maxvalue;
}
  1. 子序列和的绝对值最大
    现求子序列的最大和,再求子序列的最小和,之后两者比较一下

  2. 子序列的积最大
    维护两个数组,一个是最大值,一个是最小值,在遍历到下一个元素的时候,分别都要记录最大值和最小值(防止负负得正之后出现了最大值)

  3. 两段重合不连续子序列最大和
    要维护两个数组,一个是从前向后记录最大值,一个是从后向前记录最大值,然后把这两个数组的和相加找一个最大值

int solve(int arr[], int n) {
	int dp[Max], lt[Max], rt[Max], maxvalue;
	dp[0] = lt[0] = dp[n+1] = rt[n+1] = maxvalue =  -INF;
	for(int i=1; i<=n; i++) {	//dp记录的是包含第i个元素的序列和最大值
		dp[i] = max(dp[i-1]+arr[i], arr[i]);
	}
	for(int i=1; i<=n; i++) {
		lt[i] = max(dp[i], lt[i-1]);	//这个找到的是前i个元素的最大值
	}
	for(int i=n; i>=1; i--) {
		dp[i] = max(dp[i+1]+arr[i], arr[i]);
	}
	for(int i=n; i>=1; i--) {
		rt[i] = max(rt[i+1], rt[i]);
	}
	for(int i=0; i<=n; i++) {
		if(maxvalue > lt[i] + rt[i+1]) {
			maxvalue = lt[i] + rt[i+1];
		}
	}
	return maxvalue;
}
  1. 子序列和最大且长度受限
    子序列和长度不超过m
    这道题的方程就是sum[i]-min(sum[j])(j<=i).维护一个单调队列,这个单调队列的目标就是快速找到前i个元素中的最小值,这个单调队列记录的是前缀和,如果现在来了一个前缀和,比在队列中的前缀和要小,那么队列中的元素就没有必要继续存储了。
#include <cstdio>
#include <list>
using namespace std;

list<int> queue;
int sum[100];


int main() {
	int n, maxn;
	for(int i=1; i<=n; i++) {	//记录的是前缀和
		scanf("%d", &sum[i]);
		sum[i] += sum[i-1];
	}
	queue.push_front(0);
	for(int i=1; i<=n; i++) {
		if(!queue.empty() && sum[queue.front()] > sum[i]) {
			queue.pop_front();
		}
		queue.push_front(i);	//后进来的元素放到列表的头部,最小的元素始终在队列的尾部
		while(!queue.empty() && i-queue.back() > m) {	//如果长度已经超过m
			queue.pop_back();
		}
		maxn = max(maxn, sum[i] - sum[queue.back()]);
	}
	cout<<maxn<<endl;
}

  1. 子矩阵和最大
    子矩阵和最大是一个二维问题,因此要转化为一维问题,一维问题就是子序列和最大问题。那么如何转化成为一维问题呢,就是把每一行元素都加到第一行上面,这样得到的就是一维序列。
//
//  main.cpp
//  homework3.5
//
//  Created by dongyu on 2019/10/30.
//  Copyright © 2019 dongyu. All rights reserved.
//

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#define Max 105
using namespace std;

//b[i]的状态转移方程:b[i]=max{b[i-1]+a[i],a[i]}。最终我们得到的最大子段和为max{b[i], 0<=i<n}
int findmax(int arr[], int n) {
    int maxvalue = -200, temp = 0;
    for(int i=0; i<n; i++) {
        if(temp > 0) {
            temp += arr[i]; //这里是用前i个数和第i个数进行比较,如果前i-1个数已经大于0,那么肯定是前i个数的和大于第i个数
        }
        else temp = arr[i]; //以上是一定包含第i个数的最大值
        if(temp > maxvalue) {
            maxvalue = temp;
        }
    }
    return maxvalue;
}

int main(int argc, const char * argv[]) {
    // insert code here...
    int n, maxvalue = -200;
    int input[Max][Max], arr[Max];
    scanf("%d", &n);
    for(int i=0; i<n; i++) {
        for(int j=0; j<n; j++) {
            scanf("%d", &input[i][j]);
        }
    }
    for(int i=0; i<n; i++) {
        memset(arr, 0, sizeof(arr));
        for(int j=i; j<n; j++) {//从第i行开始,到第j行结束,将这些行每一行元素都加到第一行
            for(int k=0; k<n; k++) {
                arr[k] += input[j][k];
            }
            int temp = findmax(arr, n);
            if(temp > maxvalue) {
                maxvalue = temp;
            }
        }
    }
    cout<<maxvalue<<endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值