leetcode
leetcode 2751 ~ 2800
替换字符串中的问号使分数最小

替换字符串中的问号使分数最小

难度:

标签:

题目描述

You are given a string s. s[i] is either a lowercase English letter or '?'.

For a string t having length m containing only lowercase English letters, we define the function cost(i) for an index i as the number of characters equal to t[i] that appeared before it, i.e. in the range [0, i - 1].

The value of t is the sum of cost(i) for all indices i.

For example, for the string t = "aab":

  • cost(0) = 0
  • cost(1) = 1
  • cost(2) = 0
  • Hence, the value of "aab" is 0 + 1 + 0 = 1.

Your task is to replace all occurrences of '?' in s with any lowercase English letter so that the value of s is minimized.

Return a string denoting the modified string with replaced occurrences of '?'. If there are multiple strings resulting in the minimum value, return the lexicographically smallest one.

 

Example 1:

Input: s = "???"

Output: "abc"

Explanation: In this example, we can replace the occurrences of '?' to make s equal to "abc".

For "abc", cost(0) = 0, cost(1) = 0, and cost(2) = 0.

The value of "abc" is 0.

Some other modifications of s that have a value of 0 are "cba", "abz", and, "hey".

Among all of them, we choose the lexicographically smallest.

Example 2:

Input: s = "a?a?"

Output: "abac"

Explanation: In this example, the occurrences of '?' can be replaced to make s equal to "abac".

For "abac", cost(0) = 0, cost(1) = 0, cost(2) = 1, and cost(3) = 0.

The value of "abac" is 1.

 

Constraints:

  • 1 <= s.length <= 105
  • s[i] is either a lowercase English letter or '?'.

代码结果

运行时间: 157 ms, 内存: 18.2 MB


/*
 * 思路:
 * 1. 使用Java Stream进行处理,替换问号为不与左右相邻字符相同的最小字母。
 * 2. 通过使用Stream的特性,进行逐步转换。
 */
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class SolutionStream {
    public String modifyString(String s) {
        char[] arr = s.toCharArray();
        return IntStream.range(0, arr.length)
                .mapToObj(i -> {
                    if (arr[i] == '?') {
                        return (char) IntStream.range('a', 'z' + 1)
                                .filter(c -> (i > 0 && arr[i - 1] != c) && (i < arr.length - 1 && arr[i + 1] != c))
                                .findFirst().orElse('a');
                    }
                    return arr[i];
                })
                .map(String::valueOf)
                .collect(Collectors.joining());
    }
}

解释

方法:

题解通过计算每个字符出现的频率来确定替换问号时最优的字符选择,以确保总分数最小。首先,统计字符串s中每个字符的数量,然后根据字符出现的频率排序,这样可以先填入出现次数少的字符,确保增加的分数最小。通过计算需要替换的问号数量和每个字母的出现次数,来确定每个字母填充问号后能达到的最低可能高度,以及最后一个高度。最后根据这些计算结果,构建最终的字符串,替换所有问号。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
在计算填充问号的过程中,具体是如何根据已有字符的出现频率来确定最优的字符选择的?
算法首先统计了所有非问号字符的出现频率,并将这些频率存储在一个数组中。接着,算法通过一个`queue`列表,这个列表基于字符出现的次数进行排序,使得出现次数最少的字符排在前面。根据这个排序,算法尝试填充问号,优先填充出现次数少的字符。这是因为填充出现次数少的字符可以最小化可能的分数增加,即增加分布的均匀性,从而尽可能减少最高频率字符的出现次数,使总分数尽可能低。
🦆
算法中的`queue = sorted(range(26), key=lambda it: cnt[it])`这一步骤是如何影响字母填充问号的顺序的?为什么要选择出现次数少的字符填充?
这一步骤通过将字符按照出现次数进行升序排序,决定了填充问号的顺序。排序结果`queue`中,索引最低的是出现次数最少的字符。选择出现次数少的字符填充问号的原因是,这样可以在尽量不增加已经频繁出现的字符的同时,增加字符串中的字符多样性。这种策略有助于保持字符出现频率的平衡,从而达到最小化最终字符串中任何字符达到过高频率的风险,这是因为高频率的字符出现次数如果过多,会导致总分数增加。
🦆
在处理最后一个高度和前一个高度时,`lastAll = anums - (lastH * index - highs)`这个计算是如何得出的,它代表什么含义?
这个计算步骤是为了确定在达到最后一个高度之前,还需要填充多少问号。其中`lastH`是当前已处理字符中出现次数最多的字符的数量,`index`是参与排序并计算的字符数,`highs`是到目前为止已处理的所有字符的累计出现次数。`lastAll`计算的是在达到最后一个高度前,还需要填充的问号数量。这个值是通过从总问号数`anums`中减去已经通过较低高度字符填充的区域面积来得出的。这个计算有助于确定如何分配剩余的问号,以确保填充后的字符串字符频率尽可能平衡。

相关问题