动态规划算法专题(七):两个数组的dp问题

news2025/1/15 8:39:35

目录

1、最长公共子序列

1.1 算法原理 

 1.2 算法代码

2、不相交的线

2.1 算法原理

 2.2 算法代码

3、不同的子序列

3.1 算法原理 

3.2 算法代码

4、通配符匹配(hard ★★★)

4.1 算法原理 

4.2 算法代码

5、正则表达式匹配(hard ★★★)

5.1 算法原理 

5.2 算法代码 

6、交错字符串 

6.1 算法原理 

6.2 算法代码

7、两个字符串的最小ASCII删除和

7.1 算法原理 

7.2 算法代码

 8、最长重复子数组【子数组问题】

8.1 算法原理 

8.2 算法代码


1、最长公共子序列

. - 力扣(LeetCode)

1.1 算法原理 

  • 状态表示:

dp[i][j]:s1[0, i]区间以及s2[0, j]区间内的所有子序列中,最长公共子序列的长度

  • 状态转移方程:

s[i] == s[j]:

dp[i][j] + 1

s[i] != s[j]:

max(dp[i-1][j],dp[i][j-1]);

  • 初始化:

字符串存在空串情况,引入空串(多开辟一行,多开辟一列)
1. 方便初始化
2. 处理空串

  • 建表顺序:

从上往下,从左往右

  • 返回值:

dp[m][n]

 1.2 算法代码

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        int m = text1.length(), n = text2.length();
        // 初始化
        int[][] dp = new int[m + 1][n + 1];
        // 处理下标映射关系
        text1 = " " + text1;
        text2 = " " + text2;
        // 填表
        for(int i = 1; i <= m; i++) {
            for(int j = 1; j <= n; j++) {
                if(text1.charAt(i) == text2.charAt(j)) dp[i][j] = dp[i - 1][j - 1] + 1;
                else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
        return dp[m][n];
    }
}

2、不相交的线

. - 力扣(LeetCode)

2.1 算法原理

经分析,将两个数组最长的公共子序列相连,即可绘制的最大连线数。故,求最长公共子序列的长度即可。

  • 状态表示:

dp[i][j]:n1的[0, i]区间内以及n2的[0, j]区间内的所有子序列中,最长公共子序列的长度

  • 状态转移方程:

n1[i] == n2[j] --> dp[i][j] = dp[i-1][j-1] + 1

n1[i] != n2[j] --> dp[i][j] = max(dp[i-1][j], dp[i][j-1])

  • 初始化:

多开辟一行,多开辟一列

  • 建表顺序:

从上往下,从左往右

  • 返回值:

dp[m][n]

 2.2 算法代码

