Hard
题目描述
给你一个长度为 n 的整数数组 rolls 和一个整数 k。你掷一个有 k 面的骰子(编号从 1 到 k)n 次,其中第 i 次掷骰子的结果是 rolls[i]。
返回最短的掷骰子序列的长度,使得 rolls 中不存在这样的子序列。
长度为 len 的掷骰子序列是掷 k 面骰子 len 次的结果。
示例 1:
输入:rolls = [4,2,1,2,3,3,2,4,1], k = 4
输出:3
解释:每个长度为 1 的序列,[1], [2], [3], [4],都可以从 rolls 中取得。
每个长度为 2 的序列,[1, 1], [1, 2], ..., [4, 4],都可以从 rolls 中取得。
序列 [1, 4, 2] 无法从 rolls 中取得,所以我们返回 3。
注意还有其他序列无法从 rolls 中取得。
示例 2:
输入:rolls = [1,1,2,2], k = 2
输出:2
解释:每个长度为 1 的序列,[1], [2],都可以从 rolls 中取得。
序列 [2, 1] 无法从 rolls 中取得,所以我们返回 2。
注意还有其他序列无法从 rolls 中取得,但 [2, 1] 是最短的。
示例 3:
输入:rolls = [1,1,3,2,2,2,3,3], k = 4
输出:1
解释:序列 [4] 无法从 rolls 中取得,所以我们返回 1。
注意还有其他序列无法从 rolls 中取得,但 [4] 是最短的。
提示:
n == rolls.length1 <= n <= 10^51 <= rolls[i] <= k <= 10^5
解题思路
这是一道典型的贪心算法题。关键思路是:如果所有长度为 len 的序列都可以在 rolls 中找到,那么所有长度为 len-1 的序列也一定可以找到。
我们需要找到最小的长度 len,使得存在某个长度为 len 的序列无法在 rolls 中找到。
核心观察:
- 长度为 1 的序列有 k 个:[1], [2], …, [k]
- 长度为 2 的序列有 k² 个:[1,1], [1,2], …, [k,k]
- 长度为 len 的序列有 k^len 个
贪心策略: 我们从左到右遍历 rolls,对于每个长度 len:
- 维护一个集合,记录在当前位置之后还需要哪些数字来完成长度为 len 的所有可能序列
- 当集合中包含所有 1 到 k 的数字时,说明我们可以构造出所有长度为 len 的序列
- 清空集合,开始寻找长度为 len+1 的序列
当我们无法在剩余的 rolls 中找到足够的数字来构成某个长度的所有序列时,就找到了答案。
算法流程:
- 初始化
length = 1,表示当前要构造的序列长度 - 使用集合
needed记录当前长度下还需要的数字 - 遍历 rolls,每遇到一个数字就从 needed 中移除
- 当 needed 为空时,说明当前长度的所有序列都能构造,length++,重新初始化 needed
- 如果遍历完成后 needed 仍不为空,返回当前 length
代码实现
class Solution {
public:
int shortestSequence(vector<int>& rolls, int k) {
int length = 1;
unordered_set<int> needed;
// 初始化需要的数字集合
for (int i = 1; i <= k; i++) {
needed.insert(i);
}
for (int roll : rolls) {
needed.erase(roll);
// 如果当前长度的所有序列都能构造出来
if (needed.empty()) {
length++;
// 重新初始化下一个长度的需求
for (int i = 1; i <= k; i++) {
needed.insert(i);
}
}
}
return length;
}
};
class Solution:
def shortestSequence(self, rolls: List[int], k: int) -> int:
length = 1
needed = set(range(1, k + 1))
for roll in rolls:
needed.discard(roll)
# 如果当前长度的所有序列都能构造出来
if not needed:
length += 1
# 重新初始化下一个长度的需求
needed = set(range(1, k + 1))
return length
public class Solution {
public int ShortestSequence(int[] rolls, int k) {
int length = 1;
HashSet<int> needed = new HashSet<int>();
// 初始化需要的数字集合
for (int i = 1; i <= k; i++) {
needed.Add(i);
}
foreach (int roll in rolls) {
needed.Remove(roll);
// 如果当前长度的所有序列都能构造出来
if (needed.Count == 0) {
length++;
// 重新初始化下一个长度的需求
for (int i = 1; i <= k; i++) {
needed.Add(i);
}
}
}
return length;
}
}
var shortestSequence = function(rolls, k) {
let seen = new Set();
let length = 1;
for (let roll of rolls) {
seen.add(roll);
if (seen.size === k) {
length++;
seen.clear();
}
}
return length;
};
复杂度分析
| 复杂度 | 分析 |
|---|---|
| 时间复杂度 | O(n) |
| 空间复杂度 | O(k) |
说明:
- 时间复杂度:我们只需要遍历一次 rolls 数组,每次操作(插入、删除)在哈希集合中都是 O(1)
- 空间复杂度:我们使用一个大小最多为 k 的哈希集合来存储需要的数字