Medium
题目描述
Alice 正在用她的手机给 Bob 发短信。数字到字母的映射如下图所示。
为了添加一个字母,Alice 需要按对应数字键 i 次,其中 i 是该字母在按键上的位置。
- 例如,要添加字母 ’s’,Alice 需要按 ‘7’ 四次。类似地,要添加字母 ‘k’,Alice 需要按 ‘5’ 两次。
- 注意数字 ‘0’ 和 ‘1’ 不映射到任何字母,所以 Alice 不会使用它们。
但是,由于传输错误,Bob 没有收到 Alice 的短信,而是收到了一串按键记录。
- 例如,当 Alice 发送消息 “bob” 时,Bob 收到的字符串是 “2266622”。
给定一个字符串 pressedKeys 表示 Bob 收到的字符串,返回 Alice 可能发送的文本消息的总数。
由于答案可能很大,请返回对 10^9 + 7 取模的结果。
示例 1:
输入:pressedKeys = "22233"
输出:8
解释:
Alice 可能发送的文本消息有:
"aaadd", "abdd", "badd", "cdd", "aaae", "abe", "bae", 和 "ce"。
因为有 8 种可能的消息,所以返回 8。
示例 2:
输入:pressedKeys = "222222222222222222222222222222222222"
输出:82876089
解释:
Alice 可能发送的文本消息有 2082876103 种。
因为需要返回对 10^9 + 7 取模的结果,所以返回 2082876103 % (10^9 + 7) = 82876089。
约束条件:
- 1 <= pressedKeys.length <= 10^5
- pressedKeys 只包含数字 ‘2’ 到 ‘9’
提示:
- 对于由相同数字组成的子串,如何计算它最初可能代表的文本数量?
- 动态规划如何帮助我们计算所需的答案?
解题思路
这是一道经典的动态规划问题,核心思想是将问题分解为连续相同数字段的组合问题。
首先,我们需要理解手机按键映射:数字2-6对应3个字母,数字7和9对应4个字母,数字8对应3个字母。这意味着对于连续的相同数字,最多可以组成3或4个字母的组合。
解题思路:
- 分段处理:将输入字符串按照相同数字分组,每组内部计算可能的文本组合数
- 动态规划:对于长度为n的相同数字段,定义dp[i]为前i个数字的组合数
- 状态转移:dp[i] = dp[i-1] + dp[i-2] + dp[i-3] + (dp[i-4] if 数字是7或9)
- 这是因为每次可以选择1-3个相同数字组成一个字母(对于7和9可以选择4个)
- 乘法原理:各段的组合数相乘得到最终答案
优化点:
- 预计算斐波那契类型的数列,避免重复计算
- 使用取模运算防止整数溢出
- 一次遍历完成分段和计算
代码实现
class Solution {
public:
int countTexts(string pressedKeys) {
const int MOD = 1e9 + 7;
int n = pressedKeys.length();
// 预计算dp数组,dp[i]表示长度为i的相同数字串的组合数
vector<long long> dp3(n + 1), dp4(n + 1);
dp3[0] = dp4[0] = 1;
for (int i = 1; i <= n; i++) {
dp3[i] = dp3[i-1];
if (i >= 2) dp3[i] = (dp3[i] + dp3[i-2]) % MOD;
if (i >= 3) dp3[i] = (dp3[i] + dp3[i-3]) % MOD;
dp4[i] = dp4[i-1];
if (i >= 2) dp4[i] = (dp4[i] + dp4[i-2]) % MOD;
if (i >= 3) dp4[i] = (dp4[i] + dp4[i-3]) % MOD;
if (i >= 4) dp4[i] = (dp4[i] + dp4[i-4]) % MOD;
}
long long result = 1;
int i = 0;
while (i < n) {
char digit = pressedKeys[i];
int count = 0;
// 统计连续相同数字的个数
while (i < n && pressedKeys[i] == digit) {
count++;
i++;
}
// 根据数字类型选择对应的dp数组
if (digit == '7' || digit == '9') {
result = (result * dp4[count]) % MOD;
} else {
result = (result * dp3[count]) % MOD;
}
}
return result;
}
};
class Solution:
def countTexts(self, pressedKeys: str) -> int:
MOD = 10**9 + 7
n = len(pressedKeys)
# 预计算dp数组
dp3 = [0] * (n + 1)
dp4 = [0] * (n + 1)
dp3[0] = dp4[0] = 1
for i in range(1, n + 1):
dp3[i] = dp3[i-1]
if i >= 2:
dp3[i] = (dp3[i] + dp3[i-2]) % MOD
if i >= 3:
dp3[i] = (dp3[i] + dp3[i-3]) % MOD
dp4[i] = dp4[i-1]
if i >= 2:
dp4[i] = (dp4[i] + dp4[i-2]) % MOD
if i >= 3:
dp4[i] = (dp4[i] + dp4[i-3]) % MOD
if i >= 4:
dp4[i] = (dp4[i] + dp4[i-4]) % MOD
result = 1
i = 0
while i < n:
digit = pressedKeys[i]
count = 0
# 统计连续相同数字的个数
while i < n and pressedKeys[i] == digit:
count += 1
i += 1
# 根据数字类型选择对应的dp数组
if digit in '79':
result = (result * dp4[count]) % MOD
else:
result = (result * dp3[count]) % MOD
return result
public class Solution {
public int CountTexts(string pressedKeys) {
const int MOD = 1000000007;
int n = pressedKeys.Length;
// 预计算dp数组
long[] dp3 = new long[n + 1];
long[] dp4 = new long[n + 1];
dp3[0] = dp4[0] = 1;
for (int i = 1; i <= n; i++) {
dp3[i] = dp3[i-1];
if (i >= 2) dp3[i] = (dp3[i] + dp3[i-2]) % MOD;
if (i >= 3) dp3[i] = (dp3[i] + dp3[i-3]) % MOD;
dp4[i] = dp4[i-1];
if (i >= 2) dp4[i] = (dp4[i] + dp4[i-2]) % MOD;
if (i >= 3) dp4[i] = (dp4[i] + dp4[i-3]) % MOD;
if (i >= 4) dp4[i] = (dp4[i] + dp4[i-4]) % MOD;
}
long result = 1;
int idx = 0;
while (idx < n) {
char digit = pressedKeys[idx];
int count = 0;
// 统计连续相同数字的个数
while (idx < n && pressedKeys[idx] == digit) {
count++;
idx++;
}
// 根据数字类型选择对应的dp数组
if (digit == '7' || digit == '9') {
result = (result * dp4[count]) % MOD;
} else {
result = (result * dp3[count]) % MOD;
}
}
return (int)result;
}
}
var countTexts = function(pressedKeys) {
const MOD = 1e9 + 7;
const n = pressedKeys.length;
// 预计算dp数组
const dp3 = new Array(n + 1).fill(0);
const dp4 = new Array(n + 1).fill(0);
dp3[0] = dp4[0] = 1;
for (let i = 1; i <= n; i++) {
dp3[i] = dp3[i-1];
if (i >= 2) dp3[i] = (dp3[i] + dp3[i-2]) % MOD;
if (i >= 3) dp3[i] = (dp3[i] + dp3[i-3]) % MOD;
dp4[i] = dp4[i-1];
if (i >= 2) dp4[i] = (dp4[i] + dp4[i-2]) % MOD;
if (i >= 3) dp4[i] = (dp4[i] + dp4[i-3]) % MOD;
if (i >= 4) dp4[i] = (dp4[i] + dp4[i-4]) % MOD;
}
let result = 1;
let i = 0;
while (i < n) {
const digit = pressedKeys[i];
let count = 0;
// 统计连续相同数字的个数
while (i < n && pressedKeys[i]
复杂度分析
| 复杂度类型 | 值 |
|---|---|
| 时间复杂度 | O(n) |
| 空间复杂度 | O(n) |
其中 n 是输入字符串的长度。时间复杂度为 O(n) 是因为需要预计算 dp 数组和遍历一次字符串。空间复杂度为 O(n) 是因为需要存储两个长度为 n+1 的 dp 数组。
相关题目
. Decode Ways (Medium)