算法设计与分析(二)

53.Question

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

Example

Input: [-2,1,-3,4,-1,2,1,-5,4],
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.

思路

题目简单明了,就是在一个整数串中找出和最大的子串,把最大和输出就可以。因为这个星期老师讲的是分治(divide and conquer)算法,这道题非常的经典,有很多种解法,这一次我尝试了用分治去求解,顺便列举一下以前曾做过的解答。

  • 枚举,时间复杂度为O(n)
    这是最简单粗暴的一种方法了,很容易理解。列举所有的可能性,在这之中找到和最大的那一个。但这种方法缺点也显而易见,就是效率很低,对于n个整数的串,一共有1+2+3+…+n种可能性,显然并不是一种好方法。因为枚举的代码比较简单,我就不写出来了。

  • 时间复杂度为O(n)的算法
    O(n)复杂度的代码写出来非常简单,但要理解就比较困难。以前做这道题目时使用的是枚举的方法,所以提交时一直超时,过不了,后来还是求助同学才做了出来。这个算法的数学原理是对于最大子串 nums[i..j] 和任意k),必定满足前缀子串 nums[i..k] 的和大于0。

    算法步骤如下:

    1. 从第一位开始顺序遍历数串。
    2. 求和sum = sum + nums[i],如果sum > max,那么max = sum。
    3. 如果sum < 0,那么sum = 0。因为sum为负数时,计算下一个整数nums[i+1]时,sum+nums[i+1]必定小于nums[i+1],所以前面数串的和sum就可以直接舍弃掉了。
class Solution {
public:
    int maxSubArray(vector<int>& nums) 
    {
        int max = -2147483648;
        int sum = 0;
        for (int i = 0; i < nums.size(); i++)
        {
          sum += nums[i];
          if (sum > max) max = sum;
          if (sum < 0) sum = 0;
        }
        return max;
    }
};
  • 分治,时间复杂度为O(nlogn)
    这次作业的重头戏,按照老师课堂上讲的内容,分治就是把一个大问题分成若干个同样结构的小问题,一直分一直分,分到剩下一个为止,以我的理解就是用一种递归的方法来求解。
    求一个数串的最大子串,可以把它分成若干个子串,再求这几个子串里面的最大值,求这个最大值时也继续分成更小的子串,如此类推,就能得到整个数串的最大子串。
    因为之前学快速排序时有类似的做法,所以一开始我的想法是把数串分成左右两个子串,两个子串再分,分到只剩一个数时,这个数就是长度为1的子串的最大值,然后再一层一层倒推回来,那么就能求得整个数串的最大子串。但随后就碰到问题了,如果最大子串跨越了左右两个子串,那么按照上面的做法是得不到这个最大值的。
    那么只能在比较左右两个子串的最大值时,把两个子串合起来的串也考虑进去?显然是不行的,这就回到了枚举算法的那种情况,把数串分成两个子串也就没有意义了。后来我发现了在求跨越子串的最大值时,并不需要全部枚举,它有一个特点,就是跨越子串的最大值等于左子串后缀子串的最大值加上右子串前缀子串的最大值,枚举的数量就只是左右子串长度的和。
    所以最后能总结出,用分治的算法求最大子串,把数串分成左右两个子串,再加上跨越子串,三个子串的最大值作比较就能得到数串的最大值。
    具体代码如下:
class Solution {
public:
    int maxSubArray(vector<int>& nums) 
    {
      return findMax(nums, 0, nums.size() - 1);   //进行递归
    }

    int findMax(vector<int>& nums, int head, int tail)
    {
      int mid;
      //子串只有一个数
      if (head == tail) 
        return nums[head];
      else
      {
        mid = (head + tail) / 2;
        return max(findMax(nums, head, mid),      //左子串
                   maxOfMid(nums,head,tail),      //跨越子串
                   findMax(nums, mid + 1, tail)); //右子串
      }
    }

    //求三个数的最大值
    int max(int num1, int num2, int num3)
    {
      if (num1 >= num2 && num1 >= num3) return num1;
      else if(num2 >= num1 && num2 >= num3) return num2;
      else return num3;
    }

    //求跨越子串的最大值
    int maxOfMid(vector<int>& nums, int head, int tail)
    {
      int fontMax = -2147483648;
      int backMax = -2147483648;
      int mid = (head + tail) / 2;
      int temp;
      //跨越子串的左部分
      temp = 0;
      for (int i = mid; i >= head; i--)
      {
        temp += nums[i];
        if (temp > fontMax) fontMax = temp;
      }
      //跨越子串的右部分
      temp = 0;
      for (int i = mid + 1; i <= tail ; i++)
      {
        temp += nums[i];
        if (temp > backMax) backMax = temp;
      }
      return fontMax + backMax;
    }
};

猜你喜欢

转载自blog.csdn.net/Maple_Lai/article/details/82718641
今日推荐