【算法提高:动态规划】1.2 最长上升子序列模型(TODO最长公共上升子序列)

news2024/9/24 11:28:05

文章目录

  • 题目列表
    • 1017. 怪盗基德的滑翔翼
    • 1014. 登山
    • 482. 合唱队形
    • 1012. 友好城市(⭐排序后 最长上升子序列模型)
    • 1016. 最大上升子序列和
    • 1010. 拦截导弹
      • 解法1——最长递减子序列 + 贪心
      • 解法2——最长递减子序列 + 最长递增子序列(⭐贪心结论)
    • 187. 导弹防御系统⭐⭐⭐⭐⭐(至少需要多少个 上升/下降 子序列)(dfs + 最少需要多少最长上升子序列)⭐⭐⭐⭐⭐
    • 272. 最长公共上升子序列⭐⭐⭐⭐⭐
      • 解法1——一起计算⭐⭐⭐⭐⭐
      • 解法2——先求最长公共子序列,再求最长上升子序列长度❌(错误解法!)
  • 补充相关题目列表
    • 673. 最长递增子序列的个数
    • 115. 不同的子序列
  • 相关链接

题目列表

1017. 怪盗基德的滑翔翼

https://www.acwing.com/problem/content/1019/
在这里插入图片描述
注意 基德 可以从任意地方开始选择向左或向右出发。
所以需要 dp 两次。

import java.io.BufferedInputStream;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sin = new Scanner(new BufferedInputStream(System.in));
        int k = sin.nextInt();
        while (k-- != 0) {
            int n = sin.nextInt(), ans = 1;
            int[] a = new int[n], dp = new int[n];
            Arrays.fill(dp, 1);
            for (int i = 0; i < n; ++i) {
                a[i] = sin.nextInt();
                for (int j = 0; j < i; ++j) {
                    if (a[i] < a[j]) dp[i] = Math.max(dp[i], dp[j] + 1);
                }
                ans = Math.max(ans, dp[i]);
            }
            Arrays.fill(dp, 1);
            for (int i = n - 1; i >= 0; --i) {
                for (int j = n - 1; j > i; --j) {
                    if (a[i] < a[j]) dp[i] = Math.max(dp[i], dp[j] + 1);
                }
                ans = Math.max(ans, dp[i]);
            }
            System.out.println(ans);
        }
    }
}

1014. 登山

https://www.acwing.com/problem/content/1016/
在这里插入图片描述
因为一旦开始向下走之后就不会再往上走了。

所以向上走的状态只能从向上走的状态转移过来;向下走的状态既能从向上走的状态转移过来,也可以从向下走的状态转移过来。

import java.io.BufferedInputStream;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sin = new Scanner(new BufferedInputStream(System.in));
        int n = sin.nextInt();
        int[] a = new int[n];
        for (int i = 0; i < n; ++i) a[i] = sin.nextInt();
        int[][] dp = new int[n][2];
        int ans = 1;
        for (int i = 0; i< n; ++i) {
            dp[i][0] = dp[i][1] = 1;
            for (int j = 0; j < i; ++j) {
                if (a[i] > a[j]) {
                    dp[i][0] = Math.max(dp[i][0], dp[j][0] + 1);
                }
                else if (a[i] < a[j]) {
                    dp[i][1] = Math.max(dp[i][1], Math.max(dp[j][0], dp[j][1]) + 1);
                }
            }
            ans = Math.max(ans, Math.max(dp[i][0], dp[i][1]));
        }
        System.out.println(ans);
    }
}

482. 合唱队形

https://www.acwing.com/problem/content/484/
在这里插入图片描述
计算每个节点作为结尾位置时向左和向右的递减子序列的最长长度即可。

注意最后要求的答案是需要几位同学出列 而不是 最多保留几位同学。

import java.io.BufferedInputStream;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sin = new Scanner(new BufferedInputStream(System.in));
        int n = sin.nextInt();
        int[] t = new int[n];
        for (int i = 0; i < n; ++i) t[i] = sin.nextInt();
        int[][] dp = new int[n][2];
        for (int i = 0; i < n; ++i) {
            dp[i][0] = dp[i][1] = 1;
            for (int j = 0; j < i; ++j) {
                if (t[i] > t[j]) dp[i][0] = Math.max(dp[i][0], dp[j][0] + 1);
            }
        }
        int ans = 1;
        for (int i = n - 1; i >= 0; --i) {
            for (int j = n - 1; j > i; --j) {
                if (t[i] > t[j]) dp[i][1] = Math.max(dp[i][1], dp[j][1] + 1);
            }
            ans = Math.max(ans, dp[i][0] + dp[i][1] - 1);
        }
        System.out.println(n - ans);
    }
}

