【1754. 构造字典序最大的合并字符串】

news/2024/4/17 6:17:09/文章来源:https://blog.csdn.net/Sugar_wolf/article/details/128430049

来源:力扣(LeetCode)

描述:

给你两个字符串 word1word2 。你需要按下述方式构造一个新字符串 merge :如果 word1word2 非空,选择 下面选项之一 继续操作:

  • 如果 word1 非空,将 word1 中的第一个字符附加到 merge 的末尾,并将其从 word1 中移除。
    • 例如,word1 = "abc"merge = "dv" ,在执行此选项操作之后,word1 = "bc" ,同时 merge = "dva"
  • 如果 word2 非空,将 word2 中的第一个字符附加到 merge 的末尾,并将其从 word2 中移除。
    • 例如,word2 = "abc"merge = "" ,在执行此选项操作之后,word2 = "bc" ,同时 merge = "a"

返回你可以构造的字典序 最大 的合并字符串 merge

长度相同的两个字符串 ab 比较字典序大小,如果在 ab 出现不同的第一个位置,a 中字符在字母表中的出现顺序位于 b 中相应字符之后,就认为字符串 a 按字典序比字符串 b 更大。例如,"abcd" 按字典序比 "abcc" 更大,因为两个字符串出现不同的第一个位置是第四个字符,而 d 在字母表中的出现顺序位于 c 之后。

示例 1:

输入:word1 = "cabaa", word2 = "bcaaa"
输出:"cbcabaaaaa"
解释:构造字典序最大的合并字符串,可行的一种方法如下所示:
- 从 word1 中取第一个字符:merge = "c",word1 = "abaa",word2 = "bcaaa"
- 从 word2 中取第一个字符:merge = "cb",word1 = "abaa",word2 = "caaa"
- 从 word2 中取第一个字符:merge = "cbc",word1 = "abaa",word2 = "aaa"
- 从 word1 中取第一个字符:merge = "cbca",word1 = "baa",word2 = "aaa"
- 从 word1 中取第一个字符:merge = "cbcab",word1 = "aa",word2 = "aaa"
- 将 word1 和 word2 中剩下的 5 个 a 附加到 merge 的末尾。

示例 2:

输入:word1 = "abcabc", word2 = "abdcaba"
输出:"abdcabcabcaba"

提示:

  • 1 <= word1.length, word2.length <= 3000
  • word1 和 word2 仅由小写英文组成

方法一:贪心算法

思路与算法

题目要求合并两个字符串 word1 与 word2,且要求合并后的字符串字典序最大。首先需要观察一下合并的选择规律,假设当前需要从 word1 的第 i 个字符和 word2 的第 j 个字符选择一个字符加入到新字符串 merge 中,需要进行分类讨论:

  • 如果 word1[i] > word2[j],此时我们的最优选择是移除 word1[i] 加入到 \textit{merge}merge 中,从而保证 merge 的字典序最大;
  • 如果 word1[i] < word2[j],此时我们的最优选择是移除 word2[j] 加入到 merge,从而保证 merge 的字典序最大;
  • 如果 word1[i] = word2[j],此时则需要进一步讨论,结论如下:
    • 如果 word1[i] 从 i 开始的后缀字典序大于 word2[j] 从 j 开始的后缀,则此时优先选择移除 word1[i] 加入到 merge 中;
    • 如果 word1[i] 从 i 开始的后缀字典序小于 word2[j] 从 j 开始的后缀,则此时优先选择移除 word2[j] 加入到 merge 中;
    • 如果 word1[i] 从 i 开始的后缀字典序等于 word2[j] 从 j 开始的后缀,则此时任选一个均可;

当两个字符相等时,则我们最优选择为后缀较大的字符串,分类讨论如下:

假设 word1[i] = word2[j],此时两个字符串分别从 i, j 开始还有 l 个字符相等,则此时 word1[i+k] = word2[j+k], k∈ [0, l − 1],第 l + 1 个字符时二者不相等,即满足 word1[i + l] != word2[j + l] ,我们可以假设 word1[i + l] < word2[j + l] 。

