【刷穿 LeetCode】1221. 分割平衡字符串 : 归纳法证明从「最小分割点」进行分割可以得到最优解

简介: 【刷穿 LeetCode】1221. 分割平衡字符串 : 归纳法证明从「最小分割点」进行分割可以得到最优解

网络异常,图片无法展示
|

题目描述



这是 LeetCode 上的 1221. 分割平衡字符串 ,难度为 简单


Tag : 「贪心」、「双指针」


在一个 平衡字符串 中,'L' 和 'R' 字符的数量是相同的。


给你一个平衡字符串 s,请你将它分割成尽可能多的平衡字符串。


注意:分割得到的每个字符串都必须是平衡字符串。


返回可以通过分割得到的平衡字符串的 最大数量 。


示例 1:


输入:s = "RLRRLLRLRL"
输出:4
解释:s 可以分割为 "RL"、"RRLL"、"RL"、"RL" ,每个子字符串中都包含相同数量的 'L' 和 'R' 。
复制代码


示例 2:


输入:s = "RLLLLRRRLR"
输出:3
解释:s 可以分割为 "RL"、"LLLRRR"、"LR" ,每个子字符串中都包含相同数量的 'L' 和 'R' 。
复制代码


示例 3:


输入:s = "LLLLRRRR"
输出:1
解释:s 只能保持原样 "LLLLRRRR".
复制代码


示例 4:


输入:s = "RLRRRLLRLL"
输出:2
解释:s 可以分割为 "RL"、"RRRLLRLL" ,每个子字符串中都包含相同数量的 'L' 和 'R' 。
复制代码


提示:


  • 1 <= s.length <= 1000
  • s[i] = 'L' 或 'R'
  • s 是一个 平衡 字符串


基本分析



题目确保了 s 为一个平衡字符串,即必然能分割成若干个 LR 子串。


一个合法的 LR 子串满足 L 字符和 R 字符数量相等,常规检查一个字符串是否为合格的 LR 子串可以使用 O(n)O(n) 的遍历方式,可以使用记录前缀信息的数据结构,而对于成对结构的元素统计,更好的方式是转换为数学判定,使用 1 来代指 L 得分,使用 -1 来代指 R 得分。


那么一个子串为合格 LR 子串的充要条件为 整个 LR 子串的总得分为 00


这种方式最早应该在 (题解) 301. 删除无效的括号 详细讲过,可延伸到任意的成对结构元素统计题目里去。


贪心



回到本题,题目要求分割的 LR 子串尽可能多,直观上应该是尽可能让每个分割串尽可能短。


我们使用「归纳法」来证明该猜想的正确性。


首先题目数据保证给定的 s 本身是合法的 LR 子串,假设从 [0...a][0...a] 可以从 s 中分割出 长度最小LR 子串,而从 [0...b][0...b] 能够分割出 长度更大LR 子串(即 a <= b )。


网络异常,图片无法展示
|


我们来证明起始时(第一次分割)「将从 b 分割点将 s 断开」调整为「从 a 分割点将 s 断开」结果不会变差:


  1. b 点首次分割调整为从 a 点首次分割,两种分割形式分割点两端仍为合法 LR 子串,因此不会从“有解”变成“无解”;
  2. b 分割后的剩余部分长度小于从 a 分割后的剩余部分,同时由 b 分割后的剩余部分会被由 a 分割后的剩余部分所严格覆盖,因此「对 a 分割的剩余部分再分割所得的子串数量」至少 与「从 b 点分割的剩余部分再分割所得的子串数量」相等(不会变少)。


网络异常,图片无法展示
|


至此,我们证明了对于首次分割,将任意合格分割点调整为最小分割点,结果不会变得更差(当 a < b 时还会更好)。


同时,由于首次分割后的剩余部分仍为合格的 LR 子串,因此归纳分析所依赖的结构没有发生改变,可以将上述的推理分析推广到每一个决策的回合(新边界)中。


至此,我们证明了只要每一次都从最小分割点进行分割,就可以得到最优解。


代码:


class Solution {
    public int balancedStringSplit(String s) {
        char[] cs = s.toCharArray();
        int n = cs.length;
        int ans = 0;
        for (int i = 0; i < n; ) {
            int j = i + 1, score = cs[i] == 'L' ? 1 : -1;
            while (j < n && score != 0) score += cs[j++] == 'L' ? 1 : -1;
            i = j;
            ans++;
        }
        return ans;
    }
}
复制代码


  • 时间复杂度:O(n)O(n)
  • 空间复杂度:调用 toCharArray 会拷贝新数组进行返回(为遵循 String 的不可变原则),因此使用 toCharArray 复杂度为 O(n)O(n),使用 charAt 复杂度为 O(1)O(1)


最后



这是我们「刷穿 LeetCode」系列文章的第 No.1221 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。


在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。


为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…


在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

相关文章
|
3月前
|
Go
【LeetCode 热题100】DP 实战进阶:最长递增子序列、乘积最大子数组、分割等和子集(力扣300 / 152/ 416 )(Go语言版)
本文深入解析三道经典的动态规划问题:**最长递增子序列(LIS)**、**乘积最大子数组** 和 **分割等和子集**。 - **300. LIS** 通过 `dp[i]` 表示以第 `i` 个元素结尾的最长递增子序列长度,支持 O(n²) 动态规划与 O(n log n) 的二分优化。 - **152. 乘积最大子数组** 利用正负数特性,同时维护最大值与最小值的状态转移方程。 - **416. 分割等和子集** 转化为 0-1 背包问题,通过布尔型 DP 实现子集和判断。 总结对比了三题的状态定义与解法技巧,并延伸至相关变种问题,助你掌握动态规划的核心思想与灵活应用!
125 1
|
5月前
|
Go 索引
【LeetCode 热题100】394:字符串解码(详细解析)(Go语言版)
本文详细解析了 LeetCode 热题 394:字符串解码。题目要求对编码字符串如 `k[encoded_string]` 进行解码,其中 `encoded_string` 需重复 `k` 次。文章提供了两种解法:使用栈模拟和递归 DFS,并附有 Go 语言实现代码。栈解法通过数字栈与字符串栈记录状态,适合迭代;递归解法则利用函数调用处理嵌套结构,代码更简洁。两者时间复杂度均为 O(n),但递归需注意栈深度问题。文章还总结了解题注意事项及适用场景,帮助读者更好地掌握字符串嵌套解析技巧。
140 6
|
6月前
|
存储 机器学习/深度学习 缓存
🚀 力扣热题 394:字符串解码(详细解析)(Go语言版)
文章提供了两种解法:栈结构和递归解法。栈解法通过维护数字栈与字符串栈,依次处理 `[` 和 `]`,构造解码结果;递归解法则利用函数调用逐层解析嵌套结构。两者时间复杂度均为 $O(n)$,空间复杂度也为 $O(n)$。栈解法直观易懂,适合初学者;递归解法优雅简洁,适合处理深度嵌套规则。掌握这两种方法,可灵活应对类似问题,提升解题能力。
186 11
|
11月前
|
JavaScript
力扣3333.找到初始输入字符串Ⅱ
【10月更文挑战第9天】力扣3333.找到初始输入字符串Ⅱ
114 1
|
11月前
|
C++
Leetcode第43题(字符串相乘)
本篇介绍了一种用C++实现的字符串表示的非负整数相乘的方法,通过逆向编号字符串,将乘法运算转化为二维数组的累加过程,最后处理进位并转换为字符串结果,解决了两个大数相乘的问题。
75 9
|
11月前
|
算法 C++
Leetcode第八题(字符串转换整数(atoi))
这篇文章介绍了LeetCode上第8题“字符串转换整数(atoi)”的解题思路和C++的实现方法,包括处理前导空格、正负号、连续数字字符以及整数溢出的情况。
123 0
|
11月前
【LeetCode 22】459.重复的子字符串
【LeetCode 22】459.重复的子字符串
93 0
|
11月前
【LeetCode 20】151.反转字符串里的单词
【LeetCode 20】151.反转字符串里的单词
74 0
|
11月前
【LeetCode 19】541.反转字符串II
【LeetCode 19】541.反转字符串II
60 0
|
11月前
【LeetCode 18】6.2.反转字符串
【LeetCode 18】6.2.反转字符串
61 0