草庐IT

阿里巴巴笔试题 -- 动态规划实现两个字符串的最短编辑记录

Bug 终结者 2024-02-25 原文

📢📢📢📣📣📣

哈喽!大家好,我是【Bug 终结者,【CSDN新星创作者】🏆,阿里云技术博主🏆,51CTO人气博主🏆,INfoQ写作专家🏆

一位上进心十足,拥有极强学习力的【Java领域博主】😜😜😜

🏅【Bug 终结者】博客的领域是【面向后端技术】的学习,未来会持续更新更多的【后端技术】以及【学习心得】。 偶尔会分享些前端基础知识,会更新实战项目,面向企业级开发应用
🏅 如果有对【后端技术】、【前端领域】感兴趣的【小可爱】,欢迎关注【Bug 终结者】💞💞💞


❤️❤️❤️ 感谢各位大可爱小可爱! ❤️❤️❤️

目录

一、题目说明

什么是最短编辑距离呢?假定有两个字符串s1和s2,允许对字符串进行以下三种操作:

  1. 插入一个字符

  2. 删除一个字符

  3. 替换一个字符

将字符串s1转换成字符串s2的最少操作次数就是字符串s1和字符串s2之间的最短编辑距离。两个字符串的最短编辑距离越短,意味着两个字符串越相似

时间复杂度:O(m*n)

空间复杂度:O(m*n)

二、思路分析

下图为分析

上图可以看到,第一个字符串经过 新增、删除、修改后变为第二个字符串

例:

String str1 = "ABC";
String str2 = "BBC"
//我们经过以下操作将str1替换为str2

"ABC" --> "ABC" // 删除字符 B 即可

三、递归实现

♻️核心源码

package com.wanshi.test;

import org.junit.Test;

public class Test4 {

    @Test
    public void test() {
        String str1 = "ABC";
        String str2 = "BBC";
        long start = System.currentTimeMillis();
        int res = ld(str1, str2);
        long end = System.currentTimeMillis();
        System.out.println(res);
        System.out.println("运算时间:" + (end - start));
    }

    /**
     * 计算最短编辑距离
     * @param str1  第一个字符串
     * @param str2  第二个字符串
     * @return  最短编辑距离次数
     */
    private int ld(String str1, String str2) {
        //如果内容相同了,直接返回即可,无操作
        if (str1.equalsIgnoreCase(str2)) {
            return 0;
        }

        //如果第一个字符串是空,直接返回第二个字符串的长度,操作为插入第二个字符串的个数
        if (str1.equalsIgnoreCase("")) {
            return str2.length();
        }

        //如果第二个字符串是空,直接返回第一个字符串的长度,操作为插入第一个字符串的个数
        if (str2.equalsIgnoreCase("")) {
            return str1.length();
        }

        int ldRes = 0;

        //截取出两个字符串的第一个字符
        String str1Last = str1.substring(0, 1);
        String str2Last = str2.substring(0, 1);

        //截取后面的字符
        String str1Content = str1.substring(1);
        String str2Content = str2.substring(1);

        //如果相同就进入下一次计算
        if (str1Last.equalsIgnoreCase(str2Last)) {
            ldRes = ld(str1Content, str2Content);
        } else {

            //判断3次计算结果那次运算结果最少,就返回哪一个
            String strInsert = str1Last + str2;
            String strReplace = str1Last + str2Content;
            String strDel = str2Content;

            int ldInsert = ld(str1, strInsert);
            int ldReplace = ld(str1, strReplace);
            int ldDel = ld(str1, strDel);

            //获取三个操作中最短的编辑次数
            ldRes = Math.min(ldInsert, Math.min(ldReplace, ldDel)) + 1;
        }
        return ldRes;
    }
}

⏰效果图

⚠️递归实现的缺点

我们把两个字符串换为以下字符

String str1 = "最小编辑机双向停机短信提醒及距离是把一个字符串";
String str2 = "关小编辑机双向停机短信提醒及优化出账时间的需求";

再次执行查看

跑到最后我笔记本风扇刷刷的!

不敢再跑下去了,性能实在是太低了

我的笔记本 4+16的,速度算快的了,可还是因为在执行期间风扇一直转!

下面我们优化代码,这个代码必须优化,真的是性能太低太低!

为什么会这么慢呢?

第一我们的字符串太长,然后我们又是使用的递归计算,递归计算就是**一层一层往下走,然后一层一层往上返,执行步骤极多,导致一直出不来结果,所以需要优化代码**!

四、递归+动态规划实现

动态规划最核心的思想,就在于拆分子问题,记住过往,减少重复计算

动态规划的好处就是:可以大大提高系统的性能,使程序得到非常显著的性能提升!

♻️核心源码

package com.wanshi.test;

import org.junit.Test;

import java.util.HashMap;
import java.util.Map;

public class Test3 {

