Medium

题目描述

给你一个字符串 ss[i] 要么是小写英文字母,要么是 '?'

对于长度为 m 且只包含小写英文字母的字符串 t,我们定义索引 i 的函数 cost(i) 为在它之前(即范围 [0, i - 1])出现的与 t[i] 相等的字符数量。

t 的值是所有索引 icost(i) 之和。

例如,对于字符串 t = "aab"

  • cost(0) = 0
  • cost(1) = 1
  • cost(2) = 0

因此,"aab" 的值是 0 + 1 + 0 = 1

你的任务是将 s 中所有的 '?' 替换为任意小写英文字母,使得 s 的值最小。

返回一个表示修改后字符串的字符串,其中所有 '?' 都被替换。如果有多个字符串都能达到最小值,返回字典序最小的那个。

示例 1:

输入:s = "???" 输出:"abc" 解释:在这个例子中,我们可以将 '?' 替换为使 s 等于 "abc"。 对于 "abc"cost(0) = 0cost(1) = 0cost(2) = 0"abc" 的值是 0。 其他一些值为 0 的修改包括 "cba""abz""hey"。 在所有这些中,我们选择字典序最小的。

示例 2:

输入:s = "a?a?" 输出:"abac" 解释:在这个例子中,'?' 可以被替换为使 s 等于 "abac"。 对于 "abac"cost(0) = 0cost(1) = 0cost(2) = 1cost(3) = 0"abac" 的值是 1。

约束条件:

  • 1 <= s.length <= 10^5
  • s[i] 要么是小写英文字母,要么是 '?'

解题思路

解题思路

这道题的核心思路是理解成本函数的计算方式和贪心策略。

首先分析成本函数:对于字符 c 出现 x 次,其总成本为 0 + 1 + 2 + … + (x-1) = x*(x-1)/2。这意味着字符出现次数越多,边际成本递增越快。

因此,要使总成本最小,我们应该尽可能平衡各字符的出现次数。具体策略分为两步:

  1. 确定替换字符:统计已有字符的频次,对于每个问号,选择当前频次最小的字符进行替换。可以用优先队列维护 26 个字母的频次,每次取出频次最小的字母。

  2. 保证字典序最小:虽然我们知道了要添加哪些字符,但为了保证结果字典序最小,需要将这些字符按字母序排列,然后从左到右依次填入问号位置。

算法步骤:

  • 统计原字符串中各字母的出现频次
  • 使用最小堆维护 26 个字母的频次
  • 对于每个问号,选择频次最小的字母,并更新其频次
  • 将选择的字母按字典序排序
  • 从左到右将排序后的字母填入问号位置

这样既能保证总成本最小,又能保证字典序最小。

代码实现

class Solution {
public:
    string minimizeStringValue(string s) {
        vector<int> count(26, 0);
        int questionMarks = 0;
        
        // Count existing characters and question marks
        for (char c : s) {
            if (c != '?') {
                count[c - 'a']++;
            } else {
                questionMarks++;
            }
        }
        
        // Use priority queue to always pick the character with minimum frequency
        priority_queue<pair<int, char>, vector<pair<int, char>>, greater<pair<int, char>>> pq;
        for (int i = 0; i < 26; i++) {
            pq.push({count[i], 'a' + i});
        }
        
        // Determine which characters to add
        string toAdd = "";
        for (int i = 0; i < questionMarks; i++) {
            auto [freq, ch] = pq.top();
            pq.pop();
            toAdd += ch;
            pq.push({freq + 1, ch});
        }
        
        // Sort the characters to add for lexicographical order
        sort(toAdd.begin(), toAdd.end());
        
        // Replace question marks with sorted characters
        int addIndex = 0;
        for (int i = 0; i < s.length(); i++) {
            if (s[i] == '?') {
                s[i] = toAdd[addIndex++];
            }
        }
        
        return s;
    }
};
class Solution:
    def minimizeStringValue(self, s: str) -> str:
        import heapq
        
        count = [0] * 26
        question_marks = 0
        
        # Count existing characters and question marks
        for c in s:
            if c != '?':
                count[ord(c) - ord('a')] += 1
            else:
                question_marks += 1
        
        # Use heap to always pick the character with minimum frequency
        heap = []
        for i in range(26):
            heapq.heappush(heap, (count[i], chr(ord('a') + i)))
        
        # Determine which characters to add
        to_add = []
        for _ in range(question_marks):
            freq, ch = heapq.heappop(heap)
            to_add.append(ch)
            heapq.heappush(heap, (freq + 1, ch))
        
        # Sort the characters to add for lexicographical order
        to_add.sort()
        
        # Replace question marks with sorted characters
        result = []
        add_index = 0
        for c in s:
            if c == '?':
                result.append(to_add[add_index])
                add_index += 1
            else:
                result.append(c)
        
        return ''.join(result)
public class Solution {
    public string MinimizeStringValue(string s) {
        int[] count = new int[26];
        int questionMarks = 0;
        
        // Count existing characters and question marks
        foreach (char c in s) {
            if (c != '?') {
                count[c - 'a']++;
            } else {
                questionMarks++;
            }
        }
        
        // Use priority queue to always pick the character with minimum frequency
        var pq = new PriorityQueue<(int freq, char ch), int>();
        for (int i = 0; i < 26; i++) {
            pq.Enqueue((count[i], (char)('a' + i)), count[i]);
        }
        
        // Determine which characters to add
        var toAdd = new List<char>();
        for (int i = 0; i < questionMarks; i++) {
            var (freq, ch) = pq.Dequeue();
            toAdd.Add(ch);
            pq.Enqueue((freq + 1, ch), freq + 1);
        }
        
        // Sort the characters to add for lexicographical order
        toAdd.Sort();
        
        // Replace question marks with sorted characters
        var result = new char[s.Length];
        int addIndex = 0;
        for (int i = 0; i < s.Length; i++) {
            if (s[i] == '?') {
                result[i] = toAdd[addIndex++];
            } else {
                result[i] = s[i];
            }
        }
        
        return new string(result);
    }
}
var minimizeStringValue = function(s) {
    const n = s.length;
    const count = new Array(26).fill(0);
    
    // Count existing letters
    for (let i = 0; i < n; i++) {
        if (s[i] !== '?') {
            count[s[i].charCodeAt(0) - 97]++;
        }
    }
    
    // Find replacements for question marks
    const replacements = [];
    const numQuestions = s.split('?').length - 1;
    
    for (let i = 0; i < numQuestions; i++) {
        let minCount = Math.min(...count);
        let chosenChar = 0;
        
        // Find lexicographically smallest char with minimum count
        for (let j = 0; j < 26; j++) {
            if (count[j] === minCount) {
                chosenChar = j;
                break;
            }
        }
        
        replacements.push(String.fromCharCode(97 + chosenChar));
        count[chosenChar]++;
    }
    
    // Sort replacements to ensure lexicographically smallest result
    replacements.sort();
    
    // Replace question marks
    let result = '';
    let replaceIndex = 0;
    
    for (let i = 0; i < n; i++) {
        if (s[i] === '?') {
            result += replacements[replaceIndex++];
        } else {
            result += s[i];
        }
    }
    
    return result;
};

复杂度分析

复杂度分析
时间复杂度O(n log 26) = O(n),其中 n 是字符串长度。需要遍历字符串统计字符,对于每个问号进行堆操作,排序替换字符的时间为 O(k log k),k 是问号数量,最坏情况下 k = n
空间复杂度O(26 + k) = O(n),需要存储 26 个字符的频次和待替换的字符列表

相关题目