class Solution {
    public int maxUncrossedLines(int[] nums1, int[] nums2) {
        // 即:最长公共子序列问题
        int m = nums1.length, n = nums2.length;
        int[][] dp = new int[m + 1][n + 1];
        for(int i = 1; i <= m; i++) {
            for(int j = 1; j <= n; j++) {
                if(nums1[i - 1] == nums2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
                else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
        return dp[m][n];
    }
}

3、不同的子序列

 . - 力扣(LeetCode)

3.1 算法原理 

  • 状态表示:

dp[i][j]:s[0, i]区间的所有子序列中,有多少个t[0, j]区间内的子串

  • 状态转移方程:

dp[i][j] = dp[i-1][j-1](s[j] == t[i]时) + dp[i][j-1]

  • 初始化:

上面多开辟一行,左边多开辟一列:
1. 引入空串
2. 里面的值要保证后续的填表是正确的(第一行代表t为空串,1;第一列代表s为空串,0)
3. 下标的映射关系

  • 建表顺序:

从上往下每一行,每一行从左往右

  • 返回值:

dp[m][n]

3.2 算法代码

class Solution {
    public int numDistinct(String s, String t) {
        int m = t.length();
        int n = s.length();
        // 处理下标映射关系
        s = " " + s;
        t = " " + t;
        int[][] dp = new int[m + 1][n + 1];
        // 初始化
        for(int j = 0; j <= n; j++) dp[0][j] = 1;
        // 从上往下 从左往右 建表
        for(int i = 1; i <= m; i++) {
            for(int j = 1; j <= n; j++) {
                dp[i][j] = dp[i][j - 1];
                if(s.charAt(j) == t.charAt(i)) dp[i][j] += dp[i - 1][j - 1];
            }
        }
        return dp[m][n];
    }
}

4、通配符匹配(hard ★★★)

 . - 力扣(LeetCode)

4.1 算法原理 

  • 状态表示:

dp[i][j]:s的[0, i]区间内的子串,能否被p[0 , j]区间内的子串所匹配

  • 状态转移方程:

1. p[j]是普通字符 --> (p[j]==s[i] && dp[i-1][j-1])==true --> true

2. p[j]是 '?' --> dp[i-1][j-1]

3. p[j]是 '*' --> 替换n个字符 --> dp[i-n][j-1]

故,p[j] == '*' --> dp[i][j]=dp[i][j-1] || dp[i-1][j-1] || dp[i-2][j-1] || ...

  • 优化(当p[j] == '*'时):

dp[i][j]=dp[i][j-1] || dp[i-1][j]

  • 建表顺序:

从上往下每一行,
从左往右每一列

  • 返回值:

dp[m][n];
其中,m为s串的长度,n为p串的长度。

4.2 算法代码

class Solution {
    public boolean isMatch(String s, String p) {
        int m = s.length();
        int n = p.length();
        boolean[][] dp = new boolean[m + 1][n + 1];
        // 初始化
        dp[0][0] = true;
        boolean check = false;
        for(int j = 0; j < n; j++) 
            if(p.charAt(j) == '*') dp[0][j + 1] = true;
            else break;
        // 处理下标映射关系
        s = " " + s;
        p = " " + p;
        // 填表
        for(int i = 1; i <= m; i++) {
            for(int j = 1; j <= n; j++) {
                char ch = p.charAt(j);
                if(ch != '*' && ch != '?') {
                    if(ch == s.charAt(i))
                        dp[i][j] = dp[i - 1][j - 1];
                }else if(ch == '?'){
                    dp[i][j] = dp[i - 1][j - 1];
                }else {
                    dp[i][j] = dp[i][j - 1] || dp[i - 1][j];
                }
            }
        }
        return dp[m][n];
    }
}

5、正则表达式匹配(hard ★★★)

. - 力扣(LeetCode)

5.1 算法原理 

  • 状态表示:

dp[i][j]:s的[0, i]区间,是否可以用p的[0, j]区间匹配

  • 状态转移方程:

1. p[j]是'a'~'z' --> dp[i][j] = p[j]==s[i] && dp[i-1][j-1]

2. p[j]是'.' --> dp[i][j] = dp[i-1][j-1]

3. p[j]是'*' 

3.1 p[j - 1]是'.':

空串 --> dp[i][j] = dp[i][j-2]
一个字符 --> dp[i][j] = dp[i-1][j-2]
两个字符 --> dp[i][j] = dp[i-2][j-2]
三个字符 --> dp[i][j] = dp[i-3][j-2]
....

(时间复杂度会飙到O(N^3)...优化如下)

  • 优化方式1(当p[j] == '*' && p[j-1] == '.' 时):

dp[i][j] = dp[i][j-2] || dp[i-1][j-2] || dp[i-2][j-2] || dp[i-3][j-2] || ...

dp[i-1][j] = dp[i-1][j-2] || dp[i-2][j-2] || dp[i-3][j-2] || dp[i-4][j-2] || ...

所以:dp[i][j] = dp[i][j-2] || dp[i-1][j]

  • 优化方式2(当p[j] == '*' && p[j-1] == '.'时)优化状态转移方程:

'.*'匹配s的最后一个字符后,不丢弃 --> dp[i-1][j]

'.*'不用匹配了,丢弃(空串) --> dp[i][j-2]

所以:dp[i][j] = dp[i][j-2] || dp[i-1][j]

3.1 p[j - 1]是'a' ~ 'z':

dp[i][j] = dp[i][j - 2] || (p[j - 1] == s[i] && dp[i - 1][j])

  • 建表顺序:

从上往下每一行,
从左到右每一列

  • 返回值:

dp[m][n]

5.2 算法代码 

class Solution {
    public boolean isMatch(String ss, String pp) {
        int m = ss.length();
        int n = pp.length();
        // dp[i][j]:s的[0, i]区间,是否可以用p的[0, j]区间匹配
        boolean[][] dp = new boolean[m + 1][n + 1];
        // 处理下标映射关系
        ss = " " + ss; pp = " " + pp;
        char[] s = ss.toCharArray(); 
        char[] p = pp.toCharArray(); 
        // 初始化
        dp[0][0] = true;
        for(int j = 1; j + 1 <= n; j += 2) {
            if(p[j + 1] == '*') dp[0][j + 1] = true;
            else break;
        }
        for(int i = 1; i <= m; i++) {
            for(int j = 1; j <= n; j++) {
                if(p[j] == s[i] || p[j] == '.') {// p[j]是'a'~'z'或者p[j]是'.'的情况下 
                    dp[i][j] = dp[i - 1][j - 1] ? true : false;
                }else if(p[j] == '*') {// p[j]是'*' --> 依赖p[j - 1]的值
                    if(p[j - 1] == '.') dp[i][j] = dp[i][j - 2] || dp[i - 1][j];
                    else dp[i][j] = dp[i][j - 2] || (p[j - 1] == s[i] && dp[i - 1][j]);
                }
            }
        }
        return dp[m][n];
    }
}

6、交错字符串 

. - 力扣(LeetCode)

6.1 算法原理 

  • 预处理:

s1 = " "+s1;  s2 = " "+s2;  s3 = " "+s3;

  • 状态表示dp[i][j]:

s1[1, i]区间内的子串,s2[1, j]区间内的子串,能否交错拼接成s3[1, i+j]内的子串

  • 状态转移方程:

dp[i][j] = (s1[i] == s3[i + j] && dp[i - 1][j]) || (s2[j] == s3[i + j] && dp[i][j - 1])

  • 建表顺序:

从上往下每一行,
从左往右每一列

  • 返回值:

dp[m][n]

6.2 算法代码

class Solution {
    public boolean isInterleave(String ss1, String ss2, String ss3) {
        int m = ss1.length();
        int n = ss2.length();
        if(m + n != ss3.length()) return false;
        boolean[][] dp = new boolean[m + 1][n + 1];
        ss1 = " " + ss1; ss2 = " " + ss2; ss3 = " " + ss3;
        char[] s1 = ss1.toCharArray(); 
        char[] s2 = ss2.toCharArray();
        char[] s3 = ss3.toCharArray();
        // 初始化第一个位置
        dp[0][0] = true;
        // 初始化第一列
        for(int i = 1; i <= m; i++) 
            if(s1[i] == s3[i]) dp[i][0] = true;
            else break;
        // 初始化第一行
        for(int j = 1; j <= n; j++) 
            if(s2[j] == s3[j]) dp[0][j] = true;
            else break;
        // 填表
        for(int i = 1; i <= m; i++) {
            for(int j = 1; j <= n; j++) {
                if(s1[i] == s3[i + j] && dp[i - 1][j]) dp[i][j] = true;
                else if(s2[j] == s3[i + j] && dp[i][j - 1]) dp[i][j] = true; 
            }
        }
        return dp[m][n];
    }
}

7、两个字符串的最小ASCII删除和

. - 力扣(LeetCode)

7.1 算法原理 

  • 问题转换:(正难则反)

两个字符串的最小ASCII删除和 --> 公共子序列ASCII的最大和
 

  • 状态表示:

dp[i][j]:s1[0, i]以及s2[0, j]的所有子序列中,公共子序列的ASCII最大和

包含s1[i],包含s2[j] --> s1[i]==s2[j] --> dp[i-1][j-1]+s[i / j]

包含s1[i],不包含s2[j] !--> dp[i][j-1]

不包含s1[i],包含s2[j] !--> dp[i-1][j]

不包含s1[i],不包含s2[j] --> dp[i-1][j-1]

  • 建表顺序:

从上往下每一行,
从左往右每一列

  • 返回值:

dp[m][n]

7.2 算法代码

class Solution {
    public int minimumDeleteSum(String ss1, String ss2) {
        int m = ss1.length(), n = ss2.length();
        // dp[i][j]:s1[0, i]以及s2[0, j]的所有子序列中,公共子序列的ASCII最大和
        int[][] dp = new int[m + 1][n + 1];
        // 处理下标映射关系
        ss1 = " " + ss1; ss2 = " " + ss2;
        char[] s1 = ss1.toCharArray();
        char[] s2 = ss2.toCharArray();
        // 记录总和
        int sum = 0;
        for(int j = 1; j <= n; j++) sum += s2[j];
        // 填表
        for(int i = 1; i <= m; i++) {
            sum += s1[i];
            for(int j = 1; j <= n; j++) {
                if(s1[i] == s2[j]) dp[i][j] = dp[i - 1][j - 1] + s1[i];
                dp[i][j] = Math.max(dp[i][j], Math.max(dp[i][j - 1], dp[i - 1][j]));
            }
        }
        // 返回最小删除和
        return sum - 2 * dp[m][n];
    }
}

 8、最长重复子数组【子数组问题】

. - 力扣(LeetCode)

8.1 算法原理 

注意:本题"子数组"相关的问题,并非子序列,子数组要求是连续的,故不能通过定义子序列问题的状态表示来定义子数组的状态表示

子序列:s1[0, i]区间与s2[0, j]区间所有的子序列中,......

子数组:n1中以i位置为结尾以及n2中以j位置为结尾的所有子数组中,......

  • 状态表示dp[i][j]:

1. s1[0, i]区间内的所有子数组以及s2[0, j]区间内的所有子数组中,最长公共子数组的长度???(不可行,错误。因为子数组和子序列不同,子数组是连续的,该状态表示不能解决问题)

2. s1中以i位置为结尾以及s2中以j位置为结尾的所有子数组中,最长公共子数组的长度(可行,正确)

  • 状态转移方程:

s1[i] != s2[j] --> dp[i][j] = 0
s1[i] == s2[j] --> dp[i][j] = dp[i-1][j-1]+1

  • 初始化:

1. 里面的值,要保证后续的填表是正确的 --> 根据"空数组"的定义,确定虚拟节点的值
2. 下标映射

  • 建表顺序:

从上往下

  • 返回值:

dp表中的最大值

8.2 算法代码

class Solution {
    public int findLength(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;
        int[][] dp = new int[m + 1][n + 1];
        int ret = 0;
        for(int i = 1; i <= m; i++) {
            for(int j = 1; j <= n; j++) {
                if(nums1[i - 1] == nums2[j - 1]) 
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                ret = Math.max(ret, dp[i][j]);
            }
        }
        return ret;
    }
}

END

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

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

相关文章

Vue-admin-box后台管理框架

文章目录 1、项目概述2、技术栈3、 特色功能4、基础模板5、 项目演示6、 源码地址7、 演示地址8、小结Vue-Admin-Box,一款精心打造的Vue.js后台管理模板,旨在为开发者提供高效、美观且易于扩展的后台解决方案。它集成了现代Web开发的最佳实践,包括响应式设计、模块化开发、丰…

服务器数据恢复—服务器宕机导致挂载的V7000存储文件系统损坏的数据恢复案例

服务器存储数据恢复环境&#xff1a; 一台挂载在Windows server服务器上的v7000存储。存储空间划分了一个分区&#xff0c;采用NTFS文件系统&#xff0c;存放oracle数据库。 服务器存储故障&#xff1a; 服务器在运行过程中宕机&#xff0c;于是管理员重启服务器。服务器进入系…

针对珠宝,手表等配饰的高保真虚拟试穿解决方案IDM-VTON

今天给大家介绍一种针对配饰的高保真虚拟试穿解决方案IDM-VTON&#xff0c;该方案旨在填补当前虚拟试穿技术在高端配饰&#xff08;如珠宝和手表&#xff09;领域的空白。现有的虚拟试穿模型主要集中在服装上&#xff0c;IDM-VTON结合了2D虚拟试穿技术与其他计算机视觉模型&…

N1安装grafana

apt-get install -y adduser libfontconfig1 musl wget https://dl.grafana.com/enterprise/release/grafana-enterprise_10.0.10_arm64.debdpkg -i grafana-enterprise_10.0.10_arm64.debsystemctl daemon-reload systemctl enable grafana.service --now ip:3000 默认密码…

[Linux] 层层深入理解文件系统——(3)磁盘组织存储的文件

标题&#xff1a;[Linux] 层层深入理解文件系统——&#xff08;3&#xff09;磁盘组织组织存储的文件 个人主页水墨不写bug 目录 一、磁盘中的文件 1&#xff09;磁盘的物理结构 2&#xff09;磁盘的CHS寻址法 3&#xff09;磁盘的空间管理 二、磁盘如何组织存储文件 三…

止步阿里一面。。。

时间过的真快&#xff0c;转眼间国庆已经过去一周了&#xff0c;又到了新的一周&#xff0c;继续分享最新的面经。 今天分享的是粉丝在阿里巴巴的一面&#xff0c;考察了数据库、redis、kafka、ES和项目&#xff0c;数据库和redis不用多说&#xff0c;项目必用面试必考&#x…

【隐私计算篇】替换半同态使用全同态加速计算联邦机器学习算法的实证分析

1. 背景介绍 联邦学习&#xff08;Federated Learning&#xff0c;FL&#xff09;是隐私计算中常见的一种技术范式&#xff0c;其本质是一种面向可信数据流通的分布式机器学习框架&#xff0c;允许多个参与方在不共享其本地数据的前提下&#xff0c;协同训练机器学习模型。与传…

Python | Leetcode Python题解之第479题最大回文数乘积

题目&#xff1a; 题解&#xff1a; class Solution:def largestPalindrome(self, n: int) -> int:if n 1:return 9upper 10 ** n - 1for left in range(upper, upper // 10, -1): # 枚举回文数的左半部分p, x left, leftwhile x:p p * 10 x % 10 # 翻转左半部分到其…

【C语言刷力扣】1748.唯一元素的和

题目&#xff1a; 法一 解题思路&#xff1a; 由于 nums.length 小于100&#xff0c;新建数组 num[101]&#xff0c;用来遍历存放 nums[i]出现的次数。 int sumOfUnique(int* nums, int numsSize) {int result 0;int num[101] {0}; // memset(num, 0, sizof(num));for (int…

写一个菜谱网站,数据库应该怎么设计?AI回答我动手!

大部分问题使用chatglm4-9b进行回答&#xff0c;总体效果不错&#xff0c;大家可以来试试。关于如何使用ChatGLM4-9b模型&#xff0c;可以参考&#xff1a;在启智AI平台实践ChatGLM4-9B聊天机器人MindSpore-CSDN博客 设计数据库 问题 写一个菜谱网站&#xff0c;数据库应该怎…

mongodb-7.0.14分片副本集超详细部署

mongodb介绍&#xff1a; 是最常用的nosql数据库&#xff0c;在数据库排名中已经上升到了前六。这篇文章介绍如何搭建高可用的mongodb&#xff08;分片副本&#xff09;集群。 环境准备 系统系统 BC 21.10 三台服务器&#xff1a;192.168.123.247/248/249 安装包&#xff1a…

STM32L010F4 最小系统设计

画一个 STM32L010F4 的测试板子...... by 矜辰所致前言 最近需要用到一个新的 MCU&#xff1a; STM32L010F4 &#xff0c;上次测试的 VL53L0X 需要移植到这个芯片上&#xff0c;网上一搜 STM32L010F4&#xff0c;都是介绍资料&#xff0c;没有最小系统&#xff0c;使用说明等。…

element plus的el-select分页

摘要&#xff1a; el-select的数据比较多的时候&#xff0c;必须要分页&#xff0c;处理方案有全部数据回来&#xff0c;或者添加搜索功能&#xff0c;但是就有个问题就是编辑的时候回显问题&#xff0c;必须要保证select的数据有对应的id与name匹配回显&#xff01; <el-fo…

Pytest基于fixture的参数化及解决乱码问题

我们知道&#xff0c;Pytest是Python技术栈下进行自动化测试的主流测试框架。支持灵活的测试发现、执行策略&#xff0c;强大的Fixture夹具和丰富的插件支持。 除了通过pytest的parametrize标签进行参数化外&#xff0c;我们通过fixture的param参数也可以比较方便地实现参数化…

Android从上帝视角来看PackageManagerService

戳蓝字“牛晓伟”关注我哦&#xff01; 用心坚持输出易读、有趣、有深度、高质量、体系化的技术文章&#xff0c;技术文章也可以有温度。 前言 阅读该篇之前&#xff0c;建议先阅读下面的系列文章&#xff1a; Android深入理解包管理–PackageManagerService和它的“小伙伴…

SPI

Flash-W25Q64 -- 这一章的重点是重点是知道标准SPI通信&#xff0c;地址会算&#xff0c;FLASH时序要会看 目录 Flash-W25Q64 回顾 FLASH SPI简介 单片机&#xff08;32&#xff09;上的SPI W25Q64 模块之间应该怎么通信呢&#xff1f;-- 看时序图 代码编写 更改代码…

Vue Data UI——Vue 3 数据可视化组件库

文章目录 1、Vue Data UI2、核心特点2.1.Vue 3 的深度集成2.2 丰富的可视化组件2.3 灵活的定制性2.4 易于集成2.5 文件导出功能2.6 多主题支持3、如何在项目中使用 Vue Data UI?3.1 安装 Vue Data UI3.2 全局注册组件3.3 局部引入组件3.4 使用通用组件3.5 TypeScript 集成4、总…

城市交通系统优化策略:透视拥堵之困,探索流畅之道

在快速城市化的今天&#xff0c;交通拥堵已成为众多大中型城市的“城市病”&#xff0c;严重影响居民生活质量、经济运行效率及环境质量。本文旨在深度剖析城市交通拥堵的根源&#xff0c;并提出一系列行之有效的优化策略&#xff0c;旨在构建更加顺畅、高效、绿色的城市交通系…

R语言实现logistic回归曲线绘制

方式一&#xff1a;编制函数 x<-rnorm(10000)#设置随机种子 #编写绘图函数代码快 f <- function(x){y 1/(1 exp(-x))plot(x,y)}#sigmoid函数 f(x)​ 方式二&#xff1a;Sigmoid函数代码 x<-rnorm(10000)#设置随机种子 #编写绘图函数代码块 #y<-1/(1exp(-x)) y&…

【NOIP1997 普及组第一题】棋盘问题

题目背景 NOIP1997 普及组第一题 题目描述 设有一个NM 方格的棋盘 (1≤N≤100,1≤M≤100) 求出该棋盘中包含有多少个正方形、多少个长方形&#xff08;不包括正方形&#xff09;。 例如&#xff1a;当 N2,M3时&#xff1a; 正方形的个数有 8 个&#xff1a;即边长为 1 的正…