最近碰到了一道面试题,虽然不难但是临试没想出好的解法,记录下来以作分享。
题目:消除字符串中全部的b
和连续的ac
用例:
‘aabbc‘ -> ‘a‘
‘aaabbbccc‘ -> ‘‘
‘abcdcba‘ -> ‘dca‘
注意结合用例理解这个题目的意思,转化后的字符串中不能有任何b
和连续的ac
,而不是仅对初始值进行一次转换。
既然最后得到的字符串中不能有任何b
和连续的ac
,那么我们可以很容易地想到使用正则连续地进行处理,直到处理前后的字符串相同,可以很容易地写出下面的代码:
function solution (str) {
const reg = /ac|b/g
let strCopy = str
do {
str = strCopy
strCopy = str.replace(reg, ‘‘)
} while (strCopy !== str)
return strCopy
}
但是暴力法显然不够好,它每次重复地去执行替换,对于aaaaaccccc
这种字符串,需要执行5次。
那么有没有更好的方法呢?通过观察我们发现其实需要替换的字符序列必定符合{n个a}{m个b}{n个c}(m, n不都为0)
这种格式。b我们可以先不去管它,先从格式开头的a入手。那么如果找到了a,怎么知道后面的字符序列中有没有c出现呢?而c出现的个数是否能跟a的个数匹配呢?我们可以先保存连续a的数量,然后如果后面出现了符合格式的c,则减少a的个数,直到a耗尽或者格式匹配失败。
function solution(str) {
let countA = 0 // 连续a的个数
let result = ‘‘
for (let i = 0; i < str.length; i++) {
if (str[i] === ‘a‘) {
countA++
} else if (str[i] === ‘b‘) {
continue
} else if (str[i] === ‘c‘) {
if (countA === 0) {
result += ‘c‘
} else {
countA--
}
} else {
// 遇到其他字符,则保存的a需要释放
while (countA) {
result += ‘a‘
countA--
}
result += str[i]
}
}
// 最后需要释放所有保存的a
while (countA) {
result += ‘a‘
countA--
}
return result
}
存储a的解法有点类似于栈,遇到a入栈,遇到c出栈,遇到abc之外的字符排空栈。
这是一种时间复杂度O(n)的解法,但是在遇到类似aaaaaad
这种字符串的时候,它还不够好,因为最后保存的a都要释放出来,有额外的时间开销。
这是本题较好的一种解法,设两个指针cur和loc分别从头开始出发,cur每次移动一格,另一个指针loc保留当前的操作位置,如果cur指向的字符是c且loc指向的是a,则将loc回移一位(ac抵消了),如果遇到其他非b的字符,则将loc处的字符置为cur处的字符,一直进行直到到cur到达字符串尾部,此时取字符串开头到loc指针之间的子串即为本题的解。这种解法妙就妙在loc处的字符是即时更新的,一些边界条件都自动消除了。
画一个图更好理解一点,比如有字符串abeabcdaabbcg
,它经过处理后应该得到aedag
,下面是操作过程的图解:
这种方法在C++, Java中是可以实现in-place更新的,但Javascript字符串是不可变的,所以体现不出来。
function solution(str) {
let result = str.split(‘‘)
let location = -1
for (let i = 0; i < str.length; i++) {
let cur = str[i]
if (cur === ‘c‘ && location >= 0 && result[location] === ‘a‘) {
location --
} else if (cur !== ‘b‘) {
result[++location] = cur
}
}
return result.slice(0, location + 1).join(‘‘)
}
最后做个技术总结,这道题难度不大,考察的是对字符串算法的理解,双指针、栈、动态规划等思想在字符串算法问题中还是有很多应用的,还是要通过学习去总结归纳。
原文:https://www.cnblogs.com/SteelArm/p/12773014.html