地址:. - 力扣(LeetCode)
题目描述:给你一个字符串 word ,你可以向其中任何位置插入 “a”、“b” 或 “c” 任意次,返回使 word 有效 需要插入的最少字母数。
如果字符串可以由 “abc” 串联多次得到,则认为该字符串 有效 。
提示:
- 1 <= word.length <= 50
- word 仅由字母 “a”、“b” 和 “c” 组成。
题解
动态规划
d[i]表示前i个字符拼凑成若干个 abc所需要的最小插入数
d[0] = 0
如果word[i] 单独存在于一组 abc中,d[i]=d[i−1]+2
那怎么知道word[i]是不是单独存在一组abc中呢?
首先 我们能知道的是c>b>a
如果word[i]>word[i−1] 那就能肯定是在同一组abc中的
比如字符串ab,b>a 我们能肯定它们是在同一组abc中的
比如字符串ac,c>a 我们能肯定它们是在同一组abc中的
比如字符串bc,c>b 我们能肯定它们是在同一组abc中的
除了上述情况之外,其他的情况word[i]都是单独成一组的
比如ca,a<c,我们能肯定a就是单独成一组的,不可能跟前面的c成一组,因为这违背了abc这样的顺序
既然是单独成一组,就需要再插入两个元素构成一组 所以d[i]=d[i−1]+2
如果 word[i]>word[i−1],那么 word[i]可以和 word[i−1] 在同一组 abc 中,d[i]=d[i−1]−1
这种情况就是word[i]跟前面可以成一组,但是我们要知道的是在word[i]插入进word[0]-word[i-1]之前,我们通过动态规划,已经计算好了word[0]-word[i-1]这个字符串拼凑成若干个 abc所需要的最小插入数
那就是可以理解为,这个时候,word[0]-word[i-1]的最后三位字符串已经成组成abc了
这个时候我们把word[i]添加进去成组,也就意味着d[i-1],也就是word[0]-word[i-1]这个字符串拼凑成若干个 abc所需要的最小插入数需要减1,因为word[i]替换占位了,相应的插入数就要减少了,所以d[i]=d[i−1]−1
通过一个例子加深理解:
word=aacabc
答案应该是:3 a(bc)a(b)cabc
第一步:求d[1],因为a前面是空字符,所以字符串a肯定是自成一组的,d[1] = d[0]+2 = 2
第二步:求d[2],因为a前面是a,所以字符串word[1]:a自成一组的,d[2] = d[1]+2 = 4
第三步:求d[3],因为c前面是a,所以字符串word[2]:c跟前面的word[1]:a是同一组的,d[3] = d[2]-1 = 3
第四步:求d[4],因为a前面是c,所以字符串word[3]:a自成一组,d[4] = d[3]+2 = 5
第五步:求d[5],因为b前面是a,所以字符串word[4]:b跟前面的word[3]:a是同一组的,d[5] = d[4]-1 = 4
第六步:求d[6],因为c前面是b,所以字符串word[5]:c跟前面的word[4]:b是同一组的,d[6] = d[5]-1 = 3
所以答案就是d[6] = 3
代码实现
/**
* @param {string} word
* @return {number}
*/
var addMinimum = function(word) {
const n = word.length
const d = new Array(n+1).fill(0)
for(let i=1;i<=n;i++){
d[i] = d[i-1]+2
if(i>1&&word[i-1]>word[i-2]){
d[i] = d[i-1]-1
}
}
return d[n]
};
状态转移时,最多只会依赖前一个状态 d[i−1],因此使用滚动数组优化可以使得空间复杂度降至 O(1)
/**
* @param {string} word
* @return {number}
*/
var addMinimum = function(word) {
const n = word.length
let d = 0
for(let i=1;i<=n;i++){
d+=2
if(i>1&&word[i-1]>word[i-2]){
// d = (d-2)-1 d-2是为了还原状态 因为前面+2了
d-=3
}
}
return d
};