题目来源: Leetcode LCP 19. 秋叶收藏集
小扣出去秋游,途中收集了一些红叶和黄叶,他利用这些叶子初步整理了一份秋叶收藏集 leaves, 字符串 leaves 仅包含小写字符 r 和 y, 其中字符 r 表示一片红叶,字符 y 表示一片黄叶。
出于美观整齐的考虑,小扣想要将收藏集中树叶的排列调整成「红、黄、红」三部分。每部分树叶数量可以不相等,但均需大于等于 1。每次调整操作,小扣可以将一片红叶替换成黄叶或者将一片黄叶替换成红叶。请问小扣最少需要多少次调整操作才能将秋叶收藏集调整完毕。
示例 1:
输入:leaves = "rrryyyrryyyrr"
输出:2
解释:调整两次,将中间的两片红叶替换成黄叶,得到 "rrryyyyyyyyrr"
示例 2:
输入:leaves = "ryr"
输出:0
解释:已符合要求,不需要额外操作
提示:
3 <= leaves.length <= 10^5
leaves
中只包含字符 'r'
和字符 'y'
初看此题可以确定需要将字符串分为三段:
而题解即为三段替换次数总和的最小值,那么如何确定这个最小值呢?有分析可知,后一阶段的决策要取决于前一阶段,因此可以考虑使用动态规划。具体做法是定义一个 3 × l e n g t h 3\times{length} 3×length的数组 d p dp dp,其中 l e n g t h length length为字符串串长,结构示意图如下:
其中 d p [ i ] [ 0 ] , d p [ i ] [ 1 ] , d p [ i ] [ 2 ] dp[i][0],dp[i][1],dp[i][2] dp[i][0],dp[i][1],dp[i][2]的含义是第 i i i片叶子处于阶段 1 / 2 / 3 1/2/3 1/2/3时处理所需的最小步骤数,由此可知最终结果为 d p [ n − 1 ] [ 2 ] dp[n-1][2] dp[n−1][2]。因此现在问题就转变为了确定 d p dp dp表各个阶段的值,步骤为:
首先,需要确定阶段1,对于该阶段需要先确认 d p [ 0 ] [ 0 ] dp[0][0] dp[0][0]的值,由题意可得字符串leaves
的第一个字符必须为'r'
,因此可得:
d p [ 0 ] [ 0 ] = { 0 l e a v e s [ 0 ] = = ′ r ′ 1 l e a v e s [ 0 ] = = ′ y ′ dp[0][0]=\begin{cases} 0 & leaves[0] == 'r' \\ 1 & leaves[0]=='y' \\ \end{cases} dp[0][0]={ 01leaves[0]==′r′leaves[0]==′y′
而对于其他处于阶段1的元素 d p [ i ] [ 0 ] dp[i][0] dp[i][0],其递推计算公式为:
d p [ i ] [ 0 ] = { d p [ i − 1 ] [ 0 ] l e a v e s [ i ] = = ′ r ′ d p [ i − 1 ] [ 0 ] + 1 l e a v e s [ i ] = = ′ y ′ dp[i][0]=\begin{cases} dp[i-1][0] & leaves[i] =='r' \\ dp[i-1][0] + 1 & leaves[i]=='y' \\ \end{cases} dp[i][0]={ dp[i−1][0]dp[i−1][0]+1leaves[i]==′r′leaves[i]==′y′
对于阶段2,其应从字符串leaves
的第二个字符开始(前面至少一片红叶),其初值 d p [ 1 ] [ 1 ] dp[1][1] dp[1][1]的计算如下:
d p [ 1 ] [ 1 ] = { d p [ 0 ] [ 0 ] + 1 l e a v e s [ 1 ] = = ′ r ′ d p [ 0 ] [ 0 ] l e a v e s [ 1 ] = = ′ y ′ dp[1][1]=\begin{cases} dp[0][0] + 1& leaves[1] == 'r' \\ dp[0][0] & leaves[1]=='y' \\ \end{cases} dp[1][1]={ dp[0][0]+1dp[0][0]leaves[1]==′r′leaves[1]==′y′
而对于阶段2的其他元素 d p [ i ] [ 1 ] dp[i][1] dp[i][1],由于它的前一个字符可能来自阶段2,也可能来自阶段1,因此其递推公式如下:
d p [ i ] [ 1 ] = { m i n ( d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 1 ] ) + 1 l e a v e s [ i ] = = ′ r ′ m i n ( d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 1 ] ) l e a v e s [ i ] = = ′ y ′ dp[i][1]=\begin{cases} min(dp[i-1][0], dp[i-1][1]) + 1 & leaves[i] =='r' \\ min(dp[i-1][0], dp[i-1][1]) & leaves[i]=='y' \\ \end{cases} dp[i][1]={ min(dp[i−1][0],dp[i−1][1])+1min(dp[i−1][0],dp[i−1][1])leaves[i]==′r′leaves[i]==′y′
对于阶段3,其应从字符串leaves
的第三个字符开始(前面至少一红一黄),因此其初值 d p [ 2 ] [ 2 ] dp[2][2] dp[2][2]计算公式如下:
d p [ 2 ] [ 2 ] = { d p [ 1 ] [ 1 ] l e a v e s [ 2 ] = = ′ r ′ d p [ 1 ] [ 1 ] + 1 l e a v e s [ 2 ] = = ′ y ′ dp[2][2]=\begin{cases} dp[1][1] & leaves[2] == 'r' \\ dp[1][1] + 1& leaves[2]=='y' \\ \end{cases} dp[2][2]={ dp[1][1]dp[1][1]+1leaves[2]==′r′leaves[2]==′y′
而对于阶段2的其他元素 d p [ i ] [ 2 ] dp[i][2] dp[i][2],由于它的前一个字符可能来自阶段2,也可能来自阶段3,因此其递推公式如下:
d p [ i ] [ 2 ] = { m i n ( d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 2 ] ) l e a v e s [ i ] = = ′ r ′ m i n ( d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 2 ] ) + 1 l e a v e s [ i ] = = ′ y ′ dp[i][2]=\begin{cases} min(dp[i-1][1], dp[i-1][2]) & leaves[i] =='r' \\ min(dp[i-1][1], dp[i-1][2]) + 1 & leaves[i]=='y' \\ \end{cases} dp[i][2]={ min(dp[i−1][1],dp[i−1][2])min(dp[i−1][1],dp[i−1][2])+1leaves[i]==′r′leaves[i]==′y′
这里以题目中的示例leaves = "rrryyyrryyyrr"
为例进行动态演示,下面是全过程:
from typing import List
class Solution:
def minimumOperations(self, leaves: str) -> int:
n = len(leaves)
dp = [[0, 0, 0] for _ in range(n)]
#阶段1初值计算
dp[0][0] = (1 if leaves[0] == 'y' else 0)
#阶段1其他值计算
for i in range(1, n):
dp[i][0] = dp[i - 1][0] + (1 if leaves[i] == 'y' else 0)
#阶段2初值计算
dp[1][1] = dp[0][0] + (1 if leaves[1] == 'r' else 0)
#阶段2其他值计算
for i in range(2,n):
dp[i][1] = min(dp[i-1][0],dp[i-1][1]) + (1 if leaves[i] == 'r' else 0)
#阶段3初值计算
dp[2][2] = dp[1][1] + (1 if leaves[2] == 'y' else 0)
#阶段3其他值计算
for i in range(3,n):
dp[i][2] = min(dp[i-1][1],dp[i-1][2]) + (1 if leaves[i] == 'y' else 0)
return dp[n - 1][2]
s = Solution()
leaves = "rrryyyrryyyrr"
print(s.minimumOperations(leaves))
"""
2
"""
以上便是本文的全部内容,如果觉得不错的话,可以点个赞或关注一下博主,后续将会持续更新Leetcode算法题题解,若是有错误的地方,也敬请批评指正!!!