Take K of Each Character From Left and Right
MedPrompt
You are given a string s consisting of the characters 'a', 'b', and 'c' and a non-negative integer k. Each minute, you may take either the leftmost character of s, or the rightmost character of s.
Return the minimum number of minutes needed for you to take at least k of each character, or return -1 if it is not possible to take k of each character.
Example 1:
Input: s = "aabaaaacaabc", k = 2
Output: 8
Explanation:
Take three characters from the left of s. You now have two 'a' characters, and one 'b' character.
Take five characters from the right of s. You now have four 'a' characters, two 'b' characters, and two 'c' characters.
A total of 3 + 5 = 8 minutes is needed.
It can be proven that 8 is the minimum number of minutes needed.Example 2:
Input: s = "a", k = 1
Output: -1
Explanation: It is not possible to take one 'b' or 'c' so return -1.
Constraints:
1 <= s.length <= 105sconsists of only the letters'a','b', and'c'.0 <= k <= s.length
Approaches
3 approaches with complexity analysis and trade-offs.
This approach directly translates the problem statement into a solution by checking every possible combination of characters taken from the left and right. We iterate through every possible prefix length and, for each, find the shortest suffix that, when combined with the prefix, satisfies the condition of having at least k of each character.
Algorithm
- First, handle the edge cases. If the total count of any character in
sis less thank, it's impossible, so return -1. Ifkis 0, no characters need to be taken, so return 0. - Initialize
minMinutesto the length of the stringn, which is the maximum possible answer. - Iterate through all possible prefix lengths
ifrom 0 ton. - For each
i, calculate the counts of 'a', 'b', and 'c' in the prefixs[0...i-1]. - Then, for the current prefix, iterate through all possible suffix lengths
jfrom 0 ton-i. - In this inner loop, incrementally build the suffix counts. For each
j, add the characters[n-j]to the suffix counts. - Combine the prefix and suffix counts. Check if the counts of 'a', 'b', and 'c' are all at least
k. - If the condition is met, we have found a valid combination of
icharacters from the left andjfrom the right. The total minutes arei + j. We updateminMinutes = min(minMinutes, i + j). - Since we are iterating
jfrom smallest to largest, the firstjthat satisfies the condition for a giveniwill be the smallest. We canbreakthe inner loop to optimize slightly. - After checking all
i,minMinuteswill hold the minimum total characters needed.
Walkthrough
The brute-force method involves a nested loop structure. The outer loop iterates through all possible numbers of characters to take from the left, let's say i. The inner loop iterates through all possible numbers of characters to take from the right, j. For each pair (i, j), we must ensure we don't take more characters than available (i + j <= n). We then count the characters in the chosen prefix and suffix and check if they meet the k requirement. To avoid re-calculating counts repeatedly, which would lead to an O(N^3) solution, we can calculate the prefix counts once per outer loop iteration and incrementally update the suffix counts in the inner loop. This optimization brings the complexity down to O(N^2).
class Solution { public int takeCharacters(String s, int k) { int n = s.length(); int[] totalCounts = new int[3]; for (char c : s.toCharArray()) { totalCounts[c - 'a']++; } if (totalCounts[0] < k || totalCounts[1] < k || totalCounts[2] < k) { return -1; } if (k == 0) return 0; int minMinutes = n; // i = number of chars from left for (int i = 0; i <= n; i++) { int[] prefixCounts = new int[3]; for (int p = 0; p < i; p++) { prefixCounts[s.charAt(p) - 'a']++; } int[] suffixCounts = new int[3]; // j = number of chars from right for (int j = 0; j <= n - i; j++) { if (j > 0) { suffixCounts[s.charAt(n - j) - 'a']++; } if (prefixCounts[0] + suffixCounts[0] >= k && prefixCounts[1] + suffixCounts[1] >= k && prefixCounts[2] + suffixCounts[2] >= k) { minMinutes = Math.min(minMinutes, i + j); break; // Smallest j for this i is found } } } return minMinutes; }}Complexity
Time
O(N^2), where N is the length of the string. The nested loops iterate up to N times each.
Space
O(1) extra space, as we only store counts for three characters.
Trade-offs
Pros
The logic is straightforward and follows directly from the problem description.
It is relatively easy to implement.
Cons
The time complexity of O(N^2) is too slow for the given constraints (N up to 10^5) and will likely result in a 'Time Limit Exceeded' error.
This approach improves upon the brute-force method by optimizing the search for the required suffix. For each possible prefix taken from the left, instead of linearly scanning for the shortest valid suffix from the right, we can use binary search. This is possible because the number of characters in a suffix is monotonically increasing with its length.
Algorithm
- Handle edge cases: if total counts of any character are less than
k, return -1. Ifk=0, return 0. - Precompute prefix counts:
prefixCounts[i]will store the counts of 'a', 'b', 'c' in the prefixs[0...i-1]. - Precompute suffix counts:
suffixCounts[j]will store the counts of 'a', 'b', 'c' in the suffixs[n-j...n-1]. - Initialize
minMinuteston. - Iterate
ifrom 0 ton, representing taking a prefix of lengthi. - For each
i, calculate the remaining characters needed:needed_a = max(0, k - prefixCounts[i].a), and similarly for 'b' and 'c'. - If all
neededcounts are zero or less, it means the prefix of lengthiis sufficient. UpdateminMinutes = min(minMinutes, i). - Otherwise, we need to find the smallest suffix length
jthat satisfies theseneededcounts. SincesuffixCounts[j]is monotonic, we can use binary search onjin the range[0, n-i]. - The binary search will find the smallest
jsuch thatsuffixCounts[j]provides theneededcharacters. - If a valid
jis found, updateminMinutes = min(minMinutes, i + j). - After iterating through all
i, returnminMinutes.
Walkthrough
We begin by pre-calculating two arrays: prefixCounts and suffixCounts. prefixCounts[i] stores the character counts for the first i characters, and suffixCounts[j] stores the counts for the last j characters. This precomputation takes O(N) time.
Then, we iterate through each possible number of characters i to take from the left (from 0 to n). For each i, we determine the remaining number of 'a's, 'b's, and 'c's we still need to reach k. With these requirements, we can perform a binary search on the suffixCounts array to find the minimum number of characters j we need to take from the right. The total minutes for this prefix choice is i + j. We keep track of the minimum i + j found across all choices of i.
class Solution { public int takeCharacters(String s, int k) { int n = s.length(); if (k == 0) return 0; int[][] prefixCounts = new int[n + 1][3]; for (int i = 0; i < n; i++) { prefixCounts[i + 1] = prefixCounts[i].clone(); prefixCounts[i + 1][s.charAt(i) - 'a']++; } // Check if taking all characters is sufficient if (prefixCounts[n][0] < k || prefixCounts[n][1] < k || prefixCounts[n][2] < k) { return -1; } int[][] suffixCounts = new int[n + 1][3]; for (int i = 0; i < n; i++) { suffixCounts[i + 1] = suffixCounts[i].clone(); suffixCounts[i + 1][s.charAt(n - 1 - i) - 'a']++; } int minMinutes = n; for (int i = 0; i <= n; i++) { int[] needed = new int[3]; for (int c = 0; c < 3; c++) { needed[c] = Math.max(0, k - prefixCounts[i][c]); } if (needed[0] == 0 && needed[1] == 0 && needed[2] == 0) { minMinutes = Math.min(minMinutes, i); continue; } int low = 0, high = n - i, bestJ = -1; while (low <= high) { int mid = low + (high - low) / 2; if (suffixCounts[mid][0] >= needed[0] && suffixCounts[mid][1] >= needed[1] && suffixCounts[mid][2] >= needed[2]) { bestJ = mid; high = mid - 1; } else { low = mid + 1; } } if (bestJ != -1) { minMinutes = Math.min(minMinutes, i + bestJ); } } return minMinutes; }}Complexity
Time
O(N log N). Precomputation takes O(N). The main loop runs N times, with each iteration performing a binary search that takes O(log N) time.
Space
O(N) to store the prefix and suffix count arrays.
Trade-offs
Pros
Significantly more efficient than the O(N^2) brute-force approach.
Guaranteed to pass within the time limits for the given constraints.
Cons
Requires O(N) extra space for the prefix and suffix count arrays.
More complex to implement than the brute-force approach.
While better than O(N^2), it is not the most optimal solution.
The most efficient solution involves a clever change of perspective. Instead of minimizing the number of characters we take from the ends, we can think about maximizing the number of characters we don't take. The characters not taken must form a contiguous subarray in the middle of the original string. This transforms the problem into finding the longest subarray that meets certain criteria, which is a perfect scenario for the sliding window technique.
Algorithm
- First, handle edge cases. Calculate the total counts of 'a', 'b', 'c' in
s. If any total is less thank, return -1. Ifk=0, return 0. - Reframe the problem: minimizing the characters taken from the ends is equivalent to maximizing the length of the contiguous subarray in the middle that is left behind.
- Determine the constraints for this middle subarray. If we take
kof each character, the remaining middle part must contain at mosttotal_a - k'a's,total_b - k'b's, andtotal_c - k'c's. Let's call thesemax_a,max_b,max_c. - The problem is now to find the longest subarray of
sthat satisfies these count constraints. - Use a sliding window approach with two pointers,
leftandright. - Initialize
left = 0,maxWindowLen = 0, andwindowCountsto zero. - Iterate
rightfrom 0 ton-1, expanding the window to the right. For each characters[right], increment its count inwindowCounts. - After expanding, check if the window is valid (i.e., if all character counts are within their
maxlimits). - If the window is invalid (
windowCounts[c] > max_cfor any characterc), shrink the window from the left by incrementingleftand decrementing the count ofs[left]until the window becomes valid again. - After each valid expansion (or post-shrinking), the window
s[left...right]is valid. UpdatemaxWindowLen = max(maxWindowLen, right - left + 1). - After the loop,
maxWindowLenholds the length of the longest possible middle part. The minimum minutes needed isn - maxWindowLen.
Walkthrough
Let the total counts of 'a', 'b', and 'c' in the string be total_a, total_b, and total_c. To satisfy the problem's condition, the characters we leave behind in the middle subarray must have counts no more than total_a - k, total_b - k, and total_c - k, respectively. If any of these target counts are negative, it's impossible to satisfy the condition, which we check at the beginning.
We can find the longest such subarray using a sliding window. We maintain a window [left, right] and the counts of characters within it. We expand the window by moving right. If at any point a character count in our window exceeds its allowed maximum, we shrink the window from the left by moving left forward until the window is valid again. In each step, we record the length of the current valid window and keep track of the maximum length found. The final answer is the total length of the string minus this maximum valid window length.
class Solution { public int takeCharacters(String s, int k) { if (k == 0) { return 0; } int n = s.length(); int[] totalCounts = new int[3]; for (char c : s.toCharArray()) { totalCounts[c - 'a']++; } if (totalCounts[0] < k || totalCounts[1] < k || totalCounts[2] < k) { return -1; } int[] maxWindowCounts = new int[3]; for (int i = 0; i < 3; i++) { maxWindowCounts[i] = totalCounts[i] - k; } int left = 0; int maxWindowLen = 0; int[] windowCounts = new int[3]; for (int right = 0; right < n; right++) { windowCounts[s.charAt(right) - 'a']++; while (windowCounts[0] > maxWindowCounts[0] || windowCounts[1] > maxWindowCounts[1] || windowCounts[2] > maxWindowCounts[2]) { windowCounts[s.charAt(left) - 'a']--; left++; } maxWindowLen = Math.max(maxWindowLen, right - left + 1); } return n - maxWindowLen; }}Complexity
Time
O(N), where N is the length of the string. Both the `left` and `right` pointers traverse the string at most once.
Space
O(1), as we only use a fixed-size array (size 3) to store character counts.
Trade-offs
Pros
Optimal time complexity of O(N).
Optimal space complexity of O(1).
Provides an elegant and efficient solution to the problem.
Cons
The problem transformation from minimizing ends to maximizing the middle might not be immediately obvious.
Solutions
Solution
class Solution { public int takeCharacters ( String s , int k ) { int [] cnt = new int [ 3 ]; int n = s . length (); for ( int i = 0 ; i < n ; ++ i ) { ++ cnt [ s . charAt ( i ) - 'a' ]; } if ( cnt [ 0 ] < k || cnt [ 1 ] < k || cnt [ 2 ] < k ) { return - 1 ; } int ans = 0 , j = 0 ; for ( int i = 0 ; i < n ; ++ i ) { int c = s . charAt ( i ) - 'a' ; -- cnt [ c ]; while ( cnt [ c ] < k ) { ++ cnt [ s . charAt ( j ++) - 'a' ]; } ans = Math . max ( ans , i - j + 1 ); } return n - ans ; } }class Solution : def takeCharacters ( self , s : str , k : int ) -> int : cnt = Counter ( s ) if any ( cnt [ c ] < k for c in "abc" ): return - 1 ans = j = 0 for i , c in enumerate ( s ): cnt [ c ] -= 1 while cnt [ c ] < k : cnt [ s [ j ]] += 1 j += 1 ans = max ( ans , i - j + 1 ) return len ( s ) - ansclass Solution {public: int takeCharacters(string s, int k) { int cnt[3] = {0}; for (char &c : s) ++cnt[c - 'a']; if (cnt[0] < k || cnt[1] < k || cnt[2] < k) return -1; int ans = 0, j = 0; int n = s.size(); for (int i = 0; i < n; ++i) { int c = s[i] - 'a'; --cnt[c]; while (cnt[c] < k) { ++cnt[s[j++] - 'a']; } ans = max(ans, i - j + 1); } return n - ans; }};Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.