前言
本文将围绕【最大子数组和】问题展开讨论。
对于这一问题将采用多种思路方法来解决
【循环暴搜】【贪心】【动态规划】【分治】
一、如何理解【最大子数组和】?
题目链接: [点击跳转] Leetcode 53. 最大子数组和
题目如下:
这是一道经典的数组题,可以尝试多种方法来解决。
二、方法一(循环暴搜)
(方法一时间复杂度高易超时,也可从方法二开始浏览)
在读过题之后,我们很容易就会想到:
既然是找子数组,那么我们就可以做一个嵌套循环,一个双层的循环就可以表示任意一个含两个元素的子数组了。
就像这样:
for(int i=0;i<size;i++){
for(int j=i+1;j<size;j++){
}
}//(一个指向子数组开头,一个指向子数组末尾)
在这个嵌套循环进行的过程中,将元素累加起来,并记录下最大的子数组。
最后在找一遍单个元素的子数组,取最大,就OK啦!
代码如下:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int sum=0;//子数组和
int size=nums.size();//数组大小
int maxsum=INT_MIN;//表示最大子数组
//判断两个及以上的相邻元素
for(int i=0;i<size;i++){
sum+=nums[i];
for(int j=i+1;j<size;j++){
sum+=nums[j];
maxsum=max(sum,maxsum);
}
//sum清0
sum=0;
}
//单个元素
for(int k=0;k<size;k++){
maxsum=max(maxsum,nums[k]);
}
return maxsum;
}
};
!!!!但是,如果所给数组很长,由于我们使用嵌套循环,时间复杂度为O(n^2),我们很有可能会导致超时。
三、方法二(贪心)
最大子数组有一个特点,这个数组的开头和结尾都不会有负数(因为如果是负数的话,去掉这个负数之后,一定比原来的大,那它就不是最大子数组了)
因此我们可以延申一下,最大子数组的开头和结尾都不会有 和为负数的子数组(因为如果子数组和是负数的话,去掉这个子数组之后,一定比原来的大,那它就不是最大子数组了)
所以我们只需要走一遍循环,在循环中只有子数组和不为负数,才继续往下走,否则就清空,以下一个元素为起始,继续下去,直到走完整个数组,返回最大的子数组即可。
代码如下:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int sum=0;//记录子数组
int maxsum=INT_MIN;//最大子数组
for(int i=0;i<nums.size();i++){
sum+=nums[i];
maxsum=max(maxsum,sum);
if(sum<=0){//跳出
sum=0;//清0
}
}
return maxsum;
}
};
时间复杂度为O(n)。
四、方法三(动态规划)
思路类似于贪心,首先动态规划先将问题分解成子问题。
比如说,给定的数组长度为n。
我们记录下以第0位元素结尾时的最大子数组。
我们再记录下以第1位元素结尾时的最大子数组。
我们再记录下以第2位元素结尾时的最大子数组。
我们再记录下以第3位元素结尾时的最大子数组。
······
我们再记录下以第n位元素结尾时的最大子数组。
我们发现以
当第0位元素结尾时的最大子数组>0时
第1位元素结尾时的最大子数组=第0位元素结尾时的最大子数组+第1位元素
当第0位元素结尾时的最大子数组<=0时
第1位元素结尾时的最大子数组=第1位元素
因此我们只需在一层循环中,找到以第?位元素结尾时子数组最大就可以啦!
template<typename T>
void set_max(T &a,T &b) {
a = max(a, b);
}
class Solution {
private:
public:
int maxSubArray(vector<int> &nums) {
int n = (int) nums.size();
vector<int> dp(n, 0);
dp[0] = nums[0];
int res = nums[0];
for (int i = 1; i < n; ++i) {
set_max(res, dp[i] = max(dp[i - 1] + nums[i], nums[i]));
}
return res;
}
};
时间复杂度为O(n)。
五、方法四(分治)
分治算法的核心是将一个大问题分解为多个较小规模的子问题,分别求解这些子问题,然后将子问题的解合并得到原问题的解。
采用分治算法将数组划分为左右两个子数组,然后分别求解左子数组、右子数组以及跨越中间位置的子数组的最大子数组和,最后取三者中的最大值作为原问题的解。
由于子问题具有相似的结构,可以使用递归来求解子问题。对于最大子数组和问题,递归地求解左子数组和右子数组的最大子数组和,然后再处理跨越中间位置的子数组,最后将三个部分的最大子数组和进行比较,得到原问题的解。
代码如下:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
return maxSubArrayHelper(nums, 0, nums.size() - 1);
}
int maxSubArrayHelper(vector<int>& nums, int left, int right) {
// 说明子数组只有一个元素
if (left == right) return nums[left];
int mid = left + (right - left) / 2;
// 递归计算左子数组的最大子数组和
int leftMax = maxSubArrayHelper(nums, left, mid);
// 递归计算右子数组
int rightMax = maxSubArrayHelper(nums, mid + 1, right);
// 计算跨越中间位置的子数组的最大子数组和
int crossMax = findCrossMax(nums, left, mid, right);
// 返回最大值
return max({leftMax, rightMax, crossMax});
}
int findCrossMax(vector<int>& nums, int left, int mid, int right) {
int leftSum = INT_MIN, rightSum = INT_MIN, sum = 0;
// 计算左边部分的最大子数组和
for (int i = mid; i >= left; --i) {
sum += nums[i];
leftSum = max(leftSum, sum);
}
sum = 0;
// 计算右边
for (int i = mid + 1; i <= right; ++i) {
sum += nums[i];
rightSum = max(rightSum, sum);
}
// 返回左右总和
return leftSum + rightSum;
}
};
时间复杂度为O(nlogn)。