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 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 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 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 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的情况和空串的情况

 

你可能感兴趣的:(leetcode,算法,刷题)