Hard
题目描述
给定一个由小写英文字母和特殊字符 ‘*’、’#’ 和 ‘%’ 组成的字符串 s。
同时给定一个整数 k。
根据以下规则从左到右处理字符串 s 来构建新字符串 result:
- 如果字符是小写英文字母,将其追加到
result中。 - ‘*’ 从
result中移除最后一个字符(如果存在)。 - ‘#’ 复制当前的
result并将其追加到自身。 - ‘%’ 反转当前的
result。
返回最终字符串 result 的第 k 个字符。如果 k 超出了 result 的边界,返回 ‘.’。
示例 1:
输入:s = "a#b%*", k = 1
输出:"a"
解释:
- 处理 'a':追加 'a' → result = "a"
- 处理 '#':复制结果 → result = "aa"
- 处理 'b':追加 'b' → result = "aab"
- 处理 '%':反转结果 → result = "baa"
- 处理 '*':移除最后字符 → result = "ba"
最终结果是 "ba",索引 k = 1 处的字符是 'a'。
示例 2:
输入:s = "cd%#*#", k = 3
输出:"d"
示例 3:
输入:s = "z*#", k = 0
输出:"."
约束条件:
1 <= s.length <= 10^5s只包含小写英文字母和特殊字符 ‘*’、’#’、’%’0 <= k <= 10^15- 处理
s后结果的长度不会超过10^15
解题思路
这道题的关键在于理解字符串长度可能达到 10^15,直接模拟会导致内存和时间复杂度过高。我们需要采用逆向思维来解决。
核心思路:
- 正向计算长度:首先遍历字符串,计算每一步操作后的字符串长度,但不构建实际字符串
- 逆向定位字符:从后往前遍历操作,根据当前位置 k 和字符串长度来逆推原始位置
逆向操作规则:
- 遇到
#:当前长度是前一步的2倍,通过取模运算确定 k 在前半部分还是后半部分 - 遇到
%:反转操作,k 的新位置为length - 1 - k - 遇到
*:长度增加1(逆向看是减少1的逆操作) - 遇到字母:如果 k 正好等于当前长度-1,说明找到了目标字符
优化点:
- 使用长度数组记录每步操作后的长度
- 逆向遍历时动态调整 k 的位置
- 特殊处理边界情况(k 超出范围)
这种方法避免了构建巨大字符串,时间复杂度为 O(n),空间复杂度为 O(n)。
代码实现
class Solution {
public:
char processStr(string s, long long k) {
vector<long long> lengths;
long long len = 0;
// 正向计算每步的长度
for (char c : s) {
if (c >= 'a' && c <= 'z') {
len++;
} else if (c == '*') {
len = max(0LL, len - 1);
} else if (c == '#') {
len *= 2;
} else if (c == '%') {
// 反转不改变长度
}
lengths.push_back(len);
}
if (k >= len) return '.';
// 逆向查找
for (int i = s.length() - 1; i >= 0; i--) {
if (s[i] >= 'a' && s[i] <= 'z') {
if (k == lengths[i] - 1) {
return s[i];
}
} else if (s[i] == '*') {
k++;
} else if (s[i] == '#') {
long long prevLen = (i > 0) ? lengths[i-1] : 0;
k %= prevLen;
} else if (s[i] == '%') {
long long currentLen = (i > 0) ? lengths[i-1] : 0;
k = currentLen - 1 - k;
}
}
return '.';
}
};
class Solution:
def processStr(self, s: str, k: int) -> str:
lengths = []
length = 0
# 正向计算每步的长度
for c in s:
if c.islower():
length += 1
elif c == '*':
length = max(0, length - 1)
elif c == '#':
length *= 2
elif c == '%':
# 反转不改变长度
pass
lengths.append(length)
if k >= length:
return '.'
# 逆向查找
for i in range(len(s) - 1, -1, -1):
if s[i].islower():
if k == lengths[i] - 1:
return s[i]
elif s[i] == '*':
k += 1
elif s[i] == '#':
prev_len = lengths[i-1] if i > 0 else 0
k %= prev_len
elif s[i] == '%':
current_len = lengths[i-1] if i > 0 else 0
k = current_len - 1 - k
return '.'
public class Solution {
public char ProcessStr(string s, long k) {
List<long> lengths = new List<long>();
long length = 0;
// 正向计算每步的长度
foreach (char c in s) {
if (char.IsLower(c)) {
length++;
} else if (c == '*') {
length = Math.Max(0, length - 1);
} else if (c == '#') {
length *= 2;
} else if (c == '%') {
// 反转不改变长度
}
lengths.Add(length);
}
if (k >= length) return '.';
// 逆向查找
for (int i = s.Length - 1; i >= 0; i--) {
if (char.IsLower(s[i])) {
if (k == lengths[i] - 1) {
return s[i];
}
} else if (s[i] == '*') {
k++;
} else if (s[i] == '#') {
long prevLen = i > 0 ? lengths[i-1] : 0;
k %= prevLen;
} else if (s[i] == '%') {
long currentLen = i > 0 ? lengths[i-1] : 0;
k = currentLen - 1 - k;
}
}
return '.';
}
}
var processStr = function(s, k) {
let operations = [];
let length = 0;
let reversed = false;
for (let char of s) {
if (char === '*') {
if (length > 0) {
operations.push({type: 'remove'});
length--;
}
} else if (char === '#') {
operations.push({type: 'duplicate', len: length});
length *= 2;
} else if (char === '%') {
operations.push({type: 'reverse'});
reversed = !reversed;
} else {
operations.push({type: 'add', char: char});
length++;
}
}
if (k >= length) return '.';
function findChar(pos, ops, currentReversed) {
let currentLen = 0;
let stack = [];
for (let op of ops) {
if (op.type === 'add') {
stack.push({type: 'char', char: op.char, pos: currentLen});
currentLen++;
} else if (op.type === 'remove') {
if (currentLen > 0) {
stack.pop();
currentLen--;
}
} else if (op.type === 'duplicate') {
let oldLen = op.len;
if (pos < oldLen) {
continue;
} else {
return findChar(pos - oldLen, ops.slice(0, ops.indexOf(op)), currentReversed);
}
} else if (op.type === 'reverse') {
currentReversed = !currentReversed;
pos = currentLen - 1 - pos;
}
}
if (currentReversed) {
pos = currentLen - 1 - pos;
}
return stack[pos].char;
}
let pos = k;
let currentOps = [...operations];
let currentReversed = false;
while (true) {
let newOps = [];
let currentLen = 0;
let found = false;
for (let i = 0; i < currentOps.length; i++) {
let op = currentOps[i];
if (op.type === 'add') {
if (pos === currentLen) {
return op.char;
}
newOps.push(op);
currentLen++;
} else if (op.type === 'remove') {
if (currentLen > 0) {
newOps.push(op);
currentLen--;
}
} else if (op.type === 'duplicate') {
if (pos < currentLen) {
currentOps = newOps;
found = true;
break;
} else {
pos = pos - currentLen;
currentOps = newOps;
found = true;
break;
}
} else if (op.type === 'reverse') {
pos = currentLen - 1 - pos;
newOps.push(op);
}
}
if (!found) {
currentOps = newOps;
break;
}
}
for (let op of currentOps) {
if (op.type === 'add' && op.pos === pos) {
return op.char;
}
}
return '.';
};
复杂度分析
| 项目 | 复杂度 |
|---|---|
| 时间复杂度 | O(n) |
| 空间复杂度 | O(n) |
其中 n 是字符串 s 的长度。我们需要遍历字符串两次(正向计算长度,逆向查找字符),并使用额外的数组存储每步的长度信息。