题目:
给你两个长度相同的字符串,s 和 t。
将 s 中的第 i 个字符变到 t 中的第 i 个字符需要 |s[i] - t[i]| 的开销(开销可能为 0),也就是两个字符的 ASCII 码值的差的绝对值。
用于变更字符串的最大预算是 maxCost。在转化字符串时,总开销应当小于等于该预算,这也意味着字符串的转化可能是不完全的。
如果你可以将 s 的子字符串转化为它在 t 中对应的子字符串,则返回可以转化的最大长度。
如果 s 中没有子字符串可以转化成 t 中对应的子字符串,则返回 0。
算法:
方法一:双指针
双指针left,right,保证[left,right]之间的字符的剩余maxCost>=0得到有效的字符,计算字符长度
func equalSubstring(s string, t string, maxCost int) int {
ans := 0
for left, right := 0, 0 ; right < len(s) && right < len(t); right ++ {
maxCost = maxCost - abs(int(s[right]) - int(t[right]))
// fmt.Println(maxCost, s[right],t[right], int(s[right] - t[right]))
for maxCost < 0 {
maxCost = maxCost + abs(int(s[left]) - int(t[left]))
left ++
}
// maxCost >= 0时,计算长度
if right - left + 1 > ans {
ans = right - left + 1
}
}
return ans
}
func abs(a int) int {
if a < 0 {
return -a
}
return a
}
方法二:二分查找
计算模型:我们只需要找出成本不超过 maxCost 的最大长度区间,为什么想到用二分呢,因为假设答案字符串的长度为ans,则长度小于等于ans也满足题意,而长度大于ans的字符串长度均不满足,存在二段性,就可以猜猜猜,于是可以使用二分。至于这个ans长度的起始位置,其实我并不关心。
构造cost的前缀和sum是为了在O(1)时间求出cost
func equalSubstring(s string, t string, maxCost int) int {
n := len(s)
sum := make([]int, n + 1)
for i := range s {
sum[i + 1] = sum[i] + abs(int(s[i]) - int(t[i]))
}
left, right := 1, n
// left right相等时意味着一个有效的结果,有效结果的有边界
// 再比这个边界大一点,就超过maxCost了
for left < right {
// 其实mid没必要完全1/2,只是1/3,1/4效率不够高,mid左右偏移一没有影响
// 设置mid := (left + right + 1) >> 1,是因为(left + right) >> 1是向0(左取整)
// 为了mid=(left + right) >> 1时left = mid防止陷入死循环,小技巧get
mid := (left + right + 1) >> 1
// fmt.Println(left, mid, right)
// 猜有效的子字符串长度为mid,cost不超过maxCost是否有解
if check(sum, mid, maxCost) {
left = mid
} else {
right = mid - 1
}
}
// 为什么返回left呢?因为left是mid赋值的,而mid是有效长度
// 你也可以用另一个变量存储mid
if check(sum, left, maxCost) {
return left
}
return 0
}
// sum[mid,n]是否存在sum[i] - sum[mid] <= max
func check(sum []int, mid, max int) bool {
for i := mid; i < len(sum); i ++ {
// 修改nums[i-mid,i]的总cost是否小于maxCost
if sum[i] - sum[i - mid] <= max {
return true
}
}
return false
}
func abs(a int) int {
if a < 0 {
return -1 * a
}
return a
}