【LeetCode】最小覆盖子串

88 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第15天,点击查看活动详情

最小覆盖子串

原题:leetcode-cn.com/problems/mi…

描述

给你一个字符串 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