第21章 最长上升子序列和最大子段和

1、蒜头君的最大子段和

算法分析

方法一:(动态规划)
1、设 f(i) 表示以第i 个数字为结尾的最大连续子序列的 和 是多少。
2、初始化 f(1)=nums[1]
3、转移方程 f(i)=max(f(i−1)+nums[i],nums[i])。可以理解为当前有两种决策,一种是将第 i个数字和前边的数字拼接起来;另一种是第 i 个数字单独作为一个新的子序列的开始。
4、最终答案为 ans=max(f(k)),1≤k

方法二:分治法,时间复杂度,这里就不写了

时间复杂度

Java 代码

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
    static int N = 1000010;
    static long[] f = new long[N];
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        String[] s1 = br.readLine().split(" ");
        for(int i = 1;i <= n;i ++)
        {
            f[i] = Integer.parseInt(s1[i - 1]);
        }
        long res = f[1];
        for(int i = 2;i <= n;i ++)
        {
            f[i] = Math.max(f[i - 1] + f[i], f[i]);
            res = Math.max(res, f[i]);
        }
        System.out.println(res);
    }

}

2、蒜头君的最大子矩阵和

算法分析

最暴力的做法
先计算出前缀和矩阵,再枚举两个对角点(i1,j1),(i2,j2),则阴影部分的总和为,f[i1][j1] - f[i1][j2 - 1] - f[i2 - 1][j1] + f[i2 - 1][j2 - 1],找出所以子矩阵的最大值
n = 400,最坏情况运行的次数是400 * 400 * 400 * 400 = 2.56 * 10^10,肯定gg

  • 1、预处理出前缀矩阵和或者每一列的前缀和
  • 2、枚举开始行i1,枚举结束行i2,并处理出从i1行到i2行中每一列的值(即计算出绿色的值),每一列看出一个整体,并求出最大连续序列和,该值表示从i1行到i2行的最大子矩阵和
    image.png

时间复杂度

Java 代码 (前缀和矩阵写法)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
    static int N = 410;
    static long[][] f = new long[N][N];
    static long[] temp = new long[N];
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] s1 = br.readLine().split(" ");
        int n = Integer.parseInt(s1[0]);
        int m = Integer.parseInt(s1[1]);
        for(int i = 1;i <= n;i ++)
        {
            String[] s2 = br.readLine().split(" ");
            for(int j = 1;j <= m;j ++)
            {
                f[i][j] = Integer.parseInt(s2[j - 1]);
            }
        }
        //预处理出前缀和矩阵
        for(int i = 1;i <= n;i ++)
        {
            for(int j = 1;j <= m;j ++)
            {
                f[i][j] += f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1];
            }
        }
        long res = Integer.MIN_VALUE;
        for(int i1 = 1;i1 <= n;i1 ++)
        {
            for(int i2 = 1;i2 <= i1;i2 ++)
            {
                for(int j = 1;j <= m;j ++)
                {
                    temp[j] = f[i1][j] - f[i2 - 1][j] - f[i1][j - 1] + f[i2 - 1][j - 1];
                }
                long cnt = temp[1];
                //求temp数组的最大子序列和
                for(int j = 2;j <= m;j ++)
                {
                    temp[j] = Math.max(temp[j - 1] + temp[j],temp[j]);
                    cnt = Math.max(cnt, temp[j]);
                }
                res = Math.max(res, cnt);
            }
        }
        System.out.println(res);
    }
}

Java 代码(每一列的前缀和)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
    static int N = 410;
    static long[][] f = new long[N][N];
    static long[] temp = new long[N];
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] s1 = br.readLine().split(" ");
        int n = Integer.parseInt(s1[0]);
        int m = Integer.parseInt(s1[1]);
        for(int i = 1;i <= n;i ++)
        {
            String[] s2 = br.readLine().split(" ");
            for(int j = 1;j <= m;j ++)
            {
                f[i][j] = Integer.parseInt(s2[j - 1]);
            }
        }
        //预处理出每一列的前缀和
        for(int j = 1;j <= m;j ++)
        {
            for(int i = 1;i <= n;i ++)
            {
                f[i][j] += f[i - 1][j];
            }
        }
        long res = Integer.MIN_VALUE;
        for(int i1 = 1;i1 <= n;i1 ++)
        {
            for(int i2 = 1;i2 <= i1;i2 ++)
            {
                for(int j = 1;j <= m;j ++)
                {
                    temp[j] = f[i1][j] - f[i2 - 1][j];
                }
                long cnt = temp[1];
                //求temp数组的最大子序列和
                for(int j = 2;j <= m;j ++)
                {
                    temp[j] = Math.max(temp[j - 1] + temp[j],temp[j]);
                    cnt = Math.max(cnt, temp[j]);
                }
                res = Math.max(res, cnt);
            }
        }
        System.out.println(res);
    }
}

3、蒜头君的环形矩阵

这是一道很有意思的题目,由于是环形,先把给出的矩形进行延伸,如图所示

image.png

这就与蒜头君的最大子矩阵和的思路类似了,相当于求图片中的大矩形中最大区域是红色区域的大小的非空子矩阵和,假设i1是开始行,i2是结束行,i2i1不能相差长度n的距离,把i1i2行的每一列看出一个整体,即转换为求环形最大连续子序列和问题,在2n的长度,找出长度最大是n的子序列和

  • 1.最大连续子序列和的这个子段没有包含头尾。所以直接dp[i] = max(dp[i-1]+a[i],a[i])

  • 2.最大连续子序列和的这个子段包含了头尾。最大和 = 累积和 - 连续子段最小和。

  • 3、还需要对全负的情况进行判断,返回情况1的答案

