前言
这是刷算法题的第七天,用到的语言是JS 题目:力扣 2712. 使所有字符相等的最小成本 (中等)
一、题目内容
给你一个下标从 0 开始、长度为 n 的二进制字符串 s ,你可以对其执行两种操作:
选中一个下标 i 并且反转从下标 0 到下标 i(包括下标 0 和下标 i )的所有字符,成本为 i + 1 。
选中一个下标 i 并且反转从下标 i 到下标 n - 1(包括下标 i 和下标 n - 1 )的所有字符,成本为 n - i 。
返回使字符串内所有字符 相等 需要的 最小成本 。
反转 字符意味着:如果原来的值是 '0' ,则反转后值变为 '1' ,反之亦然。
示例 1:
输入:s = "0011"
输出:2
解释:执行第二种操作,选中下标 i = 2 ,可以得到 s = "0000" ,成本为 2 。可以证明 2 是使所有字符相等的最小成本。
示例 2:
输入:s = "010101"
输出:9
解释:执行第一种操作,选中下标 i = 2 ,可以得到 s = "101101" ,成本为 3 。
执行第一种操作,选中下标 i = 1 ,可以得到 s = "011101" ,成本为 2 。
执行第一种操作,选中下标 i = 0 ,可以得到 s = "111101" ,成本为 1 。
执行第二种操作,选中下标 i = 4 ,可以得到 s = "111110" ,成本为 2 。
执行第二种操作,选中下标 i = 5 ,可以得到 s = "111111" ,成本为 1 。
使所有字符相等的总成本等于 9 。可以证明 9 是使所有字符相等的最小成本。
提示:
1 <= s.length == n <= 10^5^
s[i] 为 '0' 或 '1'
二、解题方法
1.暴力解法
思路:读懂题目很难
遍历字符串,当相邻的两个字符不相同时,则开始比较需要使用 第一种操作 / 第二种操作。
并且不需要反转字符,因为每遍历一次,前面 / 后面的字符全都是一样的,都是 0 / 1
故只需要判断 哪种操作成本更小,使用Math.min(num1, num2) 。
初始化成本为0,并将字符串的长度作为循环的条件。
代码如下(示例):
/**
* @param {string} s
* @return {number}
*/
var minimumCost = function(s) {
// 思路:读懂题目很难
// 遍历字符串,当相邻的两个字符不相同时,则开始比较需要使用 第一种操作 / 第二种操作
// 并且不需要反转字符,因为每遍历一次,前面 / 后面的字符全都是一样的,都是 0 / 1
// 故只需要判断 哪种操作成本更小,使用Math.min(num1, num2)
// 初始化成本为0,并将字符串的长度作为循环的条件
let sum = 0
for(let i = 0; i < s.length - 1; i++) {
if(s[i] !== s[i + 1]) { // 如果不相等,则比较操作成本,并累加到sum
sum += Math.min(i+1, s.length - 1 - i)
}
}
return sum
};
2.官方题解
2.1 方法一:动态规划
思路与算法:
我们可以维护一个前缀全部变成 0 或 1 的最小成本,同时维护后缀全部变成 0 和 1 的最小成本来求解答案。
定义 suf[i][0] 表示从第 i 个字符开始的后缀全部变成 0 所需要的最小成本,定义 suf[i][1] 表示从第 i 个字符的后缀全部变成 1 所需的最小成本,转移方程为:
若 s[i] 为 1,则:
suf[i][1]=suf[i+1][1]
suf[i][0]=suf[i+1][0]+(n−i)
若 s[i] 为 0,则:
suf[i][1]=suf[i+1][0]+(n−i)
suf[i][0]=suf[i+1][0]
前缀的状态 pre[i][0] 和 pre[i][1] 的定义和转移过程类似,遍历所有的 i,求解 min(pre[i][0]+suf[i+1][0],pre[i][1]+suf[i+1][1]) 的最小值即可。
代码如下(示例):
var minimumCost = function(s) {
const n = s.length;
const suf = Array.from({ length: n + 1 }, () => [0, 0]);
for (let i = n - 1; i >= 0; i--) {
if (s[i] === '1') {
suf[i][1] = suf[i + 1][1];
suf[i][0] = suf[i + 1][1] + (n - i);
} else {
suf[i][1] = suf[i + 1][0] + (n - i);
suf[i][0] = suf[i + 1][0];
}
}
let pre = [0, 0];
let res = Infinity;
for (let i = 0; i < n; i++) {
if (s[i] === '1') {
pre[0] = pre[1] + i + 1;
} else {
pre[1] = pre[0] + i + 1;
}
res = Math.min(res, Math.min(pre[0] + suf[i + 1][0], pre[1] + suf[i + 1][1]));
}
return res;
};
作者:力扣官方题解
链接:https://leetcode.cn/problems/minimum-cost-to-make-all-characters-equal/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
复杂度分析:
时间复杂度:O(n),其中 n 是字符串 s 的长度。状态转移的时间复杂度为 O(1),共有 O(n) 个状态,因此总体时间复杂度为 O(n)。
空间复杂度:O(n)。存储后缀状态的空间复杂度为 O(n)。
链接: 力扣本题官方题解
来源:力扣(LeetCode)
2.2 方法二:一次遍历(即本题我的解法)
思路与算法:
我们并不关心字符最终会变成 0 还是 1,只要它们相等即可。因此需要关注每对相邻字符的相等关系。一次操作有如下性质:
一次操作可以且一定改变一对相邻字符的关系。
对于两个相邻且不相等的字符,必须经过一次操作才能使它们相等。
对某两个相邻字符操作结束后,左侧和右侧所有的相邻字符的相等关系不变。
因此,我们只需枚举所有的相邻字符,对不同的进行操作。操作时选择成本更小的一侧,其总和就是答案。
var minimumCost = function(s) {
const n = s.length;
let res = 0;
for (let i = 1; i < n; i++) {
if (s[i] !== s[i - 1]) {
res += Math.min(i, n - i);
}
}
return res;
};
作者:力扣官方题解
链接:https://leetcode.cn/problems/minimum-cost-to-make-all-characters-equal/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
复杂度分析:
时间复杂度:O(n),其中 n 是字符串 s 的长度。
空间复杂度:O(1)。
链接: 力扣本题官方题解
来源:力扣(LeetCode)