• 力扣经典题目解析--最小覆盖子串


    原题地址: . - 力扣(LeetCode)

    给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。

    注意:

    • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
    • 如果 s 中存在这样的子串,我们保证它是唯一的答案。

    示例 1:

    输入:s = "ADOBECODEBANC", t = "ABC"
    输出:"BANC"
    解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。
    

    示例 2:

    输入:s = "a", t = "a"
    输出:"a"
    解释:整个字符串 s 是最小覆盖子串。
    

    示例 3:

    输入: s = "a", t = "aa"
    输出: ""
    解释: t 中两个字符 'a' 均应包含在 s 的子串中,
    因此没有符合条件的子字符串,返回空字符串。

    暴力法

    暴力法就是遍历所有子串,然后统计子串中每个字符出现的次数,然后跟t中每个字符出现的次数做比较,如果包含t中所有字符并且字符出现的次数大于等于t,则说明该子串符合条件,找出所有满足条件的子串,通过比较得到最小子串

    1. public String minWindow(String s, String t) {
    2. // 保存自小子串
    3. String result = "";
    4. // 统计t中字符串数量
    5. Map tCharStatis = statis(t);
    6. // 遍历s
    7. for (int i = 0; i < s.length(); i++) {
    8. // 遍历子串,子串范围为[i,j)
    9. for (int j = i + t.length(); j <= s.length(); j++) {
    10. String subStr = s.substring(i, j);
    11. Map subCharStatis = statis(subStr);
    12. // 如果子串包含t,则判断是否为最小子串
    13. if (check(subCharStatis, tCharStatis)) {
    14. if ("".equals(result) || subStr.length() < result.length()) {
    15. result = subStr;
    16. }
    17. }
    18. }
    19. }
    20. return result;
    21. }
    22. // 统计字符串中单个字符数量
    23. private Map statis(String str) {
    24. Map map = new HashMap<>();
    25. for (int i = 0; i < str.length(); i++) {
    26. int count = map.getOrDefault(str.charAt(i), 0);
    27. count++;
    28. map.put(str.charAt(i), count);
    29. }
    30. return map;
    31. }
    32. // 判断子串是否包含t
    33. private boolean check(Map subStatis, Map tStatis) {
    34. for (Character c : tStatis.keySet()) {
    35. int subCount = subStatis.getOrDefault(c, 0);
    36. if (subCount < tStatis.get(c)) return false;
    37. }
    38. return true;
    39. }

    效果:

    代码没问题,但是时间复杂度太高,没法满足需求 

    滑动窗口

    暴力法的缺点是显而易见的:时间复杂度过大,超出了运行时间限制。在哪些方面可以优化呢?

    仔细观察可以发现,我们在暴力求解的时候,做了很多无用的比对:对于字符串“ADOBECODEBANC”,当找到一个符合条件的子串“ADOBEC”后,我们会继续仍以“A”作为起点扩展这个子串,得到一个符合条件的“ADOBECO”——它肯定符合条件,也肯定比之前的子串长,这其实是完全不必要的。

    代码实现上,我们可以定义两个指针:指向子串“起始点”的左指针,和指向子串“结束点”的右指针。它们一个固定、另一个移动,彼此交替向右移动,就好像开了一个大小可变的窗口、在不停向右滑动一样,所以这就是非常经典的滑动窗口解决问题的应用场景。所以有时候,滑动窗口也可以归类到双指针法。

    1. public String minWindow1(String s, String t) {
    2. // 保存自小子串
    3. String result = "";
    4. // 统计t中字符串数量
    5. Map tCharStatis = statis(t);
    6. int lp = 0;
    7. int rp = t.length();
    8. while (rp <= s.length()) {
    9. String subStr = s.substring(lp, rp);
    10. Map subCharStatis = statis(subStr);
    11. // 子串符合条件,则左指针右移
    12. if (check(subCharStatis, tCharStatis)) {
    13. if ("".equals(result) || subStr.length() < result.length()) {
    14. result = subStr;
    15. }
    16. lp++;
    17. } else {
    18. //不符合条件则右指针右移继续寻找
    19. rp++;
    20. }
    21. }
    22. return result;
    23. }
    24. // 统计字符串中单个字符数量
    25. private Map statis(String str) {
    26. Map map = new HashMap<>();
    27. for (int i = 0; i < str.length(); i++) {
    28. int count = map.getOrDefault(str.charAt(i), 0);
    29. count++;
    30. map.put(str.charAt(i), count);
    31. }
    32. return map;
    33. }
    34. // 判断子串是否包含t
    35. private boolean check(Map subStatis, Map tStatis) {
    36. for (Character c : tStatis.keySet()) {
    37. int subCount = subStatis.getOrDefault(c, 0);
    38. if (subCount < tStatis.get(c)) return false;
    39. }
    40. return true;
    41. }

    效果:

    比之前有进步,但是还需进一步优化 

    滑动窗口优化

    我们判断S是否满足包含T中所有字符的时候,调用的方法check其实又是一个暴力法:遍历T中所有字符频次,一一比对。上面的复杂度分析也可以看出,遍历s只用了线性时间,但每次都要遍历一遍T的频次哈希表,这就耗费了大量时间。

    我们已经知道,每次指针的移动,只涉及到一个字符的增减。所以我们其实不需要知道完整的频次HashMap,只要获取改变的这个字符的频次,然后再和T中的频次比较,就可以知道新子串是否符合要求了。

    1. public String minWindow(String s, String t) {
    2. // 保存最小子串
    3. String result = "";
    4. // 统计t中字符串数量
    5. Map tCharStatis = statis(t);
    6. Map subCharStatis = new HashMap<>();
    7. int lp = 0;
    8. int rp = 1;
    9. while (rp <= s.length()) {
    10. char newChar = s.charAt(rp - 1);
    11. // t中包含该字符再统计
    12. if (tCharStatis.containsKey(newChar)) {
    13. int count = subCharStatis.getOrDefault(newChar, 0);
    14. subCharStatis.put(newChar, count + 1);
    15. }
    16. // 子串符合条件,则左指针右移
    17. while (check(subCharStatis, tCharStatis) && lp < rp) {
    18. if ("".equals(result) || rp - lp < result.length()) {
    19. result = s.substring(lp, rp);
    20. }
    21. char removedChar = s.charAt(lp);
    22. if (tCharStatis.containsKey(removedChar)) {
    23. subCharStatis.put(removedChar, subCharStatis.getOrDefault(removedChar, 0) - 1);
    24. }
    25. lp++;
    26. }
    27. rp++;
    28. }
    29. return result;
    30. }
    31. // 统计字符串中单个字符数量
    32. private Map statis(String str) {
    33. Map map = new HashMap<>();
    34. for (int i = 0; i < str.length(); i++) {
    35. int count = map.getOrDefault(str.charAt(i), 0);
    36. count++;
    37. map.put(str.charAt(i), count);
    38. }
    39. return map;
    40. }
    41. // 判断子串是否包含t
    42. private boolean check(Map subStatis, Map tStatis) {
    43. for (Character c : tStatis.keySet()) {
    44. int subCount = subStatis.getOrDefault(c, 0);
    45. if (subCount < tStatis.get(c)) return false;
    46. }
    47. return true;
    48. }

  • 相关阅读:
    让企业纷纷从CRM系统转至智能名片,它究竟“智能”在哪些地方?
    从指定 URL 读取图像并以 OpenCV 格式返回的函数(从指定 URL 读取图像并使其可由 OpenCV 处理。)
    重学Spring总结
    MySQL学习笔记(一 mysql简介)
    基于微信理发预约小程序系统设计与实现 开题报告
    c# 学习笔记 PropertyChangedEventHandler、 =>、DependencyObject、DataContext
    网约车市场重构进行时,高端出行能否撬动大格局?
    开源时代:刘韧对话任旭东崔宝秋章文嵩蒋涛
    Windows下安装Anaconda、Pycharm以及iflycode插件图解
    【OpenCV】-边缘检测汇总示例
  • 原文地址:https://blog.csdn.net/ting4937/article/details/136447021