04.子串,啟動!

直来直往1發表於2024-07-07

子串

  • 子串要求連續,與子序列不同,子序列不要求連續。

和為k的字串

給你一個整數陣列 nums 和一個整數 k ,請你統計並返回 該陣列中和為 k 的子陣列的個數。
子陣列是陣列中元素的連續非空序列

  • 方法
    https://www.bilibili.com/video/BV1gN411E7Zx/?spm_id_from=333.337.search-card.all.click&vd_source=c1e82f2c861119afab65688242bdf6f3
    1.求出字首和陣列sums[i],sum[i] = sums[i-1]+nums[i]
    2.若nums[i]=10,k為6,且字首和中出現了2次4,則可以確定的是,每一個字首和為4的元素之後起到i的和即為我們想要的子串
    3.藉助雜湊表,鍵為各個字首和,值為當前該字首和出現的次數
class Solution {
    public int subarraySum(int[] nums, int k) {
        int n = nums.length;
        int[] sums = new int[n];
        sums[0] = nums[0];
        for (int i = 1; i < n; i++) {
            sums[i] = sums[i-1] + nums[i];
        }
        HashMap<Integer, Integer> map= new HashMap<>();
        map.put(0,1);
        int count = 0;
        for (int i = 0; i < n; i++) {
            count+=map.getOrDefault(sums[i]-k,0);//map.getOrDefault若查詢失敗會返回預設值而不是null
            map.put(sums[i],map.getOrDefault(sums[i],0)+1);
        }
        return count;
    }
}

滑動視窗最大值

  • 方法
    1.維護一個單調遞減棧,即棧底為當前棧內最大的元素,且保證棧內的元素一定在當前遍歷的視窗內;
    2.視窗每向後移動一次,首先要維護棧內的遞減性,然後還要將左側超出視窗外的元素在棧中移除。
    將視窗外的元素移除,分為兩種情況:
    a.要移除的是當前最大值,則我們要pollLast(),移除棧底
    b.要移除的不是當前最大值,說明在之前已經被彈出過。
    換句話說,我們可以判斷當前棧底元素下標是否在視窗內(deque.peekLast() <= (i-k)),若在則deque.pollLast(),否則直接跳過。
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        if (n == 0) {
            return new int[0];
        }
        int[] res = new int[n - k + 1];
        Deque<Integer> deque = new LinkedList<>();
        for (int i = 0; i < n; i++) {
            //保證棧內從棧頂到棧底為遞增的(first為棧頂)
            //peek == peekFirst
            while (!deque.isEmpty() && nums[i] > nums[deque.peek()]) {
                deque.pop();//pop == pollFirst
            }
            deque.push(i); //push == addFirst
            if(i>=k){
                //移除視窗外的元素
                if(deque.peekLast() <= (i-k)){
                    deque.pollLast();
                }
            }
            if (i >= k - 1) {
                //記錄結果
                res[i - k + 1] = nums[deque.peekLast()];
            }
        }
        return res;

    }
}

最小覆蓋子串

給你一個字串 s 、一個字串 t 。返回 s 中涵蓋 t 所有字元的最小子串。如果 s 中不存在涵蓋 t 所有字元的子串,則返回空字串 "" 。
注意:
對於 t 中重複字元,我們尋找的子字串中該字元數量必須不少於 t 中該字元數量。
如果 s 中存在這樣的子串,我們保證它是唯一的答案。

  • 分析
    前面已經做過幾道關於異位詞的題目,若只關心字串的原材料,我們可以利用一個陣列來描述一個字串的資訊。
    此處若s 中的子串涵蓋 t 所有字元,則該子串的每個原材料數量一定不小於t。
  • 方法
    維護一個視窗,右邊界一直向後擴張,擴張到子串的每個原材料數量都不小於t,即包含t,然後再從左側縮小,每縮小一次就判斷子串是否仍然包含t,直至縮小至不包含,為避免後續有更短的包含t的子串,要繼續向後擴張至包含t,再從左側縮小,在這個同時要維護一對最終結果的左右邊界,直至左邊界或右邊界越界
class Solution {
    public String minWindow(String s, String t) {
        int[] count_t = count(t);
        int[] count_s = new int[128];
        int l = 0;
        int r = -1;
        int l_res = 0;
        int r_res = Integer.MAX_VALUE;
        while(l < s.length()){
            if(has(count_s,count_t)){
                if(r-l < r_res-l_res){
                    l_res = l;
                    r_res = r;
                }
                count_s[s.charAt(l)]--;
                l++;
            }else {
                if(r==s.length()-1){
                    break;
                }
                r++;
                count_s[s.charAt(r)]++;
            }
        }
        return r_res==Integer.MAX_VALUE ? "" : s.substring(l_res,r_res+1);
    }
    public int[] count(String s){
        int[] counts = new int[128];
        char[] chars = s.toCharArray();
        for (char c: chars) {
            counts[c]++;
        }
        return counts;
    }
    public boolean has(int[] s,int[] t){
        for (int i = 0; i < 128; i++) {
            if(s[i]<t[i]){
                return false;
            }
        }
        return true;
    }
}

相關文章