《编程之美》183页,问题2.14——求子数组的字数组之和的最大值。(整数数组)

我开始以为可以从数组中随意抽调元素组成子数组,于是就有了一种想法,把最大的元素抽出来,判断是大于0还是小于等于0,如果大于0就对除了这个最大值外剩下的数组部分进行递归:

C#中求数组的子数组之和的最大值_C#C#中求数组的子数组之和的最大值_C#_02
using System;
using System.Collections.Generic;
using System.Linq;


namespace MaxSumSubArray
{
    class Program
    {
        static void Main(string[] args)
        {
            List<int> array = new List<int>() { -1, -2, 1, 2, -1, 1, -3, 6 - 3, -4, 1 };
            List<int> subArray = new List<int>();
            if (array.Max() > 0)
            {
                MaxSumSubArray(array, subArray);
                PrintArray(subArray);
            }
            else
            {
                Console.Write("The max sub-array is {" + array.Max() + "}");
            }
            Console.ReadLine();
        }

        private static void PrintArray(List<int> subArray)
        {
            Console.WriteLine("The max-sum sub-array is:");
            foreach (int num in subArray)
            {
                Console.Write(num);
                Console.Write(" ");
            }
        }

        private static void MaxSumSubArray(List<int> array, List<int> subArray)
        {
            if (array.Max() > 0)
            {
                subArray.Add(array.Max());
                array.Remove(array.Max());
                MaxSumSubArray(array,subArray);
            }
        }
    }
}
View Code

这样做其实就是我想麻烦了,因为最后的结果证明,这样做和遍历数组把大于0的元素都抽出来是一样的,根本用不着递归:

C#中求数组的子数组之和的最大值_编程之美_03

如果数组中没有正整数,那就变成求数组中最大值的问题了。T.T

后来看答案发现,题中说的子数组还必须得是连续的,不能随便抽调。于是开始重新想问题……

我发现,连续子数组的和是不是最大的,要把所有的子数组都找出来以后才能确定。

那没什么好办法了,只能遍历了,首先从第一个元素开始,第一个子数组就是第一个元素本身,第二个子数组就是第一个元素和第二个元素组成的,第三个……

然后从第二个元素开始……

等把这些子数组都列出来以后,分别加和,比较一下哪个最大哪个就是目标子数组了。

代码如下:

using System;
using System.Collections.Generic;
using System.Linq;

namespace MaxSumSubArray
{
    class Program
    {
        static void Main(string[] args)
        {
            List<int> array = new List<int>() { -2, 5, 3, -6, 4, -8, 6 };
            List<List<int>> subArrays = new List<List<int>>();
            int subSum = 0;
            int origin = 0;
            for (int k = 1; k <= array.Count(); k++)
            {
                for (int i = k; i <= array.Count(); i++)
                {
                    subArrays.Add(new List<int>());
                    for (int j = k; j <= i; j++)
                    {
                        subArrays[origin].Add(array[j - 1]);
                        subSum += array[j - 1];
                    }
                    subArrays[origin].Add(subSum);
                    origin++;
                    subSum = 0;
                }
            }
            int max = subArrays[0][subArrays[0].Count() - 1];
            int destination = 0;
            for (int i = 0; i < subArrays.Count(); i++)
            {
                int sumIndex = subArrays[i].Count() - 1;
                if (subArrays[i][sumIndex] > max)
                {
                    max = subArrays[i][subArrays[i].Count() - 1];
                    destination = i;
                }
            }
            PrintArray(subArrays[destination]);
            Console.ReadLine();
        }

        private static void PrintArray(List<int> array)
        {
            Console.WriteLine("The max-sum sub-array is:");
            for (int i = 0; i < array.Count() - 1; i++)
            {
                Console.Write(array[i]);
                Console.Write(" ");
            }
            Console.WriteLine();
            Console.WriteLine("The sum of the array is:" + array[array.Count() - 1]);
        }
    }
}

这里我用了泛型数组的嵌套,因为泛型数组是动态的,所以嵌套的泛型数组就相当于一个动态的二维数组:

List<List<int>> subArrays = new List<List<int>>();

subArrays数组中每一个元素都是一个整型数组List<int>。

接下来是三层for循环:

最内层的for循环就是从数组的某一个元素一直加到某一个元素;

中层for循环是从数组某一个元素开始,一直到最后一个元素;

最外层的for循环是用来指定子数组的首元素。

通过三层for循环就可以求出所有的子数组以及他们的和,我把每一个子数组都做为一个元素存在List<int>型数组中,并且int型数组中的最后一位元素用来存子数组的和。

接下来我们遍历所有子数组的末尾元素,比较出所有子数组中和最大的子数组并将其打印。

至此,我们就求出了数组中和最大的子数组:

C#中求数组的子数组之和的最大值_编程之美_04

以上就是经过我的思考所得出的解法。对比书中的解法发现我的解法基本上是书中写的没有优化过的解法一,但细节实现不一样,我的方法不光可以找出最大子数组和,还可以打印对应数组。书中说可以省略一层for循环来避免重复计算对算法进行优化,但是我没想通,改成书中说的那样发现测试结果不对。如果哪位高手有好意见,请给出优化代码。QQQ~!——2015.11.18


2015.12.29我又在网上看到了这个题,于是又经过一番思考,得出了一种新的解法,思路如下:

1、如果数组中全是非正数,则最大子数组就是该数组的最大值。这种情况下,根本就不需要继续遍历,极大的减少了计算量,直接得出答案。

2、如果数组中全是非负数,则最大字数组就是该数组本身。这种情况下,根本就不需要继续遍历,极大的减少了计算量,直接得出答案。

3、数组中有正数也有负数,则最大子数组的开头肯定是非负数,结尾也肯定为非负数!也就是说,子数组的核心成员就锁定在那些非负数上。我要找出数组中所有的非负数,记录他们在数组中的位置。最终的目标是计算每两个非负数在数组中的距离。(即两个非负数以及其之间的数所组成的子数组的和)

代码如下:

using System;
using System.Collections.Generic;
using System.Linq;

namespace MaxSubarray
{
    class Program
    {
        private static List<int> indexArray = new List<int>();
        private static List<int> results = new List<int>();
        static void Main(string[] args)
        {
            //int[] a = { 1, -2, 3, 10, -4, 7, 2, -5 };
            int[] a = { -2, 5, 3, -6, 4, -8, 6 };
            //Select the ones >=0.
            for (int i = 0;i< a.Length;i++)
            {
                if (a[i] > 0)
                {
                    indexArray.Add(i);
                }
            }
            //Calc all the sums.
            CalcResults(a);
            //Output the final result.
            Console.WriteLine(results.Max());
            Console.Read();
        }

        private static void CalcResults(int[] array)
        {
            int sum;
            for (int i = 0; i < indexArray.Count(); i++)
            {
                for (int j = 0; j <= i; j++)
                {
                    sum = CalcSum(indexArray[j], indexArray[i], array);
                    results.Add(sum);
                }
            }
        }

        private static int CalcSum(int from, int to, int[] array)
        {
            int sum = 0;
            for (int i = from; i <= to; i++)
            {
                sum += array[i];
            }
            return sum;
        }
    }
}

这里主要用到了三个方法:

1、方法一:计算数组中两点之间元素所组成的子数组的和;

2、方法二:计算出所有的非负数元素间的组合情况,并用方法一求出两个非负数元素之间所组成的子数组的和;

3、方法三:找出这些和中最大的值就是最大子数组的和。

该方法主要的思想是缩小了搜索范围,分况治之。