    @Test
    public void t1() {
        String str1 = "最小编辑机双向停机短信提醒及距离是把一个字符串";
        String str2 = "关小编辑机双向停机短信提醒及优化出账时间的需求";
        Map<String, Integer> ldMap = new HashMap<>();
        long start = System.currentTimeMillis();
        int res = ld(str1, str2, ldMap);
        long end = System.currentTimeMillis();
        System.out.println(res);
        System.out.println("运算时间:" + (end - start));
    }

    /**
     * 计算最短编辑距离
     * @param str1  第一个字符串
     * @param str2  第二个字符串
     * @param ldMap map缓存
     * @return  最短编辑距离次数
     */
    private int ld(String str1, String str2, Map<String, Integer> ldMap) {
        //如果内容相同了,直接返回即可,无操作
        if (str1.equalsIgnoreCase(str2)) {
            return 0;
        }

        //如果第一个字符串是空,直接返回第二个字符串的长度,操作为插入第二个字符串的个数
        if (str1.equalsIgnoreCase("")) {
            return str2.length();
        }

        //如果第二个字符串是空,直接返回第一个字符串的长度,操作为插入第一个字符串的个数
        if (str2.equalsIgnoreCase("")) {
            return str1.length();
        }

        //键名
        String strKey = str1 + "_" +str2;

        //如果map中存在该key,直接返回即可
        if (ldMap.containsKey(strKey)) {
            return ldMap.get(strKey);
        }
        int ldRes = 0;

        //截取出两个字符串的第一个字符
        String str1Last = str1.substring(0, 1);
        String str2Last = str2.substring(0, 1);

        //截取后面的字符
        String str12 = str1.substring(1);
        String str22 = str2.substring(1);

        //如果相同就进入下一次计算
        if (str1Last.equalsIgnoreCase(str2Last)) {
            ldRes = ld(str12, str22, ldMap);
        } else {
            //判断3次计算结果那次运算结果最少,就返回哪一个
            int ldInsert = ld(str1, (str1Last + str2), ldMap);
            int ldReplace = ld(str1, (str1Last + str22), ldMap);
            int ldDel = ld(str1, str22, ldMap);

            //获取三个操作中最短的编辑次数
            ldRes = Math.min(ldInsert, Math.min(ldReplace, ldDel)) + 1;

        }

        //存入map缓存,提高性能
        if (!ldMap.containsKey(strKey)) {
            ldMap.put(strKey, ldRes);
        }

        return ldRes;
    }

}

⏰效果图

✅递归+动态规划的好处

我们可以看到,只用递归实现跑这个跑的电脑差点爆炸,我们采用动态规划的方式优化代码后,性能瞬间就上来了,可见动态规划的好处!

提升了程序的性能,增加可用性,增强了程序的健壮性

⛵小结

以上就是【Bug 终结者】对两个字符串的最短编辑记录简单的概述,本题为阿里巴巴笔试题动态规划是常见的考题类型,所以说,大家要掌握好动态规划,合理的在系统中使用会极大的提高系统的性能!

如果这篇【文章】有帮助到你,希望可以给【Bug 终结者】点个赞👍,创作不易,如果有对【后端技术】、【前端领域】感兴趣的小可爱,也欢迎关注❤️❤️❤️ 【Bug 终结者】❤️❤️❤️,我将会给你带来巨大的【收获与惊喜】💝💝💝!

有关阿里巴巴笔试题 -- 动态规划实现两个字符串的最短编辑记录的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. Ruby 解析字符串 - 2

    我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?

  3. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  4. ruby-on-rails - unicode 字符串的长度 - 2

    在我的Rails(2.3,Ruby1.8.7)应用程序中,我需要将字符串截断到一定长度。该字符串是unicode,在控制台中运行测试时,例如'א'.length,我意识到返回了双倍长度。我想要一个与编码无关的长度,以便对unicode字符串或latin1编码字符串进行相同的截断。我已经了解了Ruby的大部分unicode资料,但仍然有些一头雾水。应该如何解决这个问题? 最佳答案 Rails有一个返回多字节字符的mb_chars方法。试试unicode_string.mb_chars.slice(0,50)

  5. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  6. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  7. ruby - 如何以所有可能的方式将字符串拆分为长度最多为 3 的连续子字符串? - 2

    我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123

  8. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  9. ruby - 如何使用文字标量样式在 YAML 中转储字符串? - 2

    我有一大串格式化数据(例如JSON),我想使用Psychinruby​​同时保留格式转储到YAML。基本上,我希望JSON使用literalstyle出现在YAML中:---json:|{"page":1,"results":["item","another"],"total_pages":0}但是,当我使用YAML.dump时,它不使用文字样式。我得到这样的东西:---json:!"{\n\"page\":1,\n\"results\":[\n\"item\",\"another\"\n],\n\"total_pages\":0\n}\n"我如何告诉Psych以想要的样式转储标量?解

  10. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

    exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

随机推荐