1012. 友好城市(⭐排序后 最长上升子序列模型)

https://www.acwing.com/problem/content/1014/

在这里插入图片描述

按照一岸排序,求另一岸此时的最长递增子序列。

import java.io.BufferedInputStream;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sin = new Scanner(new BufferedInputStream(System.in));
        int n = sin.nextInt();
        int[][] a = new int[n][2];
        for (int i = 0; i < n; ++i) {
            a[i][0] = sin.nextInt();
            a[i][1] = sin.nextInt();
        }
        // 按照一岸排序
        Arrays.sort(a, (x, y) -> x[0] - y[0]);
        // 求另一岸此时的最长递增子序列
        int ans = 0;
        int[] dp = new int[n];
        Arrays.fill(dp, 1);
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < i; ++j) {
                if (a[i][1] > a[j][1]) dp[i] = Math.max(dp[i], dp[j] + 1);
            }
            ans = Math.max(ans, dp[i]);
        }
        System.out.println(ans);
    }
}

1016. 最大上升子序列和

https://www.acwing.com/problem/content/1018/

在这里插入图片描述

跟最长上升子序列长度的方法差不多,只修改了递推公式部分,将 + 1 修改成了 + a[i]。

import java.io.BufferedInputStream;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sin = new Scanner(new BufferedInputStream(System.in));
        int n = sin.nextInt();
        int[] a = new int[n];
        for (int i = 0; i < n; ++i) a[i] = sin.nextInt();
        int[] dp = new int[n];
        Arrays.setAll(dp, i -> a[i]);
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < i; ++j) {
                if (a[i] > a[j]) {
                    dp[i] = Math.max(dp[i], dp[j] + a[i]);
                }
            }
            ans = Math.max(ans, dp[i]);
        }
        System.out.println(ans);
    }
}

1010. 拦截导弹

https://www.acwing.com/problem/content/1012/

在这里插入图片描述
使用最长上升子序列模型解决问题1。(对于这题是个最长递减子序列)。

对于问题2,当一个新的导弹来临时,我们首先检查是否有已经存在的系统可以拦截它。如果可以,我们应该选择一个能够拦截这个新来的导弹并且当前最高高度最低的系统来拦截它
结论:从上面的贪心可以推出来,是在求最长上升子序列。

解法1——最长递减子序列 + 贪心

import java.io.BufferedInputStream;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sin = new Scanner(new BufferedInputStream(System.in));
        final int N = 1001;
        int[] a = new int[N], dp = new int[N];
        int n = 0;
        while (sin.hasNext()) {
            a[n++] = sin.nextInt();
        }

        // 使用动态规划求解最多能拦截的导弹数
        int ans1 = 0;
        Arrays.fill(dp, 1);
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < i; ++j) {
                if (a[i] <= a[j]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
            ans1 = Math.max(ans1, dp[i]);
        }
        System.out.println(ans1);
		
		// 贪心
        List<Integer> ls = new ArrayList<>();
        for (int i = 0; i < n; ++i) {
            int k = 0;
            while (k < ls.size() && ls.get(k) < a[i]) k++;
            if (k >= ls.size()) ls.add(a[i]);
            else ls.set(k, a[i]);
        }
        System.out.println(ls.size());
    }
}

解法2——最长递减子序列 + 最长递增子序列(⭐贪心结论)

import java.io.BufferedInputStream;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sin = new Scanner(new BufferedInputStream(System.in));
        final int N = 1001;
        int[] a = new int[N], dp = new int[N], dp2 = new int[N];
        int n = 0;
        while (sin.hasNext()) {
            a[n++] = sin.nextInt();
        }
		
		// 不递增和递增子序列的最大长度
        int ans1 = 0, ans2 = 0;
        Arrays.fill(dp, 1);
        Arrays.fill(dp2, 1);
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < i; ++j) {
                if (a[i] <= a[j]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                } else dp2[i] = Math.max(dp2[i], dp2[j] + 1);
            }
            ans1 = Math.max(ans1, dp[i]);
            ans2 = Math.max(ans2, dp2[i]);
        }
        System.out.println(ans1 + "\n" + ans2);
    }
}

187. 导弹防御系统⭐⭐⭐⭐⭐(至少需要多少个 上升/下降 子序列)(dfs + 最少需要多少最长上升子序列)⭐⭐⭐⭐⭐

https://www.acwing.com/problem/content/189/

在这里插入图片描述
注意数据范围是 50 比较小。

