题目链接&描述
Leetcode 926:求将一个01字符串反转成单调递增的最小反转次数
题目思路
1. 前缀后缀和
整体思路比较清晰好懂,每一个位置上形成单调递增字符串的反转次数=前面1的个数 + 后面0的个数。这样就可以转化成000...111的形式。所以我们只需维护一个数组记录每一个位置前面1的个数和每一个位置后面0的个数即可,代码如下:
public int minFlipsMonoIncr(String s) {
int n = s.length();
int[] pre1 = new int[n+1];
for(int i=1; i<=n; i++) {
pre1[i] = pre1[i-1] + s.charAt(i-1)-'0';
}
int res = n;
// 后面的0的个数可以通过前缀和求差的方式得到 n-j - (pre1[n] - pre1[j])
for(int i=0; i<=n; i++) {
res = Math.min(res, pre1[i]+n-i+(pre1[n]-pre1[i]));
}
return res;
}
每一个位置后面0的个数可以通过前缀1的个数求差获得,整体算法时间复杂度: ;空间复杂度:
。
2. 动态规划
另一种思路是通过DP,我们建立一个二维DP数组表示前i-1个元素单调递增且第i个元素为0需要的最小反转次数,
表示前i个元素单调递增且第i个元素为1需要的最小反转次数。定义好状态,接下来需要定义转移方程,分两种情况:
1.如果考虑将当前位置变化转化成0形成递增,那么该位置的状态就依赖于前面dp[0]的状态,因为要保证前面全0才能单调递增
2.如果考虑将当前位置变化转化成1形成递增,那么比较灵活,前面可以为0或者1,那么该位置dp[1]的状态应该是前面的较小值就可以保证单调递增
整体代码如下: 时间复杂度: 比前缀和算法少了一次循环;空间复杂度:
。空间可以优化成
,因为每一次状态更新只依赖前面的状态,无需使用数组,这里用数组便于理解。
public int minFlipsMonoIncr(String s) {
// 状态 - dp[i][0/1] 前i-1个元素单调递增且当前为转化为0、1的最小反转次数
int n = s.length();
int[][] dp = new int[n][2];
// 初始化
dp[0][0] = s.charAt(0)=='1'? 1:0;
dp[0][1] = s.charAt(0)=='0'? 1:0;
for(int i=1; i<n; i++) {
dp[i][0] = dp[i-1][0] + (s.charAt(i)=='1'?1:0);
// 如果当前为转化成1 那么前面可以0递增也可1递增取最小
dp[i][1] = Math.min(dp[i-1][1], dp[i-1][0]) + (s.charAt(i)=='0'? 1:0);
}
return Math.min(dp[n-1][0], dp[n-1][1]);
}