–the whole thinking process will be recorded

//this problem is easy to understand, but just don’t know the meaning for such a problem
//this problem is follow up of LC003 longest subsequence without repeating characters

//let’s think about this: how do we find the substring which every charater in this substring appears more or equal to k times
//first think about brute force: the total number of substring is O(n^2), and we need to check everyone of them to get the suitable, and among all suitable, we get the longest. so the total time complexity will be O(n^3)
//another thought would be DP, because we only needs to get the longest length. if dp is a possible way then how can we construct this dp, i.e. dp[i] OR dp[i][j] stands for what
//now the following method is not dp, I extract it from one of the comments in solution

class Solution {
    public int longestSubstring(String s, int k) {
        int d = Integer.MIN_VALUE;
        
        for (int numUniqueTarget = 1; numUniqueTarget <= 26; numUniqueTarget++) {
            d = Math.max(d, helper(s, k, numUniqueTarget));
            
        }
        return d;
    }
    
    private int helper(String s, int k, int numUniqueTarget) {
        int[] map = new int[128]; //why 128?because it contains every lowcase in the array so we don't have tp do map[s.charAt(i) - 'a'], instead, just use map[s.charAt(i)]
        int numUnique = 0; //count the number of unique in current substring
        int numNoLessThanK = 0; //count the number of unqiue char that has repeated >= K times
        int begin = 0; //begin pointer
        int end = 0; //end pointer
        int d = 0; //the length of "Substring with At Least K Repeating Characters" under current numUniqueTarget
        
        //the general idea of following loops: outter loop, which fixed end pointer, and the inner loop which moves begin pointer
        while (end < s.length()) {
            if (map[s.charAt(end)] == 0) { //if never see this char before
                numUnique++; //add 1
                map[s.charAt(end)]++; //update by 1
            }
            if (map[s.charAt(end)] == k) { //if this char reaches k
                numNoLessThanK++; //means that the number of char that has repeat >= k time have one more
                end++; //undate end
            }
            //now let's move start pointer
            while (numUnique > numUniqueTarget) { //while we keep numUnique larger than target, means this substring is what we want so we can keeps moving begin pointer
                if (map[s.charAt(begin)] == k) { //if this char only remains k times
                    map[s.charAt(begin)]--;//update it
                    numNoLessThanK--;
                }
                if (map[s.charAt(begin)] == 0) { //if map only left 0
                    numUnique--; //means the remaining unqiue character will minus 1
                    begin++; //and we keeps begin going
                }
                
            }
            if (numUnique == numUniqueTarget && numNoLessThanK == numUniqueTarget) { //if the remaining num of unique is exactly we want and the current NoLessThanK is exactly what we want
                    d = Math.max(d, end - begin); //then we get the subarray's length, and update d if necessary
            }
            
        }
        return d;
    }
}

//however, the above code is LTE, how? let’s go through this all over again
//this solution uses two pointer technique, the key point for such a solution is: smartly decide the condition to expand and shrink pointer begin and end.
//and those two pointers explore the string in this way:
//-- find all sub-strings which have “h=1” unique character(s) and each character in the substring repeats at least “k” times
// – find all sub-strings which have “h=2” unique character(s) and each character in the substring repeats at least “k” times
// – …
// – find all sub-strings which have “h=26” unique character(s) and each character in the substring repeats at least “k” times
// – and we’re done! at h = 26, we’re done. Take max of all the above valid substrings (by tracking with --max-- variable) – that’ll be our answer.

// Details: (for a fixed h)
// basically, we want to find a window (i to j) which has “h” unique chars and if all h occur at least K times, we have a candidate solution
// [Rules for window Expansion] so expand (j++) as long as the num of unique characters between ‘i’ to ‘j’ are h or less (unique <= h)
// during expansion, also track chars that are “noLessThanK” (which occur at least k)
// end of the loop update max (max len of valid window which have h unique chars and all h have at least k occurences)
// once we see – unique = h + 1 – , we just expanded our window with (h+1)th unique char, so we should start to shrink now.
// [Rules to window Shrink window] shrink as long as we have unique chars > h (update counts, unique, NoLessThanK along the way)

I just don’t know why we should shrink substring, don’t we want to find the longest substring? as long as numUnique > numUniqueTarget, I think we should get this length. when numUnique < numUniqueTarget, I believe it is time to expand instead of shrink?
well, above thought are jsur wrong, because we didn’t construct the whole map and then do some query and update first, instead, we update them based on the move of end and begin pointer. But that doesn’t make sense, so I’m mre confused.