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

news2024/11/18 9:40:00

来源:力扣(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.coloradmin.cn/o/112977.html

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

相关文章

Python常用基础语法知识点大全

介绍 Python 是一门独特的语言&#xff0c;快速浏览一下他的要点&#xff1a; 面向对象&#xff1a;每一个变量都是一个类&#xff0c;有其自己的属性&#xff08;attribute&#xff09;与方法&#xff08;method&#xff09;。语法块&#xff1a;用缩进&#xff08;四个空格…

Qml 中用 Shader 实现圣诞树旋转灯

一、前言 2022年圣诞节到来啦&#xff0c;很高兴这次我们又能一起度过~ 这次给大家带来一个简单漂亮圣诞树灯。 当然了&#xff0c;本篇文章主要是讲解一下如何在 Qml 中使用 GLSL 来实现自己的特效。 至于代码嘛&#xff0c;我比较喜欢在 Shaderjoy 上寻找&#xff0c;那里有很…

Biotin-PEG-Biotin,生物素-聚乙二醇-生物素聚乙二醇试剂供应

一&#xff1a;产品描述 1、名称 英文&#xff1a;Biotin-PEG-Biotin 中文&#xff1a;生物素-聚乙二醇-生物素 2、CAS编号&#xff1a;N/A 3、所属分类&#xff1a;Biotin PEG 4、分子量&#xff1a;可定制&#xff0c;2000/10000/3400/1000/20000/500 5、质量控制&…

c++继承知识点

目录1.继承的概念及定义1.1继承的概念1.2 继承定义1.2.1定义格式1.2.2继承关系和访问限定符1.2.3继承基类成员访问方式的变化2.基类和派生类对象赋值转换3.继承中的作用域4.派生类的默认成员函数5.继承与友元6. 继承与静态成员如何定义一个不被继承的类7.继承的一个题目8. 复杂…

目标检测之YOLOv2算法分析

要点 Batch Normalization 训练 若batchsize64,某一层的某一个神经元会输出64个响应值&#xff0c;对这64个响应值求均值&#xff0c;标准差&#xff0c;然后标准化&#xff0c;对标准化的结果乘λβ\lambda \betaλβ,其中λ\lambdaλ和 β\betaβ是需要训练的参数&#xf…

Windows平台RTMP、RTSP播放器录像模块精细化控制

技术背景 上篇文章&#xff0c;我们介绍了Unity平台RTMP、RTSP播放器录像功能&#xff0c;这里&#xff0c;我们详细的介绍下&#xff0c;做个RTSP或RTMP拉流端录像模块有哪些需要考虑的技术点&#xff1f; 在我们常规的考量&#xff0c;RTMP或RTSP流录制&#xff0c;无非就是…

在gitee上新建仓库并上传文件

一、进入到自己gitee的个人主页&#xff0c;点击图示新建仓库 二、根据图示操作&#xff0c;最后点击创建 三、如果没有配置git全局设置&#xff0c;需要配置一下(配置过的可以跳过这一步) 四、打开你要上传的文件&#xff0c;在里面右击鼠标&#xff0c;点击如图所示 五、输入…

spring之Bean的循环依赖问题

文章目录一、Bean的循环依赖之Set注入模式下1、Husband类2、Wife类3、Spring配置文件4、测试类5、测试结果6、结论二、Bean的循环依赖之构造方法注入模式下1、Husband类2、Wife类3、Spring配置文件4、测试类5、运行结果三、Spring解决循环依赖的机理三级缓存&#xff08;面试常…

PyQt5 基本布局管理 及 信号槽机制

一&#xff1a;布局设计 & 信号槽机制 效果实现如下&#xff1a; 对于窗口整体设计左右布局 对于左边布局&#xff0c;包括有水平布局(用户信息 左上方一块)垂直布局(多个按钮 左下方一块) 对于右边布局&#xff0c;主要是窗口切换&#xff0c;通过按下左边布局的左下方侧按…

SQLAlchemy连接MySQL及记录的查询、更新、删除、多表关联查询

SQLAlchemy是Python的ORM库&#xff0c;支持多种数据库。 建立连接 连接MySQL要用到Engine&#xff0c;Engine集成了连接池pool和方言Dialect&#xff08;支持不通数据库的SQL语法&#xff09;&#xff0c;最后都统一成标准DBAPI。 from sqlalchemy import create_engine en…

TypeScript

现在说起TypeScript想必大家都不会陌生的&#xff0c;当初从碎片信息中了解TypeScript&#xff0c;我认为他的变量声明和Rust语言有几分相似&#xff0c;是一门比较严格的语言&#xff0c;今天正式的来学习他 JavaScript易学习&#xff0c;易用&#xff0c;以至于大多数人对于…

软件体系结构 思维导图

软件体系结构 思维导图 软件体系结构思维导图 源文件放在 GitHub 仓库 使用 Xmind 即可打开查看 课程评价 比较抽象和理论化&#xff0c;如果光看 PPT 肯定看不懂&#xff0c;得听课或者看视频 后面实验试图基于 SpringBoot 去实战教学&#xff0c;可惜没系统学过只能照搬…

Kafka Consumer开发

Kafka Consumer - 消费者 跟生产者一样&#xff0c;消费者也属于kafka的客户端&#xff0c;不过kafka消费者是从kafka读取数据的应用&#xff0c;侧重于读数据。一个或多个消费者订阅kafka集群中的topic&#xff0c;并从broker接收topic消息&#xff0c;从而进行业务处理。今天…

一种嵌入式项目的参数保存方案

设计背景 嵌入式项目中&#xff0c;为了保证系统的正常运转&#xff0c;通常需要保存一部分数据至非易失存储设备如flash中。此处提供了一种通用的方案用于快速在项目中集成参数保存功能&#xff0c;该方案有以下几点特征&#xff1a; 接口简便&#xff0c;方便快速集成以及使用…

东北大学2023分布式操作系统实验

1.实验目的 建立伪分布式&#xff08;有条件的可以建立分布式环境&#xff09;的Hadoop环境&#xff0c;并成功运行示例程序。 2.Hadoop简介 2.1 Hadoop项目基础结构 在其核心&#xff0c;Hadoop主要有两个层次&#xff0c;即&#xff1a; 加工/计算层(MapReduce)存储层(Ha…

Python pandas有几千个库函数,你用过几个?(1)

对Python的 pandas 库所有的内置元类、函数、子模块等全部浏览一遍&#xff0c;然后挑选一些重点学习一下。我安装的库版本号为1.3.5&#xff0c;如下&#xff1a; >>> import pandas as pd >>> pd.__version__ 1.3.5 >>> print(pd.__doc__)pandas…

C++ STL vector list set map容器循环通过迭代器删除元素注意事项

先说说写这篇博客的原因吧&#xff0c;同事转部门了&#xff0c;把他手头的工作交接给了我。他以前维护的一个模块&#xff0c;会将外部输入的数据缓存起来分段处理&#xff0c;处理完了就会清除缓存数据&#xff0c;最近出现了一个bug&#xff0c;缓存数据一直不清除&#xff…

【SpringMVC】非注解的处理器映射器和适配器

项目目录 1.导入的依赖 pom.xml <properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target><…

【K3s】第2篇 一篇文章学习实践K3s部署安装

目录 1、docker安装 2、docker-compose安装 3、K3s安装 3.1 k3s与install.sh文件准备 3.2 k3s 安装步骤 4、查看k3s部署状态 1、docker安装 方式一 https://fanjufei.blog.csdn.net/article/details/123500511https://fanjufei.blog.csdn.net/article/details/123500511 …

12.24

接口测试 ​ <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-width…