题目
标题和出处
标题:从英文中重建数字
难度
4 级
题目描述
要求
给你一个字符串 s \texttt{s} s,其中包含字母顺序打乱的用英文单词表示的若干数字 0-9 \texttt{0-9} 0-9,按升序返回原始的数字。
示例
示例 1:
输入: s = "owoztneoer" \texttt{s = "owoztneoer"} s = "owoztneoer"
输出: "012" \texttt{"012"} "012"
示例 2:
输入: s = "fviefuro" \texttt{s = "fviefuro"} s = "fviefuro"
输出: "45" \texttt{"45"} "45"
数据范围
- 1 ≤ s.length ≤ 10 5 \texttt{1} \le \texttt{s.length} \le \texttt{10}^\texttt{5} 1≤s.length≤105
- s[i] \texttt{s[i]} s[i] 为 ["e","g","f","i","h","o","n","s","r","u","t","w","v","x","z"] \texttt{["e","g","f","i","h","o","n","s","r","u","t","w","v","x","z"]} ["e","g","f","i","h","o","n","s","r","u","t","w","v","x","z"] 这些字符之一
- s \texttt{s} s 保证是一个符合题目要求的字符串
解法
思路和算法
由于题目给定的字符串 s s s 总是有效的,因此一定可以从字符串 s s s 重建数字。
一共有 10 10 10 个数字,每个数字对应的单词都可以转换成每个字母的出现次数。根据每个单词对应的每个字母的出现次数和字符串 s s s 中的每个字母的出现次数,可以构建一个十元一次方程组计算得到每个数字的出现次数。
如果直接构建十元一次方程组并计算每个数字的出现次数,则非常复杂。为了简化计算,可以统计每个字母分别在哪些数字中出现,然后决定计算顺序。
根据统计结果可知,数字 0 , 2 , 4 , 6 , 8 0,2,4,6,8 0,2,4,6,8 对应的单词中各有一个唯一字母(指该字母只在这一个单词中出现)且唯一字母在单词中只出现一次,分别是 z,w,u,x,g \text{z,w,u,x,g} z,w,u,x,g,因此可以通过这些唯一字母得到数字 0 , 2 , 4 , 6 , 8 0,2,4,6,8 0,2,4,6,8 的出现次数。
得到数字 0 , 2 , 4 , 6 , 8 0,2,4,6,8 0,2,4,6,8 的出现次数之后,排除这些数字对应的单词中每个字母的出现次数,可以得到新的唯一字母,分别在数字 1 , 3 , 5 , 7 1,3,5,7 1,3,5,7 的单词中出现且每个唯一字母在单词中只出现一次:
- 数字 1 1 1 的唯一字母是 o \text{o} o(在 0 , 1 , 2 , 4 0,1,2,4 0,1,2,4 中出现, 0 , 2 , 4 0,2,4 0,2,4 已排除);
- 数字 3 3 3 的唯一字母是 h \text{h} h(在 3 , 8 3,8 3,8 中出现, 8 8 8 已排除);
- 数字 5 5 5 的唯一字母是 f \text{f} f(在 4 , 5 4,5 4,5 中出现, 4 4 4 已排除);
- 数字 7 7 7 的唯一字母是 s \text{s} s(在 6 , 7 6,7 6,7 中出现, 6 6 6 已排除)。
得到数字 1 , 3 , 5 , 7 1,3,5,7 1,3,5,7 的出现次数之后,排除这些数字对应的单词中每个字母的出现次数,此时只剩下数字 9 9 9。由于数字 9 9 9 对应的单词中有 2 2 2 个 n \text{n} n、 1 1 1 个 i \text{i} i 和 1 1 1 个 e \text{e} e,为了方便计算,使用只出现一次的字母 i \text{i} i 或 e \text{e} e 计算数字 9 9 9 的出现次数。
实现方面,首先使用哈希表记录字符串 s s s 中每个字母的出现次数,然后按照上述顺序分别计算每个数字的出现次数。对于每个数字,得到出现次数之后,需要更新哈希表,该数字对应的单词中的每个字母的出现次数都需要减去该数字的出现次数(如果同一个单词中有重复字母,则分别计算应该减去的次数)。
- 例如,数字 3 3 3 对应的单词是 three \text{three} three,如果数字 3 3 3 出现了 4 4 4 次,则在更新哈希表时,单词中的 5 5 5 个字母的出现次数都需要减去 4 4 4,即字母 t,h,r \text{t,h,r} t,h,r 的出现次数都需要减去 4 4 4,字母 e \text{e} e 的出现次数需要减去 8 8 8(由于单词中有 2 2 2 个字母 e \text{e} e,每个字母的出现次数减去 4 4 4,因此一共减去 8 8 8)。
得到每个数字的出现次数之后,按照 0 0 0 到 9 9 9 的顺序将每个数字按照出现次数拼接到结果字符串中,即可保证结果是升序。
代码
class Solution {
public String originalDigits(String s) {
String[] wordsArray = {"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"};
int[] digitsInOrder = {0, 2, 4, 6, 8, 1, 3, 5, 7, 9};
String lettersInOrder = "zwuxgohfse";
Map<Character, Integer> map = new HashMap<Character, Integer>();
int length = s.length();
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
map.put(c, map.getOrDefault(c, 0) + 1);
}
int[] digitCounts = new int[10];
for (int i = 0; i < 10; i++) {
int digit = digitsInOrder[i];
char letter = lettersInOrder.charAt(i);
int curCount = map.getOrDefault(letter, 0);
if (curCount > 0) {
digitCounts[digit] = curCount;
String word = wordsArray[digit];
int wordLength = word.length();
for (int j = 0; j < wordLength; j++) {
char c = word.charAt(j);
map.put(c, map.get(c) - curCount);
if (map.get(c) == 0) {
map.remove(c);
}
}
}
}
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 10; i++) {
int digitCount = digitCounts[i];
for (int j = 0; j < digitCount; j++) {
sb.append(i);
}
}
return sb.toString();
}
}
复杂度分析
-
时间复杂度: O ( n + C ) O(n + C) O(n+C),其中 n n n 是字符串 s s s 的长度, C C C 是数字的个数, C = 10 C = 10 C=10。假设每个数字对应的单词长度都是常数。
遍历字符串统计每个字母的出现次数需要 O ( n ) O(n) O(n) 的时间。
统计每个数字的出现次数需要 O ( C ) O(C) O(C) 的时间。
生成结果字符串需要 O ( n ) O(n) O(n) 的时间。 -
空间复杂度: O ( n + ∣ Σ ∣ + C ) O(n + |\Sigma| + C) O(n+∣Σ∣+C),其中 n n n 是字符串 s s s 的长度, Σ \Sigma Σ 是字符串 s s s 中可能出现的所有字母的集合, C C C 是数字的个数, ∣ Σ ∣ = 15 |\Sigma| = 15 ∣Σ∣=15, C = 10 C = 10 C=10。需要 O ( ∣ Σ ∣ ) O(|\Sigma|) O(∣Σ∣) 的空间记录每个字母的出现次数,需要 O ( C ) O(C) O(C) 的空间记录每个数字的出现次数,需要 O ( n ) O(n) O(n) 的空间存储结果字符串。由于 Java 中的 String \texttt{String} String 类型的对象不可变,因此结果字符串的 O ( n ) O(n) O(n) 空间不可省略。