76. 最小覆盖子串

滑动窗口

import java.util.Arrays;

class Solution {
    public String minWindow(String s, String t) {

        if (s.length() < t.length()){
            return "";
        }
        /**
         * 滑动窗口
         * 用哈希表数组记录t中每个字符在s中是否出现
         * 右指针right一直右移,直到满足条件,然后记录下此时区间的边界,如果长度更小则更新l和r
         */
        int[] tCount = new int[128];
        int right = -1;
        int l = 0;
        int r = -1;

        for (int i = 0; i < t.length(); i++) {
            tCount[t.charAt(i)]++;
        }

        /**
         * 左指针i从0开始,只有当区间包含所有t的字符后才会移动
         */
        for (int i = 0; i < s.length() - t.length() + 1; i++) {

            /**
             * 只要区间没有完全包含t的元素,右指针right就不断移动
             * 同时right不能越界
             * 每次循环都要找出数组的最大值,时间复杂度为O(n^2)
             */
            while (Arrays.stream(tCount).max().getAsInt() > 0 && right < s.length() - 1){

                right++;
                tCount[s.charAt(right)]--;
            }

            /**
             * 只有当包含t的所有字符且区间长度更小且r < l时才会更新
             */
            if (Arrays.stream(tCount).max().getAsInt() == 0) {

                if (r < l) {

                    r = right;
                    l = i;
                }
                else if (r - l + 1 > right - i + 1){

                    r = right;
                    l = i;
                }
            }

            /**
             * 在左指针移动之前,要抛弃掉当前字符
             */
            tCount[s.charAt(i)]++;
        }

        return s.substring(l, r + 1);
    }
}

/**
 * 时间复杂度 O(n^2)
 * 空间复杂度 O(n)
 */

优化1——不用每次都找出数组最大值,同时简化判断条件

import java.util.Arrays;

class Solution {
    public String minWindow(String s, String t) {

        if (s.length() < t.length()){
            return "";
        }
        /**
         * 滑动窗口
         * 用哈希表数组记录t中每个字符在s中是否出现
         * 右指针right一直右移,直到满足条件,然后记录下此时区间的边界,如果长度更小则更新l和r
         */
        int[] tCount = new int[128];
        int num = t.length();
        int right = -1;
        int l = 0;
        int r = -1;

        for (int i = 0; i < t.length(); i++) {
            tCount[t.charAt(i)]++;
        }

        /**
         * 左指针i从0开始,只有当区间包含所有t的字符后才会移动
         */
        for (int i = 0; i < s.length() - t.length() + 1; i++) {

            /**
             * 只要区间没有完全包含t的元素,右指针right就不断移动
             * 同时right不能越界
             * 使用一个变量num同步记录t中字符出现的次数,当次数大于0时才减1,这样就不用每次都比较tCount的最大值了
             */
            while (num > 0 && right < s.length() - 1){

                right++;

                if (tCount[s.charAt(right)] > 0) {
                    num--;
                }

                tCount[s.charAt(right)]--;
            }

            /**
             * 只有当包含t的所有字符且区间长度更小且r < l时才会更新
             */
            if (num == 0 && (r < l || r - l + 1 > right - i + 1)) {
                
                r = right;
                l = i;
            }

            /**
             * 在左指针移动之前,要抛弃掉当前字符,同时维护num
             * 如果这个字符的次数是负数,说明其不在t中,num就不用更新
             */
            if (tCount[s.charAt(i)] == 0) {
                num++;
            }

            tCount[s.charAt(i)]++;
        }

        return s.substring(l, r + 1);
    }
}

/**
 * 时间复杂度 O(n)
 * 空间复杂度 O(n)
 */

优化2——简化边界判断条件

import java.util.Arrays;

class Solution {
    public String minWindow(String s, String t) {

        /**
         * 哈希表存储t中所有字符出现的次数
         */
        int[] arr = new int[128];

        for (int i = 0; i < t.length(); i++) {
            arr[t.charAt(i)]++;
        }

        /**
         * 统计总次数,以此判断是否子串包含了所有t的字符
         */
        int sum = Arrays.stream(arr).sum();

        /**
         * s中的字符个数可能会大于t,当出现多个相同元素时,记录真实的个数信息
         */
        int[] real = Arrays.copyOf(arr, arr.length);

        int left = 0;
        int right = 0;
        String result = s + t;

        while (right <= s.length() - 1){

            /**
             * 当s中存在t中的字符时,该字符次数减1,总次数减1
             */
            if (arr[s.charAt(right)] > 0){

                arr[s.charAt(right)]--;
                real[s.charAt(right)]--;
                sum--;
                right++;
            }
            else {

                /**
                 * 当该字符不在t中或者s中个数大于t中的个数时,单独记录下真实的次数信息
                 */
                real[s.charAt(right)]--;
                right++;
            }

            /**
             * 当num == 0说明t中所有字符均在子串中
             */
            if (sum == 0){

                /**
                 * 为了尽可能取到最短的子串,尝试将left右移
                 * 如果real[s.charAt(left)] < 0,说明这个字符不在t中,或者是多余的,就可以略过
                 */
                while (real[s.charAt(left)] < 0){

                    real[s.charAt(left)]++;
                    left++;
                }

                /**
                 * 保留最短的子串
                 */
                String res = s.substring(left, right);

                if (res.length() < result.length()){
                    result = res;
                }

                /**
                 * 处理完当前区间后,left右移一位继续判断,同时当前的left位置的字符次数和总字符次数要加1
                 */
                arr[s.charAt(left)]++;
                real[s.charAt(left)]++;
                left++;
                sum++;
            }
        }

        /**
         * 如果不存在这样的子串,那么result不会发生变化
         */
        return result.equals(s + t) ? "" : result;
    }
}

/**
 * 时间复杂度 O(n)
 * 空间复杂度 O(n)
 */

优化3——不使用额外数组

class Solution {
    public String minWindow(String s, String t) {
        int[] hash = new int[128];
        for (int i = 0; i < t.length(); i++) {
            hash[t.charAt(i)]++;
        }
        int count = t.length();
        int slow = 0;
        int fast = 0;
        String min = s + " ";
        while (fast < s.length()) {
            char word = s.charAt(fast);
            if (hash[word] > 0) {
                count--;
            }
            hash[word]--;
            if (count == 0) {
                while (hash[s.charAt(slow)] < 0) {
                    hash[s.charAt(slow)]++;
                    slow++;
                }
                if (min.length() > fast - slow + 1) {
                    min = s.substring(slow, fast + 1);
                }
                hash[s.charAt(slow)]++;
                slow++;
                count++;
            }
            fast++;
        }
        return min.length() == s.length() + 1 ? "" : min;
    }
}

/**
 * 时间复杂度 O(n)
 * 空间复杂度 O(n)
 */

https://leetcode-cn.com/problems/minimum-window-substring/

posted @ 2021-11-29 11:38  振袖秋枫问红叶  阅读(39)  评论(0)    收藏  举报