最长公共子序列:【二进制枚举 + 暴搜 + 动态规划 + 经验】

51 篇文章 0 订阅
24 篇文章 0 订阅

😊😊 😊😊
不求点赞,只求耐心看完,指出您的疑惑和写的不好的地方,谢谢您。本人会及时更正感谢。希望看完后能帮助您理解算法的本质
😊😊 😊😊

题目描述:

给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。

若这两个字符串没有公共子序列,则返回 0。

示例 1:

输入:text1 = “abcde”, text2 = “ace”
输出:3
解释:最长公共子序列是 “ace”,它的长度为 3。

示例 2:
输入:text1 = “abc”, text2 = “abc”
输出:3
解释:最长公共子序列是 “abc”,它的长度为 3。

示例 3:
输入:text1 = “abc”, text2 = “def”
输出:0
解释:两个字符串没有公共子序列,返回 0。

提示:

1 <= text1.length <= 1000
1 <= text2.length <= 1000 输入的字符串只含有小写英文字符。

一、二进制枚举:

思路:

  1. 明确什么是子序列?
    在这里插入图片描述
    字符串定义见上图;
  2. 从上图发现,将一个字符串的每一位字符都看作一个数,那么对于一个数有选或者不选两种选择。等价于:1234的每位数可以组成多少个不同的数字,每个数字选或者不选,可得 ⇒ 2n种。n表示长度(位数)。
  3. 所以长度为 n 的字符串,一共有 2n 个子序列。判断两个字符串的最长公共子序列,假设另一个字符串长度为 m,则应该有 2m 个子序列。
  4. 采用暴力枚举:字符串1的 2n个子序列与字符串2的 2m个子序列进行枚举判断;时间复杂度为: 2 n ∗ 2 m 2^n * 2^m 2n2m;指数级别超时!
    代码:
for (int i=0; i < (1<<n); i++) 遍历的不是每一位字符,而是字符串1的每一种子序列
	for (int j=0; j < (1<<m); j ++) 遍历的不是每一位字符,而是字符串2的每一种子序列
  1. 记录当前的两个子序列:
string st1="";
for (int k=0; k < s1.size(); k ++)  遍历s1的每位字符
	if (i>>k&1)	 1表示选,0表示不选。用二进制的特性表示选法
		st1 += s1[k];	累加字符串s1的第k位字符

经验:一个序列(不区分数据类型),它的子序列的个数应该等于:2序列长度len;然后要想枚举它的每一个子序列,则外层循环每一种选法,一共2len种选法,内层循环负责取出当前选法里的每一个字符,从而组成一个子序列。

代码:

#include <bits/stdc++.h>
using namespace std;

string s1, s2;
int n, m;

int lcs(string s1, string s2) {
    int ans = 0;
    for (int i = 0; i < (1 << n); i++) {
        for (int j = 0; j < (1 << m); j++) {
            string t1 = "", t2 = "";
            for (int k = 0; k < n; k++) {
                if (i & (1 << k)) {
                    t1 += s1[k];
                }
            }
            for (int k = 0; k < m; k++) {
                if (j & (1 << k)) {
                    t2 += s2[k];
                }
            }
            if (t1 == t2) {
                ans = max(ans, (int)t1.size());
            }
        }
    }
    return ans;
}

int main() {
    cin >> n >> m >> s1 >> s2;
    cout << lcs(s1, s2) << endl; // 输出3
    return 0;
}

二、暴搜算法:

思路:倒序递归!

  1. 判断s1和s2是否为空,如果有一个为空,则返回0。
  2. 如果s1和s2的最后一个字符相同,则最长公共子序列的长度为它们去掉最后一个字符后的子串的最长公共子序列长度加1。
  3. 如果s1和s2的最后一个字符不相同,则最长公共子序列的长度为它们分别去掉最后一个字符后的子串的最长公共子序列长度的最大值。

代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e3 + 10, M = 1e3 + 10;
int n, m;
string str1, str2;

int lcs (string s1, string s2)
{
    if (s1.empty() || s2.empty())
        return 0;
    if (s1.back() == s2.back())
        return lcs(s1.substr(0, s1.size()-1), s2.substr(0, s2.size()-1)) + 1;
    else{
        return max(lcs(s1.substr(0, s1.size()-1), s2), lcs(s1, s2.substr(0, s2.size()-1)));
    }
}
int main()
{
    cin >> n >> m >> str1 >> str2;
    cout << lcs(str1, str2);
    return 0;
}

三、本题考察算法:LCS😊

思路:

考虑前两种做法,都是直接在序列本身上进行判断,等价于见一个杀一个,毫无章法,现在采用动态规划,递推规律的解法:即从子问题推导原问题,并且沿途记录子问题的答案,以方便后续递推大问题的时候可以直接查表利用!
acbd
abedc
如上两个 序列,假如两个字符串的序列长度均为1,此时的答案=1,那么在考虑长度为2的情况,很明显还是=1,再考虑长度为3的情况,此时第一个字符串中有 a b ab ab子序列,第二个字符串中也有ab子序列,所以此时答案 = 2,考虑长度为4,同理此时答案为 3(abd);所以说递推出来的最大值即为最优解:3。

从左往右,从小到大,顺序递推,找出通用的递推公式,即能做到不重不漏地枚举!

  1. 由于是两个序列,所以说我们的dp数组是二维的;
    f[i][j] :考虑第一个序列的前i个字符,第二个序列的前 j 个字符的公共子序列的长度的最大值。
  2. 属性:最大值;
  3. 计算:即通用的递推公式!找最后一个不同点。这里只解释下为什么会取:
    f [ i ] [ i ] = m a x ( f [ i − 1 ] [ j ] , f [ i ] [ j − 1 ] ) ; f[i][i] = max(f[i-1][j], f[i][j-1]); f[i][i]=max(f[i1][j],f[i][j1])
    在这里插入图片描述

代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e3 + 10;
int f[N][N];
int n, m;
char s1[N], s2[N];

int main()
{
    cin >> n >> m >> (s1+1) >> (s2+1);
    
    //因为有f[i-1],所以下标从1开始!
    for (int i=1; i <= n; i ++)
    {
        for (int j=1; j <= m; j ++)
        {
            //假设存在上一次的计算的f[i][j], 那么经过j++后的f[i][j]必然包含上一次的f[i][j](子集),
            //那么此次的f[i][j]则会继承上一次的最优解。你有的我一定有!我可能有的,你一定没有!
            f[i][j] = max(f[i-1][j], f[i][j-1]);
            if (s1[i] == s2[j]) //这就是我可能有的!
                f[i][j] = f[i-1][j-1] + 1;
        }
    }
    cout << f[n][m];
    
    return 0;
}

在这里插入图片描述

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 将二进制数1101001和1011按照竖式相加的方法进行计算: ``` 1101001 + 1011 -------- 1101100 ``` 因此,二进制数1101001加上1011等于1101100。 ### 回答2: 二进制数1101001 1011表示的是十进制数为4291。我们可以通过二进制转换为十进制的方法来得到这个结果。 二进制数是由0和1组成的,每个位置表示数值的2的幂次。 从右向左看,第一个数字是1,它表示2的0次方,即1乘以1等于1。 第二个数字是1,它表示2的1次方,即1乘以2等于2。 第三个数字是0,它表示2的2次方,即0乘以4等于0。 第四个数字是1,它表示2的3次方,即1乘以8等于8。 第五个数字是0,它表示2的4次方,即0乘以16等于0。 第六个数字是0,它表示2的5次方,即0乘以32等于0。 第七个数字是1,它表示2的6次方,即1乘以64等于64。 第八个数字是1,它表示2的7次方,即1乘以128等于128。 第九个数字是0,它表示2的8次方,即0乘以256等于0。 将所有的结果相加:1 + 2 + 0 + 8 + 0 + 0 + 64 + 128 + 0 = 203。 所以,二进制数1101001 1011表示的十进制数为203。 请注意,如果我们要将1101001 1011转换为十六进制,我们可以将其划分为4位二进制数的组合。在此情况下,11010011011可以被分割为0011 0100 1101,然后再将每个二进制数转换为十六进制即可。 ### 回答3: 二进制数1101001 1011可以转换为十进制数4379。转换方法如下: 将二进制数从右往左依次标记为第0位、第1位、第2位...... 从最右边的位开始,第0位是1,所以对应的值为2^0 = 1。 第1位是1,对应的值为2^1 = 2。 第2位是0,对应的值为0。 第3位是0,对应的值为0。 第4位是1,对应的值为2^4 = 16。 第5位是0,对应的值为0。 第6位是1,对应的值为2^6 = 64。 第7位是1,对应的值为2^7 = 128。 第8位是1,对应的值为2^8 = 256。 第9位是1,对应的值为2^9 = 512。 将所有对应的值相加: 1 + 2 + 0 + 0 + 16 + 0 + 64 + 128 + 256 + 512 = 4379。 所以二进制数1101001 1011转换为十进制数为4379。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值