如何求最大子数组之和

问题概述:

有一个长度为n的数组,这n个元素可以使正数也可以是负数,数组中连续的一个或多个元素可以组成一个连续的子数组,一个数组可以有多个这样连续的子数组,求子数组各个元素和的最大值。

方法一:蛮力法

找出所有子数组元素,进行求和,然后找到和的最大值

public static int findSecond(int[] arr){
        int n = arr.length;
        int thisSum=  0;
        int maxSum = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i; j < n; j++) {
                thisSum = 0;
                for (int k = i; k <j ; k++) {
                    thisSum += arr[k];
                }
                if (thisSum > maxSum){
                    maxSum = thisSum;
                }
            }
        }
        return maxSum;
    }

当然我们期望的并不是通过蛮力法解出,面试官也不太会对你认可

动态规划方法

上面的求解时间复杂度十分不理想,可以使用动态规划的方法来降低求解的时间复杂度。

首先可以根据数组的最后一个元素arr[n-1]与最大子数组的关系分为以下三种情况进行讨论

(1)最大子数组包含arr[n-1],即以arr[n-1]结尾。
(2)最大子数组单独构成最大子数组。
(3)最大子数组不包含arr[n-1],那么求arr[1,…,n-1]的最大子数组可以转换成求arr[1,…,n-2]的最大子数组。

通过上面的分析我们可以知道,

public static int maxSubArr(int[] arr) {
        int n=arr.length;
        int nAll = arr[0];//有n个数字数组的最大子数组的和
        int nEnd = arr[0];//有n个数字数组包含最后一个元素的子数组的最大和
        for (int i = 1; i < n; i++) {
            nEnd = max(nEnd+arr[i], arr[i]);
            nAll = max(nEnd, nAll);
        }
        return nAll;
    }

计算最大子数组的开始值和结束值位置

代码编写:

private static int begin = 0;
private static int end = 0;

public static int maxSubArray(int[] arr){
        int maxSum = Integer.MIN_VALUE;
        int nSum = 0;
        int nStart = 0;
        for (int i = 0; i < arr.length; i++) {
            if (nSum < 0){
                nStart = i;
                nSum = arr[i];
            }else {
                nSum += arr[i];
            }
            if (nSum > maxSum){
                maxSum = nSum;
                begin = nStart;
                end = i;
            }
        }
        return maxSum;
    }
