【LeetCode】LCP 19. 秋叶收藏集

LCP 19. 秋叶收藏集

  • 题目描述
  • 分析思路

题目描述

题目来源:力扣(LeetCode)
小扣出去秋游,途中收集了一些红叶和黄叶,他利用这些叶子初步整理了一份秋叶收藏集 leaves, 字符串 leaves 仅包含小写字符 ry, 其中字符 r 表示一片红叶,字符 y 表示一片黄叶。
出于美观整齐的考虑,小扣想要将收藏集中树叶的排列调整成「红、黄、红」三部分。每部分树叶数量可以不相等,但均需大于等于 1。每次调整操作,小扣可以将一片红叶替换成黄叶或者将一片黄叶替换成红叶。请问小扣最少需要多少次调整操作才能将秋叶收藏集调整完毕。

示例 1:
输入:leaves = “rrryyyrryyyrr”
输出:2
解释:调整两次,将中间的两片红叶替换成黄叶,得到 “rrryyyyyyyyrr”

示例 2:
输入:leaves = “ryr”
输出:0
解释:已符合要求,不需要额外操作

提示:
3 <= leaves.length <= 10^5
leaves 中只包含字符 ‘r’ 和字符 ‘y’

分析思路

首先,分析题目的要求是将树叶的排列调整为[红、黄、红]三部分。因为,对于整个数组leaves来讲,需要有两个插入点将该数组转换为三个部分,且不同的插入点组合满足最小变换的要求。例如,leaves的长度为[0...n-1],满足最小调整要求的插入点分别是ij,满足条件0。这里有两个问题需要说明:

  1. 因为要将数组划分为三个部分,所以ij不能相等,且ij均大于0,且小于n。
  2. 因为要将数组划分为三个部分,则i的范围是1<=i 代码如下:
import java.util.Scanner;

public class StringSort {
     
    public static void main(String[] args) {
     
        Scanner sc = new Scanner(System.in);
        String leaves = sc.nextLine();
        int res = solution(leaves);
        System.out.println(res);
    }

    private static int solution(String leaves) {
     
        int len = leaves.length();
        if (len < 3 || len > 100000) {
     
            return 0;
        }
        int exchange = len;
        for (int right = len - 1; right > 1; right--) {
     
            for (int left = 1; left < right; left++) {
     
                int times = changeStrToRYR(leaves, left, right);
                if (times < exchange) {
     
                    exchange = times;
                }
            }
        }
        return exchange;
    }

    private static int changeStrToRYR(String str, int start, int end) {
     
        int res = 0;
        for (int i = 0; i < start; i++) {
     
            if (str.charAt(i) == 'y') {
     
                res++;
            }
        }
        for (int i = start; i < end; i++) {
     
            if (str.charAt(i) == 'r') {
     
                res++;
            }
        }
        for (int i = end; i < str.length(); i++) {
     
            if (str.charAt(i) == 'y') {
     
                res++;
            }
        }
        return res;
    }
}

然而,该方法存在着时间复杂度高,重复计算的问题,因此考虑对其进行优化。题目要求的排列顺序为[红、黄、红],因此考虑以红叶数目为基准。具体思路是如下:

sum[n] 表示 [0, n) 区间内红叶数量. 假设整理后红叶的区间为[0, i)[j, n), 那么黄叶区间为 [i, j)。对于左右两个区间,操作次数为区间长度减去红叶的数量,对于中间的区间,操作次数就是红叶的数量。具体为:

  • 区间 [0, i) 的操作次数为: i − s u m [ i ] i - sum[i] isum[i]。表示区间长度-区间内红叶的数目,即黄叶调整为红叶的次数;
  • 区间 [i, j) 的操作次数为: s u m [ j ] − s u m [ i ] sum[j] - sum[i] sum[j]sum[i]。表示区间 [0, j)内红叶的数目 - 区间[0, i)内红叶的数目,即中间区间红叶的数目;
  • 区间 [j, n) 的操作次数为: n − j − ( s u m [ n ] − s u m [ j ] ) n-j-(sum[n]-sum[j]) nj(sum[n]sum[j]), 即区间[j,n)的叶子数-区间[j,n)的红叶数,即右侧区间中黄叶调整为红叶的次数。

因此。需要操作的总次数为 ( i − s u m [ i ] ) + ( s u m [ j ] − s u m [ i ] ) + [ n − j − ( s u m [ n ] − s u m [ j ] ) ] + (i - sum[i]) + (sum[j] - sum[i])+ [ n - j - (sum[n] - sum[j]) ] + (isum[i])+(sum[j]sum[i])+[nj(sum[n]sum[j])]+,整理后得到
f ( i , j ) = n − s u m [ n ] + ( i − 2 × s u m [ i ] ) − ( j − 2 × s u m [ j ] ) f(i, j) = n - sum[n] + (i - 2 \times sum[i]) - (j - 2 \times sum[j]) f(i,j)=nsum[n]+(i2×sum[i])(j2×sum[j])
其中,约束条件为 0 < i < j < n 0 < i < j < n 0<i<j<n。为了让操作次数最少,我们希望 j 确定时 i − 2 × s u m [ i ] i - 2 \times sum[i] i2×sum[i] 最小。用 min[x] 记录 [1, x] 区间内的 i − 2 × s u m [ i ] i - 2 \times sum[i] i2×sum[i] 的最小值,然后将 jn - 1 遍历到 2min[j - 1] 一定是在位置 j 左侧的位置 i 上计算出来的最小的 i − 2 × s u m [ i ] i - 2 \times sum[i] i2×sum[i](min[] 是一个递减数组,这一点不太好理解)。最后将其导入到 f ( i , j ) f(i,j) f(ij)中计算结果。
代码如下:

    private static int solution2(String leaves) {
     
        int len = leaves.length();
        if (len < 3 || len > 100000) {
     
            return 0;
        }
        int[] sum = new int[len + 1];
        int[] min = new int[len + 1];
        int tmpMin = len;
        for (int i = 0; i < len; i++) {
     
            sum[i + 1] = sum[i] + (leaves.charAt(i) == 'r' ? 1 : 0);
            if (i > 0 && i < len - 1) {
     
                tmpMin = Math.min(tmpMin, i - 2 * sum[i]);
                min[i] = tmpMin;
            }
        }
        int exchange = len;
        for (int j = len - 1; j > 1; j--) {
     
            exchange = Math.min(exchange, len - sum[len] + min[j - 1] - (j - 2 * sum[j]));
        }
        return exchange;
    }

算法的时间复杂度为O(n)。

你可能感兴趣的:(编程题,leetcode,秋叶收藏集,动态规划,状态转移,力扣)