例如 word1 = “bcadea" 与 word2 = “_bcadf ”,此时 i = 0, j = 1, l = 4。

  • 假设我们每次都选择从当前位置后缀较大的字符串,由于两个字符串分别从 i,ji,j 开始连续 ll 个字符相等,此时可以知道 word2 向右移动了 l 个位置到达了 j + l ,此时 word1 向右移动了 t 个位置到达了 i + t,此时一定满足 t ≤ l,word2 优先向右移动到达字符 word2[j + l] 处,此时字典序较大的字符 word2[j + l] 优先进行合并。如果 word2 移动 k 个字符时,word1 最多也移动 k 个字符,由于两个字符串同时移动 k 个位置会遇到相同字符时总是选择字典序较大的后缀,因此 word2 一定先移动 l 个位置,可以参考如下图所示:

1

2

3
4
5
6
7

  • 假设我们每次都选择从当前位置后缀较小的字符串,由于两个字符串分别从 i, j 开始连续 l 个字符相等,此时可以知道 word1 向右移动了 l 个位置到达了 i + l,此时 word2 向右移动了 t 个位置到达了 j + t,此时一定满足 t ≤ l ,word1 优先向右移动到达字符 word1[i + l] 处,此时字典序较小的字符 word1[i+k] 优先进行合并。如果 word1 移动 k 个字符时,word2 最多也移动 k 个字符,而每次同时移动 k 个位置遇到相同字符时总是选择字典序较小的后缀,因此 word1 一定先移动 l 个位置,可以参考如下图所示:

1
2
3
4

5
6
7

  • 我们观察到不论以何种方式进行合并,两个字符串一共移动了 l + t 个位置,此时字符串 merge 也合并了长度为 l + t 的字符串 s,不论以何种方式进行合并的字符串 s总是相同的,而此时下一个字符优先选择字典序较大的字符进行合并这样保证合并后的字典序最大。我们可以观察到上述示例中的 s = “bcbcad”。

其余的特殊情况跟上述思路一样,综上我们可以得到结论每次选择字典序较大的后缀进行移除一定可以保证得到最优的结果,其余的选择方法不一定能够保证得到最优结果。

代码:

class Solution {
public:string largestMerge(string word1, string word2) {string merge;int i = 0, j = 0;while (i < word1.size() || j < word2.size()) {if (i < word1.size() && word1.substr(i) > word2.substr(j)) {merge.push_back(word1[i++]);} else {merge.push_back(word2[j++]);}}return merge;}
};

执行用时:192 ms, 在所有 C++ 提交中击败了26.79%的用户
内存消耗:390.3 MB, 在所有 C++ 提交中击败了35.72%的用户
复杂度分析
时间复杂度: O((m+n)×max(m,n)),其中 m, n 分别表示两个字符串的长度。每次压入字符时需要进行后缀比较,每次两个字符串后缀比较的时间复杂度为 O(max(m,n)),一共最多需要比较 m + n 次,因此总的时间复杂度为 O((m+n)×max(m,n))。
空间复杂度:O(m + n),其中 m,n 分别表示两个字符串的长度。每次比较时都会生成两个字符串的后缀,所需要的空间为 O(m + n)。

方法二:后缀数组

思路与算法

  此种与方法一同样的思路,我们在比较两个字符串 word1 , word2 的后缀时,直接利用后缀数组来比较两个后缀的字典序大小。在两个 word1 与 word2 的中间添加一个字符 ‘@’ 来表示 word1 的结尾, ‘@’ 比所有的英文字母都小,且比字符串的末尾 ‘*’ 要大。设字符串word1 , word2 的长度分别为 m, n 我们计算出合并后的字符串 str 的后缀排名 rank,则 word1 中的第 i 个后缀对应着 str 的第 i 个后缀,word2 中的第 j 个后缀对应着 str 的第 m + 1 + j 个后缀。进行合并时我们可以直接比较两个字符串的后缀排序,每次选取后缀较大的进行合并即可。

