[关闭]
@LIUHUAN 2016-05-06T21:03:48.000000Z 字数 3888 阅读 1313

Maximum Product Subarray

algorithm


涉及到公式请到作业部落查看:Maximum Product Subarray

问题描述

Find the contiguous subarray within an array (containing at least one number) which has the largest product.
For example, given the array [2,3,-2,4],
the contiguous subarray [2,3] has the largest product = 6.

问题来自于Leetcode:Maximum Product Subarray

问题分析

  • 简单来说就是在一个数组中找到一个子数组,使得这个子数组的元素的乘积是所有子数组中最大的。
  • 拿到这个问题,我们看一下和Maximum Subarray类似,但是又有点不同,类似的地方在求解的是一个子数组,使得最大。不同的地方在于,这个求解的是子数组各个元素的乘积,我们知道,数组中如果有负数那么有可能存在两个负数相乘,得到的乘积最大,这种情况,和最大子数组不同,最大子数组要相加就可以了。并不需要多考虑这种负数相乘的情况,也就是说,这个题比最大子数组的题多了一种情况,那么我们就把多出的这种情况考虑进来就可以了。

解决方案

1.动态规划方法

  1. int maxProduct_O3n(vector<int>& nums) {
  2. int n = nums.size();
  3. if(n == 0)
  4. return 0;
  5. if(n == 1)
  6. return nums[0];
  7. vector<int> smax(n,1);
  8. vector<int> smin(n,1);
  9. vector<int> dp(n,0);
  10. smax[0] = nums[0];
  11. smin[0] = nums[0];
  12. Max[0] = nums[0];
  13. for(int i=1;i<n;++i){
  14. smax[i] = max( max(smax[i-1]*nums[i],nums[i]),smin[i-1]*nums[i] );
  15. smin[i] = min ( min(smin[i-1]*nums[i],nums[i]) , smax[i-1]*nums[i]);
  16. Max[i] = max(Max[i-1],smax[i]);
  17. }
  18. return Max[n-1];
  19. }
  • 可以看出来时间复杂度为,空间复杂度为.
  • 上面的代码部分明显存在可以优化的部分,比如数组,只是在求的时候使用到,所以可以用一个变量代替,在求解的时候,代表到为止的最大子数组乘积的值,并交换更新。
  • 其次,对于两个数组也是,在求的时候只用到了,之前的并没有用到。所以分别用两个变量代替即可(命名重复(-__-)b),下面给出优化后的代码。优化后的代码在Leetcode上面跑的比上面的代码快了很多。
  1. int maxProduct(vector<int>& nums) {
  2. int n = nums.size();
  3. if(n == 0)
  4. return 0;
  5. if(n == 1)
  6. return nums[0];
  7. int smax;
  8. int smin;
  9. int dp;
  10. smax = nums[0];
  11. smin= nums[0];
  12. dp = nums[0];
  13. for(int i=1;i<n;++i){
  14. int tmax = max ( max(smax * nums[i],nums[i]) ,smin * nums[i]);
  15. smin= min ( min(smin*nums[i],nums[i]) , smax*nums[i]);
  16. smax = tmax;
  17. dp = max(dp,smax);
  18. }
  19. return dp;
  20. }
  • 时间复杂度仍然为,空间复杂度为.

2.分治的方法

  • 由于这个题是最大子数组和的变形,还没有涉及到扩展到二维数组的情况,二维情况后续会写。所以最大子数组和里面有个分治的算法,在这里面也可以使用。主要的点是,我们要分别在跨越中点的子数组的时候,我们需要考虑到左边最小值和右边最小值,在两者都是负数的情况下,可能乘积也是最大的,所以在求跨越中点的子数组的时候,不仅仅要求的最大的,还要同时求得最小的,并且在取最大值得时候,要考虑左右两个最小的子数组(为负数乘以负数)乘积的情况。
  • 下面给出基于分治算法的递归实现。
  1. int maxProduct(vector<int> & nums) {
  2. int n = nums.size();
  3. if(n == 0)
  4. return 0;
  5. if(n == 1)
  6. return nums[0];
  7. return maxProduct_help(nums,0,n-1);
  8. }
  9. int maxProduct_help(vector<int> &nums,int begin,int end){
  10. if (begin == end)
  11. return nums[end];
  12. int mid = (begin + end) >> 1;
  13. /**左边子数组的最大值子数组乘积的值*/
  14. int smax_left = maxProduct_help(nums,begin,mid);
  15. /**右边子数组的最大值子数组乘积的值*/
  16. int smax_right= maxProduct_help(nums,mid+1,end);
  17. /**跨越中点的最大值子数组乘积的值*/
  18. int max_cross = maxCrossMid(nums,begin,end);
  19. return max(max(smax_left,smax_right),max_cross);
  20. }
  21. /*maxCrossMid函数的时间复杂度实际为O(n)*/
  22. int maxCrossMid(vector<int> & nums,int begin,int end){
  23. if(begin == end)
  24. return nums[end];
  25. int mid = (begin + end ) >> 1;
  26. int max_left = nums[mid];
  27. int min_left = nums[mid];
  28. int max_right = nums[mid+1];
  29. int min_right = nums[mid+1];
  30. int sum = 1;
  31. /*计算以mid结尾的最大(小)的子数组乘积,左边子数组*/
  32. for(int i=mid;i>=begin;--i){
  33. sum *= nums[i];
  34. if(sum > max_left )
  35. max_left = sum;
  36. if(sum < min_left)
  37. min_left = sum;
  38. }
  39. sum = 1;
  40. /*计算以mid+1开始的最大(小)的子数组乘积,右边子数组*/
  41. for(int i=mid+1;i<=end;++i){
  42. sum *= nums[i];
  43. if(sum > max_right)
  44. max_right = sum;
  45. if(sum < min_right)
  46. min_right = sum;
  47. }
  48. /*左边最小和右边最小乘积结果可能是比左边最大和右边最大成绩结果要大*/
  49. return max(min_left*min_right,max_left*max_right);
  50. }
  • 在求解的过程中我们也可以看到,,所以并不需要考虑交叉乘积的值最大。
  • 时间复杂度,根据主定理,我们可以知道这个解的下界是也就是,但是要比要快。

参考内容:



添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注