前言: 本文将用于记录博主的做题记录
一, 最大子数组和
题目链接 :https://leetcode.cn/problems/maximum-subarray/?envType=study-plan-v2\&envId=top-100-liked

求出数组内最大的子数组和, 由于题目要求子数组必须连续, 所以注意不要把他和子序列问题混为一谈了
对于这类在一段区间的子问题, 我首先想到就使用动态规划来解决.
按照经验来思考, 我们先初始化一个dp表, 假设dpi : 以 i位置为结尾的最大的子数组和,
先按照这样的思路来尝试解决问题
数组元素有正有负,这代表我们每次加入这个最终的子数组中的数,都会对和造成影响,如果是正数,则无脑往里进 即可, 但是如果是负数呢?难道就直接舍弃前面已经累积的和吗,万一后面后面是一个更大的数字,完全足以抵消让你目前加入这个负数带来的代价 还有累增.
为了填满dp表, 我们自然要遍历一次数组,当但是对于我们每次遍历到的数组元素numsi, 我们需要做一个判断, 那就是该不该让numsi 加入到子数组中?有两种选择
-
- 加入, 让numsi 抱大腿, 去在先前的dpi-1: 以numsi-1 为结尾的最大子数组和上做累加,那么dpi = dpi-1 + numsi
-
- 不加入, 舍弃前面的累加结果,自己作为新的子数组开头单干来, 那么此时dpi = numsi 即可
对于以上这两种情况, 并不清楚那种情况是最优的, 所以要取max
下面是使用动态规划的代码
代码参考一(动态规划)
java
``````java
class Solution {
public int maxSubArray(int[] nums) {
int n = nums.length;
if(n == 1) return nums[0];
int []dp = new int [n+1];
//dp[i]:以i位置为结尾的最大的子数组之和
int ret = Integer.MIN_VALUE;
for(int i = 1;i <= n;i++){
dp[i] = Math.max(nums[i-1],dp[i-1] + nums[i-1]);
ret = Math.max(ret,dp[i]);
}
return ret;
}
}
dp表长度为n+1,为了映射到nums中的元素, 所以要使用i-1来成功映射到nums\[\]中的元素
不过这里也分享另外一种空间复杂度更低的解法
代码参考二(贪心)
java
class Solution {
public int maxSubArray(int[] nums) {
int n = nums.length;
if(n == 1) return nums[0];
int sum = 0;//初始情况下
int ret = nums[0];
for(int i = 0;i < n;i++){
if(sum > 0){//sum对结果起到增益效果
sum += nums[i];
}else{
sum = nums[i];
}
ret = Math.max(sum,ret);
}
return ret;
}
}
总体思想不变,但是核心在于遍历中的两个判断, 这里的sum 表示为前面的子数组累加和,
在遍历数组时,当sum > 0 时,前面的累加结果为正, 则无论如何考虑,我们都会让sum 累加当前元素numsi . 这是为什么呢? 如果numsi > 0 还好, 万一numsi 是一个非常小的负数呢? 这也能说加上numi 起到增益吗?注意我在代码中的注释并不是加上numsi 会对结果起到增益,而是让numsi 加上一个前面的sum正数对结果起到增益,
假设, 前面的计算sum = 3 ,当前numsi很小,为 -100,
那就有两种选择
-
- 抱大腿,继承前面的状态, 3 +(-100) = -97
-
- 不继承,自己单干, 作为新的子数组的开头, sum = -100
把两者对比,可以发现sum > 0 时, 还是抱大腿直接累加最优,和nums正负无关
这也是sum <= 0 时. sum = numsi 这个代码的含义: numsi自己单干!
二, 乘积最大的子数组
题目链接 : https://leetcode.cn/problems/maximum-product-subarray/description/

又是子数组问题, 我们尝试使用动态规划,先按照经验,初始化一个dp表,
dpi : 以i位置为结尾的所有子数组中乘积的最大值,按照这个思路来尝试解题
由于numsi 有正有负, 所以上面的方法是不行的, 因为即使前面的状态是最大值,但是乘一个负数或者0就全部作废; 如果前面的状态是一个很小的负数,再乘一个负数就变正了
一个dp表光存储最大值肯定太局限了, 所以我们就引入两个dp表, 一个存子数组乘积最大值,另一个存储子数组乘积最小值
这样一来,就有两种情况了, 对于numsi ,先前的一个只存最大值的dp表只需要和它做一次判断即可, 这里则需要和两个dp表中保存的状态来在做一个判断,
代码参考一(动态规划)
java
class Solution {
public int maxProduct(int[] nums) {
int n = nums.length;
if(n == 1) return nums[0];
int []dp1 = new int [n+1];//大
int []dp2 = new int [n+1];//小
int ret = Integer.MIN_VALUE;
for(int i = 1;i <= n;i++){
int x = nums[i-1];
int y = x * dp1[i-1];//情况1:乘前面保存的状态最大值
int z = x * dp2[i-1];//情况2:乘前面保存状态的最小值
dp1[i] = Math.max(x,Math.max(y,z));//MAX维护dp1的最大值
dp2[i] = Math.min(x,Math.min(y,z));//MIN维护dp2的最小值
ret = Math.max(dp1[i],ret);//从dp1表中的最大值取舍
}
return ret;
}
}
我们会发现,dpi 的更新,只会依赖dpi-1 前一个状态的值, 先前位置保存的状态都用不到, 所以我们可以来使用一个滚动变量来做空间优化
代码参考二(空间优化)
java
class Solution {
public int maxProduct(int[] nums) {
int n = nums.length;
if (n == 1) return nums[0];
// 用三个变量代替原本的 dp 数组
int maxVal = nums[0]; // 最大乘积
int minVal = nums[0]; // 最小乘积
int ret = nums[0]; // 全局最大值
for (int i = 1; i < n; i++) {
int x = nums[i];
// 三种可能的结果
int a = x;
int b = maxVal * x;
int c = minVal * x;
maxVal = Math.max(a, Math.max(b, c));
minVal = Math.min(a, Math.min(b, c));
// 更新全局最大值
ret = Math.max(ret, maxVal);
}
return ret;
}
}
以上就是有关子数组问题的解题思路,如有纰漏还请指出~~