代码:

vector<int> sortCharacters(const string & text) {int n = text.size();vector<int> count(128), order(n);for (auto c : text) {count[c]++;}    for (int i = 1; i < 128; i++) {count[i] += count[i - 1];}for (int i = n - 1; i >= 0; i--) {count[text[i]]--;order[count[text[i]]] = i;}return order;
}vector<int> computeCharClasses(const string & text, vector<int> & order) {int n = text.size();vector<int> res(n, 0);res[order[0]] = 0;for (int i = 1; i < n; i++) {if (text[order[i]] != text[order[i - 1]]) {res[order[i]] = res[order[i - 1]] + 1;} else {res[order[i]] = res[order[i - 1]];}}return res;
}vector<int> sortDoubled(const string & text, int len, vector<int> & order, vector<int> & classfiy) {int n = text.size();vector<int> count(n), newOrder(n);for (int i = 0; i < n; i++) {count[classfiy[i]]++;}for (int i = 1; i < n; i++) {count[i] += count[i - 1];}for (int i = n - 1; i >= 0; i--) {int start = (order[i] - len + n) % n;int cl = classfiy[start];count[cl]--;newOrder[count[cl]] = start;}return newOrder;
}vector<int> updateClasses(vector<int> & newOrder, vector<int> & classfiy, int len) {int n = newOrder.size();vector<int> newClassfiy(n, 0);newClassfiy[newOrder[0]] = 0;for (int i = 1; i < n; i++) {int curr = newOrder[i];int prev = newOrder[i - 1];int mid = curr + len;int midPrev = (prev + len) % n;if (classfiy[curr] != classfiy[prev] || classfiy[mid] != classfiy[midPrev]) {newClassfiy[curr] = newClassfiy[prev] + 1;} else {newClassfiy[curr] = newClassfiy[prev];}}return newClassfiy;
}vector<int> buildSuffixArray(const string& text) {vector<int> order = sortCharacters(text);vector<int> classfiy = computeCharClasses(text, order);int len = 1;int n = text.size();for (int i = 1; i < n; i <<= 1) {order = sortDoubled(text, i, order, classfiy);classfiy = updateClasses(order, classfiy, i);}return order;
}class Solution {
public:string largestMerge(string word1, string word2) {int m = word1.size(), n = word2.size();string str = word1 + "@" + word2 + "*";vector<int> suffixArray = buildSuffixArray(str); vector<int> rank(m + n + 2);for (int i = 0; i < m + n + 2; i++) {rank[suffixArray[i]] = i;}string merge;int i = 0, j = 0;while (i < m || j < n) {if (i < m && rank[i] > rank[m + 1 + j]) {merge.push_back(word1[i++]);} else {merge.push_back(word2[j++]);}}return merge;}
};