在上一题的代码框架基础上 增加一个 dfs 爆搜。

import java.io.BufferedInputStream;
import java.util.*;

public class Main {
    final static int N = 52;
    static int[] q = new int[N], up = new int[N], down = new int[N];
    static int ans = 0, n = 0;

    public static void main(String[] args) {
        Scanner sin = new Scanner(new BufferedInputStream(System.in));
        while (true) {
            n = sin.nextInt();
            if (n == 0) return;
            ans = 0;
            dfs(0, 0, 0);
            System.out.println(ans);
        }
    }

    // u,su,sd 表示枚举到了第一个数,当前上升子序列的个数,当前下降子序列的个数
    static void dfs(int u, int su, int sd) {
        if (su + sd >= ans) return;
        if (u == n) {
            ans = su + sd;
            return;
        }

        // 情况1:将当前数放到上升子序列中
        int k = 0;
        while (k < su && up[k] >= q[u]) k++;
        int t = up[k];
        up[k] = q[u];
        if (k < su) dfs(u + 1, su, sd);
        else dfs(u + 1, su + 1, sd);
        up[k] = t;      // 恢复现场

        // 情况2:将当签署放到下降子序列中
        k = 0;
        while (k < sd && down[k] <= q[u]) k++;
        t = down[k];
        down[k] = q[u];
        if (k < sd) dfs(u + 1, su, sd);
        else dfs(u + 1, su, sd + 1);
        down[k] = t;
    }
}

Q:为什么不用 bfs 来求解?
A:因为 bfs 需要的空间太大,可能会爆炸。(bfs 存一层,dfs 存一个路径)。

272. 最长公共上升子序列⭐⭐⭐⭐⭐

https://www.acwing.com/problem/content/274/

在这里插入图片描述

解法1——一起计算⭐⭐⭐⭐⭐

dp[i][j] 表示 两个数组 0 ~ i 和 0 ~ j 之间的 且以 b[j] 为结尾的 最长公共上升子序列长度。

import java.io.BufferedInputStream;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sin = new Scanner(new BufferedInputStream(System.in));
        int n = sin.nextInt();
        int[] a = new int[n], b = new int[n];
        for (int i = 0; i < n; ++i) a[i] = sin.nextInt();
        for (int i = 0; i < n; ++i) b[i] = sin.nextInt();

        // dp[i][j] 表示 两个数组 0 ~ i 和 0 ~ j 之间的最长公共上升子序列长度。
        int[][] dp = new int[n + 1][n + 1];
        for (int i = 1; i <= n; ++i) {
            int maxV = 1;      // 可以上升的长度
            for (int j = 1; j <= n; ++j) {
                dp[i][j] = dp[i - 1][j];
                if (a[i - 1] == b[j - 1]) dp[i][j] = Math.max(dp[i][j], maxV);  	// 相等,是公共子序列
                if (b[j - 1] < a[i - 1]) maxV = Math.max(maxV, dp[i - 1][j] + 1);	// 更新可以上升的长度
            }
        }
        int ans = Arrays.stream(dp[n]).max().getAsInt();
        System.out.println(ans);
    }
}

解法2——先求最长公共子序列,再求最长上升子序列长度❌(错误解法!)

比如
1 2 3 9 8 7 6 5 4序列

9 8 7 6 5 4 1 2 3 序列
筛选出的最长公共子序列应该是9 8 7 6 5 4,但是我们需要的最长公共上升子序列应该是 1 2 3

所以最长公共上升子序列并不一定在 最长公共子序列当中,它可能会在某个比较短的公共子序列当中。

补充相关题目列表

673. 最长递增子序列的个数

https://leetcode.cn/problems/number-of-longest-increasing-subsequence/description/
在这里插入图片描述

class Solution {
    public int findNumberOfLIS(int[] nums) {
        int n = nums.length, mxL = 1, ans = 0;
        int[] dp = new int[n], cnt = new int[n];
        Arrays.fill(dp, 1);
        Arrays.fill(cnt, 1);
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < i; ++j) {
                if (nums[i] > nums[j]) {
                    if (dp[j] + 1 > dp[i]) {
                        dp[i] = dp[j] + 1;
                        cnt[i] = cnt[j];
                    } else if (dp[j] + 1 == dp[i]) {
                        cnt[i] += cnt[j];
                    }
                }
            }
            mxL = Math.max(mxL, dp[i]);
        }
        for (int i = 0; i < n; ++i) {
            if (dp[i] == mxL) ans += cnt[i];
        }
        return ans;
    }
}

115. 不同的子序列

