一、题目描述
给你一个整数数组 nums
,请你找出数组中乘积最大的非空连续
子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
测试用例的答案是一个 32-位 整数。
示例 1:
输入: nums = [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例 2:
输入: nums = [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
提示:
1 <= nums.length <= 2 * 10^4
-10 <= nums[i] <= 10
nums
的任何前缀或后缀的乘积都 保证 是一个 32-位 整数
二、解题思路
这个问题是求解最大乘积子数组,可以采用动态规划的方法来解决。由于数组中存在负数,而负数乘以负数会变成正数,所以最大的乘积可能来自于当前最大值的累积,也可能来自于当前最小值的累积(如果当前元素是负数的话)。因此,在遍历数组的过程中,我们需要同时记录当前的最大乘积和最小乘积。
解题思路如下:
-
初始化两个变量,imax和imin,分别用来存储到当前位置为止的最大乘积和最小乘积。同时,需要一个变量max用来记录全局的最大乘积。
-
遍历数组,对于每个元素num,更新imax和imin:
- 如果num大于0,那么imax和imin分别乘以num,更新它们的值。
- 如果num小于0,那么imax和imin交换,因为负数乘以较小的数会得到更大的数。
- 更新全局最大乘积max。
- 遍历完成后,max就是我们要找的最大乘积。
三、具体代码
java
public class Solution {
public int maxProduct(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int max = nums[0]; // 全局最大乘积
int imax = nums[0]; // 当前最大乘积
int imin = nums[0]; // 当前最小乘积
for (int i = 1; i < nums.length; i++) {
if (nums[i] < 0) {
// 如果当前元素是负数,交换imax和imin
int temp = imax;
imax = imin;
imin = temp;
}
// 更新最大乘积和最小乘积
imax = Math.max(nums[i], imax * nums[i]);
imin = Math.min(nums[i], imin * nums[i]);
// 更新全局最大乘积
max = Math.max(max, imax);
}
return max;
}
}
四、时间复杂度和空间复杂度
1. 时间复杂度
- 该算法包含一个循环,循环次数与输入数组
nums
的长度成正比,记为n
。 - 在循环内部,每次迭代都执行了常数时间的操作,包括比较、乘法、赋值和更新最大值。
- 因此,整个算法的时间复杂度是
O(n)
,其中n
是数组nums
的长度。
2. 空间复杂度
- 该算法只使用了固定数量的额外空间,即
imax
、imin
和max
这三个整数变量,不管输入数组nums
的大小如何,所使用的额外空间都保持不变。 - 因此,空间复杂度是
O(1)
,即常数空间复杂度。
综上所述,该算法的时间复杂度是O(n)
,空间复杂度是O(1)
。这意味着算法的运行时间随着输入数组大小的增加而线性增加,而所需的额外空间不会随着输入数组大小的增加而增加。
五、总结知识点
-
动态规划:这是一种在数学、管理科学、计算机科学、经济学和生物信息学等领域中使用的,通过把原问题分解为相对简单的子问题的方式来求解复杂问题的方法。在这个问题中,我们通过维护当前的最大乘积和最小乘积来找到全局最大乘积。
-
分治思想:虽然代码中没有直接使用分治算法,但是动态规划可以看作是分治思想的一个特例。分治思想是将一个问题分解成若干个规模较小的相同问题,然后将这些小问题的解决方案合并起来解决原来的问题。
-
贪心算法:在每一步选择中都采取在当前状态下最好或最优的选择,希望通过局部最优的选择达到全局最优的结果。在这个问题中,我们每次都选择是当前最大乘积或最小乘积的累积,这是基于当前状态的局部最优选择。
-
边界条件处理 :在代码的开始部分,对输入数组
nums
进行了空值和长度的检查,这是一种常见的编程实践,用于处理边界情况,避免空指针异常或逻辑错误。 -
数学运算:代码中使用了乘法和比较运算符来计算乘积和比较大小,这是编程中基本的数学操作。
-
循环结构 :使用
for
循环来遍历数组,这是编程中常用的控制结构,用于重复执行一系列操作。 -
条件语句 :使用
if
语句来检查当前元素是否为负数,并根据条件执行不同的代码块,这是编程中用于分支选择的基本结构。 -
变量交换 :在遇到负数时,通过引入临时变量
temp
来交换imax
和imin
的值,这是一种常见的编程技巧。 -
数学函数的使用 :使用
Math.max
和Math.min
函数来获取两个数中的最大值和最小值,这是Java标准库中提供的实用函数。
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。