剑指offer-30、连续⼦数组的最⼤和

题⽬描述

输⼊⼀个整型数组,数组⾥有正数也有负数。数组中的⼀个或连续多个整数组成⼀个⼦数组。求所有⼦数组的和的最⼤值。要求时间复杂度为 O(n) .

示例1

输⼊:1,-2,3,10,-4,7,2,-5

返回值:18

输⼊的数组为 {1,-2,3,10,-4,7,2,-5} ,和最⼤的⼦数组为 {3,10,-4,7,2} ,因此输出为该⼦数组的和 18 。

思路及解答

暴⼒破解

通过两层循环枚举所有可能的子数组起点和终点,计算每个子数组的和并记录最大值。

java 复制代码
public class Solution {
    public int maxSubArray(int[] nums) {
        int n = nums.length;
        int maxSum = Integer.MIN_VALUE; // 初始化为最小整数,以处理全负数数组

        for (int i = 0; i < n; i++) {
            int currentSum = 0; // 记录从i开始到j的子数组和
            for (int j = i; j < n; j++) {
                currentSum += nums[j]; // 累加当前元素
                if (currentSum > maxSum) {
                    maxSum = currentSum; // 更新全局最大和
                }
            }
        }
        return maxSum;
    }
}
  • 时间复杂度:O(n²),因为有两层嵌套循环。
  • 空间复杂度:O(1),只使用了常数级别的额外空间。

分治法

分治法将数组分成左右两半,分别递归求解左右半边的最大子数组和,再计算跨越中点的最大子数组和,最后合并结果。

java 复制代码
public class Solution {
    public int maxSubArray(int[] nums) {
        return divideAndConquer(nums, 0, nums.length - 1);
    }

    private int divideAndConquer(int[] nums, int left, int right) {
        if (left == right) {
            return nums[left]; // 递归基:只有一个元素
        }

        int mid = left + (right - left) / 2;
        int leftMax = divideAndConquer(nums, left, mid); // 左半部分的最大子数组和
        int rightMax = divideAndConquer(nums, mid + 1, right); // 右半部分的最大子数组和
        int crossMax = maxCrossingSum(nums, left, mid, right); // 跨越中点的最大子数组和

        return Math.max(Math.max(leftMax, rightMax), crossMax); // 返回三者中的最大值
    }

    private int maxCrossingSum(int[] nums, int left, int mid, int right) {
        int leftSum = Integer.MIN_VALUE;
        int sum = 0;
        for (int i = mid; i >= left; i--) { // 从中点向左扫描
            sum += nums[i];
            if (sum > leftSum) {
                leftSum = sum;
            }
        }

        int rightSum = Integer.MIN_VALUE;
        sum = 0;
        for (int i = mid + 1; i <= right; i++) { // 从中点向右扫描
            sum += nums[i];
            if (sum > rightSum) {
                rightSum = sum;
            }
        }

        return leftSum + rightSum; // 合并左右两部分的和
    }
}
  • 时间复杂度:O(n log n),由递归树深度(log n)和每层合并操作(n)决定。
  • 空间复杂度:O(log n),递归调用栈的深度。

动态规划

⾸先我们定义这个问题:

dpi 表示下标以i结尾的连续⼦数组的最⼤和,假设数组⼤⼩为 n ,那么最终求解的就是 dpn-1 。

下标以 i 结尾的连续⼦数组的最⼤和,怎么求呢?

要想求 dpi ,那我们现在假设⼀下,假设下标以i-1 结尾的连续⼦数组的最⼤和为 dpi-1 ,数组第 i 个元素是 numsi ,那么当前的连续⼦数组的最⼤和,要么是前⾯的加上当前的元素: dpi-1+numsi ,要么是舍弃掉之前的 dpi-1(这个很可能是负数) ,取现在的 numsi ;

因此,状态转移⽅程为:dpi = max{dpi-1+numsi , numsi}

但是,值得注意的是, Max{dpi-1+numsi,numsi} 求得的仅仅是以 i 下标结尾的⼦数组的最⼤和,之前计算的连续⼦数组最⼤和需要保存起来,不断的和当前计算的最⼤和⽐较,取最⼤值。

java 复制代码
public int FindGreatestSumOfSubArray(int[] array) {
	int res = array[0]; //记录当前所有⼦数组的和的最⼤值
	int[] dp = new int[array.length]; 
	dp[0] = array[0]; 
	
	for (int i = 1; i < array.length; i++) {
		dp[i] = Math.max(dp[i-1] + array[i], array[i]);
		res = Math.max(max, res);
	}
	
	return res;
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

这里只用到了dpi和dpi-1,显然还可以再优化。即

java 复制代码
public int FindGreatestSumOfSubArray(int[] array) {
	int res = array[0]; //记录当前所有⼦数组的和的最⼤值
	int max = array[0]; //包含array[i]的连续数组最⼤值
	
	for (int i = 1; i < array.length; i++) {
		max = Math.max(max + array[i], array[i]);
		res = Math.max(max, res);
	}
	
	return res;
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(1),只使用了两个变量。
相关推荐
Thomas.Sir22 分钟前
第13课:MyBatis-Plus整合SpringBoot4.x,分页、条件查询、逻辑删除实战
java·spring boot
meiying88381 小时前
多快递公司快递单号批量查询接口怎么对接?Python 聚合查询实现
java·大数据·python
古法安卓1 小时前
Android-GPU压测工具
android·java·android studio
SL_staff1 小时前
10人团队如何用轻量私有化文档平台实现知识复用提效?技术视角拆解B/S架构下的协作闭环
java·开源·全栈
释厄6232 小时前
同性线套娃律——圆面积必然=1+2+...+n 的求和·圆心不与 π 相关
java·人工智能·算法·1024程序员节
猫猫.1532 小时前
IO、存储、硬盘、文件系统详细总结
java·java-ee
SL_staff2 小时前
设备离线时业务不中断:JVS-IOT高可用架构解析
java·物联网·产品
郭小李3 小时前
mybatis 动态 sql
java·sql·mybatis
瑞码空间3 小时前
JSP与Servlet核心开发与CRUD实战笔记
java·笔记·servlet·jsp
知守观3 小时前
技术考古:iBATIS 2.3.4 时代的持久层,是如何用 Map 玩转 307 个存储过程的?
java·后端·mybatis