Problem of The Day: Construct String With Repeat Limit
Problem Statement
- Notes: need to review.
Editorial
Approach 1: Greedy Character Frequency Distribution
class Solution:
def repeatLimitedString(self, s: str, repeatLimit: int) -> str:
freq = [0] * 26
for char in s:
freq[ord(char) - ord("a")] += 1
result = []
current_char_index = 25 # Start from the largest character
while current_char_index >= 0:
if freq[current_char_index] == 0:
current_char_index -= 1
continue
use = min(freq[current_char_index], repeatLimit)
result.append(chr(current_char_index + ord("a")) * use)
freq[current_char_index] -= use
if freq[current_char_index] > 0: # Need to add a smaller character
smaller_char_index = current_char_index - 1
while smaller_char_index >= 0 and freq[smaller_char_index] == 0:
smaller_char_index -= 1
if smaller_char_index < 0:
break
result.append(chr(smaller_char_index + ord("a")))
freq[smaller_char_index] -= 1
return "".join(result)
using System;
using System.Text;
public class Solution {
public string RepeatLimitedString(string s, int repeatLimit) {
int[] freq = new int[26];
foreach (char c in s) {
freq[c - 'a']++;
}
StringBuilder result = new StringBuilder();
int currentCharIndex = 25; // Start from the largest character
while (currentCharIndex >= 0) {
if (freq[currentCharIndex] == 0) {
currentCharIndex--;
continue;
}
int use = Math.Min(freq[currentCharIndex], repeatLimit);
result.Append((char)(currentCharIndex + 'a'), use);
freq[currentCharIndex] -= use;
if (freq[currentCharIndex] > 0) { // Need to add a smaller character
int smallerCharIndex = currentCharIndex - 1;
while (smallerCharIndex >= 0 && freq[smallerCharIndex] == 0) {
smallerCharIndex--;
}
if (smallerCharIndex < 0) {
break;
}
result.Append((char)(smallerCharIndex + 'a'));
freq[smallerCharIndex]--;
}
}
return result.ToString();
}
}
Explanation
- Greedy Strategy: To build the lexicographically largest string, always prioritize the largest available characters (
'z'down to'a'). - Frequency Array: Use a 26-slot array for counts. This is more efficient than a heap for small, fixed-size alphabets.
- Repeat Limit Handling:
- Append the largest available character up to the
repeatLimit. - If count remains, use exactly one next-best character (a “breaker”) to reset the streak.
- Why only one breaker? To return to the largest possible character as quickly as possible.
- Append the largest available character up to the
-
Termination: Stop when no more “breaker” characters are available but the primary character still needs to be reset.
- Time Complexity: O(N * K), where N is string length and K=26. Each character addition may involve a small scan for the next best character.
- Space Complexity: O(K) for the frequency array.
Approach 2: Heap-Optimized Greedy Character Frequency Distribution
class Solution:
def repeatLimitedString(self, s: str, repeatLimit: int) -> str:
max_heap = [(-ord(c), cnt) for c, cnt in Counter(s).items()]
heapify(max_heap)
result = []
while max_heap:
char_neg, count = heappop(max_heap)
char = chr(-char_neg)
use = min(count, repeatLimit)
result.append(char * use)
if count > use and max_heap:
next_char_neg, next_count = heappop(max_heap)
result.append(chr(-next_char_neg))
if next_count > 1:
heappush(max_heap, (next_char_neg, next_count - 1))
heappush(max_heap, (char_neg, count - use))
return "".join(result)
using System;
using System.Collections.Generic;
using System.Text;
public class Solution {
public string RepeatLimitedString(string s, int repeatLimit) {
Dictionary<char, int> counts = new Dictionary<char, int>();
foreach (char c in s) {
if (!counts.ContainsKey(c)) counts[c] = 0;
counts[c]++;
}
PriorityQueue<(char, int), char> maxHeap = new PriorityQueue<(char, int), char>(Comparer<char>.Create((a, b) => b.CompareTo(a)));
foreach (var kvp in counts) {
maxHeap.Enqueue((kvp.Key, kvp.Value), kvp.Key);
}
StringBuilder result = new StringBuilder();
while (maxHeap.Count > 0) {
(char character, int count) = maxHeap.Dequeue();
int use = Math.Min(count, repeatLimit);
result.Append(character, use);
if (count > use && maxHeap.Count > 0) {
(char nextChar, int nextCount) = maxHeap.Dequeue();
result.Append(nextChar);
if (nextCount > 1) {
maxHeap.Enqueue((nextChar, nextCount - 1), nextChar);
}
maxHeap.Enqueue((character, count - use), character);
}
}
return result.ToString();
}
}
Explanation
- Priority Queue: Use a max-heap to keep track of characters and their frequencies, ordered by character lexicographical value.
- Greedy Selection: Always pop the largest character from the heap to ensure the lexicographically largest result.
- Repeat Limit: Append the character up to
repeatLimittimes. - Breaker Character: If more of the current character remains but the limit is reached, pop the next largest character as a single “breaker” to reset the streak, then push both back into the heap.
-
Efficiency: Similar to the frequency array approach, but more flexible for larger alphabets.
- Time Complexity: O(N log K), where N is string length and K is the number of unique characters.
- Space Complexity: O(K) for the heap and frequency storage.
Leave a comment