Facebook Pixel

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:

  1. Define a function cost(i) for an index i as the number of characters equal to t[i] that appeared before i.
  2. The value of t is the sum of cost(i) over all indices i.

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:

  1. 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.

  2. Priority Queue Initialization: We initialize a priority queue (heapq in Python) with tuples containing the count of each character (from a to z) and the character itself. This ensures that the character with the minimum count is always accessible at the top of the heap.

  3. 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.
  4. 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 string s with the sorted replacements from t.
  5. 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 Evaluator

Example Walkthrough

Let's work through the solution approach with a small example. Consider the input string s = "a?b??a".

  1. Count Frequencies:

    • Initial non-'?' character counts are: a: 2, b: 1.
  2. 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.
  3. 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 list t.
    • 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' to t.
    • 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' to t.
  4. Sort and Replace:

    • Auxilliary list t is ['c', 'd', 'e']. Already lexicographically smallest, so no sorting needed.
    • Replace the '?' in s with c, d, e at respective positions, resulting in the string: "acbdea".
  5. Final Output:

    • The final modified string is "acbdea", which has minimized value while being lexicographically smallest.

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.


Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:
Question 1 out of 10

Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!


Load More