本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。
为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。
由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。
给你两个长度相同的字符串,s 和 t。
将 s 中的第 i 个字符变到 t 中的第 i 个字符需要 |s[i] - t[i]| 的开销(开销可能为 0),也就是两个字符的 ASCII 码值的差的绝对值。
用于变更字符串的最大预算是 maxCost。在转化字符串时,总开销应当小于等于该预算,这也意味着字符串的转化可能是不完全的。
如果你可以将 s 的子字符串转化为它在 t 中对应的子字符串,则返回可以转化的最大长度。
如果 s 中没有子字符串可以转化成 t 中对应的子字符串,则返回 0。
示例 1:
java
输入:s = "abcd", t = "bcdf", maxCost = 3
输出:3
解释:s 中的 "abc" 可以变为 "bcd"。开销为 3,所以最大长度为 3。
示例 2:
java
输入:s = "abcd", t = "cdef", maxCost = 3
输出:1
解释:s 中的任一字符要想变成 t 中对应的字符,其开销都是 2。因此,最大长度为 1。
示例 3:
java
输入:s = "abcd", t = "acde", maxCost = 0
输出:1
解释:a -> a, cost = 0,字符串未发生变化,所以最大长度为 1。
提示:
1 <= s.length <= 10^5t.length == s.length0 <= maxCost <= 10^6s和t都只含小写英文字母。
假定字符串 s s s 和 t t t 的长度均为 n n n ,对任意 0 ≤ i < n 0\le i \lt n 0≤i<n ,将 s [ i ] s[i] s[i] 变为 t [ i ] t[i] t[i] 的开销是 ∣ s [ i ] − t [ i ] ∣ | s[i] - t[i] | ∣s[i]−t[i]∣ ,由此可建立一个长度为 n n n 的数组 d i f f diff diff ,其中 d i f f [ i ] = ∣ s [ i ] − t [ i ] ∣ diff[i] = | s[i] - t[i] | diff[i]=∣s[i]−t[i]∣ 。
创建数组 d i f f diff diff 后,问题转换为计算数组 d i f f diff diff 中元素和不超过 m a x C o s t maxCost maxCost 的最长子数组长度 。用前缀和+二分查找 ,或者不定长滑窗很容易解决此题。此处用滑窗更优。
解法 不定长滑窗
由于 d i f f diff diff 的的每个元素都是非负的,因此可以用滑动窗口的方法得到符合要求的最长子数组的长度。
滑动窗口的思想是,维护两个指针 l e f t left left 和 r i g h t right right 表示数组 d i f f diff diff 的子数组的开始下标和结束下标,满足子数组的元素和不超过 m a x C o s t maxCost maxCost ,子数组的长度是 r i g h t − l e f t + 1 right−left+1 right−left+1 。初始时, l e f t left left 和 r i g h t right right 的值都是 0 0 0 。
还要维护子数组的元素和 c o s t cost cost ,初值为 0 0 0 。在移动两个指针的过程中,更新 c o s t cost cost 的值,判断子数组的元素和是否大于 m a x C o s t maxCost maxCost ,并决定应该如何移动指针。
为了得到符合要求的最长子数组的长度,应遵循以下两点原则:
- 当 l e f t left left 的值固定时, r i g h t right right 的值应尽可能大;
- 当 r i g h t right right 的值固定时, l e f t left left 的值应尽可能小。
基于上述原则,滑动窗口的做法如下:
- 将 d i f f [ r i g h t ] diff[right] diff[right] 的值加到 c o s t cost cost ;
- 如果 c o s t ≤ m a x C o s t cost≤maxCost cost≤maxCost ,则子数组的元素和不超过 m a x C o s t maxCost maxCost ,使用当前子数组的长度 r i g h t − l e f t + 1 right−left+1 right−left+1 更新最大子数组的长度;
- 如果 c o s t > m a x C o s t cost>maxCost cost>maxCost ,则子数组的元素和大于 m a x C o s t maxCost maxCost ,需要向右移动指针 l e f t left left 并同时更新 c o s t cost cost 的值,直到 c o s t ≤ m a x C o s t cost≤maxCost cost≤maxCost ,此时子数组的元素和不超过 m a x C o s t maxCost maxCost ,使用子数组的长度 r i g h t − l e f t + 1 right−left+1 right−left+1 更新最大子数组的长度;
- 将指针 r i g h t right right 右移一位,重复上述步骤,直到 r i g h t right right 超出数组下标范围。
遍历结束之后,即可得到符合要求的最长子数组的长度,即字符串可以转化的最大长度。
java
class Solution {
public int equalSubstring(String s, String t, int maxCost) {
int n = s.length();
int ans = 0;
int cost = 0;
for (int l = 0, r = 0; r < n; ++r) {
cost += Math.abs(s.charAt(r) - t.charAt(r));
while (cost > maxCost) {
cost -= Math.abs(s.charAt(l) - t.charAt(l));
++l;
}
ans = Math.max(ans, r - l + 1);
}
return ans;
}
}
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( 1 ) O(1) O(1)