最大子数组问题 - (暴力求解+分治法)

本文探讨了两种寻找最佳股票买卖时机的方法:暴力求解和分治法。暴力求解通过遍历所有可能的买卖组合来确定最大收益;而分治法则通过递归地将问题分解为更小的子问题来降低时间复杂度。

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

股票问题
这里写图片描述
1.暴力求解 2. 分治法

题目解析:根据价格的变化,求出股票在哪天买入哪天卖出,收益会最大。


暴力求解:遍历每一个子数组区间,比较大小,并记录最大收益的开始和结束的下标

代码实现:

using System;

namespace cchoop
{
    class Program
    {
        public static void Main()
        {
            int[] price = new int[] { 100, 113, 110, 85, 105, 102, 86, 63, 81, 101, 94, 106, 101, 79, 94, 90, 97 };
            int[] priceChange = new int[price.Length - 1];
            for (int i = 1; i < price.Length; i++)
            {
                priceChange[i - 1] = price[i] - price[i - 1];
            }

            int sum = priceChange[0];
            int startIndex = 0;
            int endIndex = 0;

            for (int i = 0; i < priceChange.Length; i++)
            {
                int tempSum = 0;
                for (int j = i; j < priceChange.Length; j++)
                {
                    tempSum += priceChange[j];
                    if (tempSum > sum)
                    {
                        startIndex = i;
                        endIndex = j;
                        sum = tempSum;
                    }
                }
            }

            Console.WriteLine("最大收益为:{0},第{1}天买入,第{2}天卖出", sum, startIndex, endIndex + 1);
        }
    }
}

分治法

解题思路:将数组分成两份,最大子数组(元素之和最大)只有三种情况:
(1)最大子数组在mid左边,即:startIndex和endIndex都在mid左边
(2)最大子数组一半在左边,一半在右边,即:startIndex在 mid左边,endIndex在mid右边
(3)最大子数组在mid右边,即:startIndex和endIndex都在mid右边
那么比较三种情况下的最大子数组的结果接可以了,最终得出最大子数组
那么继续运用递归,继续给mid左边和右边进行分治求解,将复杂的问题进行简化,以此降低了代码的时间复杂度,提高了程序的运行效率。

代码实现:

using System;

namespace cchoop
{
    class Program
    {
        public static void Main()
        {
            int[] price = new int[] { 100, 113, 110, 85, 105, 102, 86, 63, 81, 101, 94, 106, 101, 79, 94, 90, 97 };
            int[] priceChange = new int[price.Length - 1];
            for (int i = 1; i < price.Length; i++)
            {
                priceChange[i - 1] = price[i] - price[i - 1];
            }

            MaxProfit maxProfit = GetMaxProfit(priceChange, 0, priceChange.Length - 1);
            Console.WriteLine("最大收益为:{0},第{1}天买入,第{2}天卖出", maxProfit.ProfitNum, maxProfit.StartIndex, maxProfit.EndIndex + 1);
        }

        public static MaxProfit GetMaxProfit(int[] priceChange, int low, int high)
        {
            if (low == high)
            {
                MaxProfit maxProfit;
                maxProfit.StartIndex = low;
                maxProfit.EndIndex = high;
                maxProfit.ProfitNum = priceChange[low];
                return maxProfit;
            }

            int mid = (low + high) / 2;
            //startIndex、endIndex都在mid左边
            MaxProfit maxProfitLeft = GetMaxProfit(priceChange, low, mid);
            //startIndex、endIndex都在mid右边
            MaxProfit maxProfitRight = GetMaxProfit(priceChange, mid + 1, high);

            //startIndex在mid左边,endIndex在mid右边
            int startIndex = mid;
            int endIndex = mid;
            int totalProfit = priceChange[mid];
            int tempProfit = 0;
            //左边
            for (int i = mid; i >= 0; i--)
            {
                tempProfit += priceChange[i];
                if (tempProfit > totalProfit)
                {
                    totalProfit = tempProfit;
                    startIndex = i;
                }
            }
            //右边
            tempProfit = totalProfit;
            for (int i = mid + 1; i < priceChange.Length; i++)
            {
                tempProfit += priceChange[i];
                if (tempProfit > totalProfit)
                {
                    totalProfit = tempProfit;
                    endIndex = i;
                }
            }
            MaxProfit maxProfitMid;
            maxProfitMid.StartIndex = startIndex;
            maxProfitMid.EndIndex = endIndex;
            maxProfitMid.ProfitNum = totalProfit;

            //比较 三个maxProfit
            MaxProfit tempMaxProfit = maxProfitLeft;
            if (maxProfitMid.ProfitNum > tempMaxProfit.ProfitNum)
            {
                tempMaxProfit = maxProfitMid;
            }
            if (maxProfitRight.ProfitNum > tempMaxProfit.ProfitNum)
            {
                tempMaxProfit = maxProfitRight;
            }
            return tempMaxProfit;
        }
    }

    struct MaxProfit
    {
        public int StartIndex;
        public int EndIndex;
        public int ProfitNum;
    }
}

