最大子段和 分治算法(算法设计与分析作业2)

20211003238 2022-09-24 23:54:37

题目描述

给定n个整数(可能为负数)组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的子段和的最大值。当所给的整数均为负数时,定义子段和为0。(要求算法的时间复杂度为O(n)。)

输入格式:
输入有两行:
第一行是n值(1<=n<=10000);
第二行是n个整数。
输出样式:
输出最大子段和。
输入样例:
6
-2 11 -4 13 -5 -2
输出样例:
20

解题思路

首先题目规定输入数字个数最小为1,输入个数为1时轻而易举可以知道结果了;当输入数字个数为2时就有三种情况,第一种是第一个数单独最大,第二种是第二个数单独最大,第三种是两个数相加最大;当输入的数字等于3的时候就可以划分子问题来解决了,可以划分为左边一个数字,右边两个数字,先求解左边最大的情况再求解右边最大的情况,最后再求解全部输入的最大的情况;当输入数字大于3也是这样子划分子问题来解决。

具体过程:
从中间划分整个数组,然后再递归左边和右边两部分再从中间划分数组,直到把问题划分到最小子问题也就是长度为1再开始从简单问题开始解决,最后逐步得到结果,而把划分的两部分合成一部分的大问题如何解决呢,这时候就需要从中间切断的数组开始分别向左遍历和向右遍历得到左边最长和右边最长子段,最后看是左边大还是右边大,亦或是两者之和最大。

复杂度分析:
每次都是解决左右两个子问题,并且最后要把n个子问题答案合并,时间复杂度为T(n) = 2 T(n/2) + n,用主定理分析得d=1=log2/2,故T(n) = nlogn
原地操作,空间复杂度为O(1)

伪代码:

getSubMaxSum(*ints, left, right)
    mid = (left + right) / 2
    //递归划分左右两边
    leftMAx = getSubMaxSum(ints, left, mid)
    rightMax = getSubMaxSum(ints, mid + 1, right)
    maxSum = max(leftMAx, rightMax);
    //解决合并问题,从中间开始分别向左和向右遍历得到答案
    temp = 0
    sum1 = ints[mid]

    for i = mid to left
        do temp += ints[i]
             sum1 = max(temp, sum1)
   
    temp = 0;
    sum2 = ints[mid + 1]

    for i = mid + 1 to right 
        do temp += ints[i]
             sum2 = max(temp, sum2)
   
    maxSum = max(sum1 + sum2, maxSum)

完整代码

分治法

#include <bits/stdc++.h>

using namespace std;

int getSubMaxSum(int *ints, int left, int right) {
    if (left == right) {
        return ints[0];
    }
    int mid = (left + right) / 2, maxSum;
    int leftMAx = getSubMaxSum(ints, left, mid);
    int rightMax = getSubMaxSum(ints, mid + 1, right);
    maxSum = max(leftMAx, rightMax);
    int temp = 0, sum1 = ints[mid];
    for (int i = mid; i >= left; i--) {
        temp += ints[i];
        sum1 = max(temp, sum1);
    }
    temp = 0;
    int sum2 = ints[mid + 1];
    for (int i = mid + 1; i <= right; i++) {
        temp += ints[i];
        sum2 = max(temp, sum2);
    }
    maxSum = max(sum1 + sum2, maxSum);
    return maxSum;
}

int main() {
    int length, count = 0;
    cin >> length;
    int *ints = new int[length];
    //输入时直接判断是不是负数,全为负数直接输出0并结束
    for (int i = 0; i < length; ++i) {
        cin >> ints[i];
        if (ints[i] < 0) {
            count++;
        }
    }
    if (count == length) {
        cout << 0 <<endl;
    } else {
        cout << getSubMaxSum(ints, 0, length - 1) << endl;
    }

    return 0;
}

自己不用分治法解决的代码

#include <bits/stdc++.h>

using namespace std;

int solve(int *ints, int length) {
    //长度小于1直接返回,但题目规定长度最小为1if (length < 1) {
        return 0;
    }
    int max = ints[0], temp, last = ints[0], num = 0;
    if (max < 0) {
        num++;
    }
    for (int i = 1; i < length; ++i) {
        if (ints[i] < 0) {
            num++;
        }
        temp = ints[i] + last;
        if (ints[i] > temp) {
            last = ints[i];
            temp = ints[i];
        } else {
            last = temp;
        }
        if (temp > max) {
            max = temp;
        }
    }
    if (num == length) {
        return 0;
    }
    return max;
}

int main() {
    int length;
    cin >> length;
    int *ints = new int[length];
    for (int i = 0; i < length; ++i) {
        cin >> ints[i];
    }
    cout << solve(ints, length) << endl;
    return 0;
}

对分治法的体会和思考

分治法就是将一个难以解决的大问题分解成许多个简单的子问题,之后对子问题进行逐个击破,最终得到结果。问题可以分解成许多小问题,分解的子问题的解可以合并成最终问题的解并且子问题之间相互独立就可以使用分治法解决问题。感觉使用分治法就分为分解子问题,解决子问题,合并子问题三步进行。分析复杂度时也是分为分解子问题和解决子问题和合并子问题来研究,首先看把问题分为多少个子问题和每个子问题的规模(如果规模都一样,一分为二,时间就是解决一个子问题的2倍且每个子问题规模为一半),然后再看合并子问题的时间就可以得到时间复杂度,最后用主定理分析就好。

...全文
142 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

434

社区成员

发帖
与我相关
我的任务
社区描述
广东外语外贸大学信息科学与技术学院
算法 高校
社区管理员
  • brisksea
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