3614. Process String with Special Operations II
- String
- Simulation
Intuition
The result can be as long as 10^15, so we cannot build it (that was fine for the easy version, where n <= 20). But we do not need the whole string, only one character. The key observation: the index k in the final string can be traced backwards through the operations to the single original letter that produced it. At each step we only need the length of the buffer, not its contents.
So first compute the length after every step in a forward pass, then walk the operations in reverse, rewriting k to the position it occupied before each operation, until k lands exactly on an appended letter.
Approach
- Forward pass: compute
len[i], the buffer length after stepi:- letter ->
+1 '*'->-1(if non-empty)'#'->*2'%'-> unchanged Cap each length at a value above10^15to avoid overflow; valid inputs never need a larger exact value (see note).
- letter ->
- If
k >= finalLength, return'.'. - Backward pass over steps
i = n-1 .. 0, withbefore = len[i-1](the length before stepi):- letter: if
k == before(the index of the appended letter), return that letter; otherwisekis unchanged. '*':kis unchanged (only a trailing character was removed).'#': ifk >= before, setk -= before(it lies in the duplicated second copy).'%': setk = before - 1 - k(reverse the position).
- letter: if
The backward pass always resolves to a letter because k is a valid in-bounds index.
Complexity
- Time: O(n), one forward pass and one backward pass
- Space: O(n), the array of lengths
Edge Cases
kout of bounds (including an empty final result) ->'.''*'/'#'on an empty buffer -> length stays0'%'mapping relies on the exact pre-op length, which is preserved (never wrongly capped) for valid inputs- Large
knear10^15-> handled with 64-bit integers in Java
Note
Capping intermediate lengths is safe: since '*' removes at most one character, a length exceeding ~10^15 could not be brought back under 10^15 within n <= 10^5 steps, so for inputs satisfying the "final length <= 10^15" guarantee, every relevant length stays well under the cap and is therefore stored exactly. The cap only protects against overflow on adversarial inputs.
Code
class Solution:
def processStr(self, s: str, k: int) -> str:
"""
3614. Process String with Special Operations II
Time: O(n)
Space: O(n)
"""
CAP = 2 * 10**15 # above max k and max length; guards against overflow
# Forward pass: length of the buffer after each step.
lengths = []
cur = 0
for ch in s:
if ch == "*":
if cur > 0:
cur -= 1
elif ch == "#":
cur = min(cur * 2, CAP)
elif ch == "%":
pass
else:
cur = min(cur + 1, CAP)
lengths.append(cur)
if k >= cur:
return "."
# Backward pass: trace k to the letter that produced it.
for i in range(len(s) - 1, -1, -1):
ch = s[i]
before = lengths[i - 1] if i > 0 else 0
if ch == "*":
continue
elif ch == "#":
if k >= before:
k -= before
elif ch == "%":
k = before - 1 - k
else:
if k == before:
return ch
return "."/**
* 3614. Process String with Special Operations II
* Time: O(n)
* Space: O(n)
*/
class Solution {
public String processStr(String s, long k) {
final long CAP = 2_000_000_000_000_000L; // above max k and max length
int n = s.length();
long[] lengths = new long[n];
long cur = 0;
// Forward pass: length of the buffer after each step.
for (int i = 0; i < n; i++) {
char ch = s.charAt(i);
if (ch == '*') {
if (cur > 0) {
cur -= 1;
}
} else if (ch == '#') {
cur = Math.min(cur * 2, CAP);
} else if (ch == '%') {
// length unchanged
} else {
cur = Math.min(cur + 1, CAP);
}
lengths[i] = cur;
}
if (k >= cur) {
return ".";
}
// Backward pass: trace k to the letter that produced it.
for (int i = n - 1; i >= 0; i--) {
char ch = s.charAt(i);
long before = i > 0 ? lengths[i - 1] : 0;
if (ch == '*') {
continue;
} else if (ch == '#') {
if (k >= before) {
k -= before;
}
} else if (ch == '%') {
k = before - 1 - k;
} else {
if (k == before) {
return String.valueOf(ch);
}
}
}
return ".";
}
}