<think>好的,我现在需要帮助用户理解如何用分治法解决最大子数组问题。首先,我要回忆一下分治法的基本思想,也就是将问题分解成更小的子问题,解决后再合并结果。最大子数组问题通常是指在一个数组中找到一个连续的子数组,使其和最大。 用户可能已经知道这个问题可以用暴力法或者动态规划来解决,但分治法的实现可能不太清楚。我需要先理清楚分治法在这个问题上的步骤。根据引用内容,分治法需要将数组分成左右两部分,分别求解左右的最大子数组,然后处理跨越中点的最大子数组,最后比较三者取最大。 接下来,我需要详细解释每个步骤。首先是分解,把数组从中间分成两部分。左右子数组最大子数组可以通过递归继续分解,直到基本情况,也就是只有一个元素时,最大子数组就是该元素本身。然后是合并步骤,需要处理跨越中点的子数组。这里可能需要详细说明如何计算跨越中点的最大子数组,比如从中点向左和向右扩展,分别找到最大和,然后合并起来。 用户可能需要具体的例子来理解这个过程,比如像示例中的数组[-3, 1, 5, -2, 3, -5]。分治法的递归过程如何分解这个数组,如何计算左右和跨中点的最大值,最后合并得到结果。此外,时间复杂度的分析也很重要,分治法的时间复杂度通常是O(n log n),需要解释为什么是这个结果。 同时,要注意代码实现的细节。比如,分治法的递归终止条件,如何处理左右子数组的递归调用,以及如何实现跨中点的情况。可能需要参考引用中的Java或Python代码结构,但用户可能需要Python的实现,所以需要转换成Python代码。 还要考虑到用户可能的疑问,比如为什么分治法的时间复杂度比动态规划的O(n)更差,但分治法可能在特定情况下有用,比如并行处理或者作为教学示例。同时,需要指出分治法在处理跨越中点的情况时的关键点,确保用户理解合并步骤的正确性。 最后,生成相关问题时要围绕分治法的其他应用、优化方法、与其他算法的比较等,这样用户能进一步扩展知识。</think>### 分治法解决最大子数组问题的原理及实现 #### 1. 问题定义 最大子数组问题要求在给定数组 $A$ 中找到一个连续的子数组,使得其元素之和最大。例如,数组 $[-3, 1, 5, -2, 3, -5]$ 的最大子数组为 $[1, 5, -2, 3]$,和为 $7$。 #### 2. 分治法核心思想 分治法将数组划分为左右两部分,递归求解左半部分、右半部分的最大子数组和,同时计算**跨越中点**的最大子数组和,最终取三者中的最大值[^1][^2]。 #### 3. 算法步骤 1. **分解** 将数组从中间位置 $mid$ 划分为左右两个子数组:$A[low, mid]$ 和 $A[mid+1, high]$。 2. **递归求解** -子数组最大和:$left\_max = \text{findMaxSubarray}(A, low, mid)$ -子数组最大和:$right\_max = \text{findMaxSubarray}(A, mid+1, high)$ 3. **跨越中点的最大子数组和** - **向左扩展**:从中点向左累加,记录最大和 $left\_sum$ - **向右扩展**:从中点向右累加,记录最大和 $right\_sum$ - 跨越中点的最大和为 $cross\_max = left\_sum + right\_sum$ 4. **合并结果** 最终结果为 $\max(left\_max, right\_max, cross\_max)$。 #### 4. 关键代码实现(Python) ```python def max_subarray(nums): def find_max_crossing(low, mid, high): # 向左扩展 left_sum = float('-inf') current = 0 for i in range(mid, low-1, -1): current += nums[i] if current > left_sum: left_sum = current # 向右扩展 right_sum = float('-inf') current = 0 for i in range(mid+1, high+1): current += nums[i] if current > right_sum: right_sum = current return left_sum + right_sum def helper(low, high): if low == high: return nums[low] mid = (low + high) // 2 left_max = helper(low, mid) right_max = helper(mid+1, high) cross_max = find_max_crossing(low, mid, high) return max(left_max, right_max, cross_max) return helper(0, len(nums)-1) ``` #### 5. 时间复杂度分析 - **递推关系**:$T(n) = 2T(n/2) + O(n)$(递归分解左右子问题 + 线性时间处理跨中点情况) - **最终复杂度**:$O(n \log n)$,优于暴力法的 $O(n^2)$,但弱于动态规划的 $O(n)$[^3]。 #### 6. 示例分析 以数组 $[-3, 1, 5, -2, 3, -5]$ 为例: 1. 分解到最小单元后,递归计算左半部分 $[-3, 1, 5]$ 和右半部分 $[-2, 3, -5]$ 的最大子数组和。 2. 计算跨越中点(例如 $mid=2$)的最大子数组,即从 $5$ 向左扩展到 $1$,向右扩展到 $-2, 3$,总和为 $1+5+(-2)+3=7$。 3. 最终合并结果为 $\max(左=6, 右=3, 跨中点=7)=7$。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cchoop

有用的话请杯肥宅水

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值