题目链接
题目描述
给你两个长度相同的字符串 s
和 t
,以及一个整数 maxCost
。你可以将 s
中的第 i
个字符变成 t
中的第 i
个字符,每次操作的代价为 |s[i] - t[i]|
(即两个字符的ASCII码差值的绝对值)。
请你返回使 s
的子串与 t
的对应子串相等的最大长度。这里的子串是连续的字符序列。
示例
- 输入:
s = "abcd", t = "bcdf", maxCost = 3
-
- 输出:
3
- 解释:从索引 0 到 2 的子串 "abc" 变为 "bcd",总代价为 |'a'-'b'| + |'b'-'c'| + |'c'-'d'| = 1 + 1 + 1 = 3,正好等于 maxCost,所以长度为 3。
- 输出:
- 输入:
s = "abcd", t = "cdef", maxCost = 3
-
- 输出:
1
- 解释:子串 "a" 变为 "c" 代价为 2,子串 "ab" 变为 "cd" 代价为 2+2=4 > 3,所以最长长度为 1。
- 输出:
- 输入:
s = "abcd", t = "acde", maxCost = 0
-
- 输出:
1
- 解释:只有 "a" 与 "a" 相等,代价为 0,所以长度为 1。
- 输出:
解法分析:滑动窗口法
核心思路
本题采用滑动窗口(双指针)技术解决,核心思想是维护一个窗口,使得窗口内所有字符转换的总代价不超过maxCost
。通过移动窗口,找到满足条件的最大窗口长度,即为所求的最长子串长度。
代码实现
class Solution:
def equalSubstring(self, s: str, t: str, maxCost: int) -> int:
n, l, cost, ans = len(s), 0, 0, 0
for i in range(n):
# 计算当前字符转换的代价,并累加到总代价
cost += abs(ord(s[i]) - ord(t[i]))
# 当总代价超过maxCost时,移动左指针缩小窗口
while cost > maxCost:
cost -= abs(ord(s[l]) - ord(t[l]))
l += 1
# 更新最长有效子串长度
ans = max(ans, i - l + 1)
return ans
代码解析
- 初始化变量:
-
n
:字符串s
和t
的长度(两者长度相同)l
:滑动窗口的左指针,初始化为0cost
:记录窗口内字符转换的总代价,初始化为0ans
:记录最长有效子串的长度,初始化为0
- 遍历字符串:
-
- 使用
i
作为右指针遍历字符串,从0到n-1 cost += abs(ord(s[i]) - ord(t[i]))
:计算当前字符s[i]
转换为t[i]
的代价,并累加到总代价cost
- 使用
- 调整窗口:
-
- 当
cost > maxCost
时,说明窗口内总代价超过预算,需要移动左指针l
cost -= abs(ord(s[l]) - ord(t[l]))
:减去左指针位置字符的转换代价l += 1
:左指针右移,缩小窗口范围,直到总代价cost
≤maxCost
- 当
- 更新最长长度:
-
ans = max(ans, i - l + 1)
:计算当前窗口的长度(i - l + 1
),并更新最长有效子串长度- 窗口
[l, i]
内的字符转换总代价不超过maxCost
,因此该窗口长度即为有效子串长度
关键技巧说明
- 滑动窗口模型:
-
- 右指针
i
不断向右扩展,扩大窗口范围 - 当窗口内总代价超过预算时,左指针
l
向右移动,缩小窗口范围 - 确保窗口内总代价≤
maxCost
,从而保证窗口内子串可转换为与t
对应子串相等
- 右指针
- 代价计算:
-
- 使用
abs(ord(s[i]) - ord(t[i]))
计算单个字符的转换代价,其中ord()
函数获取字符的ASCII码 - 累加代价和减去代价的操作均在O(1)时间内完成
- 使用
- 窗口长度计算:
-
- 窗口长度为
i - l + 1
,其中i
是右指针索引,l
是左指针索引(左闭右闭区间) - 该长度即为在预算内可转换的最长子串长度
- 窗口长度为
复杂度分析
- 时间复杂度:O(n),其中n是字符串的长度。左右指针各遍历字符串一次,每个字符最多被访问两次。
- 空间复杂度:O(1),只使用了常数级别的额外空间。
示例详解
以输入s = "abcd", t = "bcdf", maxCost = 3
为例:
- 初始状态:
n=4, l=0, cost=0, ans=0
i=0
:
-
- 代价:
|'a'-'b'|=1
→cost=1
cost≤3
,不进入循环- 窗口长度:
0-0+1=1
→ans=1
- 代价:
i=1
:
-
- 代价:
|'b'-'c'|=1
→cost=2
cost≤3
,不进入循环- 窗口长度:
1-0+1=2
→ans=2
- 代价:
i=2
:
-
- 代价:
|'c'-'d'|=1
→cost=3
cost≤3
,不进入循环- 窗口长度:
2-0+1=3
→ans=3
- 代价:
i=3
:
-
- 代价:
|'d'-'f'|=2
→cost=5
cost>3
,进入循环:
- 代价:
-
-
cost -= |'a'-'b'|=1
→cost=4
,l=1
cost>3
,继续循环:
-
-
-
-
cost -= |'b'-'c'|=1
→cost=3
,l=2
-
-
-
- 窗口长度:
3-2+1=2
→ans=max(3, 2)=3
- 窗口长度:
- 最终返回
ans=3
,符合示例1的输出。
总结
该解法利用滑动窗口技术高效地解决了在预算内构造相同子串的问题,通过维护窗口内转换总代价不超过预算,确保窗口内子串可转换为与目标串对应子串相等。算法时间复杂度为O(n),空间复杂度为O(1),是解决该问题的最优解法之一。这种滑动窗口控制累计代价的思路,可广泛应用于类似的子数组累加约束问题。