惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 司徒正美
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Last Week in AI
Last Week in AI
大猫的无限游戏
大猫的无限游戏
博客园 - Franky
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
爱范儿
爱范儿
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
博客园 - 叶小钗
博客园_首页
有赞技术团队
有赞技术团队
WordPress大学
WordPress大学
宝玉的分享
宝玉的分享
V
V2EX
V
Visual Studio Blog
博客园 - 三生石上(FineUI控件)
S
SegmentFault 最新的问题
量子位
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Apple Machine Learning Research
Apple Machine Learning Research
美团技术团队

OhYee 博客

小鹏辅助驾驶测评|OhYee 博客 小鹏非支持手机开启自动解锁|OhYee 博客 使用函数计算实现 301 重定向|OhYee 博客 针对 HTML 内容使用 Ant Design 图片弹框|OhYee 博客 博客进程泄露及僵尸进程解决|OhYee 博客 蓝易云服务器体验|OhYee 博客 SSH 调起本地 VSCode|OhYee 博客 【2022 秋招内推】阿里云后端研发工程师|OhYee 博客 使用函数计算获取 IP 地址信息|OhYee 博客 正确获取客户端 IP/HTTP Header 也可能重复|OhYee 博客 评测 Oculus Quest2 及 BigScreen|OhYee 博客 NextJS 热重载保留状态|OhYee 博客 如何优雅地贴 gist 代码|OhYee 博客 Linux 精细化文件权限|OhYee 博客 VSCode 容器开发环境|OhYee 博客 Clash 的不兼容更新排查|OhYee 博客 Zeek 导出 PCAP|OhYee 博客 记一次 ssh 配置问题|OhYee 博客 Git Commit 规范化工具|OhYee 博客 谈谈《星之卡比-探索发现》|OhYee 博客 VSCode 快捷键绑定 Shell 命令|OhYee 博客 ASN.1 语法及 X.509 证书格式解析解析|OhYee 博客 腾讯企业邮箱忽略 MX 记录发信|OhYee 博客 Chrome/Edge 标签组插件|OhYee 博客 【应届内推】阿里云后端研发工程师|OhYee 博客 损坏的 Typecho 备份处理为 JSON|OhYee 博客 VS Code VIM 插件高效使用|OhYee 博客 SSH 正反向代理|OhYee 博客 Let's Encrypt 根证书过期引发的问题|OhYee 博客 OpenWRT 忽略内核依赖|OhYee 博客
LeetCode 777. 在LR字符串中交换相邻字符|OhYee 博客
2020-11-28 · via OhYee 博客

LeetCode 777. 在LR字符串中交换相邻字符

题目描述

在一个由 'L' , 'R' 和 'X' 三个字符组成的字符串(例如"RXXLRXRXL")中进行移动操作。一次移动操作指用一个"LX"替换一个"XL",或者用一个"XR"替换一个"RX"。现给定起始字符串start和结束字符串end,请编写代码,当且仅当存在一系列移动操作使得start可以转换成end时, 返回True。

示例

输入: start = "RXXLRXRXL", end = "XRLXXRRLX"
输出: True
解释:
我们可以通过以下几步将start转换成end:
RXXLRXRXL ->
XRXLRXRXL ->
XRLXRXRXL ->
XRLXXRRXL ->
XRLXXRRLX

提示

  • 1 <= len(start) = len(end) <= 10000。
  • startend中的字符串仅限于'L', 'R'和'X'。

题解

暴力解

首先,稍微画一下各种情况,可以发现一种需要特别警惕的情景:RRXX可以被转换成RXRXXRRXXRXRXXRR这些状态。
同理,L也存在类似的情况

将这种情景推广,用更为准确的情况描述,就是“X表示空位,L字符可以向左移动到空位,R字符可以向右移动到空位”

那么可以在考虑该移动策略的前提下,枚举所有可能。对于相同位置的字符,有:

起始字符 结束字符 结果
L L 可以转换
L R 无法转换
L X 无法转换
R L 无法转换
R R 可以转换
R X 可能可以转换
X L 可能可以转换
X R 无法转换
X X 可以转换

