小扣出去秋游,途中收集了一些红叶和黄叶,他利用这些叶子初步整理了一份秋叶收藏集 leaves
,
字符串 leaves
仅包含小写字符 r 和 y, 其中字符 r 表示一片红叶,字符 y 表示一片黄叶。
出于美观整齐的考虑,小扣想要将收藏集中树叶的排列调整成「红、黄、红」三部分。每部分树叶数量可以不相等,
但均需大于等于 1。每次调整操作,小扣可以将一片红叶替换成黄叶或者将一片黄叶替换成红叶。
请问小扣最少需要多少次调整操作才能将秋叶收藏集调整完毕。
示例 1:
输入:leaves = "rrryyyrryyyrr"
输出:2
解释:调整两次,将中间的两片红叶替换成黄叶,得到 "rrryyyyyyyyrr"
示例 2:
输入:leaves = "ryr"
输出:0
解释:已符合要求,不需要额外操作
提示:
3 <= leaves.length <= 10^5
leaves 中只包含字符 'r' 和字符 'y'
自我解答(动态规划)
憋了好久,最后终于做出来了:执行用时:616 ms, 在所有 Python3 提交中击败了84.91%的用户
从最大子序列和
中得到启示:
为了简化最优组合问题,转换成以所有元素结尾的最优问题。从中找出原问题最优解
我们知道,要将leaves
转换成ryr
模式,关键是需要确定两个分界点的最佳位置,以最小代价实现ryr
。
显然,与最大子序列和
类似,组合的数量太多,不可能都考虑。
转化成另一个最优问题:
fry_(i): 表示以 i 结尾,转化为 ry
模式 的最小变换次数
这么转化的原因在于:
确定了 fry_(i)
,就意味着,leaves[i+1:] 需要变换成 r
模式, 其变化次数 与 前者相加,
就是一个完整的 ryr
变化次数了,而且,如果从末尾往前遍历,很容易记录下 “变换成 r
模式"
的次数,并且遍历过程中,还可以同时考虑 fry_(i)
, 一举两得。
如此一来,接下来似乎只需要找出 fry_(i)
和 fry_(i-1)
的 最优子问题结构
,这个动态规划
思路就圆满了。的确是的,但也没那么简单,还需要从中再抽象出另一个子问题。
一开始,我思考这两者是否存在简单包含关系:
假设 i 处的叶子是 "r", 这就意味着,需要改成 ”y“,那么这种情况,是否可以直接描述为:
fry_(i) = fry_(i-1) + 1
答案是不行的。
因为 fry_(i-1) 同样是转换成 ry
模式的次数,同样固定住了 leaves[i-1] == "y",
然而,fry_(i)的前一个,可以是 "y", 也可以是 "r", 如:
"rrryyy" 和 "rrrrry"
由此,便可以提炼出一个 ”i 位置前,都是 ‘r’ “ 的子问题,将其描述为 fr_(i-1),
因此,我们就得到了:
当 leaves[i] == "y" 时:
fry_(i) = min(fry_(i-1), fr_(i-1))
当 leaves[i] == "r" 时:
fry_(i) = min(fry_(i-1), fr_(i-1)) + 1
由此,这个动态规划思路便圆满了。
可以设计出自顶向下的递归算法,时间复杂度O(n)
class Solution:
def minimumOperations(self, leaves: str) -> int:
length = len(leaves)
self.leaves = leaves
self.min_num = float("inf")
end_reds = 0
self.r_num = [0] * length
if leaves[0] == "y": self.r_num[0] = 1
if leaves[length - 1] == "y": end_reds += 1
self.get_ry_num(length - 2, end_reds)
return self.min_num
def get_ry_num(self, i, end_reds):
turny, turnr = 0, 0
if self.leaves[i] == "r":
turny = 1
else:
turnr = 1
if i > 1:
ry_num = min(self.get_ry_num(i - 1, end_reds + turnr), self.r_num[i - 1]) + turny
else:
ry_num = self.r_num[i - 1] + turny
self.r_num[i] = self.r_num[i - 1] + 1 if self.leaves[i] == "y" else self.r_num[i - 1]
all_num = ry_num + end_reds
if all_num < self.min_num:
self.min_num = all_num
return ry_num
也可以,自底向上,非递归,省去了递归栈的维护
class Solution2:
def minimumOperations(self, leaves: str) -> int:
length = len(leaves)
min_ryr = float("inf")
_rnums = [0] * (length + 1)
for i in range(length - 1, -1, -1):
plus = 1 if leaves[i] == "y" else 0
_rnums[i] = _rnums[i + 1] + plus
last_rnum = 1 if leaves[0] == "y" else 0
last_min_ry = last_rnum
for i in range(1, length - 1):
turnr = turny = 0
if leaves[i] == "r":
turny = 1
else:
turnr = 1
this_min_ry = min(last_rnum, last_min_ry) + turny
this_min_ryr = this_min_ry + _rnums[i + 1]
if this_min_ryr < min_ryr:
min_ryr = this_min_ryr
last_min_ry = this_min_ry
last_rnum += turnr
return min_ryr