https://leetcode.cn/problems/distinct-subsequences/description/

在这里插入图片描述
提示:

1 <= s.length, t.length <= 1000
s 和 t 由英文字母组成

class Solution {
    public int numDistinct(String s, String t) {
        int m = s.length(), n = t.length();
        // dp[i][j] 表示考虑s从0~i-1的子序列中,以t[0:j]出现的次数(注意这里j就表示j-1结尾了,i表示考虑0~i-1)
        int[][] dp = new int[m + 1][n + 1];
        for (int i = 0; i <= m; ++i) dp[i][0] = 1;      // s[0:i-1]中出现""的次数是1
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                dp[i][j] = dp[i - 1][j];                // 至少也和 i - 1 一样
                // 如果一样,说明当前i-1可以匹配到j-1结尾,从i-1,j-1转移过来
                // 转移是 + ,因为求的是出现的个数:i-1匹配到了说明出现了新一批匹配完全的s中以i-1为结尾和t[0:j]匹配上的子序列
                if (s.charAt(i - 1) == t.charAt(j - 1)) dp[i][j] += dp[i - 1][j - 1];
            }
        }
        return dp[m][n];
    }
}

相关链接

【算法】最长递增子序列:动态规划&贪心+二分查找

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/797096.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

K8s集群部署-详细步骤

不够详细&#xff0c;后面有时间再编辑 安装 关闭防火墙 systemctl stop firewalld systemctl disable firewalld 关闭swap, selinux swapoff -a && sed -i / swap / s/^\(.*\)$/#\1/g /etc/fstab setenforce 0 && sed -i s/^SELINUX.*/SELINUXdisabled/ /…

Safari 查看 http 请求

文章目录 1、开启 Safari 开发菜单2、显示 JavaScript 控制台 1、开启 Safari 开发菜单 Safari 设置中&#xff0c;打开开发菜单选项 *** 选择完成后&#xff0c;Safari 的目录栏就会出现一个 开发 功能。 2、显示 JavaScript 控制台 开启页面后&#xff0c;在开发中选中 显…

Android 之 动画合集之补间动画

本节引言&#xff1a; 本节带来的是Android三种动画中的第二种——补间动画(Tween)&#xff0c;和前面学的帧动画不同&#xff0c;帧动画 是通过连续播放图片来模拟动画效果&#xff0c;而补间动画开发者只需指定动画开始&#xff0c;以及动画结束"关键帧"&#xff0…

提示计算机丢失MSVCP140.dll怎么办?这三个修复方法可解决

最近在使用电脑的过程中&#xff0c;遇到了一个问题&#xff0c;即缺少了MSVCP140.dll文件。这个文件是一个动态链接库文件&#xff0c;常用于Windows操作系统中的应用程序中。由于缺少这个文件&#xff0c;会导致计算机系统无法运行某些软件或游戏。丢失MSVCP140.dll可能是由于…

【技术分享】oracle数据库相关操作

-- 截断表 TRUNCATE TABLE TABLE_NAME;-- 删除表 DROP TABLE TABLE_NAME;-- 查询表 SELECT * FROM TABLE_NAME;-- 添加一条记录 INSERT INTO TABLE_NAME(COLUMN) VALUES(VALUE);-- 删除记录 DELETE FROM TABLE_NAME WHERE COLUMNVALUE;-- 修改记录 UPDATE TABLE_NAME SET…

Android性能优化之Thread native层源码分析(InternalError/Out of memory)

近期处理Bugly上OOM问题&#xff0c;很多发生在Thread创建启动过程&#xff0c;虽然最后分析出是32位4G虚拟内存不足导致&#xff0c;但还是分析下Java层Thread 源码过程&#xff0c;可能会抛出的异常InternalError/Out of memory。 Thread报错堆栈&#xff1a; Java线程创建…

数据库|手把手教你成为 TiDB 的 Contributor

一、背景 最近笔者在 AskTUG 回答问题的时候发现&#xff0c;在 6.5.0 版本出现了几个显示未启动必要组件 NgMonitoring 的问题贴。经过排查发现&#xff0c;是 ngmonitoring.toml 中的配置文件出现了问题。文件中的 endpoints 应该是以逗号分隔的&#xff0c;但是却写成了以空…

JavaWeb 项目实现(二) 注销功能

3.注销功能 接前篇&#xff0c;实现了登录功能之后&#xff0c;现在实现注销功能。 因为我们实现登录就是在Session中记录了用户信息。 所以注销功能&#xff0c;就是在Session中移除用户信息。 代码&#xff1a;删除Session中的用户信息&#xff0c;跳转登录页面 package…

