请计算给出的数组(至少含有一个数字)中具有最大和的子数组(子数组要求在原数组中连续)
例如:给出的数组为[−2,0,−3,4,−2,2,2,−5,4],
子数组[−2,0,−3,4,−2,2,2,−5,4],具有最大的和:6.
/* *从头开始累加,直到和为负。此时前面这段不能给后面的串带来正收益,应舍弃,sum清零 *然后在开始统计最大的sum. */ public class Solution { public int maxSubArray(int[] A) { if(A.length == 0) return 0; int sum = 0, max = A[0]; for(int i = 0; i < A.length; i++) { sum += A[i]; if(max < sum) max = sum; if(sum < 0) sum = 0; } return max; } }
class Solution { public: int maxSubArray(int A[], int n) { return divide(A, 0, n-1); } //分治方法 int divide(int A[], int left, int right){ if(left == right){ return A[left]; } int mid = (left + right)/2; //假设最大子序列和的部分全在左边或右边 int max1 = divide(A, left, mid),max2 = divide(A, mid + 1, right); //如果跨过中间,前半部分必须以mid结尾,后半部分开头必须是mid+1 int max3 = INT_MIN, tmp = 0; for(int i = mid; i >= left; i--){ tmp += A[i]; max3 = max(max3, tmp); } int max4 = INT_MIN, sum = 0; for(int i = mid+1; i <= right; i++){ sum += A[i]; max4 = max(max4, sum); } return max(max(max1,max2), max3+max4); } };
Leetcode#53.Maximum Subarray(最大子序和)
package Array;
/**
* 53.Maximum Subarray(最大子序和)
* 给定一个序列(至少含有 1 个数),从该序列中寻找一个连续的子序列,使得子序列的和最大。
* 例如,给定序列 [-2,1,-3,4,-1,2,1,-5,4],
* 连续子序列 [4,-1,2,1] 的和最大,为 6。
*/
public class Solution53 {
public static void main(String[] args) {
Solution53 solution53 = new Solution53();
int[] arr = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
System.out.println(solution53.maxSubArray(arr));
}
/**
* maxSum 必然是以nums[i](取值范围为nums[0] ~ nums[n-1])结尾的某段构成的,也就是说maxSum的candidate必然是以nums[i]结果的。如果遍历每个candidate,然后进行比较,那么就能找到最大的maxSum了。
* 假设把nums[i]之前的连续段叫做sum。可以很容易想到:
* 1\. 如果sum>=0,就可以和nums[i]拼接在一起构成新的sum。因为不管nums[i]多大,加上一个正数总会更大,这样形成一个新的candidate。
* 2\. 反之,如果sum<0,就没必要和nums[i]拼接在一起了。因为不管nums[i]多小,加上一个负数总会更小。此时由于题目要求数组连续,所以没法保留原sum,所以只能让sum等于从nums[i]开始的新的一段数了,这一段数字形成新的candidate。
* 3\. 如果每次得到新的candidate都和全局的maxSum进行比较,那么必然能找到最大的max sum subarray.
* 在循环过程中,用maxSum记录历史最大的值。从nums[0]到nums[n-1]一步一步地进行。
*
* [@param nums
* @return](/profile/547241) */
public int maxSubArray(int[] nums) {
int sum = 0; //或者初始化为 sum = INT_MIN 也OK。
int maxSum = nums[0];
for (int i = 0; i < nums.length; i++) {
if (sum >= 0) {
sum += nums[i];
} else {
sum = nums[i];
}
if (sum > maxSum) {
maxSum = sum;
}
}
return maxSum;
}
/**
* 遍历array,对于每一个数字,我们判断,(之前的sum + 这个数字) 和 (这个数字) 比大小,如果(这个数字)自己就比 (之前的sum + 这个数字) 大的话,那么说明不需要再继续加了,直接从这个数字,开始继续,因为它自己已经比之前的sum都大了。
* 反过来,如果 (之前的sum + 这个数字)大于 (这个数字)就继续加下去。
* 利用动态规划做题。
* 只遍历数组一遍,当从头到尾部遍历数组A, 遇到一个数有两种选择 (1)加入之前subArray (2)自己另起一个subArray
* 设状态S[i], 表示以A[i]结尾的最大连续子序列和,状态转移方程如下:
* S[i] = max(S[i-1] + A[i],A[i])
* 从状态转移方程上S[i]只与S[i-1]有关,与其他都无关,因此可以用一个变量来记住前一个的最大连续数组和就可以了。
* 这样就可以节省空间了。
* 时间复杂度:O(n) 空间复杂度:O(1)
*/
public int maxSubArray_2(int[] nums) {
int sum = 0; //或者初始化为 sum = INT_MIN 也OK。
int maxSum = nums[0];
//动态规划
for (int i = 0; i < nums.length; i++) {
sum = Math.max(sum + nums[i], nums[i]);
maxSum = Math.max(sum, maxSum);
}
return maxSum;
}
}
/**
* 53. Maximum Subarray
* 最大子序和
* 给定一个整数数组 nums,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
* 示例:
* 输入: [-2,1,-3,4,-1,2,1,-5,4],
* 输出: 6
* 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
*
* @author shijiacheng
*
*/
public class Solution {
public int maxSubArray(int[] nums) {
if(nums == null || nums.length == 0) {
return 0;
}
int sum = nums[0];//默认选择第一个数
int a = nums[0];
for(int i = 1;i<nums.length;i++) {
if(a>0) {
a += nums[i];
}else {
a = nums[i];
}
if(sum < a) {
sum = a;
}
}
return sum;
}
public static void main(String[] args) {
Solution s = new Solution();
int[] nums = {-2,1,-3,4,-1,2,1,-5,4};
int sum = s.maxSubArray(nums);
System.out.println(sum);
}
}
/*
最大连续子序列和
例如,给定的数组[−2,1,−3,4,−1,2,1,−5,4 ],相邻子阵[ 4,−1,2,1 ]拥有最大的总和= 6。点击显示更多的练习。
更多的实践:
如果您已经解决了O(n)的解决方案,请尝试使用分而治之的方法编写另一个解决方案,这是比较微妙的。
*/
/*
分析:动归
*/
class Solution {
public:
int maxSubArray(int A[], int n) {
int result=INT_MIN,f=0;
for(int i=0;i<=n-1;++i){
f=max(f+A[i],A[i]);
result=max(result,f);
}
return result;
}
};
class Solution { public: int maxSubArray(int A[], int n) { int max=0; for(int i=0;i<n;i++) { if(max<A[i]) max=A[i]; } if(max<=0) { max=A[0]; for(int i=1;i<n;i++) { if(max<A[i]) max=A[i]; } return max; } else return maxsum(A,0,n-1); } int maxsum(int a[],int left,int right) { int center; int lefts,rights,s1,s2; int midsum,leftsum,rightsum; int sum=0; if(right==left) return a[left]; center=(left+right)/2; leftsum=maxsum(a,left,center); rightsum=maxsum(a,center+1,right); s1=0;lefts=0; for(int i=center;i>=0;i--) { lefts+=a[i]; if(s1<lefts)s1=lefts; } s2=0;rights=0; for(int i=center+1;i<=right;i++) { rights+=a[i]; if(s2<rights)s2=rights; } midsum=s1+s2; if(leftsum<midsum)sum=midsum; else sum=leftsum; if(sum<rightsum)sum=rightsum; return sum; } };
/* 思路1:动态规划 状态定义: F[i]:以i下标为结尾的子数组中的最大和 此时已知F[i]的值,想知道F[i+1],怎么做呢? 可以这么想: 如果 F[i]+A[i+1] > A[i+1],是说明把他们连起来是有利的,那么 F[i+1]=F[i]+A[i+1] 如果 F[i]+A[i+1] < A[i+1],说明把他们连起来不利,干脆就让A[i+1]作为新的子数组,F[i+1] = A[i+1] 状态转移方程; F[i] = max(F[i-1]+A[i],A[i]); */ /* class Solution { public: int maxSubArray(int* A, int n) { //特殊情况 if(A==nullptr || n==0){ return 0; } vector<int> F(n); F[0] = A[0]; //计算 for(int i=1;i<n;i++){ F[i] = max(F[i-1]+A[i],A[i]); } //F数组保存的是以各个下标为截至的子数组的最大和,因此需要进行升序排序,返沪最大的一个和 sort(F.begin(),F.end()); return F[n-1]; } }; */ /* 思路2:贪心 其实和动规的流程差不多,也是:对我有利才吸收,不然我就另起炉灶 */ class Solution { public: int maxSubArray(int* A, int n) { //特殊情况 if(A==nullptr || n==0){ return 0; } //保存当前最大的子数组和,但这里要用数组的首元素作为初始,因为数组中可能都是负数 int totalMax = A[0]; //保存当前的子数组和 int sum = A[0]; for(int i=1;i<n;i++){ if(sum+A[i] >= A[i]){ //对我有利,吸收 sum += A[i]; }else{ //对我不利,我另起炉灶 sum = A[i]; } //每遍历一个元素,就要判断最大值 totalMax = max(totalMax,sum); } return totalMax; } };
def maxSubArray(self , A ): # write code here max_sum = [] ss = 0 for i in range(len(A)): ss += A[i] max_sum.append(ss) if ss < 0: ss =0 if max_ss: return max(max_ss) else: return max(A)
两种方法——
arr[i]
为以元素i结尾的子数组的最大和,那么arr[i] = max(arr[i-1]+nums[i], nums[i])
,最大的arr[i]
即是答案 显然,贪心法的时间复杂度为O(n)
,分治法的时间复杂度为O(nlogn)
一直对“贪心”算法的概念比较模糊(或排斥),可能因为贪心这个词语不是那么正面吧,但这恰恰表明了贪心的核心:
每一步都获取当前位置/状态下的最优值
。联想到动态规划算法,不也是每一步都获取了当前的最优值吗?但贪心最后的解是每一步最优值中再取最优值(够贪心吧?),而动态规划直接取dp数组的最后一个值即可。
方法一:贪心
// // Created by jt on 2020/9/3. // #include <algorithm> using namespace std; class Solution { public: /** * * @param A int整型一维数组 * @param n int A数组长度 * @return int整型 */ int maxSubArray(int* A, int n) { // write code here int maxVal = A[0]; for (int i = 1; i < n; ++i) { A[i] = max(A[i], A[i-1]+A[i]); maxVal = max(maxVal, A[i]); } return maxVal; } };
方法二:分治
// // Created by jt on 2020/9/3. // #include <iostream> using namespace std; class Solution { public: /** * * @param A int整型一维数组 * @param n int A数组长度 * @return int整型 */ int maxSubArray(int* A, int n) { // write code here return divideAndConquer(A, 0, n-1); } private: int divideAndConquer(int *A, int left, int right) { if (left > right) return INT32_MIN; if (left == right) return A[left]; int mid = left + (right-left)/2; int leftMax = divideAndConquer(A, left, mid); int rightMax = divideAndConquer(A, mid+1, right); int midMax = A[mid], tmp = A[mid]; for (int i = mid - 1; i >= left; --i) { tmp += A[i]; midMax = max(tmp, midMax); } tmp = midMax; for (int i = mid + 1; i <= right; ++i) { tmp += A[i]; midMax = max(tmp, midMax); } return max(midMax, max(leftMax, rightMax)); } };
import java.util.*; public class Solution { /** * * @param A int整型一维数组 * @return int整型 */ public int maxSubArray (int[] A) { // write code here // 最初最大值选取仅含第一个值 int max = A[0]; // 遍历选取起始点 for (int i = 0; i < A.length; i++) { int sum = 0; // 从当前起始点到数组末尾的子数组 for (int j = i; j < A.length; j++) { sum += A[j]; // 计算从该起始点起的子数组和 max = Math.max(max, sum); // 如果当前子数组和比max大,刷新max的值 } } return max; } }
import java.util.*; public class Solution { /** * * @param A int整型一维数组 * @return int整型 */ public int maxSubArray (int[] nums) { // write code here int length = nums.length; int[] subArray = new int[length]; int curMax = nums[0]; subArray[0] = nums[0]; for (int i = 1; i < nums.length; i++) { subArray[i] = Math.max(nums[i], subArray[i - 1] + nums[i]); curMax = Math.max(curMax,subArray[i]); } return curMax; } }