题目描述&链接
Leetcode - LCP 19 : 寻找能够满足"rrryyyrr" 这种形式的最小翻转次数。
题目思路
与[亚麻高频题] Leetcode.926 属于同类型的题目,稍微加了一点难度。
1. 前缀和
同样可以使用前缀和进行计算,我们维护一个数组记录第i个位置前"r"树叶的总数,并可以通过前缀和求差来求出相应"y"树叶的个数。不同于LC926的01翻转我们这里要维护三个区间:
: 红树叶1;
: 黄树叶;
: 红树叶2
[0,end] 维护"r..yyy..r"格式的翻转次数:
([0, i]处需要翻转的"y"的个数) +
([i, j]处需要翻转"r"的个数) +
([j, end]处需要翻转"y"的个数)
需要进行两层遍历分别遍历 i,j,代码如下 :
public int minimumOperations(String leaves) {
// 前缀和,记录每一个位置r的个数,暴力会超时,需要优化
// 题目要求只能[2,n-1]位置可以有y
int n = leaves.length();
int[] cr = new int[n+1];
cr[0] = 0;
for(int i=1; i<=n; i++) {
cr[i] = cr[i-1] + (leaves.charAt(i-1)=='r'? 1:0);
}
int res = n;
for(int i=1; i<n; i++) {
for(int j=n-1; j>i; j--) {
res = Math.min(res, i-cr[i] + cr[j]-cr[i] + n-j-(cr[n]-cr[j]));
}
}
return res;
}
时间复杂度: ; 空间复杂度:
;这种思路很好理解但是遗憾的是会超时。
2. 动态规划
题目有着明确的状态(红1,黄2,红3),这种有明确状态求最值问题通常都让我们联想到是不是可以用DP求解。首先DP状态如何定义,和LC926一样我们可以建立二维数组保存状态,
表示前面i-1个元素保持题目格式情况下当前位置i翻转成 红1/黄1/红2 的最小翻转次数。 接下来如何确定状态转移方程,我们需要考虑三种情况:
1. 当前位置i是红1:那么前面一定只能是全红1 :
dp[i][0] = dp[i-1][0] + (arr[i]=="y")
2. 当前位置i是黄1: 那么前面可以是全红1"rrrr" 或者 红1和黄1都有"rrryyy" :
dp[i][1] = min(dp[i-1][0], dp[i-1][1]) + (arr[i]=="r")
3. 当前位置i是红2: 那么前面可以是红1黄1 或者 红1黄1红2都有:
dp[i][2] = min(dp[i-1][1], dp[i-1][2]) + (arr[i]=="y")
状态和转移方程已经有了,接下来就是状态初始化由于求最小值 dp[*][0]都定义成,最后直接返回dp[end][2]。注意这里dp[end][0/1]虽然也能计算出来但是没有意义因为叶子格式不符合提议。
public int minimumOperations(String leaves) {
// dp做法
int n = leaves.length();
// 状态 - dp[i][0/1/2] -> 前i满足条件当前位置为0/1/2的最小反转数
int[][] dp = new int[n][3];
// 初始化 - 第一位必须是0
dp[0][0] = leaves.charAt(0)=='r'? 0: 1;
dp[0][1] = Integer.MAX_VALUE;
dp[0][2] = Integer.MAX_VALUE;
dp[1][2] = Integer.MAX_VALUE;
for(int i=1; i<n; i++) {
dp[i][0] = dp[i-1][0] + (leaves.charAt(i)=='r'? 0: 1);
dp[i][1] = Math.min(dp[i-1][0], dp[i-1][1]) + (leaves.charAt(i)=='y'? 0: 1);
if(i>=2) dp[i][2] = Math.min(dp[i-1][1], dp[i-1][2]) + (leaves.charAt(i)=='r'? 0: 1);
}
return dp[n-1][2];
}
时间复杂度: ; 空间复杂度:
。