• 2024/3/5打卡最长上升子序列**----线性DP,贪心,单调栈


    目录

    题目:

    DP分析:

    代码:

    3.6更新 贪心 第一个思考方式

    先上代码:

    解析:

    贪心 第二个思考方式 (与上面的思路差不多,但是换了个角度)

    思路:

    代码:


     所有的思路很重要!!!

    题目:

    给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。

    输入格式

    第一行包含整数 N。

    第二行包含 N 个整数,表示完整序列。

    输出格式

    输出一个整数,表示最大长度。

    数据范围

    1≤N≤1000,
    −10^9≤数列中的数≤10^9

    输入样例:

    1. 7
    2. 3 1 2 1 8 5 6

    输出样例:

    4

    DP分析:

    代码:

    1. import java.io.*;
    2. import java.util.*;
    3. class Main{
    4. static int N = 1010;
    5. static int[] w = new int[N];
    6. static int[] f = new int[N];
    7. public static void main(String[] args) throws IOException{
    8. BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    9. int n = Integer.parseInt(in.readLine());
    10. String[] s = in.readLine().split(" ");
    11. for(int i=1;i<=n;i++){
    12. w[i] = Integer.parseInt(s[i-1]);
    13. }
    14. // DP
    15. for(int i=1;i<=n;i++){
    16. f[i] = 1; // 只有自身
    17. for(int j=1;j
    18. if(w[j]// 保证序列单调上升
    19. f[i] = Math.max(f[i],f[j]+1);
    20. }
    21. }
    22. // 不一定f[n]就是最长的,因为f[n]必定包含w[n]这个数,最长上升子序列可能不包含w[n]
    23. int res = 0;
    24. for(int i=1;i<=n;i++) res = Math.max(res,f[i]);
    25. System.out.println(res);
    26. }
    27. }

     PS:

    本来想尝试单调栈的。发现比如  3 1 2 1 8 5 6 。栈会删去 2 ,存进 1。而最长上升子序列为 1 2 5 6。


    3.6更新 贪心 第一个思考方式

            上面说到不可以使用单调栈。并且使用上面的代码,时间复杂度是O(n^2),如果N增大,就会超时。

    单调栈:单调栈(思路+示例)-CSDN博客

    但是参考了一篇大佬的代码 AcWing 896. 最长上升子序列 II - AcWing  我悟了。

    先上代码:

    1. // 类似于单调栈的做法,但是更多的有贪心的思想在。
    2. // 替换掉第一个大于等于该值的在栈中的元素,目的是保证可以最大限度的增加上升子序列的长度。
    3. /* ex:3 1 2 1 8 5 6
    4. 使用st[]存储值
    5. i = 1, st[] = {3}
    6. i = 2, st[] = {1}
    7. i = 3, st[] = {1,2}
    8. i = 4, st[] = {1,2} 此时这个1是被w[4]替换掉w[2]的1
    9. i = 5, st[] = {1,2,8}
    10. i = 6, st[] = {1,2,5}
    11. i = 7, st[] = {1,2,5,6}
    12. */
    13. import java.io.*;
    14. import java.util.*;
    15. class Main{
    16. static int N = 100010;
    17. static int[] w = new int[N];
    18. static int[] st = new int[N]; // 单调栈
    19. public static void main(String[] args) throws IOException{
    20. BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    21. int n = Integer.parseInt(in.readLine());
    22. String[] s = in.readLine().split(" ");
    23. for(int i=1;i<=n;i++){
    24. w[i] = Integer.parseInt(s[i-1]);
    25. }
    26. int res = 0;
    27. int tt = 0; // 栈顶
    28. for(int i=1;i<=n;i++){
    29. if(tt==0||w[i]>st[tt]){ // 如果栈中没有值或者当前值大于栈顶元素,加入进去
    30. st[++tt] = w[i];
    31. res = Math.max(res,tt);
    32. }
    33. else{ // 否则,替换掉第一个大于等于该值的在栈中的元素
    34. int idx = tt;
    35. while(idx>0&&w[i]<=st[idx])
    36. idx--;
    37. st[idx+1] = w[i];
    38. }
    39. }
    40. System.out.println(res);
    41. }
    42. }

    解析:

            使用了单调栈+贪心的思想。

            如果当前该值 w[i] 大于栈顶元素,就加入栈 st[ \ ]进去。

            否则,找到栈中第一个大于等于 w[i] 的值,用 w[i] 替换掉该值。

                                                                                    (用二分,虽然这个代码没用)

    ex:

             a=[3,1,4,1,5,9,2]
             i = 1, st[\ ] = {3}\\ i = 2, st[\ ] = {1}\\ i = 3, st[\ ] = {1,4}\\ i = 4, st[\ ] = {1,4} \\ i = 5, st[\ ] = {1,4,5}\\ i = 6, st[\ ] = {1,4,5,9}\\ i = 7, st[\ ] = {1,2,5,9}\\

            疑惑:

    1. 一般的单调栈会将第 4 步的时候,删去 1,4 ,再将 a[4] 装入进去,但是会缩短上升子序列的长度。并且在 7 步的时候,数字 9 被保留了下来,没有被去除换成 2
    2. 最后的序列为 (1,2,5,9),而准确的最长上升子序列应该是 ({1,4,5,9}),这是为什么。

            第一个问题:为什么有这种替换操作?主要是贪心,我们并不想在求解的过程中导致最长上升子序列越算越短。因此,如果我们目前算出的结果还没以前的长,会暂时保留以前的结果,当然也不丢弃目前的结果,因为之后继续计算的话,目前的结果可能更优。

            为了实现上述目的,我们可以用新序列从左到右逐渐覆盖掉旧序列。当新序列长度 <原序列长度时,原序列没有被完全覆盖,因此保证长度不减小;当新序列长度 ≥原序列长度时,原序列已经被完全覆盖,现在就是以新序列为基础进行计算了。

            因此就产生了这种特殊的替换方式。

            第二个问题最后的最长上升子序列不是准确的?因为由于贪心的思想,存在这种替换方式,导致最长上升子序列中某些值被替换掉,但是上升子序列的长度没有发生改变,只有里面的值发生了变化,因此,在求解该题中最长上升子序列的长度时可以被使用,但是要输出最长上升子序列的值的时候,就不行了。

            核心就是因为栈中储存的不只有一个序列,是旧序列和新序列合并的产物,因此不一定是最终最长上升子序列。

     


    贪心 第二个思考方式 (与上面的思路差不多,但是换了个角度)

    思路:

            换一种思路思考贪心的问题,如果我们要将 w[i] 作为上升子序列的末尾元素。那么我们可以设计一个数组 q[\ ],存储当上升子序列长度分别为 [1,2,3...\ n] 的时候最小的末尾值,其中,末尾值都是跟随数组下标的上升而严格上升的

            如果想要将 w[i] 装入到序列中,我们只需要在长度为 q[\ ] 中找到大于等于 w[i] 的第一个末尾值 q[j] ,此时 q[j-1] < w[i],\ q[j]>=w[i] 。此时我们将 w[i] 装入到 q[j-1] 的末尾,此时,序列的长度就增加了1,因此就要更新 q[j] 的末尾值为 w[i] (这个操作就跟上面那种方法替换大于等于 w[i] 的第一个值思路一样),从而保证了序列中的值尽可能小来增加最长子序列的可能性。

            因为数组 q[\ ] 中的值是严格单调递增的,因此可以使用二分来找到该 j 点。

     

    代码:

    1. import java.io.*;
    2. import java.util.*;
    3. class Main{
    4. static int N = 100010;
    5. static int[] w = new int[N];
    6. static int[] q = new int[N]; // 存储每个上升子序列长度的最小末尾值
    7. public static void main(String[] args) throws IOException{
    8. BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    9. int n = Integer.parseInt(in.readLine());
    10. String[] s = in.readLine().split(" ");
    11. for(int i=1;i<=n;i++){
    12. w[i] = Integer.parseInt(s[i-1]);
    13. }
    14. int len = 0; // q数组的长度
    15. for(int i=1;i<=n;i++){
    16. int l = 0, r = len; // l只能从0开始,因为存值的时候要+1
    17. while(l// 找到q[l]
    18. int mid = l+r+1>>1;
    19. if(q[mid]
    20. else r = mid-1;
    21. }
    22. q[r+1] = w[i]; // 更新l+1的末尾值
    23. len = Math.max(len,r+1); // 取当前数组q和更新的数组q长度的最大值
    24. }
    25. System.out.println(len);
    26. }
    27. }

  • 相关阅读:
    Linux Command——ls
    高并发系统设计之限流
    网络损伤仪HoloWAN的使用背景
    安全运营中心自动化究竟是好还是坏
    C语言入门Day_24 函数与指针
    总在用户态调试 C# 程序,终还是搭了一个内核态环境
    原型制作与图解
    教你实现物联网HMI/网关的趋势功能
    解决方案 | 法大大电子签助力融资租赁突围数字化
    haproxy keepalive实践
  • 原文地址:https://blog.csdn.net/weixin_63001635/article/details/136486681