简介
926. 将字符串翻转到单调递增 的算法问题大多使用动态规划(DP)来求解。本文介绍了一种利用数学推导的来解决这个问题的方法,简单有趣。
问题描述
如果一个二进制字符串,是以一些 0(可能没有 0)后面跟着一些 1(也可能没有 1)的形式组成的,那么该字符串是 单调递增 的。
给你一个二进制字符串 s,你可以将任何 0 翻转为 1 或者将 1 翻转为 0 。
返回使 s 单调递增的最小翻转次数。示例 1:
输入: s = "00110"
输出: 1
解释: 翻转最后一位得到 00111.示例 2:
输入: s = "010110"
输出: 2
解释: 翻转得到 011111,或者是 000111。示例 3:
输入: s = "00011000"
输出: 2
解释: 翻转得到 00000000。
问题分析
首先,我们换个角度来重新描述这个问题:
给你一个长度为 二进制字符串 s。
请你选取前 个字符,将前 个字符中的所有 1 翻转为 0,将之后的( 个)字符的所有 0 翻转为 1。求:当 为多少时翻转次数 最小?
通过上面的描述我们不难看出:
翻转次数 = 前 个字符中 1 的个数 + 后面的 个字符中 0 的个数。
假设:前 个字符中有 个 0
则:前 个字符中有 个 1
又因为:长度为 二进制字符串 s 中总共有 个 0
则:后面的 个字符中有 个 0
那么我们可以得到:
即:
再次回到我们的问题:
求解 ?
答案呼之欲出,我们只需要遍历一遍根据上述公式计算并记录下函数的最小值即可。
代码实现
这里有一个细节:由于 是固定的常量,所以可以在遍历的过程中忽略,在遍历结束后加上。
/**
* 将字符串翻转到单调递增
* @param {String} s e.g. "AABBAAB"
*/
function minFlipsMonoIncr(s) {
const n = s.length
// partial 的最小值
let partial_min = 0
// 记录 0 的个数
let a = 0
for (let i = 0; i < n; i++) {
// 计数 `a(x)`
if (s[i] === '0') {
a++
}
const x = i + 1
// 公式中 `x - 2 * a(x)` 的部分
const partial = x - 2 * a
partial_min = Math.min(partial_min, partial)
}
// 追加上 a(n)
return a + partial_min
}
复杂度分析
- 时间复杂度:
O(n)
- 空间复杂度:
O(1)
写在最后
如果你有其他有意思的方法,欢迎留言分享。
如果你想了解动态规划(DP)版的求解,可以参看官方的解题。