执行用时:160 ms, 在所有 C++ 提交中击败了41.07%的用户
内存消耗:61.5 MB, 在所有 C++ 提交中击败了59.53%的用户
复杂度分析
时间复杂度: O(∣Σ∣+(m+n)×log(m+n)),其中 m, n 表示字符串 word1 与 word2 的长度, ∣Σ∣ 表示字符集的大小,在此 ∣Σ∣ 取 128 。时间复杂度主要取决于后缀数组的计算与字符串的遍历,其中后缀数组的计算需要的时间复杂度为 O(∣Σ∣+(m+n)×log(m+n)),我们通过后缀数组计算出每个后缀的排序需要的时间复杂度为 O(m + n)O(m+n,遍历两个字符串并通过比较后缀的大小来进行合并需要的时间复杂度为 O(m + n),因此总的时间复杂度为O(∣Σ∣+(m+n)×log(m+n))
空间复杂度:O(m + n)。计算后缀数组时需要存放临时的字符串以及后缀排序,需要的空间均为 O(m + n),因此总的空间复杂度为 O(m + n)。
author:LeetCode-Solution

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Blender——苹果纹理绘制

效果图 前言 在进行纹理绘制之前&#xff0c;首先要具有苹果三维模型。 关于苹果的建模请参考&#xff1a;Blender——“苹果”建模_行秋的博客 1.苹果UV的展开 1.1首先点击UV Eidting&#xff0c;滑动三维模型&#xff0c;使其大小适中。 1.2打开左上角的UV选区同步&#x…

Go语言开发小技巧易错点100例(四)

往期回顾&#xff1a; Go语言开发小技巧&易错点100例&#xff08;一&#xff09;Go语言开发小技巧&易错点100例&#xff08;二&#xff09;Go语言开发小技巧&易错点100例&#xff08;三&#xff09; 本期看点&#xff08;技巧类用【技】表示&#xff0c;易错点用…

(1)Qt的基本数据类型以及基本输出

基础类型 因为Qt是一个C框架, 因此C中所有的语法和数据类型在Qt中都是被支持的, 但是Qt中也定义了一些属于自己的数据类型, 下边给大家介绍一下这些基础的数据类型。 类型名称注释备注qint8signed char有符号8位数据qint16signed short16位数据类型qint32signed short32位有符号…

你以为架构师天天就画图写PPT吗,告诉你其他事儿多了去了~

V-xin&#xff1a;ruyuan0330 获得600页原创精品文章汇总PDF 目录 一、多系统订阅数据回顾二、核心数据的监控系统三、电商库存数据如何监控四、数据计算链路追踪五、百亿流量下的数据链路追踪六、自动化数据链路分析七、下篇预告 上篇文章《为什么我建议线上高并发量的代码&a…

原神私服 grasscutter搭建及食用教程 v3.3

本教程搭建过程食用vmware虚拟机服务端搭建过程及其简单。照着教程操作即可。本次对应的版本是3.3的版本&#xff0c;后期会持续更新。 一.资源下载准备&#xff1a; 1.vmwera16虚拟机下载安装自己百度吧&#xff0c;非常简单。一路next安装完后再输入一个百度来的秘钥即可。…

Redis原理篇—数据结构

Redis原理篇—数据结构 笔记整理自 b站_黑马程序员Redis入门到实战教程 底层数据结构 动态字符串SDS 我们都知道 Redis 中保存的 Key 是字符串&#xff0c;value 往往是字符串或者字符串的集合。可见字符串是 Redis 中最常用的一种数据结构。 不过 Redis 没有直接使用C语言中…

基于PHP的动漫电影信息管理系统

有需要请私信或看评论链接哦 可远程调试 基于PHP的动漫电影管理系统一 介绍 此动漫电影信息管理系统基于原生PHP开发&#xff0c;数据库mysql&#xff0c;前端bootstrap。系统角色分为用户和管理员&#xff0c;用户注册登录后可观看/下载/收藏/留言/评分动漫电影等&#xff0c…

python代码~创意圣诞树

2022年圣诞节到来啦&#xff0c;很高兴这次我们又能一起度过~ 圣诞节(Christmas)本身是一个宗教节&#xff0c;用来庆祝耶稣的诞辰&#xff0c;因而又名耶诞节 Hope all your Christmas dreams come true!    愿你所有的圣诞梦想都成真!    Hope you enjoy the happiness o…

Web前端105天-day63-HTML5_CORE

HTML5CORE03 目录 前言 一、复习 二、SVG 三、Echarts 四、Webworker 五、回调地狱 六、Promise 七、promiseajax 八、promise_axios 九、async_await 总结 前言 HTML5CORE03学习开始 一、复习 跨域 浏览器的同源策略限定: 网页中利用 AJAX 请求数据, 必须访问同源…

ICMP V6(计算机网络-网络层)

IPv6 使用的 ICMP IETF 制定的与IPv6配套使用的ICMP新版本&#xff0c;即ICMPv6 ICMPv6报文作为IPv6分组有效载荷进行传输&#xff0c;对应的IPv6“下一个首部字段”的值为58 ICMPv6 的报文格式和 IPv4 使用的 ICMP 的相似&#xff0c;即前 4 个字节的字段名称都是一样的&…

Akka 进阶(二)Mailbox 邮箱

目录一 默认邮箱配置二 内置邮箱三 自定义邮箱四 配置邮箱五 RequiresMessageQueue接口Actor中的邮箱是一个队列结构&#xff0c;所有发送过来的消息都会在该队列进行排队&#xff0c;在默认情况下&#xff0c;它遵循先进先出&#xff08;FIFO&#xff09;的模式&#xff0c;假…

Linux-5 基础命令

Linux-5 基础命令 查看类命令 此类命令仅能查看文件中的内容 ls是用来查看目录中的内容cat是用来查看文件中的内容 查看文件 cat 选项 -n&#xff1a;显示文件内容的行数-A&#xff1a;显示文件中的特殊字符&#xff08;如果从Windows拷贝配置文件到Linux&#xff0c;很容易出…

自然语言处理NLP——图神经网络与图注意力模型(GNN、GCN、GAT)

目录 系列文章目录 一、图神经网络 1.图与图嵌入 2.GNN动机 2.1 CNN的缺陷与非结构性数据 2.2 图嵌入的缺陷 3.GNN详解 3.1 GNN简介 3.2 GNN模型 3.3 GNN框架 3.4 GNN局限与优化 二、图卷积神经网络 1.卷积 2.GCN详解 2.1 GCN动机 2.2 GCN简介 2.3 GCN思想与模…

Qt之悬浮球菜单

一、概述 最近想做一个炫酷的悬浮式菜单&#xff0c;考虑到菜单展开和美观&#xff0c;所以考虑学习下Qt的动画系统和状态机内容&#xff0c;打开QtCreator的示例教程浏览了下&#xff0c;大致发现教程中2D Painting程序和Animated Tiles程序有所帮助&#xff0c;如下图所示&a…

论文投稿指南——中文核心期刊推荐(自然科学总论)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

Nginx学习笔记2【尚硅谷】

host文件修改时&#xff0c;可以更改用户组权限或者复制到某个有权限的位置修改完再复制替换之前的文件。 在server{}中&#xff0c;listenserver_name两个加一起是唯一的。 代理服务器就是一个网关。 配置Nginx反向代理&#xff1a; 注意&#xff1a;在写proxy_pass时&#xf…

化学试剂Biotin-PEG-COOH,Biotin-PEG-acid,生物素-聚乙二醇-羧基

英文名称&#xff1a;Biotin-PEG-COOH&#xff0c;Biotin-PEG-acid 中文名称&#xff1a;生物素-聚乙二醇-羧基 生物素-PEG-COOH是一种含有生物素和羧酸的线性杂双功能PEG试剂。它是一种有用的带有PEG间隔基的交联或生物结合试剂。生物素能以高特异性和亲和力与亲和素和链霉亲…

MySQL实现主从复制(Windows)的明细操作步骤

文章目录一、教学视频地址二、设计思路三、具体步骤一、教学视频地址 视频地址&#xff1a;视频链接 二、设计思路 准备两个5.7版本的MySQL&#xff0c;一个用作主数据库&#xff0c;另一个用作从数据库。 把主数据库做为写入数据库&#xff0c;从数据库作为读数据库。 三…

linux篇【12】:计算机网络<后序>

一.tcp接入线程池&#xff08;使用线程池&#xff09; 1.tcp初步接入线程池 我们设置了对应的任务是死循环&#xff0c;那么线程池提供服务&#xff0c;就显得有不太合适。我们给线程池抛入的任务都是短任务 因为他并没有访问任何类内成员&#xff0c;所以可以把执行方法提到…

seo综合查询,怎么看网站在移动端权重高低

移动权重就是指在手机、IPAD等的流量&#xff0c;数值越大流量越多。 未来百度流量一定会更倾向于移动端&#xff0c;移动端搜索将是百度搜索引擎的主要阵地。这一点和用户上网习惯有关系&#xff0c;因为移动网络无处不在。 那么怎么看网站在移动端权重高低&#xff1f;最…