算法分析

时间复杂度

Java 代码

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
    static int N = 100 * 2;
    static long[][] f = new long[N][N];
    static long[] a = new long[N];//最大连续和
    static long[] b = new long[N];//最小连续和
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] s1 = br.readLine().split(" ");
        int n = Integer.parseInt(s1[0]);
        int m = Integer.parseInt(s1[1]);
        for(int i = 1;i <= n;i ++)
        {
            String[] s2 = br.readLine().split(" ");
            for(int j = 1;j <= m;j ++)
            {
                f[i][j] = Integer.parseInt(s2[j - 1]);
                f[i][j + n] = f[i][j];
                f[i + n][j] = f[i][j];
                f[i + n][j + n] = f[i][j];
            }
        }
        for(int j = 1;j <= 2 * m;j ++)
        {
            for(int i = 1;i <= 2 * n;i ++)
            {
                f[i][j] += f[i - 1][j];
            }
        }
        long res = Integer.MIN_VALUE;
        for(int i1 = 1;i1 <= 2 * n;i1 ++)
        {
            for(int i2 = Math.max(1, i1 - n + 1);i2 <= i1;i2 ++)
            {
                for(int j = 1;j <= m;j ++)
                {
                    a[j] = f[i1][j] - f[i2 - 1][j];
                    b[j] = a[j];
                }
                long maxV = a[1];
                long minV = b[1];
                long sum = a[1];
                for(int j = 2;j <= m;j ++)
                {
                    sum += a[j];
                    a[j] = Math.max(a[j - 1] + a[j], a[j]);
                    b[j] = Math.min(b[j - 1] + b[j], b[j]);
                    maxV = Math.max(maxV, a[j]);
                    minV = Math.min(minV, b[j]);
                }
                //特判全是0的情况
                if(maxV < 0) res = Math.max(res, maxV);
                else res = Math.max(res, Math.max(maxV, sum - minV));
            }
        }
        System.out.println(res);
    }
}

4、跳木桩

算法分析

反向求最长上升自序列

时间复杂度

Java 代码

import java.util.Scanner;

public class Main {
    static int N = 1010;
    static int n;
    static int[] h = new int[N];
    static int[] f = new int[N];
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        n = scan.nextInt();
        for(int i = 1;i <= n ;i ++)
        {
            h[i] = scan.nextInt();
        }
        int res = 0;
        //反向LIS问题
        for(int i = n;i >= 1;i --)
        {
            f[i] = 1;
            for(int j = n;j > i;j --)
            {
                if(h[i] >= h[j])
                    f[i] = Math.max(f[i], f[j] + 1);
            }
            res = Math.max(res, f[i]);
        }
        System.out.println(res);
    }
}

5、删除最小元素

算法分析

**目标:求最多能踩多少个点 **

  • 条件1:按照编号递增的顺序来浏览
  • 条件2、相邻两个元素不能相同
  • 条件3、一旦开始上升,就不能下降了

总结:所有以高度a[i]为转折点,左边到a[i]单调下降,a[i]到右边单调上升,求所有情况中最长的步数

  • 步骤1、预处理出以每个点结尾的正向的最长不上升子序列的值f[i],以每个点结尾的反向的最长不上升子序列的值g[i]
  • 步骤2、res = max(f[i] + g[i] - 1),i = 1,2,3...n

答案 = 总点数 - 最多踩的点

时间复杂度

Java 代码

import java.util.Scanner;

public class Main {
    static int N = 1010;
    static int n;
    static int[] h = new int[N];
    static int[] f = new int[N];
    static int[] g = new int[N];
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        n = scan.nextInt();
        for(int i = 1;i <= n ;i ++)
        {
            h[i] = scan.nextInt();
        }
        //正向LIS问题
        for(int i = 1;i <= n;i ++)
        {
            f[i] = 1;
            for(int j = 1;j < i;j ++)
            {
                if(h[i] <= h[j])
                    f[i] = Math.max(f[i], f[j] + 1);
            }
        }
        //反向LIS问题
        for(int i = n;i >= 1;i --)
        {
            g[i] = 1;
            for(int j = n;j > i;j --)
            {
                if(h[i] <= h[j])
                    g[i] = Math.max(g[i], g[j] + 1);
            }
        }
        int res = 0;
        for(int i = 1;i <= n;i ++) res = Math.max(res, f[i] + g[i] - 1);
        System.out.println(n - res);
    }
}

6、蒜头君闯关

算法分析

image.png

时间复杂度

Java 代码

import java.util.Scanner;

public class Main {
    static int N = 1010;
    static int n;
    static int[] a = new int[N];
    static long[] f = new long[N];
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        n = scan.nextInt();
        for(int i = 1;i <= n;i ++) a[i] = scan.nextInt();
        long res = 0;
        for(int i = 1;i <= n;i ++)
        {
            f[i] = a[i];
            for(int j = 1;j < i;j ++)
            {
                if(a[i] > a[j])
                    f[i] = Math.max(f[i], f[j] + a[i]);
            }
            res = Math.max(res, f[i]);
        }
        System.out.println(res);
    }
}

你可能感兴趣的:(第21章 最长上升子序列和最大子段和)