也即,实际上是需要考虑两种情况即可RXXL

RX为例,如果R存在右移的可能,那么必会留下一个X(空位)。因此只需要考虑是否可以移动即可。如果R想要移动,那么右侧必然存在一个X,且到这个X中间不能存在L。这样只需要将中间所有的R右移一位即可。

直接运行该算法,最坏情况下时间复杂度应该是 O(n2)O(n^2),空间复杂度为 O(1)O(1)

最坏时,可能会存在诸如RRRRRRRRRRRRRRRRRRRRRRRXXXXXX……的形式,每一个R都需要遍历后面所有的R

最优解

再次看上面的结论,如果我现在是RX的情况,我必须要确保找到下一个X之前不能有L。这时候去考虑另一个串的情况,由于在这个过程中,涉及的都是R的右移,因此至少要确保对应数目的R出现前,不能有L,否则无法一一对应。

也即,在不考虑X的情况下,将相邻的RL压缩,两个串将会得到相同的结果。
RXXLRXRXL压缩有应该是R(1)L(1)R(2)L(1)那么其可以转换成的目标串,压缩后必然也是R(1)L(1)R(2)L(1)
可以视为,去除X后,两个串相同。

接下来考虑LR的特点:R只允许右移,L只允许左移。
因此,对于串XRRRXR,尽管压缩后都是R(2)但是却无法转换成功。也即,起始串对应位置的R必须在目标串左侧。L同理。

总结下上述规律,只需要判断在两个串中,非X字符位置是否满足:

  • 字符相同
  • R 字符在起始串下标小于等于结束串
  • L 字符在结束串下表大于等于结束串

接下来要做的就是判定好边界情况

应该如何思考

如果多画一画,应该很容易发现L左移和R右移的规律。
可能再多手画几组数据,可能会有想到上面压缩的思路,但是很容易会被大量测试用例给否掉。

似乎如果将转换关系连线起来,会更易于理解,发现规律

RXXLRXRXLRRXXRLXXRRLXXRR的转换(可以看出,LR转换关系不交叉且拥有方向性)

R X R X X L X R L X R R X X R L X R L R R X R X

代码

暴力解

bool canTransform(char * start, char * end){
    char* a = start;
    char* b = end;
    while(*a != '\0') {
        // printf("%c %d\n", *a, *a);
        if (*a == 'R' && *b == 'X') {
            char* temp = a;
            while (*temp != '\0') {
                ++temp;
                if (*temp == 'L') return false;
                if (*temp == 'X') {
                    *temp = 'R';
                    break;
                }
            }
            if (*temp == '\0') return false;
            *a = 'X';
        } else if (*a == 'X' && *b == 'L') {
            char* temp = a;
            while (*temp != '\0') {
                ++temp;
                if (*temp == 'R') return false;
                if (*temp == 'L') {
                    *temp = 'X';
                    break;
                }
            }
            if (*temp == '\0') return false;
            *a = 'L';
        } else if (*a != *b) return false;
        ++a; ++b;
    }
    return true;
}

最优解

bool canTransform(char * start, char * end){
    int i = 0;
    int j = 0;
    while(start[i] != '\0' && end[j] != '\0') {
        // 找到第一个非 X 字符
        while (start[i] == 'X') ++i;
        while (end[j] == 'X') ++j;

        bool iend = start[i] == '\0';
        bool jend = end[j] == '\0';
        // 两者直接到末尾,说明全是 X 可以转换
        if (iend && jend) return true;
        // 存在一个到末尾,另一个未到末尾,说明个数不同,不能转换
        if (iend ^ jend) return false;

        // 如果对应的字符不同,说明无法转换
        // 如果起始串 R 位置大于目标串,说明无法转换
        // 如果起始串 L 位置小于目标串,说明无法转换
        if (
            start[i] != end[j] || 
            (start[i] == 'R' && i > j) || 
            (start[i] == 'L' && i < j)
        ) return false;
        
        i++; j++;
    }

    // 处理末尾部分
    while (start[i] != '\0') if (start[i++] != 'X') return false;
    while (end[j] != '\0') if (end[j++] != 'X') return false;

    return true;
}