【安全渗透】第一次作业(编码知识总结)

目录 1. ASCII编码 2、Unicode 3、UTF-8 1. ASCII编码 ASCII 是“American Standard Code for Information Interchange”的缩写&#xff0c;翻译过来是“美国信息交换标准代码”。ASCII 的标准版本于 1967 年第一次发布&#xff0c;最后一次更新则是在 1986 年&#xff0c…

QEMU源码全解析13 —— QOM介绍(2)

接前一篇文章&#xff1a;QEMU源码全解析12 —— QOM介绍&#xff08;1&#xff09; 本文内容参考&#xff1a; 《趣谈Linux操作系统》 —— 刘超&#xff0c;极客时间 《QEMU/KVM》源码解析与应用 —— 李强&#xff0c;机械工业出版社 特此致谢&#xff01; 本回开始对QOM…

django学习笔记(1)

django创建项目 先创建一个文件夹用来放django的项目&#xff0c;我这里是My_Django_it 之后打开到该文件下&#xff0c;并用下面的指令来创建myDjango1项目 D:\>cd My_Django_itD:\My_Django_it>"D:\zzu_it\Django_learn\Scripts\django-admin.exe" startpr…

记录每日LeetCode 2500.删除每行中的最大值 Java实现

题目描述&#xff1a; 给你一个 m x n 大小的矩阵 grid &#xff0c;由若干正整数组成。 执行下述操作&#xff0c;直到 grid 变为空矩阵&#xff1a; 从每一行删除值最大的元素。如果存在多个这样的值&#xff0c;删除其中任何一个。 将删除元素中的最大值与答案相加。 …

Reinforcement Learning with Code 【Chapter 7. Temporal-Difference Learning】

Reinforcement Learning with Code This note records how the author begin to learn RL. Both theoretical understanding and code practice are presented. Many material are referenced such as ZhaoShiyu’s Mathematical Foundation of Reinforcement Learning, . 文章…

Linux内核与内核空间是什么关系呢?

对内核空间的认识清晰了许多。要理解用户空间与内核空间需要有如下的几个认识&#xff1a; 内核的认识&#xff1a;从2个不同的角度来理解&#xff0c;一个是静态的角度&#xff0c;如“芦中人”所比喻&#xff0c;内核可以看做是一个lib库&#xff0c;内核对外提供的API打包…

快速远程桌面控制公司电脑远程办公

文章目录 快速远程桌面控制公司电脑远程办公**第一步****第二步****第三步** 快速远程桌面控制公司电脑远程办公 远程办公的概念很早就被提出来&#xff0c;但似乎并没有多少项目普及落实到实际应用层面&#xff0c;至少在前几年&#xff0c;远程办公距离我们仍然很遥远。但20…

1分钟上手Apifox

1、客户端右上角账号设置-生成令牌 2、IDEA下载插件 Apifox Helper 3、 配置ApiFoxHelper 令牌 4、在controller类界面右键 5、输入项目id 6、项目ID从客户端 项目设置-项目ID获取 7、导入成功 8、右键刷新查看导入的接口 9、自动生成数据&#xff08;某postman还要自己手输&a…

说一说java中的自定义注解之设计及实现

一、需求背景 比如我们需要对系统的部分接口进行token验证&#xff0c;防止对外的接口裸奔。所以&#xff0c;在调用这类接口前&#xff0c;先校验token的合法性&#xff0c;进而得到登录用户的userId/role/authority/tenantId等信息&#xff1b;再进一步对比当前用户是否有权…

MyBatis 快速入门【中】

&#x1f600;前言 本篇博文是MyBatis(简化数据库操作的持久层框架)–快速入门[上]的核心部分&#xff0c;分享了MyBatis实现sql的xml配置和一些关联配置、异常分析 &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&a…

软件外包开发的需求分析

需求分析是软件开发中的关键步骤&#xff0c;其目的是确定用户需要什么样的软件&#xff0c;以及软件应该完成哪些任务。需求分析是软件工程的早期工作&#xff0c;也是软件项目成功的基础&#xff0c;因此花费大量精力和时间去做好需求分析是值得的。今天和大家分享软件需求分…

数字孪生-数字城市效果实现方法

数字孪生-数字城市效果实现方法 效果图&#xff1a; 一、效果分析&#xff1a; .0 1、城市非主展示区域白模快速生成方案&#xff1a; 参考视频&#xff1a; 1、CityEngine 引用数据源生成。 cityengine2022一键生成城市模型&#xff0c;不用再用blendergis_哔哩哔哩_bil…