最大子数组积
原题描述
原题描述参考: leetcode’s 152 Maximum Product Subarray
Given an integer array nums, find the contiguous subarray within an array (containing at least one number) which has the largest product.
Example 1:
Input: [2,3,-2,4]
Output: 6
Explanation: [2,3] has the largest product 6.
Example 2:
Input: [-2,0,-1]
Output: 0
Explanation: The result cannot be 2, because [-2,-1] is not a subarray.
level: Medium
实现思路
求a0…ai…an序列的最大子数组的乘积. 如果想解出期望复杂度为O(n)的解法,基本思路是由积的基本性质出发.一组数的序列,连续相乘,如果是全部正数,随着n增加, 则其值只有一直递增; 如果序列中存在负数,则子数组序列的值受到负数个数的影响, 不断相乘,绝对值虽然在增大,但序列一旦包含奇数个负数则值会向数轴负数方向递增,整体上形成了跳跃式的函数图像.所以说,对于不包含0序列, 问题就转化为:
- 如果序列包含偶数个负数, 直接求值;
- 如果序列包含奇数个负数,由于是求连续子数列, 则遍历过程中记录第一个负数和最后一个负数的索引位置, 然后处理以下逻辑, e.g.
// 设k为第一个负数的索引位置, l为最后一个负数的索引位置
a0 ... ak ... al ... an
所以只要判断去除掉ak的最大序列结果大还是去除掉al的最大序列结果大.即:
prod_without_last = a0 * ....a[l-1]
prod_without_first = a[k+1] *...an
result = prod_without_first > prod_without_last ?
prod_without_first :
prod_without_last
注意, 负数的个数可能是1个的情形,其实就把序列分成了2部分, 则比较这两部分序列的大小;
求prod_without_last, prod_without_first的这两部分序列的积的方法,可以去效仿最大子数组和算法中转化思路(将数组求和变为数组求差). 即求部分积可以转变成整体除以不想包含的部分. 之所以需要这样, 详见实现部分的注释;
另外, 如果序列中包含0, 那么其实0一旦参与相乘会将以后所有的相乘结果都变为0.所以0对于求解子序列来说,其意义相当于将子序列划分为若干个不包含0的子序列,求得这几个子序列的最大乘积值; 注意,从整体的结果来看,如果不包含0子序列求解结果小于0, 则最大值是0;
所以根据以上分析给出实现2,期望时间复杂度是O(n)的解法.注意, 虽然是包含了2层循环,但第二层循环其实是将第一层循环分成了子序列进入处理,应该使用均摊分析的估算方法,整体复杂度是O(n).
实现1是优化枚举,复杂度O(n^2), 可以看出2者在leetCode网站上的执行效果.
实现1: O(n2)的方法
/**
184 / 184 test cases passed.
Status: Accepted
Runtime: 240 ms
Memory Usage: 9 MB
*/
// passed runCode on leetCode O(n2方)
int maxProduct(const vector<int>& nums) {
auto n = nums.size();
int ans = -2147483647; //small int
for (int st = 0; st < n; ++st) {
int product = 1;
for (int ed = st + 1; ed <= n; ++ed) {
// int product = 1;
// for (int i= st; i < ed; ++i) {
product *= nums[ed - 1];
//}
if (product > ans) {
ans = product;
}
}
}
return ans;
}
实现2: O(n)的方法
实现2的主体思路,是比较简单的. 但是处理0分割序列的边界条件,让实际调试的复杂程度陡然增加了不少.但可以看到, 通过结果可以看到比实现1的时间复杂度小非常多.
/**
184 / 184 test cases passed.
Status: Accepted
Runtime: 4 ms
Memory Usage: 9.1 MB
*/
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
/**
* 求不包含0的子序列部分的最大子序列积
* @param {[type]} const vector<int> &nums
* @param {[type]} int st 子序列在nums中开始的索引
* @param {[type]} int ed 子序列在nums中结束的索引
* @return {[type]}
*/
int maxProductForSection(const vector<int> &nums, int st, int ed) {
cout << "st=" << st << " ed=" << ed << endl;
auto n = nums.size();
int ans = -2147364847; // smallest int
int counter_neg = 0;
decltype(n) first_neg_idx = -1,
last_neg_idx = -1;
bool first_neg_recorded = false;
int prod = 1, prod_first = 1;
int j = st;
// 记录第一个和最后一个负数的位置index;
while (j <= ed) {
if (nums[j] < 0) {
if (!first_neg_recorded) {
last_neg_idx = first_neg_idx = j;
first_neg_recorded = true;
} else {
last_neg_idx = j;
}
++counter_neg;
}
prod *= nums[j];
// 当遇到first_neg, 记录下prod_first部分;
if (first_neg_recorded && j == first_neg_idx) {
prod_first = prod;
}
if (j == ed ) {
if (counter_neg % 2 == 0) {
ans = prod;
} else {
// 当序列中存在奇数个负数;
if (first_neg_idx == last_neg_idx) {
if (st == ed) {
ans = prod;
} else {
int m = prod_first / nums[first_neg_idx];
int n = prod / prod_first;
ans = m > n ? m : n;
}
} else {
cout << "last_neg_idx=" << last_neg_idx << endl;
// 当我们需要求包含第一个负数的序列的积(设为prod_first_half)时,运算转化成
// prod / prod_last,
// 只有在步进到最后一个索引位置才能确认完成了对最后一个负数位置
// 的记录, 此时求出最后一个负数位置到末尾的积prod_last,
// 则prod_first_half就等于prod/prod_last
//
// 求包含最后一个负数的序列的积可以转化成 prod / prod_first,
// prod_first是在步进过程中记录的前面子序列的积;
// 这样参考最大数组子序列和算法的思路, 将求积转成求除法;
int prod_last = 1;
for (int i = last_neg_idx; i <= j; ++i) {
prod_last *= nums[i];
}
int prod_first_half = prod / prod_last;
int prod_rare_half = prod / prod_first;
ans = prod_first_half > prod_rare_half ? prod_first_half : prod_rare_half;
}
// }
}
}
j++;
}
return ans;
}
int maxProduct(const vector<int> &nums) {
int n = nums.size();
int big = -2147364847; // smallest int
for (int j = 0, i = j; j < n; ++j) {
if (nums[j] == 0 || j == n - 1) {
int last_idx;
if (nums[j] == 0) {
if (big < 0) {
big = 0;
}
}
// 如果序列是开头/最后一个位置是0:
if (nums[j] == 0 && (j == 0 || j == n-1)) {
if (j == 0 && j != n - 1) {
i++;
continue;
}
if (j == n - 1) {
if (j == 0) { // n == 1 && nums[j] == 0
big = 0;
return big;
}
last_idx = j - 1; // 当nums[j] == 0 且是最后一个位置;
}
} else if (j == n - 1) {
// nums[j] != 0
last_idx = j;
} else {
// num[j] == 0, position is in the middle;
last_idx = j - 1;
}
cout << "i=" << i << " j=" << j << " last_idx=" << last_idx
<< endl;
int ans = maxProductForSection(nums, i, last_idx);
cout << " ans=" << ans << endl;
i = j + 1;
if (ans > big) {
big = ans;
}
}
}
cout << "result=" << big << endl;
return big;
}
};
int main() {
Solution s;
vector<int> a = {2,3,-2,4}
int result = s.maxProduct(a);
cout << "final result=" << result << endl;
}
leetCode上的运行结果: