[LeetCode] 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.
这个求最大子数组乘积问题是由最大子数组之和 Maximum Subarray 演变而来,但是却比求最大子数组之和要复杂,因为在求和的时候,遇到0,不会改变最大值,遇到负数,也只是会减小最大值而已。而在求最大子数组乘积的问题中,遇到0会使整个乘积为0,而遇到负数,则会使最大乘积变成最小乘积,正因为有负数和0的存在,使问题变得复杂了不少。比如,现在有一个数组 [2, 3, -2, 4],可以很容易的找出所有的连续子数组,[2],[3],[-2],[4],[2, 3],[3, -2],[-2, 4],[2, 3, -2],[3, -2, 4],[2, 3, -2, 4],然后可以很轻松的算出最大的子数组乘积为6,来自子数组 [2, 3]。但如何写代码来实现自动找出最大子数组乘积呢,博主最先想到的方比较简单粗暴,就是找出所有的子数组,然后算出每一个子数组的乘积,然后比较找出最大的一个,需要两个 for 循环,第一个 for 遍历整个数组,第二个 for 遍历含有当前数字的子数组,就是按以下顺序找出子数组: [2],[2, 3],[2, 3, -2],[2, 3, -2, 4],[3],[3, -2],[3, -2, 4],[-2],[-2, 4],[4],在本地测试的一些数组全部通过,于是兴高采烈的拿到 OJ 上测试,结果丧心病狂的 OJ 用一个有 15000 个数字的数组来测试,然后说程序的运行时间超过了要求值,一看代码,果然如此,时间复杂度 O(n2), 得想办法只用一次循环搞定。想来想去想不出好方法,于是到网上搜各位大神的解决方法。其实这道题最直接的方法就是用 DP 来做,而且要用两个 dp 数组,其中 f[i] 表示子数组 [0, i] 范围内并且一定包含 nums[i] 数字的最大子数组乘积,g[i] 表示子数组 [0, i] 范围内并且一定包含 nums[i] 数字的最小子数组乘积,初始化时 f[0] 和 g[0] 都初始化为 nums[0],其余都初始化为0。那么从数组的第二个数字开始遍历,那么此时的最大值和最小值只会在这三个数字之间产生,即 f[i-1]*nums[i],g[i-1]*nums[i],和 nums[i]。所以用三者中的最大值来更新 f[i],用最小值来更新 g[i],然后用 f[i] 来更新结果 res 即可,由于最终的结果不一定会包括 nums[n-1] 这个数字,所以 f[n-1] 不一定是最终解,不断更新的结果 res 才是,参见代码如下:
解法一:
class Solution {
public:
int maxProduct(vector<int>& nums) {
int res = nums[], n = nums.size();
vector<int> f(n, ), g(n, );
f[] = nums[];
g[] = nums[];
for (int i = ; i < n; ++i) {
f[i] = max(max(f[i - ] * nums[i], g[i - ] * nums[i]), nums[i]);
g[i] = min(min(f[i - ] * nums[i], g[i - ] * nums[i]), nums[i]);
res = max(res, f[i]);
}
return res;
}
};
我们可以对上面的解法进行空间上的优化,以下摘自 OJ 官方解答,大体思路相同,写法更加简洁:
Besides keeping track of the largest product, we also need to keep track of the smallest product. Why? The smallest product, which is the largest in the negative sense could become the maximum when being multiplied by a negative number.
Let us denote that:
f(k) = Largest product subarray, from index up to k.
Similarly,
g(k) = Smallest product subarray, from index up to k.
Then,
f(k) = max( f(k-) * A[k], A[k], g(k-) * A[k] )
g(k) = min( g(k-) * A[k], A[k], f(k-) * A[k] )
There we have a dynamic programming formula. Using two arrays of size n, we could deduce the final answer as f(n-1). Since we only need to access its previous elements at each step, two variables are sufficient.
public int maxProduct(int[] A) {
assert A.length > ;
int max = A[], min = A[], maxAns = A[];
for (int i = ; i < A.length; i++) {
int mx = max, mn = min;
max = Math.max(Math.max(A[i], mx * A[i]), mn * A[i]);
min = Math.min(Math.min(A[i], mx * A[i]), mn * A[i]);
maxAns = Math.max(max, maxAns);
}
return maxAns;
}
根据上述描述可以写出代码如下:
解法二:
class Solution {
public:
int maxProduct(vector<int>& nums) {
if (nums.empty()) return ;
int res = nums[], mn = nums[], mx = nums[];
for (int i = ; i < nums.size(); ++i) {
int tmax = mx, tmin = mn;
mx = max(max(nums[i], tmax * nums[i]), tmin * nums[i]);
mn = min(min(nums[i], tmax * nums[i]), tmin * nums[i]);
res = max(res, mx);
}
return res;
}
};
下面这种方法也是用两个变量来表示当前最大值和最小值的,但是没有无脑比较三个数,而是对于当前的 nums[i] 值进行了正负情况的讨论:
2. 当遍历到一个负数时,先用一个变量t保存之前的最大值 mx,然后此时的最大值等于之前最小值乘以这个负数和当前负数中的较大值,此时的最小值等于之前保存的最大值t乘以这个负数和当前负数中的较小值。
3. 在每遍历完一个数时,都要更新最终的最大值。
解法三:
class Solution {
public:
int maxProduct(vector<int>& nums) {
int res = nums[], mx = res, mn = res;
for (int i = ; i < nums.size(); ++i) {
if (nums[i] > ) {
mx = max(mx * nums[i], nums[i]);
mn = min(mn * nums[i], nums[i]);
} else {
int t = mx;
mx = max(mn * nums[i], nums[i]);
mn = min(t * nums[i], nums[i]);
}
res = max(res, mx);
}
return res;
}
};
下面这道题使用了一个 trick 来将上面解法的分情况讨论合成了一种,在上面的解法中分析了当 nums[i] 为正数时,最大值和最小值的更新情况,为负数时,稍有不同的就是最小值更新时要用到之前的最大值,而不是更新后的最大值,所以才要用变量t来保存之前的结果。而下面这种方法的巧妙处在于先判断一个当前数字是否是负数,是的话就交换最大值和最小值。那么此时的 mx 就是之前的 mn,所以 mx 的更新还是跟上面的方法是统一的,而在在更新 mn 的时候,之前的 mx 已经保存到 mn 中了,而且并没有改变,所以可以直接拿来用,不得不说,确实叼啊,参见代码如下:
解法四:
class Solution {
public:
int maxProduct(vector<int>& nums) {
int res = nums[], mx = res, mn = res;
for (int i = ; i < nums.size(); ++i) {
if (nums[i] < ) swap(mx, mn);
mx = max(nums[i], mx * nums[i]);
mn = min(nums[i], mn * nums[i]);
res = max(res, mx);
}
return res;
}
};
再来看一种画风不太一样的解法,这种解法遍历了两次,一次是正向遍历,一次是反向遍历,相当于正向建立一个累加积数组,每次用出现的最大值更新结果 res,然后再反响建立一个累加积数组,再用出现的最大值更新结果 res,注意当遇到0的时候,prod 要重置为1。至于为啥正反两次遍历就可以得到正确的结果了呢?主要还是由于负数个数的关系,因为负数可能会把最大值和最小值翻转,那么当有奇数个负数时,如果只是正向遍历的话,可能会出错,比如 [-1, -2, -3],累加积会得到 -1,2,-6,看起来最大值只能为2,其实不对,而如果我们再反向来一遍,累加积为 -3,6,-6,就可以得到6了。所以当负数个数为奇数时,首次出现和末尾出现的负数就很重要,有可能会是最大积的组成数字,所以遍历两次就不会漏掉组成最大值的机会,参见代码如下:
解法五:
class Solution {
public:
int maxProduct(vector<int>& nums) {
int res = nums[], prod = , n = nums.size();
for (int i = ; i < n; ++i) {
res = max(res, prod *= nums[i]);
if (nums[i] == ) prod = ;
}
prod = ;
for (int i = n - ; i >= ; --i) {
res = max(res, prod *= nums[i]);
if (nums[i] == ) prod = ;
}
return res;
}
};
Github 同步地址:
https://github.com/grandyang/leetcode/issues/152
类似题目:
Maximum Product of Three Numbers
参考资料:
https://leetcode.com/problems/maximum-product-subarray/
https://leetcode.com/problems/maximum-product-subarray/discuss/48302/2-passes-scan-beats-99
https://leetcode.com/problems/maximum-product-subarray/discuss/48261/share-my-dp-code-that-got-ac
LeetCode All in One 题目讲解汇总(持续更新中...)
[LeetCode] Maximum Product Subarray 求最大子数组乘积的更多相关文章
- [LeetCode] 152. Maximum Product Subarray 求最大子数组乘积
Given an integer array nums, find the contiguous subarray within an array (containing at least one n ...
- LeetCode Maximum Product Subarray(枚举)
LeetCode Maximum Product Subarray Description Given a sequence of integers S = {S1, S2, . . . , Sn}, ...
- 【LeetCode】Maximum Product Subarray 求连续子数组使其乘积最大
Add Date 2014-09-23 Maximum Product Subarray Find the contiguous subarray within an array (containin ...
- LeetCode: Maximum Product Subarray && Maximum Subarray &子序列相关
Maximum Product Subarray Title: Find the contiguous subarray within an array (containing at least on ...
- LeetCode Maximum Product Subarray 解题报告
LeetCode 新题又更新了.求:最大子数组乘积. https://oj.leetcode.com/problems/maximum-product-subarray/ 题目分析:求一个数组,连续子 ...
- 643. Maximum Average Subarray I 最大子数组的平均值
[抄题]: Given an array consisting of n integers, find the contiguous subarray of given length k that h ...
- [LeetCode] Maximum Product Subarray 连续数列最大积
Find the contiguous subarray within an array (containing at least one number) which has the largest ...
- Leetcode Maximum Product Subarray
Find the contiguous subarray within an array (containing at least one number) which has the largest ...
- 152.[LeetCode] Maximum Product Subarray
Given an integer array nums, find the contiguous subarray within an array (containing at least one n ...
随机推荐
- 在iOS中实现一个简单的画板App
在这个随笔中,我们要为iPhone实现一个简单的画板App. 首先需要指出的是,这个demo中使用QuarzCore进行绘画,而不是OpenGL.这两个都可以实现类似的功能,区别是OpenGL更快,但 ...
- C# 本质论 第二章 数据类型
浮点数的精度由有效数字的个数决定.除非用分数表示时,分母恰好是2的整数次幂,否则用二进制浮点类型无法准确地表示该数(0.1,表示成分数是1/10,分母10不能用有限二进制表示),二进制浮点类型无法准确 ...
- Memcached初识
高性能分布式内存对象缓存系统. 参考: Memcached官网: Memcached简介-1:Memcached简介-2: Memcached 教程 | 菜鸟教程:
- Android Service
一.在MainAcitivity界面启动Service : public class MyService extends Service intent = new Intent(MainActivi ...
- discuz模板语法
Discuz! X 模板的解析主要是 ./source/class/class_template.php 文件解析处理的,如果需要深入了解请可以看看这个文件! 模板嵌套语法 将被嵌套模板内容解析为 P ...
- Java常用的几种集合, Map集合,Set集合,List集合
Java中 Object是所有类的根 Java集合常用的集合List集合.Set集合.Map集合 Map接口常用的一些方法 size() 获取集合中名值对的数量 put(key k, value v ...
- java socket编程(li)
一.网络编程中两个主要的问题 一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输.在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以 ...
- Java设计模式——线程安全的单件模式
单件模式,也称单例模式,用以创建独一无二的.只能有一个实例的对象. 单件模式的类图是所有模式的类图中最简单的--只有一个类.尽管从类设计的视角来看单件模式很简单,但是实现上还是会遇到一些问题,本文着重 ...
- 今天思考一个问题,PHP const和static的区别
static关键字在类中是,描述一个成员是静态的,static能够限制外部的访问,因为static后的成员是属于类的,是不属于任何对象实例,其他类是无法访问的,只对类的实例共享,能一定程序对该成员尽心 ...
- 3.2 js六大数据类型
js中有六种数据类型,包括五种基本数据类型(Number,String,Boolean,Null,Undefined),和一种混合数据类型(Object). 前面说到js中变量是松散类型的,因此有时候 ...