leetcode雕蟲小技medium 424. Longest Repeating Character Replacement

題幹:https://leetcode.com/problems/longest-repeating-character-replacement/

 

這題我感覺有點hard難度的味道。

 

先說下思路,要你找修改後最長的連同字符串,這裏面變數很多,首先,改哪些位置,第二這些位置改成啥,再然後改完後的連同子串長度是多少,全都是變數,感覺複雜超高。

但是如果我們嘗試減少一個變數,例如固定住連同子串的長度,會不會簡單點呢?我們來分析這一點,讓你通過k次修改把一個長度爲X的子串改成全同,說明什麼?這個子串中字符出現最多的那個字符至少有X-k個,這樣把剩餘k個全改掉就肯定能做到。

注意題幹是說“至多”k個,所以如果那個字符比X-k還要多,就更可以做到了。

 

這就引申出了本題的核心邏輯,找到"某段內"子串中各個字符的出現次數統計數據,進而找到最大出現次數max, 用段長-max,得到的差值如果小於等於k次,就說明可以在k次操作內完成。

更高一層邏輯,就是我們怎麼找段,難不成i, j全部遍歷一遍?且每次從頭計算這個統計數據?那這不就成了O(n^3)的時間複雜度了嗎?肯定不行!

 

於是這裏就有個取巧的方法了,其實這就有點像數組找某種規則下的最大窗口(記爲由窗口左端下標i,和窗口右端下標j),這樣的窗口當然有很多個,但是我們要找的是符合上述規則的最大窗口。

這時候就直覺地使用雙指針法了(如果你做題不夠多,或者沒聽過雙指針法,八成是不會有這種直覺的)

可是直覺可以這麼用,是不是真的可以呢?需要驗證,雙指針法就是用兩個指針分別指向窗口左端和右端,然後先嚐試移動右端,直到條件不合法,再移動左端窗口,直到窗口合法,再不斷移動右窗口,直到不合法,以此類推,直到右窗口達到數組最末。

 

這裏通常就需要窗口和數組具有如下性質:

1,右端窗口越往右擴張,就越容易不滿足條件,且一旦達到一個不滿足條件的臨界點,再往右就不可能滿足了,這時只能通過移動左端,縮小窗口,才能重新使得窗口合法

2,左窗口越往右擴展(即窗口收縮)越容易滿足條件,且一旦達到滿足條件的臨界點,再往右(只要不超過右端)移動,就都能合法。

 

我們的這題符合這兩個性質嗎?不難驗證, 符合的,所以可以大膽使用。

 

另外一點,是如何更新統計窗口內字符出現字數的數據結構?難不成每個窗口都從零算一遍?當然沒必要,因爲每次我們調整窗口只移動1格而已,所以只要基於前一次的統計數據做一次更新即可。

 

說了這麼多,上代碼:

package com.example.demo.leetcode;

import java.util.HashMap;
import java.util.Map;

public class LongestRepeatingCharacterReplacement {
    /**
     * 雙指針法測試窗口,找到符合條件的最大窗口
     * @param s
     * @param k
     * @return
     */
    public int characterReplacement(String s, int k) {
        if(s.length()<1){
            return 0;
        }

        int i=0;
        int j=0;

        Map<Character, Integer> statsMap = new HashMap<>();
        //初始化Map
        statsMap.put(s.charAt(0), 1);

        int max = Integer.MIN_VALUE;

        while(j<=s.length()-1){
            while(changeAble(i, j, k, statsMap)){
                max = Math.max(j-i+1, max);
                j++;
                if(j>s.length()-1){
                    break;
                }
                updateStatMapRightForward(statsMap, s, j);
            }
            while(!changeAble(i,j,k, statsMap)){
                i++;
                if(i>s.length()-1){
                    break;
                }
                updateStatMapLeftForward(statsMap, s, i);
            }
        }
        return max;
    }

    /**
     * 本函數爲核心邏輯,檢查從start 到end這段內, 子串的長度減去最大出現字符數之差,小於等於k, 符合則返回true
     * @param start
     * @param end
     * @param k
     * @param statsMap
     * @return
     */
    private boolean changeAble(int start, int end, int k, Map<Character, Integer> statsMap){
        int length = end-start+1;
        int maxOccur = statsMap.entrySet().stream().map(r->{return r.getValue();}).max(Integer::compare).get();
        return length-maxOccur<=k;
    }

    /**
     * 擴張窗口右端
     * @param statMap
     * @param s
     * @param index
     */
    private void updateStatMapRightForward(Map<Character, Integer> statMap, String s, int index){
        if(statMap.get(s.charAt(index))==null){
            statMap.put(s.charAt(index), 1);
        }else{
            statMap.put(s.charAt(index), statMap.get(s.charAt(index))+1);
        }
    }

    /**
     * 收縮窗口左端
     * @param statMap
     * @param s
     * @param index
     */
    private void updateStatMapLeftForward(Map<Character, Integer> statMap, String s, int index){
        statMap.put(s.charAt(index-1), statMap.get(s.charAt(index-1))-1);
    }

    public static void main(String[] args) {
        LongestRepeatingCharacterReplacement demo = new LongestRepeatingCharacterReplacement();
        String s = "AAAA";
        int k = 0;

        int ret = demo.characterReplacement(s,k);

        System.out.println(ret);
    }
}

 

邊界條件注意一下:k爲0的情況和空串的情況

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章