package niuke;
/*
https://blog.csdn.net/michaelhan3/article/details/74357730
https://www.cnblogs.com/coderJiebao/p/Algorithmofnotes27.html
https://www.xuebuyuan.com/3239902.html
给定k个整数的序列{N1,N2,...,Nk },其任意连续子序列可表示为{ Ni, Ni+1, ..., Nj },
其中 1 <= i <= j <= k。最大连续子序列是所有连续子序中元素和最大的一个,例如给定序列
{ -2, 11, -4, 13, -5, -2 },其最大连续子序列为{11,-4,13},最大连续子序列和即为20。
注:为方便起见,如果所有整数均为负数,则最大子序列和为0。
解决这样一个问题是一个很有趣的过程,我们可以尝试着从复杂度比较高的算法一步一步地推出复杂度较低的算法。
*/
public class MaxSubSequence {
public static int getSolution(int[] ints) {
int i, j, k, thisSum, maxSum = 0;
int N = ints.length;
for (i = 0; i < N; i++) {
for (j = i; j < N; j++) {
thisSum = 0;
for (k = i; k <= j; k++) {
thisSum += ints[k];
if (thisSum > maxSum) {
maxSum = thisSum;
}
}
}
}
return maxSum;
}
public static int getSolution2(int[] ints) {
int i, j,thisSum,maxSum = 0;
int N = ints.length;
for (i = 0; i < N; i++) {
thisSum = 0;
for (j = i; j < N; j++) {
thisSum += ints[j];
if (thisSum > maxSum) {
maxSum = thisSum;
}
}
}
return maxSum;
}
/*
算法三:递归法(分治法)
时间复杂度:O(NlogN)
易知,对于一数字序列,其最大连续子序列和对应的子序列可能出现在三个地方。
或是整个出现在输入数据的前半部(左),或是整个出现在输入数据的后半部(右),
或是跨越输入数据的中部从而占据左右两半部分。前两种情况可以通过递归求解,
第三种情况可以通过求出前半部分的最大和(包含前半部分的最后一个元素)以及后半
部分的最大和(包含后半部分的第一个元素)而得到,然后将这两个和加在一起即可。
*/
public static int getRecursionSolution(int[] ints) {
int N=ints.length;
return MaxSubSum(ints,0,N-1);
}
private static int MaxSubSum(int[] ints, int left, int right) {
int MaxLeftSum,MaxRightSum;
if(left==right){
if(ints[left]>0){
return ints[left];
}else{
return 0;
}
}
int center=(left+right)/2;
MaxLeftSum=MaxSubSum(ints,left,center);
MaxRightSum=MaxSubSum(ints,center+1,right);
int MaxLeftBorderSum = 0;
int LeftBorderSum = 0;
int i;
for(i = center;i >= left;i--){ //找出左侧最大的值
LeftBorderSum+=ints[i];
// System.out.println(LeftBorderSum+" "+MaxLeftBorderSum);
if(LeftBorderSum > MaxLeftBorderSum)
MaxLeftBorderSum = LeftBorderSum;
}
// System.out.println(LeftBorderSum+" "+MaxLeftBorderSum);
int MaxRightBorderSum = 0;
int RightBorderSum = 0;
for(i = center+1;i <= right;i++)
{
RightBorderSum += ints[i];
if(RightBorderSum > MaxRightBorderSum)
MaxRightBorderSum = RightBorderSum;
}
// System.out.println("MaxLeftSum="+MaxLeftSum+" MaxRightSum="+MaxRightSum+" 中间="+(MaxLeftBorderSum)+"-"+MaxRightBorderSum);
return Max(MaxRightBorderSum+MaxLeftBorderSum,MaxLeftSum,MaxRightSum);
}
private static int Max(int a, int b, int c) {
{
if(a>b&&a>c)
return a;
else if(b>a&&b>c)
return b;
else
return c;
}
}
/*
动态规划算法:复杂度为 O(n)
(1)令dp[i]表示必须以A[i]结尾的连续序列的最大和(这里是说 A[i] 必须作为连续序列的末尾)。
(2)因为dp[i]要求以A[i]结尾,那么2种情况
这个最大和的连续序列只有一个元素,即以 A[i] 开始,以 A[i] 结尾。即只有A[i]
这个最大和的连续序列有多个元素,则为dp[i-1]+A[i]
于是得到状态转移方程:
dp[i] = max{A[i], dp[i-1]+A[i]}
这个式子只和 i 与 i 之前的元素有关,且边界为 dp[0] = A[0],由此从小到大枚举 i,即可得到整个 dp 数组。
接着输出 dp[0],dp[1],...,dp[n-1] 中的最大值即为最大连续子序列的和。
* */
private static int dynamicSolution(int[] ints) {
int[] dp=new int[ints.length];
dp[0]=ints[0];
for(int i=1;i<ints.length;i++){
dp[i]=max(ints[i],dp[i-1]+ints[i]);
}
int result=dp[0];
for(int i=1;i<ints.length;i++){
if(dp[i]>result){
result=dp[i];
}
}
return result;
}
private static int max(int a, int b) {
return a>b?a:b;
}
/*贪心算法
对于每一个子段,贪心的认为加上下一个数之和可能是更大
如果加上下一个数没有变大,但子段和为正,则贪心的认为加下一个数之和更大
如果加上某个数后子段和为负,那么放弃这个子段,开始新的子段
记录贪心过程中遇到的最大子段和
* */
public static int greedSolution(int[] ints){
int thisSum=0;
int maxSum=0;
for(int i=0;i<ints.length;i++){
int tmp=ints[i];
if(thisSum<0){//前面和小于0,重新开
thisSum=tmp;
}else{ //前面和大于0,则新元素添加后,贪心认为更大
thisSum+=tmp;
}
if(thisSum>maxSum){
maxSum=thisSum;
}
}
return maxSum;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] ints = { -2, 11, -4, 13, -5, -2 };
int result = getSolution(ints);
System.out.println("暴力破解O(n^3):" + result);
result = getSolution(ints);
System.out.println("优化暴力破解:" + result);
result = getRecursionSolution(ints);
System.out.println("分治法O(logn):" + result);
result = dynamicSolution(ints);
System.out.println("动态规划O(n):" + result);
result = greedSolution(ints);
System.out.println("贪心算法O(n):" + result);
}
}