开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第15天,点击查看活动详情。
最小覆盖子串
描述
给你一个字符串 S、一个字符串 T 。请你设计一种算法,可以在 O(n) 的时间复杂度内, 从字符串 S 里面找出:包含 T 所有字符的最小子串。
难度
困难
示例
输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"
思路
使用滑动窗口算法。定义两个指针 left 和 right,先不断的右移 right,直到 S[left, right] 包含了 T 中的所有字符,然后 left 右移,记录 S[left, right] 的长度为 minWindowLen,记录 left, right 的下标分别为 l, r,当 S[left, right] 中不包含 T 中任意一个字符时 right 右移一位继续循环。
如何判断 S[left, right] 中包含了 T 中的所有字符?
创建两个数组,一个用来存储 T 中所有字符以及对应的次数,另一个用来存储 S 每次滑动时每个字符出现的次数,创建一个变量 count 用来记录 S[left, right] 中包含了 T 中的字符个数,当 count == T.length 时,表示S[left, right] 中包含了 T 中的所有字符。
代码
Rust
pub struct Solution {}
impl Solution {
pub fn min_window(s: String, t: String) -> String {
let s_bytes = s.as_bytes();
let t_bytes = t.as_bytes();
if t_bytes == "".as_bytes() {
return String::new();
}
// 存储字符和对应出现的次数, ascii 码有 127 个字符
let (mut s_map, mut t_map) = ([0; 128], [0; 128]);
for i in 0..t_bytes.len() {
t_map[t_bytes[i] as usize] += 1;
}
// 记录返回结果的左右下标
let (mut l, mut r) = (-1, -1);
let mut min_window_len = i32::MAX;
// 记录每次滑动包含的字符个数
let mut count = 0;
let (mut left, mut right) = (0, 0);
while right < s_bytes.len() {
s_map[s_bytes[right] as usize] += 1;
// 包含一个字符
if t_map[s_bytes[right] as usize] >= s_map[s_bytes[right] as usize] {
count += 1;
}
while count == t.len() {
// 减少一个字符个数
if t_map[s_bytes[left] as usize] >= s_map[s_bytes[left] as usize] {
count -= 1;
}
// 重新给左右下标赋值
if right - left + 1 < min_window_len as usize {
min_window_len = right as i32 - left as i32 + 1;
l = left as i32;
r = right as i32 + 1;
}
// 移除字符出现的次数
s_map[s_bytes[left] as usize] -= 1;
left += 1;
}
right += 1;
}
if l == -1 {
return "".to_string();
}
return s[l as usize..r as usize].to_string();
}
}
#[test]
fn test_min_window() {
let s = "ADOBECODEBANC".to_string();
let t = "ABC".to_string();
println!("s={}, t={}", s, t);
let r = Solution::min_window(s, t);
println!("{}", r);
}
运行结果:
s=ADOBECODEBANC, t=ABC
BANC
Go
func minWindow(s string, t string) string {
if t == "" {
return ""
}
// 存储字符和对应出现的次数, ascii 码有 127 个字符
sMap, tMap := [128]int{}, [128]int{}
for i := 0; i < len(t); i++ {
tMap[t[i]]++
}
// 记录返回结果的左右下标
l, r := -1, -1
minWindowLen := math.MaxInt32
// 记录每次滑动包含的字符个数
count := 0
// 左指针
left := 0
// 右指针
right := 0
for right < len(s) {
sMap[s[right]]++
// 包含一个字符
if tMap[s[right]] >= sMap[s[right]] {
count++
}
for count == len(t) {
// 减少一个字符个数
if tMap[s[left]] >= sMap[s[left]] {
count--
}
// 重新给左右下标赋值
if right - left + 1 < minWindowLen {
minWindowLen = right - left + 1
l = left
r = right + 1
}
// 移除字符出现的次数
sMap[s[left]]--
left++
}
right++
}
if l == -1 {
return ""
}
return s[l:r]
}
func TestMinWindow(t *testing.T) {
s := "ADOBECODEBANC"
_t := "ABC"
r := minWindow(s, _t)
t.Logf("s=%s, t=%s\n", s, _t)
t.Log(r)
}
运行结果:
s=ADOBECODEBANC, t=ABC
BANC
Java
public class Main {
public static String minWindow(String s, String t) {
if (t == null || t.equals("")) {
return "";
}
// 存储字符和对应出现的次数, ascii 码有 127 个字符
int[] sMap = new int[128], tMap = new int[128];
for (int i = 0; i < t.length(); i++) {
tMap[t.charAt(i)]++;
}
// 记录返回结果的左右下标
int l = -1, r = -1;
int minWindowLen = Integer.MAX_VALUE;
// 记录每次滑动包含的字符个数
int count = 0;
// 左指针
int left = 0;
// 右指针
int right = 0;
while (right < s.length()) {
char c = s.charAt(right);
sMap[c]++;
// 包含一个字符
if (tMap[c] >= sMap[c]) {
count++;
}
while (count == t.length()) {
c = s.charAt(left);
// 减少一个字符个数
if (tMap[c] >= sMap[c]) {
count--;
}
// 重新给左右下标赋值
if (right - left + 1 < minWindowLen) {
minWindowLen = right - left + 1;
l = left;
r = right + 1;
}
// 移除字符出现的次数
sMap[c]--;
left++;
}
right++;
}
if (l == -1) {
return "";
}
return s.substring(l, r);
}
public static void main(String[] args) {
String s = "ADOBECODEBANC";
String t = "ABC";
String r = minWindow(s, t);
System.out.printf("s=%s, t=%s\n", s, t);
System.out.println(r);
}
}
运行结果:
s=ADOBECODEBANC, t=ABC
BANC