探索“最小覆盖子串”问题的高效解决方案
摘要
在本文中,我们将探讨解决“最小覆盖子串”问题的高效算法和编程语言实现。这个问题要求我们找到一个字符串 s
中能够覆盖字符串 t
中所有字符的最小子串。我们将分析几种不同的算法,并提供 Python、Java 和 C++ 的实现。
1. 问题描述
给定两个字符串 s
和 t
,返回 s
中能够覆盖 t
中所有字符的最小子串。如果不存在这样的子串,则返回空字符串。
2. 输入和输出格式
- 输入:字符串
s
和字符串t
- 输出:能够覆盖
t
的s
中的最小子串
3. 示例
- 示例 1:
s = "ADOBECODEBANC"
,t = "ABC"
输出:"BANC"
- 示例 2:
s = "a"
,t = "a"
输出:"a"
- 示例 3:
s = "a"
,t = "aa"
输出:""
4. 约束条件
1 <= m, n <= 10^5
s
和t
由英文字母组成
5. 算法分析
- 滑动窗口 + 字符计数:使用滑动窗口遍历
s
,同时使用哈希表记录t
中字符的出现次数。在滑动窗口内,当所有字符出现次数满足条件时,尝试缩小窗口以找到最小覆盖子串。
6. 多语言实现
我们将使用 Python、Java 和 C++ 来实现滑动窗口 + 字符计数法。
Python 实现
def minWindow(s, t):
from collections import defaultdict
need = defaultdict(int)
have = defaultdict(int)
for char in t:
need[char] += 1
left, right = 0, 0
formed = 0
ans = float('inf')
ans_window = ""
while right < len(s):
char = s[right]
have[char] += 1
if char in need and have[char] <= need[char]:
formed += 1
while formed == len(t):
if right - left + 1 < ans:
ans = right - left + 1
ans_window = s[left:right+1]
char = s[left]
have[char] -= 1
if char in need and have[char] < need[char]:
formed -= 1
left += 1
right += 1
return ans_window if ans != float('inf') else ""
# 示例
print(minWindow("ADOBECODEBANC", "ABC")) # 输出:"BANC"
print(minWindow("a", "a")) # 输出:"a"
print(minWindow("a", "aa")) # 输出:""
Java 实现
import java.util.Map;
import java.util.HashMap;
public String minWindow(String s, String t) {
Map<Character, Integer> need = new HashMap<>();
for (char c : t.toCharArray()) {
need.put(c, need.getOrDefault(c, 0) + 1);
}
int left = 0, right = 0, formed = 0, ans = Integer.MAX_VALUE;
String ansWindow = "";
Map<Character, Integer> have = new HashMap<>();
while (right < s.length()) {
char c = s.charAt(right);
have.put(c, have.getOrDefault(c, 0) + 1);
if (need.containsKey(c) && have.get(c).compareTo(need.get(c)) >= 0) {
formed++;
}
while (formed == need.size()) {
if (right - left + 1 < ans) {
ans = right - left + 1;
ansWindow = s.substring(left, right + 1);
}
c = s.charAt(left);
have.put(c, have.get(c) - 1);
if (need.containsKey(c) && have.get(c) < need.get(c)) {
formed--;
}
left++;
}
right++;
}
return ansWindow;
}
// 示例
System.out.println(minWindow("ADOBECODEBANC", "ABC")); // 输出:"BANC"
System.out.println(minWindow("a", "a")); // 输出:"a"
System.out.println(minWindow("a", "aa")); // 输出:""
C++ 实现
#include <string>
#include <unordered_map>
#include <algorithm>
std::string minWindow(std::string s, std::string t) {
std::unordered_map<char, int> need, have;
for (char c : t) {
need[c]++;
}
int left = 0, right = 0, formed = 0, ans = INT_MAX;
std::string ansWindow = "";
while (right < s.length()) {
char c = s[right];
have[c]++;
if (need.find(c) != need.end() && have[c] <= need[c]) {
formed++;
}
while (formed == need.size()) {
if (right - left + 1 < ans) {
ans = right - left + 1;
ansWindow = s.substr(left, right - left + 1);
}
c = s[left];
have[c]--;
if (need.find(c) != need.end() && have[c] < need[c]) {
formed--;
}
left++;
}
right++;
}
return ansWindow;
}
// 示例
std::cout << minWindow("ADOBECODEBANC", "ABC") << std::endl; // 输出:"BANC"
std::cout << minWindow("a", "a") << std::endl; // 输出:"a"
std::cout << minWindow("a", "aa") << std::endl; // 输出:""
7. 测试用例
- 测试用例 1:
s = "ADOBECODEBANC"
,t = "ABC"
- 测试用例 2:
s = "a"
,t = "a"
- 测试用例 3:
s = "a"
,t = "aa"
8. 测试结果
- 所有测试用例在所有语言实现中都应该返回正确的结果。
9. 总结
通过实现和比较不同语言的算法,我们可以看到滑动窗口加字符计数法是解决“最小覆盖子串”问题的有效方法。这种方法的时间复杂度为 O(m+n),空间复杂度为 O(1),适合处理大规模数据集。
10. 参考文献
11. 相关标签
- 算法
- 字符串
- 滑动窗口
- 字符计数
12. 相关企业
- Amazon
- Microsoft
13. 提示
- 在实现算法时,注意滑动窗口的更新策略和字符计数的准确性。
- 考虑使用标准库中的集合和字符串操作来简化代码。