秋叶收藏集

小扣出去秋游,途中收集了一些红叶和黄叶,他利用这些叶子初步整理了一份秋叶收藏集  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

原题链接:https://leetcode-cn.com/problems/UlBDOe