Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions problems/3614-process-string-with-special-operations-ii/analysis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# 3614. Process String with Special Operations II

[LeetCode Link](https://leetcode.com/problems/process-string-with-special-operations-ii/)

Difficulty: Hard
Topics: String, Simulation

Acceptance Rate: 26.9%

## Hints

### Hint 1

Read the constraints before reaching for the obvious approach. The string `s` is short
(at most `10^5`), but the final `result` can grow to `10^15` characters because of the
duplication operator `'#'`. Actually building `result` is impossible — you would run out
of memory long before you finished. Ask yourself: do you really need the *whole* string,
or just *one* character of it?

### Hint 2

Since you only need the `k`-th character, think about working **backwards**. If you knew
the length of `result` after every operation, could you trace where index `k` "came from"
as you undo each operation one at a time? This is a classic trick for problems where the
structure explodes in size but only a single position is queried.

### Hint 3

Do two passes. **Pass 1 (forward):** compute the length of `result` after each operation
— this is cheap and never builds the string. **Pass 2 (backward):** start with the target
index `pos = k` and walk the operations from last to first, rewriting `pos` to be the index
it occupied in the *previous* `result`. Each operator has a simple inverse mapping on the
index:

- A letter appended at the end occupies index `prevLen`; if `pos == prevLen`, that letter
*is* your answer.
- `'#'` (duplicate) doubled the string, so if `pos >= prevLen`, subtract `prevLen` to fold
it back into the first half.
- `'%'` (reverse) maps `pos` to `prevLen - 1 - pos`.
- `'*'` (remove) only chopped the tail, so a valid `pos` is unchanged.

When `pos` lands exactly on an appended letter, you are done.

## Approach

The whole difficulty here is the size of `result`. Letters add one character, `'*'` removes
one, `'%'` is length-preserving, and `'#'` *doubles* the length. After enough `'#'`
operations the string is astronomically large, so simulation is out. But the question only
asks for a single index `k`, which lets us avoid materializing the string entirely.

**Pass 1 — record lengths.** Walk `s` left to right and maintain a running length `cur`:

- lowercase letter: `cur++`
- `'*'`: `cur--` if `cur > 0` (otherwise it stays 0)
- `'#'`: `cur *= 2`
- `'%'`: unchanged

Store `lengths[i]` = length of `result` immediately after processing `s[i]`. The final
length is `cur`. If `k >= cur`, index `k` is out of bounds and the answer is `'.'`.

**Pass 2 — trace the index backwards.** Set `pos = k` (a valid index into the final
`result`). Iterate `i` from `n-1` down to `0`. Let `prev` be the length *before* operation
`i`, which is `lengths[i-1]` (or `0` when `i == 0`). Rewrite `pos` according to the inverse
of `s[i]`:

- **letter:** appending put that letter at index `prev`. If `pos == prev`, the letter
`s[i]` is exactly the character we are tracking — return it. Otherwise `pos < prev`, and
`pos` refers to a character already present before the append, so leave it unchanged.
- **`'*'`:** the operation removed the last character, so every surviving index is identical
to its index in the previous string. Leave `pos` unchanged.
- **`'#'`:** the previous string was concatenated with itself. Indices `[0, prev)` come from
the first copy; indices `[prev, 2*prev)` come from the second copy. If `pos >= prev`,
subtract `prev` to map it back onto the original copy.
- **`'%'`:** the string was reversed, so index `pos` came from index `prev - 1 - pos`.

Because every valid `pos` either resolves to an appended letter or is carried back to the
start, by the time we finish the loop we will have returned a letter. (If we somehow fall
through, returning `'.'` is a safe guard.)

**Why it works:** at every step `pos` is maintained as "the index in the current `result`
of the character we ultimately want." The inverse maps above are exact, so following them
to the beginning lands us on the precise letter that produced that position. We never store
more than the `lengths` array.

**Walkthrough (Example 2):** `s = "cd%#*#"`, `k = 3`. Forward lengths are
`[1, 2, 2, 4, 3, 6]`, final length `6`, so `k = 3` is in bounds. Tracing backward with
`pos = 3`:

- `i=5 '#'`, `prev=3`: `pos >= 3` → `pos = 0`
- `i=4 '*'`, `prev=4`: unchanged → `pos = 0`
- `i=3 '#'`, `prev=2`: `pos < 2` → `pos = 0`
- `i=2 '%'`, `prev=2`: `pos = 2 - 1 - 0 = 1`
- `i=1 'd'`, `prev=1`: `pos == 1` → return `'d'`. ✓

## Complexity Analysis

Time Complexity: O(n), where `n = len(s)` — one forward pass to build lengths and one
backward pass to trace the index.
Space Complexity: O(n) for the `lengths` array.

## Edge Cases

- **`k` out of bounds:** if `k >= finalLength` (including the case where `result` ends up
empty), return `'.'`. Example 3 (`"z*#"`, `k = 0`) ends with an empty string.
- **`'*'` on an empty string:** removing from an empty `result` is a no-op; guard with
`if cur > 0` so the length never goes negative.
- **`'#'` on an empty string:** duplicating an empty string stays empty (`0 * 2 = 0`), which
the length recurrence handles naturally.
- **Large indices:** `k` can be up to `10^15` and the length up to `10^15`, so use 64-bit
integers (`int64`) for both the running length and `pos` to avoid overflow.
- **`'%'` mapping near the boundary:** the reverse map `prev - 1 - pos` must use `prev`, the
length *before* the reverse (which equals the length after, since reverse preserves
length), to stay within bounds.
97 changes: 97 additions & 0 deletions problems/3614-process-string-with-special-operations-ii/problem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
number: "3614"
frontend_id: "3614"
title: "Process String with Special Operations II"
slug: "process-string-with-special-operations-ii"
difficulty: "Hard"
topics:
- "String"
- "Simulation"
acceptance_rate: 2686.3
is_premium: false
created_at: "2026-06-17T05:23:09.270497+00:00"
fetched_at: "2026-06-17T05:23:09.270497+00:00"
link: "https://leetcode.com/problems/process-string-with-special-operations-ii/"
date: "2026-06-17"
---

# 3614. Process String with Special Operations II

You are given a string `s` consisting of lowercase English letters and the special characters: `'*'`, `'#'`, and `'%'`.

You are also given an integer `k`.

Build a new string `result` by processing `s` according to the following rules from left to right:

* If the letter is a **lowercase** English letter append it to `result`.
* A `'*'` **removes** the last character from `result`, if it exists.
* A `'#'` **duplicates** the current `result` and **appends** it to itself.
* A `'%'` **reverses** the current `result`.



Return the `kth` character of the final string `result`. If `k` is out of the bounds of `result`, return `'.'`.



**Example 1:**

**Input:** s = "a#b%*", k = 1

**Output:** "a"

**Explanation:**

`i` | `s[i]` | Operation | Current `result`
---|---|---|---
0 | `'a'` | Append `'a'` | `"a"`
1 | `'#'` | Duplicate `result` | `"aa"`
2 | `'b'` | Append `'b'` | `"aab"`
3 | `'%'` | Reverse `result` | `"baa"`
4 | `'*'` | Remove the last character | `"ba"`

The final `result` is `"ba"`. The character at index `k = 1` is `'a'`.

**Example 2:**

**Input:** s = "cd%#*#", k = 3

**Output:** "d"

**Explanation:**

`i` | `s[i]` | Operation | Current `result`
---|---|---|---
0 | `'c'` | Append `'c'` | `"c"`
1 | `'d'` | Append `'d'` | `"cd"`
2 | `'%'` | Reverse `result` | `"dc"`
3 | `'#'` | Duplicate `result` | `"dcdc"`
4 | `'*'` | Remove the last character | `"dcd"`
5 | `'#'` | Duplicate `result` | `"dcddcd"`

The final `result` is `"dcddcd"`. The character at index `k = 3` is `'d'`.

**Example 3:**

**Input:** s = "z*#", k = 0

**Output:** "."

**Explanation:**

`i` | `s[i]` | Operation | Current `result`
---|---|---|---
0 | `'z'` | Append `'z'` | `"z"`
1 | `'*'` | Remove the last character | `""`
2 | `'#'` | Duplicate the string | `""`

The final `result` is `""`. Since index `k = 0` is out of bounds, the output is `'.'`.



**Constraints:**

* `1 <= s.length <= 105`
* `s` consists of only lowercase English letters and special characters `'*'`, `'#'`, and `'%'`.
* `0 <= k <= 1015`
* The length of `result` after processing `s` will not exceed `1015`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package main

// Process String with Special Operations II (LeetCode 3614)
//
// The final `result` can grow to 10^15 characters because of the '#'
// duplication operator, so we never build it. Instead we:
//
// 1. Forward pass: record the length of result after each operation.
// 2. Backward pass: starting from index k, undo each operation, rewriting
// the index to the position it occupied in the previous string. When the
// index lands on an appended letter, that letter is the answer.
//
// Runs in O(n) time and O(n) space, where n = len(s).
func processStr(s string, k int64) byte {
n := len(s)

// Pass 1: length of result after processing each character.
lengths := make([]int64, n)
var cur int64
for i := 0; i < n; i++ {
switch s[i] {
case '*':
if cur > 0 {
cur--
}
case '#':
cur *= 2
case '%':
// reverse: length unchanged
default:
cur++ // lowercase letter
}
lengths[i] = cur
}

// Out of bounds (covers the empty-result case as well).
if k >= cur {
return '.'
}

// Pass 2: trace index k back to the letter that produced it.
pos := k
for i := n - 1; i >= 0; i-- {
var prev int64
if i > 0 {
prev = lengths[i-1]
}
switch s[i] {
case '*':
// Removed the tail; surviving indices are unchanged.
case '#':
// Duplicated; fold the second copy back onto the first.
if pos >= prev {
pos -= prev
}
case '%':
// Reversed; mirror the index.
pos = prev - 1 - pos
default:
// Letter appended at index prev.
if pos == prev {
return s[i]
}
}
}

return '.'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package main

import "testing"

func TestSolution(t *testing.T) {
tests := []struct {
name string
s string
k int64
expected byte
}{
// Examples from the problem statement.
{"example 1: remove after reverse, result \"ba\"", "a#b%*", 1, 'a'},
{"example 2: duplicate/remove/duplicate, result \"dcddcd\"", "cd%#*#", 3, 'd'},
{"example 3: result empty, k out of bounds", "z*#", 0, '.'},

// Additional edge cases.
{"edge case: single letter, k in bounds", "x", 0, 'x'},
{"edge case: single letter, k out of bounds", "x", 1, '.'},
{"edge case: remove on empty result stays empty", "*", 0, '.'},
{"edge case: duplicate then index into second copy", "ab#", 2, 'a'},
{"edge case: reverse a simple string", "abc%", 0, 'c'},
{"edge case: large k via duplication", "a##", 3, 'a'},
{"edge case: every char removed", "abc***", 0, '.'},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := processStr(tt.s, tt.k)
if result != tt.expected {
t.Errorf("processStr(%q, %d) = %q, want %q",
tt.s, tt.k, result, tt.expected)
}
})
}
}