Longest Balanced Substring II

Medium
Watch on YouTube ↗

Solution

class Solution {
    public int longestBalanced(String s) {
        int n = s.length();
        int maxlen = 0;

        // Case 1: Only one distinct character
        // For a balanced substring with one char, any length works (all chars are equal)
        // Time Complexity: O(n)
        // Space Complexity: O(1)
        for(int i=0; i<s.length(); ) {
            char ch = s.charAt(i);
            int len = 0;
            // Count consecutive occurrences of the same character
            while(i<n && ch==s.charAt(i)) {
                i++;
                len++;
            }
            maxlen = Math.max(maxlen, len);
        }

        // Case 2: Two distinct characters in the string
        // Try skipping each character to consider pairs: (b,c), (a,c), (a,b)
        // Time Complexity: O(n) for each call, O(3n) = O(n) total
        maxlen = Math.max(maxlen, getLength(s, 'a')); // Consider 'b' and 'c'
        maxlen = Math.max(maxlen, getLength(s, 'b')); // Consider 'a' and 'c'
        maxlen = Math.max(maxlen, getLength(s, 'c')); // Consider 'a' and 'b'

        // Case 3: All three distinct characters are present
        // For balanced substring: count_a == count_b == count_c
        // Equivalent to: (a-b) == 0 AND (a-c) == 0
        // We track differences and find when the same state repeats
        // Time Complexity: O(n)
        // Space Complexity: O(n) for HashMap
        HashMap<String, Integer> prev = new HashMap<>();
        int c1=0, c2=0, c3=0;
        // Initialize with base state: all counts are 0 at index -1
        prev.put("0#0", -1);
        // Example: "abc"
        for(int i=0; i<n; i++) {
            char ch = s.charAt(i);
            // Increment counters for each character
            if(ch=='a')
                c1++;
            else if(ch=='b')
                c2++;
            else c3++;
            
            // Calculate differences to track balance
            int diff1 = c1-c2; // Difference between 'a' and 'b' counts
            int diff2 = c1-c3; // Difference between 'a' and 'c' counts
            String key = diff1 + "#" + diff2;
            
            // If we've seen this state before, substring from that point to now is balanced
            if(prev.containsKey(key)) {
                maxlen = Math.max(maxlen, i-prev.get(key));
            } else {
                // Store first occurrence of this state
                prev.put(key, i);
            }
        }

        return maxlen;

    }

    int getLength(String s, char skip) {
        int mlen = 0;
        // a --> consider b, c
        // b --> consider a, c
        // c --> consider a, b
        char first = (skip=='a') ? 'b' : 'a';
        char second = (skip=='c') ? 'b' : 'c';
        int i=0, n = s.length();

        // Process string in segments, separated by the skip character
        while( i < n) {
            int c1 = 0, c2 = 0; // Counters for first and second characters
            
            // HashMap stores difference -> first occurrence index
            // For balanced substring: c1 == c2, so c1-c2 == 0
            HashMap<Integer, Integer> prev = new HashMap<>();
            prev.put(0, i-1); // Base case: difference 0 before current segment
            
            // Process continuous segment without skip character
            // Example: "ababcababc" with skip='c'
            while( i < n && s.charAt(i)!=skip) {
                char ch = s.charAt(i);
                // Update counters
                if(ch==first)
                    c1++;
                else c2++;
                
                // Check if we've seen this difference before
                if(prev.containsKey(c1-c2)) {
                    // Substring from prev index to current is balanced
                    mlen = Math.max(mlen, i-prev.get(c1-c2));
                } 
                else prev.put(c1-c2, i); // Store first occurrence of this difference
                i++;
            }
            i++; // Skip the 'skip' character
            
        }

        return mlen;
    }
}