Medium
题目描述
给你一个长度为 n 的字符串 s 和一个整数数组 order,其中 order 是范围 [0, n - 1] 内数字的一个排列。
从时间 t = 0 开始,在每个时间步将字符串 s 中索引为 order[t] 的字符替换为 ‘*’。
如果一个子字符串包含至少一个 ‘*’,则该子字符串是有效的。
如果有效子字符串的总数大于或等于 k,则字符串是活跃的。
返回字符串 s 变为活跃的最小时间 t。如果不可能,返回 -1。
示例 1:
输入: s = "abc", order = [1,0,2], k = 2
输出: 0
解释:
t=0时,order[0]=1,字符串变为"a*c"
有效子字符串: "*", "a*", "*c", "a*c",共4个
4 >= 2,所以字符串在t=0时变为活跃。
示例 2:
输入: s = "cat", order = [0,2,1], k = 6
输出: 2
解释:
t=0: "*at",有效子字符串3个
t=1: "*a*",有效子字符串5个
t=2: "***",所有子字符串都有效,共6个
字符串在t=2时变为活跃。
示例 3:
输入: s = "xy", order = [0,1], k = 4
输出: -1
解释: 即使所有字符都替换后,也无法获得4个有效子字符串。
约束条件:
- 1 <= n == s.length <= 10^5
- order.length == n
- 0 <= order[i] <= n - 1
- s 由小写英文字母组成
- order 是从 0 到 n-1 的整数排列
- 1 <= k <= 10^9
解题思路
这是一道二分搜索题目。关键思路如下:
核心观察: 有效子字符串数量随着时间t单调递增,因此可以使用二分搜索找到最小的满足条件的时间。
计算有效子字符串数量: 对于给定时间t,我们知道前t+1个位置会被替换为’*’。计算有效子字符串的关键是:
- 总子字符串数量为 n*(n+1)/2
- 无效子字符串是那些完全不包含’*‘的子字符串
- 无效子字符串只能来自连续的非’*‘字符段
算法步骤:
- 二分搜索时间t的范围[0, n-1]
- 对于每个t,标记前t+1个order位置为’*’
- 找出所有连续的非’*‘字符段
- 对于长度为L的连续段,其子字符串数量为L*(L+1)/2
- 有效子字符串数量 = 总数 - 所有连续非’*‘段的子字符串数量
- 检查是否 >= k
时间优化: 由于k可能很大(10^9),当所有字符都替换为’*‘后仍然不够时,直接返回-1。
代码实现
class Solution {
public:
int minTime(string s, vector<int>& order, int k) {
int n = s.length();
long long total = (long long)n * (n + 1) / 2;
if (total < k) return -1;
int left = 0, right = n - 1;
int result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
vector<bool> isReplaced(n, false);
for (int i = 0; i <= mid; i++) {
isReplaced[order[i]] = true;
}
long long invalid = 0;
int i = 0;
while (i < n) {
if (!isReplaced[i]) {
int len = 0;
while (i < n && !isReplaced[i]) {
len++;
i++;
}
invalid += (long long)len * (len + 1) / 2;
} else {
i++;
}
}
long long valid = total - invalid;
if (valid >= k) {
result = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return result;
}
};
class Solution:
def minTime(self, s: str, order: List[int], k: int) -> int:
n = len(s)
total = n * (n + 1) // 2
if total < k:
return -1
left, right = 0, n - 1
result = -1
while left <= right:
mid = (left + right) // 2
is_replaced = [False] * n
for i in range(mid + 1):
is_replaced[order[i]] = True
invalid = 0
i = 0
while i < n:
if not is_replaced[i]:
length = 0
while i < n and not is_replaced[i]:
length += 1
i += 1
invalid += length * (length + 1) // 2
else:
i += 1
valid = total - invalid
if valid >= k:
result = mid
right = mid - 1
else:
left = mid + 1
return result
public class Solution {
public int MinTime(string s, int[] order, int k) {
int n = s.Length;
long total = (long)n * (n + 1) / 2;
if (total < k) return -1;
int left = 0, right = n - 1;
int result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
bool[] isReplaced = new bool[n];
for (int i = 0; i <= mid; i++) {
isReplaced[order[i]] = true;
}
long invalid = 0;
int i = 0;
while (i < n) {
if (!isReplaced[i]) {
int len = 0;
while (i < n && !isReplaced[i]) {
len++;
i++;
}
invalid += (long)len * (len + 1) / 2;
} else {
i++;
}
}
long valid = total - invalid;
if (valid >= k) {
result = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return result;
}
}
var minTime = function(s, order, k) {
const n = s.length;
const total = Math.floor(n * (n + 1) / 2);
if (total < k) return -1;
let left = 0, right = n - 1;
let result = -1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
const isReplaced = new Array(n).fill(false);
for (let i = 0; i <= mid; i++) {
isReplaced[order[i]] = true;
}
let invalid = 0;
let i = 0;
while (i < n) {
if (!isReplaced[i]) {
let len = 0;
while (i < n && !isReplaced[i]) {
len++;
i++;
}
invalid += Math.floor(len * (len + 1) / 2);
} else {
i++;
}
}
const valid = total - invalid;
if (valid >= k) {
result = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return result;
};
复杂度分析
| 复杂度类型 | 分析 |
|---|---|
| 时间复杂度 | O(n log n),其中 n 是字符串长度。二分搜索进行 O(log n) 次,每次需要 O(n) 时间计算有效子字符串数量 |
| 空间复杂度 | O(n),需要额外的布尔数组来标记哪些位置被替换 |