3081. Replace Question Marks in String to Minimize Its Value
Problem Description
You are given a string s
where each character s[i]
is either a lowercase English letter or '?'
. Your task is to replace all occurrences of '?'
in s
with any lowercase English letter, such that the resulting string t
minimizes its "value".
The "value" of a string t
, which contains only lowercase letters, is calculated as follows:
- Define a function
cost(i)
for an indexi
as the number of characters equal tot[i]
that appeared beforei
. - The value of
t
is the sum ofcost(i)
over all indicesi
.
For instance, for the string t = "aab"
:
cost(0) = 0
because there are no 'a's before index 0.cost(1) = 1
because there is one 'a' at index 0.cost(2) = 0
because there are no 'b's before index 2.- Thus, the value of "aab" is
0 + 1 + 0 = 1
.
Return a modified string by replacing all '?'
with appropriate letters to minimize the value. If multiple strings have the minimum value, return the lexicographically smallest one.
Intuition
To solve the problem, we must replace each '?'
with characters such that the total "value" is minimized. The concept hinges on understanding how a character's frequency in position impacts the total value. Specifically, the more a character appears earlier in the string, the higher the cost due to repeated appearances.
Given constraints, our approach involves:
- Using a priority queue (min-heap) to always select the letter with the least frequency, as it contributes the least additional cost when its frequency increases.
- Each '?' should be replaced by the character that, at the moment, appears the least number of times. This choice minimizes incremental increases in the "value" of the string.
- After determining the sequence of characters to replace '?', we fill in the '?' positions in
s
with these characters, ensuring that our resultant string is both minimal in value and lexicographically smallest.
This method relies on efficiently managing the selection process for replacements using the heap structure, which allows operations like insertion and update to be handled optimally, ensuring the lowest possible value for the resultant string.
Learn more about Greedy, Sorting and Heap (Priority Queue) patterns.
Solution Approach
To implement the solution, we apply a greedy strategy combined with a priority queue (min-heap) to efficiently manage the process of replacing '?'
characters in the string s
.
Steps:
-
Count Frequencies: We begin by counting the frequency of each character in the given string
s
excluding'?'
. This helps in establishing a baseline for existing character counts. -
Priority Queue Initialization: We initialize a priority queue (
heapq
in Python) with tuples containing the count of each character (froma
toz
) and the character itself. This ensures that the character with the minimum count is always accessible at the top of the heap. -
Replace Characters: For each
'?'
in the string:- We extract the character with the lowest frequency from the heap.
- Append this character to an auxiliary list
t
that tracks replacements. - Increase the character's frequency in the heap since it's now used one more time.
- Reinstate the updated tuple back into the heap to maintain correct priority.
-
Sort and Replace: After all
'?'
have been replaced with characters resulting in the minimal additional cost:- Sort the list
t
lexicographically. This step ensures that if multiple solutions have the minimum "value", the lexicographically smallest will be employed. - Replace the
'?'
positions in the strings
with the sorted replacements fromt
.
- Sort the list
-
Final Output: Construct the resultant string and output it.
This approach ensures that by selecting the character that contributes the least to the overall "value" each time we encounter a '?'
, and maintaining a sorted order at replacements, we achieve both minimum cost and lexicographic priority.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's work through the solution approach with a small example. Consider the input string s = "a?b??a"
.
-
Count Frequencies:
- Initial non-'?' character counts are:
a: 2, b: 1
.
- Initial non-'?' character counts are:
-
Priority Queue Initialization:
- Initialize a min-heap with frequencies of all lowercase letters:
[(0, 'c'), (0, 'd'), ..., (0, 'z'), (1, 'b'), (2, 'a')]
- Here,
'c'
to'z'
have a count of 0,'b'
has a count of 1, and'a'
has a count of 2.
- Initialize a min-heap with frequencies of all lowercase letters:
-
Replace Characters:
-
Replace the first
'?'
at index 1:- Extract
'c'
(count = 0). The heap is adjusted, and'c'
's count is increased to 1. - The heap after reinserting:
[(0, 'd'), (0, 'e'), ..., (1, 'c'), (1, 'b'), (2, 'a')]
- Append
'c'
to our auxiliary listt
.
- Extract
-
Replace the second
'?'
at index 3:- Next character is
'd'
(count = 0). Increase'd'
's count to 1. - The heap becomes:
[(0, 'e'), (0, 'f'), ..., (1, 'd'), (1, 'c'), (1, 'b'), (2, 'a')]
- Append
'd'
tot
.
- Next character is
-
Replace the third
'?'
at index 4:- Select
'e'
(count = 0). Increment'e'
's count to 1. - The heap now is:
[(0, 'f'), (0, 'g'), ..., (1, 'e'), (1, 'd'), (1, 'c'), (1, 'b'), (2, 'a')]
- Append
'e'
tot
.
- Select
-
-
Sort and Replace:
- Auxilliary list
t
is['c', 'd', 'e']
. Already lexicographically smallest, so no sorting needed. - Replace the
'?'
ins
withc, d, e
at respective positions, resulting in the string:"acbdea"
.
- Auxilliary list
-
Final Output:
- The final modified string is
"acbdea"
, which has minimized value while being lexicographically smallest.
- The final modified string is
This walkthrough demonstrates how choosing the least frequent characters ensures minimal contribution to the total "value", achieving the optimal solution.
Solution Implementation
1from collections import Counter
2from heapq import heapify, heapreplace
3from string import ascii_lowercase
4
5class Solution:
6 def minimizeStringValue(self, s: str) -> str:
7 # Count occurrences of each character in the string
8 char_count = Counter(s)
9
10 # Create a priority queue with (count, character) for each lowercase letter
11 priority_queue = [(char_count[char], char) for char in ascii_lowercase]
12 heapify(priority_queue)
13
14 # List to store characters to replace '?'
15 replacements = []
16
17 # Replace each '?' in the string
18 for _ in range(s.count("?")):
19 count, char = priority_queue[0] # Get the least frequent character
20 replacements.append(char) # Add it to the list of replacements
21 heapreplace(priority_queue, (count + 1, char)) # Update the count and re-heapify
22
23 # Sort the replacement characters to minimize the resulting string lexicographically
24 replacements.sort()
25
26 # Convert the original string into a list of characters
27 string_chars = list(s)
28 replacement_index = 0
29
30 # Iterate through the string and replace each '?' with a character from replacements
31 for index, char in enumerate(s):
32 if char == "?":
33 string_chars[index] = replacements[replacement_index]
34 replacement_index += 1
35
36 # Join the list back into a string and return
37 return "".join(string_chars)
38
1class Solution {
2 public String minimizeStringValue(String s) {
3 // Initialize a count array for the alphabet letters
4 int[] letterCount = new int[26];
5 int n = s.length(); // Length of the input string
6 int questionMarkCount = 0; // Counts the number of '?' characters
7 char[] chars = s.toCharArray(); // Convert the input string to a character array
8
9 // Count occurrences of each letter and number of '?'
10 for (char c : chars) {
11 if (c == '?') {
12 ++questionMarkCount;
13 } else {
14 ++letterCount[c - 'a'];
15 }
16 }
17
18 // Use a priority queue to keep track of the letters with the smallest counts
19 PriorityQueue<int[]> priorityQueue = new PriorityQueue<>((a, b) ->
20 a[0] == b[0] ? a[1] - b[1] : a[0] - b[0]);
21
22 // Add the count of each letter into the priority queue
23 for (int i = 0; i < 26; ++i) {
24 priorityQueue.offer(new int[] {letterCount[i], i});
25 }
26
27 // Array to store the index of letters that will replace '?'
28 int[] replacementIndices = new int[questionMarkCount];
29 for (int j = 0; j < questionMarkCount; ++j) {
30 int[] current = priorityQueue.poll(); // Get the letter with the smallest count
31 replacementIndices[j] = current[1]; // Store its index
32 priorityQueue.offer(new int[] {current[0] + 1, current[1]}); // Add back with incremented count
33 }
34
35 // Sort the replacement indices to minimize lexicographic order
36 Arrays.sort(replacementIndices);
37
38 // Replace '?' with the corresponding minimal letters
39 for (int i = 0, j = 0; i < n; ++i) {
40 if (chars[i] == '?') {
41 chars[i] = (char) (replacementIndices[j++] + 'a'); // Replace '?' with the replacement letter
42 }
43 }
44
45 // Return the modified string
46 return new String(chars);
47 }
48}
49
1#include <string>
2#include <queue>
3#include <vector>
4#include <algorithm>
5
6class Solution {
7public:
8 string minimizeStringValue(std::string s) {
9 int count[26] = {}; // Array to keep track of character frequencies, initialized to 0
10 int questionMarkCount = 0; // To count occurrences of '?'
11
12 // Iterate through each character in the string
13 for (char& c : s) {
14 if (c == '?') {
15 ++questionMarkCount; // Increment count of '?' characters
16 } else {
17 ++count[c - 'a']; // Increment frequency of the character
18 }
19 }
20
21 // Priority queue (min-heap) to store frequency and character index
22 std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int>>, std::greater<>> pq;
23 for (int i = 0; i < 26; ++i) {
24 pq.push({count[i], i}); // Push pair of frequency and character index onto the queue
25 }
26
27 std::vector<int> temp(questionMarkCount); // Vector to store indices of the least frequent characters to replace '?'
28
29 // Distribute '?' over the characters to balance the frequency
30 for (int i = 0; i < questionMarkCount; ++i) {
31 auto [frequency, charIndex] = pq.top();
32 pq.pop();
33 temp[i] = charIndex; // Store the character index
34 pq.push({frequency + 1, charIndex}); // Increment frequency and reinsert into the queue
35 }
36
37 // Sort the vector to ensure lexicographical order
38 std::sort(temp.begin(), temp.end());
39 int tempIndex = 0;
40
41 // Replace '?' with the least frequent character
42 for (char& c : s) {
43 if (c == '?') {
44 c = temp[tempIndex++] + 'a'; // Convert index back to character
45 }
46 }
47
48 return s; // Return the minimized string
49 }
50};
51
1function minimizeStringValue(s: string): string {
2 let count: number[] = Array(26).fill(0); // Array to keep track of character frequencies, initialized to 0
3 let questionMarkCount: number = 0; // To count occurrences of '?'
4
5 // Iterate through each character in the string
6 for (let c of s) {
7 if (c === '?') {
8 questionMarkCount++; // Increment count of '?' characters
9 } else {
10 count[c.charCodeAt(0) - 'a'.charCodeAt(0)]++; // Increment frequency of the character
11 }
12 }
13
14 // Priority queue (min-heap) to store frequency and character index
15 // Using a min-heap by sorting array with pop/push operations
16 let pq: [number, number][] = [];
17 for (let i = 0; i < 26; i++) {
18 pq.push([count[i], i]); // Push pair of frequency and character index onto the array
19 }
20
21 // Sort the array to act as a priority queue
22 pq.sort((a, b) => a[0] - b[0]);
23
24 let temp: number[] = Array(questionMarkCount); // Array to store indices of the least frequent characters to replace '?'
25
26 // Distribute '?' over the characters to balance the frequency
27 for (let i = 0; i < questionMarkCount; i++) {
28 let [frequency, charIndex] = pq.shift()!; // Get the least frequent character
29 temp[i] = charIndex; // Store the character index
30 pq.push([frequency + 1, charIndex]); // Increment frequency and reinsert into the queue
31 pq.sort((a, b) => a[0] - b[0]); // Re-sort the array
32 }
33
34 // Replace '?' with the least frequent character
35 let tempIndex: number = 0;
36 let result: string[] = s.split(''); // Convert string to an array of characters for modification
37
38 for (let i = 0; i < result.length; i++) {
39 if (result[i] === '?') {
40 result[i] = String.fromCharCode(temp[tempIndex++] + 'a'.charCodeAt(0)); // Convert index back to character
41 }
42 }
43
44 return result.join(''); // Convert the array back to a string and return it
45}
46
Time and Space Complexity
The time complexity of the code is O(n + m log m)
, where n
is the length of the string s
and m
is the number of unique characters in s
. The space complexity is O(m)
, where m
represents the size of the priority queue and other character count data structures used.
Learn more about how to find time and space complexity quickly.
Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
https algomonster s3 us east 2 amazonaws com cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue
Want a Structured Path to Master System Design Too? Don’t Miss This!