一道面试高频题:最小覆盖子串的O(n)解法

求解思路

这道题我们可以把问题想象成"欠债还债"的过程:

一开始我们欠目标字符串 tar 中所有字符的债,然后用一个窗口在源字符串 str 上滑动,不断"还债"。

具体来说,我们用右指针 r 不断向右扩展窗口来收集字符还债,当债务全部还清时(也就是窗口已经包含了 tar 的所有字符),我们就尝试收缩左边界 l 来让这个窗口尽可能小,把那些"多给的"字符收回来。

在这个过程中,我们记录下遇到的最小窗口的位置和长度,最后返回这个最小窗口对应的子串就是答案。

举个栗子

假设 str = "ADOBECODEBANC", tar = "ABC":

  1. 右指针扩展到 ADOBEC 时,debt 首次归零
  2. 左指针收缩,去掉 ADO,得到 BANC
  3. 继续右移,在 CODEBA 时再次覆盖
  4. 收缩后得到 BA,但不满足(缺C)
  5. 最终确定最小窗口是 BANC

代码实现

java 复制代码
public static String minWindow(String str, String tar) {
    if (str.length() < tar.length()) {
        return "";
    }
    char[] s = str.toCharArray();
    char[] t = tar.toCharArray();
    int[] cnts = new int[256];
    
    // 初始化欠债表:tar中的字符都标记为负数(欠债)
    for (char cha : t) {
        cnts[cha]--;
    }
    
    int len = Integer.MAX_VALUE;  // 最小覆盖子串的长度
    int start = 0;                // 最小覆盖子串的起始位置
    
    for (int l = 0, r = 0, debt = t.length; r < s.length; r++) {
        // 当前字符s[r]进入窗口,还债
        if (cnts[s[r]]++ < 0) {
            debt--;  // 只有真正需要的字符才能减少债务
        }
        
        if (debt == 0) {
            // 债务还清,找到了一个覆盖子串
            // 尝试收缩左边界,去掉多余字符
            while (cnts[s[l]] > 0) {
                cnts[s[l++]]--;
            }
            
            // 更新最小窗口
            if (r - l + 1 < len) {
                len = r - l + 1;
                start = l;
            }
        }
    }
    
    return len == Integer.MAX_VALUE ? "" : str.substring(start, start + len);
}

如果觉得有帮助,欢迎点赞、关注、转发~

相关推荐
元亓亓亓3 小时前
LeetCode热题100--416. 分割等和子集--中等
算法·leetcode·职场和发展
芒克芒克9 小时前
Java集合框架总结(面试八股)
java·开发语言·面试
顽强卖力9 小时前
第二章:什么是数据分析师?
笔记·python·职场和发展·学习方法
hqyjzsb10 小时前
从爱好到专业:AI初学者如何跨越CAIE认证的理想与现实鸿沟
大数据·c语言·人工智能·信息可视化·职场和发展·excel·业界资讯
历程里程碑10 小时前
滑动窗口解法:无重复字符最长子串
数据结构·c++·算法·leetcode·职场和发展·eclipse·哈希算法
白兰地空瓶11 小时前
告别 add(1, 2)!通过 JS 柯里化,让你的代码更加优雅
javascript·面试
小兵张健11 小时前
森果云面试经历
面试
youngee1111 小时前
hot100-55有效的括号
算法·leetcode·职场和发展
零雲12 小时前
Java面试:@Component和@Bean的区别是什么
java·开发语言·面试
我命由我1234512 小时前
Photoshop - Photoshop 工具栏(46)渐变工具
经验分享·笔记·学习·ui·职场和发展·学习方法·photoshop