<think>好的,我需要解决这个问题:找到数组最大子数组,但不能包含相邻元素。用户提到使用动态规划来实现。首先,我得理解这个问题。最大子数组通常可以使用动态规划,比如经典的Kadane算法,但这里有个限制是不能包含相邻元素。那如何处理这个限制呢? 根据动态规划的思路,可能需要定义状态状态转移方程。假设有一个数组nums,长度为n。我需要定义dp[i]表示前i个元素中满足条件的最大子数组。这时候需要考虑是否包含第i个元素。 如果包含第i个元素,那么第i-1个元素就不能选,所以dp[i]应该是dp[i-2] + nums[i]。如果不包含第i个元素,那么最大就是dp[i-1]。因此,状态转移方程可能是dp[i] = max(dp[i-1], dp[i-2] + nums[i])。这类似于打家劫舍问题的解法,即不能偷相邻的房子。 不过,这里需要注意数组的索引是从0开始还是从1开始。为了方便处理边界情况,可能需要初始化前两个元素。例如,当i=0时,dp[0]就是nums[0];当i=1时,dp[1] = max(nums[0], nums[1])。然后从i=2开始循环,应用状态转移方程。 接下来,验证这个思路是否正确。假设数组是[3, 2, 7, 10],最大子数组应该是3 + 10 =13,或者2+7=9,显然13更大。按照状态转移方程,dp[0]=3,dp[1]=max(3,2)=3。dp[2] = max(3, 3+7=10) → 10。dp[3] = max(10, dp[1]+10=13) →13。结果正确。 另一个例子是数组[-2, -3, -1],这时候最大应该是-1。根据dp数组:dp[0]=-2,dp[1]=max(-2,-3)=-2,dp[2]=max(-2, -2 + (-1)= -3) →-2。但实际正确结果应该是-1。这说明哪里出错了? 啊,这里的问题在于当数组全是负数时,应该选择最大的那个元素。可能我的状态转移方程没有考虑这种情况。也就是说,如果所有数都是负数,那么最大子数组就是最大的那个负数,而不是累加的结果。因此,在状态转移方程中,是否需要比较当前元素本身?例如,当i=2时,是否应该比较dp[i-1], dp[i-2] + nums[i],以及nums[i]本身? 或者,可能初始化需要调整。例如,当i=2时,dp[2]应该是max(dp[1], dp[0] + nums[2])。但在数组[-2,-3,-1]的情况下,dp[0]=-2,dp[1]=max(-2,-3)=-2。dp[2]=max(-2, -2 + (-1) = -3) →-2。但正确的答案应该是-1,这说明我的状态定义有问题。 哦,原来问题出在状态定义是否正确。也许应该将dp[i]定义为前i个元素中,可能包含或不包含第i个元素的最大,但必须满足不相邻的条件。或者,是否应该将状态分为两种情况:包含第i个元素不包含第i个元素的两个状态? 比如,参考打家劫舍问题的解法,状态数组可以有两个维度:dp[i][0]表示不选第i个元素时的最大值,dp[i][1]表示选第i个元素时的最大值。这样,状态转移方程可以写成: dp[i][0] = max(dp[i-1][0], dp[i-1][1]) dp[i][1] = dp[i-1][0] + nums[i] 然后最终的最大值是max(dp[n-1][0], dp[n-1][1]) 这样处理的话,对于数组[-2,-3,-1],当i=0时,dp[0][0]=0,dp[0][1]=-2。i=1时,dp[1][0]=max(0, -2)=0,dp[1][1]=0 + (-3)=-3。i=2时,dp[2][0]=max(0, -3)=0,dp[2][1]=0 + (-1)=-1。最终最大是0-1中的较大者,即0?这显然也不对,因为原数组最大的元素是-1,应该选-1。这说明可能我的状态定义需要调整。 或者,可能状态定义应该是必须选择一个元素的情况下?或者需要初始化不同的值? 这时候可能需要重新考虑状态定义。另一种方法是,当处理到第i个元素时,dp[i]表示在前i个元素中,必须选择第i个元素或者不选的情况下,最大。或者,参考其他类似问题的解法,比如打家劫舍中的状态转移,其中每个元素可以选择或不选,而这里的最大子数组必须是不相邻的。 比如,正确的打家劫舍解法是: dp[i] = max(dp[i-1], dp[i-2] + nums[i]) 这似乎可以处理大部分情况。但为什么在例子[-2,-3,-1]中,得到的结果是错误的? 让我们用这个状态转移方程来计算: 数组是[-2, -3, -1]。初始dp[0] = -2,dp[1] = max(-2, -3) = -2。dp[2] = max(dp[1], dp[0] + (-1)) = max(-2, -2 + (-1) = -3) →-2。但正确的答案应该是-1。这说明我的状态转移方程在这种情况下无法得到正确结果。 问题出在哪里?在这种情况下,最优解是单独选择最后一个元素-1,而根据状态转移方程,当处理到第三个元素时,是否可以选择单独选择它而不考虑前面的元素? 这说明状态转移方程可能需要允许从更早的位置转移过来,或者状态定义需要调整。比如,是否应该将dp[i]定义为前i个元素中的最大子数组,不管是否选第i个元素? 或者,是否应该将状态转移方程中的dp[i-2] + nums[i]改为max(nums[i], dp[i-2] + nums[i])?即,比较是否单独选当前元素更好,还是加上之前的最大。 例如,当i=2时,可能有两种情况:选当前元素,那么可以加上dp[i-2],或者只选当前元素(如果前面的加上反而更差)。在这种情况下,状态转移方程应为: dp[i] = max(dp[i-1], dp[i-2] + nums[i], nums[i]) 或者,是否应该将dp[i-2] + nums[i]nums[i]比较?或者是否在状态转移中已经隐含了这一点? 比如,当处理到i=2时,dp[0]是-2,所以dp[i-2] + nums[i]是-2 + (-1) = -3,而nums[i]是-1。这时候,如果状态转移方程中的第二项是max(dp[i-2] + nums[i], nums[i]),那么应该是max(-3, -1) →-1。然后dp[i] = max(dp[i-1](即-2), -1) →-1,这样结果就正确了。 因此,原来的状态转移方程可能需要调整为: dp[i] = max(dp[i-1], dp[i-2] + nums[i], nums[i]) 或者,是否在初始化时考虑这种情况? 或者,是否应该将状态转移方程中的项拆分为两种情况:是否包含当前元素? 这可能意味着需要重新设计状态定义。比如,使用两个状态数组,一个表示包含当前元素的最大,另一个表示不包含当前元素的最大。例如: - include[i] 表示包含第i个元素的最大,此时不能包含i-1,所以 include[i] = exclude[i-1] + nums[i] - exclude[i] 表示不包含第i个元素的最大,此时可以是include[i-1]或者exclude[i-1]中的较大值,即exclude[i] = max(include[i-1], exclude[i-1]) 这样,对于每个i,max(include[i], exclude[i])就是当前的最大。 让我们用这个方法来处理例子[-2, -3, -1]。 初始化: 对于i=0: include[0] = nums[0] = -2 exclude[0] = 0 (因为不选任何元素,但题目可能要至少选一个?或者是否允许不选任何元素?这时候问题就变成允许子数组为空的话为0,但题目可能要非空。) 如果是非空的子数组,那么初始化可能需要调整。例如,当数组全为负数时,必须选最大的那个负数。 所以,问题需要明确是否允许子数组为空。假设题目要必须选择一个子数组(非空),那么当所有元素为负数时,必须选最大的那个。 这时候,状态转移可能需要重新考虑。如果允许exclude[i]表示不选第i个元素,但必须至少选一个之前的元素,那么如何处理初始情况? 例如,当i=0时,必须选它,所以exclude[0]可能不存在或者设置为负无穷。或者,另一种方式: 如果必须选择至少一个元素,那么初始化的时候,include[0] = nums[0],而exclude[0]可能不存在,因为不选第0个元素的话就没有元素被选,但题目不允许这种情况。因此,exclude数组可能在这种情况下无法应用。 这可能意味着,原来的状态转移方程需要重新设计。 回到原来的例子,假设必须选至少一个元素,那么对于i=0,最大为nums[0]。对于i=1,最大是max(nums[0], nums[1])。对于i>=2,dp[i] = max(dp[i-1], dp[i-2] + nums[i]) 在例子[-2, -3, -1]中: dp[0] = -2 dp[1] = max(-2, -3) = -2 dp[2] = max(-2, (-2) + (-1)= -3) →-2。但正确结果应该是-1。这说明原来的方法无法处理这种情况。 这时候,问题出在原来的状态转移方程是否允许选择当前元素单独作为子数组,而不需要考虑之前的元素是否被选。比如,当i=2时,可能单独选nums[2](-1)比之前的dp[i-1]更大,所以应该将dp[i]设为max(dp[i-1], nums[i], dp[i-2]+nums[i]) 这样,对于i=2: max(dp[1] (-2), nums[2] (-1), dp[0] + nums[2] (-2 + (-1) =-3)) →max(-2, -1, -3) →-1 这时候,dp[2] = -1,这样结果就正确了。 所以,正确的状态转移方程应该是: dp[i] = max(dp[i-1], nums[i], dp[i-2] + nums[i]) 这可能才是正确的,因为需要考虑单独选当前元素的情况。而原来的方程只考虑了是否选当前元素加上前面的最大值(i-2),或者不选当前元素(i-1的情况)。但如果在i的位置单独选当前元素比之前的最大值更大,那需要更新。 例如,在数组中的某个元素本身是很大的正数,但前面都是负数的情况。这时候,单独选该元素是最优解。 所以,状态转移方程应该包含三个可能的选项:当前元素不选(取dp[i-1]),或者选当前元素但只能加上i-2处的值(dp[i-2] + nums[i]),或者单独选当前元素(nums[i])。然而,在原来的方程中,dp[i-2] + nums[i]可能已经比单独选当前元素大吗?比如,如果dp[i-2]是负数,那么dp[i-2] + nums[i]可能比nums[i]小。这时候,应该取较大的那个。所以,正确的表达式应该是max(dp[i-1], max(nums[i], dp[i-2] + nums[i])) 或者更简洁地,max(dp[i-1], dp[i-2] + nums[i], nums[i]) 因此,修改后的状态转移方程应该是这样的。 现在,重新计算例子[-2,-3,-1]: i=0:dp[0] = -2 i=1:dp[1] = max(-2, -3) = -2 i=2:dp[2] = max(-2, max(-1, -2 + (-1) =-3)) →max(-2, -1) →-1 正确的结果是-1。 另一个例子:[3,2,7,10] i=0:3 i=1: max(3,2)=3 i=2:max(3, max(7,3+7=10)) →max(3,10)=10 i=3:max(10, max(10+10=20,3+10=13)) →max(10,20)=20?但正确的应该是3+10=13?或者原例子中的正确解是3+7=102+10=12,或者3+10=13?或者我可能弄错了。 原数组是[3,2,7,10],不允许选相邻的元素。可能的子数组包括: 选3,不能选2,可选7或10:比如3+7=10,或者3+10=13。 选2,不能选37,可选10:2+10=12. 选7的话,不能选210,所以7+3=10或者7单独。 选10的话,不能选7,所以3+10=13或2+10=12。 所以最大的应该是13。按照修改后的状态转移方程: i=3时,dp[3] = max(dp[2](10), max(10, dp[1]+10=3+10=13)) →max(10,13)=13 →正确。 这样,状态转移方程是正确的。 所以正确的状态转移方程是: dp[i] = max(dp[i-1], dp[i-2] + nums[i], nums[i]) 或者等价于: dp[i] = max(dp[i-1], dp[i-2] + nums[i]) 但是否nums[i]本身可能更大? 比如,当nums[i]本身比dp[i-2]+nums[i]还大时,比如当dp[i-2]是负数的时候。 这时候,必须将nums[i]单独考虑进去,所以正确的状态转移方程应该包括比较这三个选项。 或者,是否可以将dp[i-2]可能为负数的情况考虑进去?例如,当dp[i-2]是负数,那么加上nums[i]可能比nums[i]本身小,这时候选nums[i]。 所以,状态转移方程应该是: dp[i] = max(dp[i-1], dp[i-2] + nums[i], nums[i]) 这样就能覆盖所有情况。 因此,正确的动态规划实现应该这样处理。 接下来,初始化的问题。对于i=0,dp[0] = nums[0]。对于i=1,dp[1] = max(nums[0], nums[1])。对于i>=2,应用上述方程。 现在,编写代码: 初始化: n = len(nums) if n ==0: return 0 if n ==1: return nums[0] dp = [0]*n dp[0] = nums[0] dp[1] = max(nums[0], nums[1]) for i in range(2, n): dp[i] = max(dp[i-1], dp[i-2] + nums[i], nums[i]) return dp[-1] 测试用例: 案例1:nums = [3,2,7,10] dp[0]=3 dp[1]=max(3,2)=3 dp[2]=max(3, 3+7=10,7) →10 dp[3]=max(10, 3+10=13, 10) →13 →正确。 案例2:nums = [-2,-3,-1] dp[0] =-2 dp[1] = max(-2,-3)=-2 dp[2] = max(-2, (-2)+(-1)=-3, -1) →max(-2, -3, -1)= -1 →正确。 另一个测试用例:nums = [5, 1, 1, 5] 最大是5+5=10。按状态转移方程: dp[0] =5 dp[1]=max(5,1)=5 dp[2]=max(5,5+1=6,1) →6 dp[3]=max(6,5+5=10,5) →10 →正确。 这样,代码是正确的。 但是,当数组长度为0时,返回0?但题目可能假设数组非空。需要处理输入数组为空的情况吗?题目中用户提到“数组”,但可能假设非空。不过代码中加入处理更好。 综上,动态规划的实现步骤如下: 1. 处理边界情况,数组为空或长度为1。 2. 初始化dp数组,前两个元素的情况。 3. 从第三个元素开始遍历,应用状态转移方程。 4. 返回最后一个元素的值。 现在,参考用户提供的引用中的相关部分,比如引用[3]中提到将问题定义为连续子数组的最后一个元素,这可能与动态规划的状态定义有关。例如,在最大子数组问题中,通常将dp[i]定义为以i结尾的最大子数组,然后取最大值。但这里的问题不同,因为不允许相邻元素,所以需要不同的状态定义。 用户提供的引用[2]中的代码是处理连续子数组最大的问题,其状态定义为dp[i]是以nums[i]结尾的最大。但本题的情况不同,所以不能直接使用相同的方法。 综上,正确的动态规划解决方案应该是上述的代码。</think>为了解不含相邻元素的最大子数组,可以采用动态规划算法。以下是具体实现步骤: ### 算法思路 1. **状态定义** 定义 `dp[i]` 表示前 `i+1` 个元素中,满足“不含相邻元素”条件的最大子数组。注意,这里的子数组必须至少包含一个元素。 2. **状态转移方程** 对于第 `i` 个元素,有两种选择: - **不选第 `i` 个元素**:此时最大为 `dp[i-1]`。 - **选第 `i` 个元素**:此时不能选第 `i-1` 个元素,因此最大为 `dp[i-2] + nums[i]`。此外,还需考虑单独选择第 `i` 个元素的情况(例如,当 `dp[i-2]` 为负数时,单独选 `nums[i]` 可能更优)。 综上,状态转移方程为: $$dp[i] = \max(dp[i-1], \ dp[i-2] + nums[i], \ nums[i])$$ 3. **边界条件** - 若数组长度为 1,直接返回 `nums[0]`。 - 若数组长度为 2,返回 `max(nums[0], nums[1])`。 ### 代码实现 ```python def max_non_adjacent_sum(nums): if not nums: return 0 n = len(nums) if n == 1: return nums[0] # 初始化前两个状态 dp = [0] * n dp[0] = nums[0] dp[1] = max(nums[0], nums[1]) # 递推计算后续状态 for i in range(2, n): dp[i] = max(dp[i-1], dp[i-2] + nums[i], nums[i]) return dp[-1] ``` ### 示例分析 以数组 `[-2, -3, -1]` 为例: 1. `dp[0] = -2`(必须选第一个元素)。 2. `dp[1] = max(-2, -3) = -2`。 3. `dp[2] = max(-2, (-2) + (-1) = -3, -1) = -1`。 最终结果为 `-1`,符合预期。 ### 复杂度 - **时间复杂度**:$O(n)$,仅需一次遍历。 - **空间复杂度**:$O(n)$,可用滚动变量优化至 $O(1)$[^5]。 ### 优化空间复杂度 通过仅保留前两个状态值,可将空间复杂度降至 $O(1)$: ```python def max_non_adjacent_sum_optimized(nums): if not nums: return 0 n = len(nums) if n == 1: return nums[0] prev_prev, prev = nums[0], max(nums[0], nums[1]) for i in range(2, n): current = max(prev, prev_prev + nums[i], nums[i]) prev_prev, prev = prev, current return prev ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值