Unrolled Linked List with Insertion-Point Splitting
Company: Indeed
Role: Machine Learning Engineer
Category: Coding & Algorithms
Difficulty: hard
Interview Round: Onsite
Implement indexed get and insert for an unrolled linked list. Each node holds a fixed-capacity character array, its occupied size, and a pointer to the next node. Preserve the reported splitting rule: when inserting inside a full node, move the suffix beginning at the insertion position into a new following node, then insert the new character into the original node.
Implement `edit_unrolled_list(blocks: string[], capacity: int, operations: string[][]) -> string[]`.
- Each initial block string contains that node's occupied characters in order. Unused slots are omitted.
- `["get", index]` returns `"VALUE:"` followed by the character at the global zero-based index.
- `["insert", index, value]` inserts one character and returns `"BLOCKS:"` followed by all occupied block strings joined with `|`.
- Numeric indices are valid decimal strings. Return one result per operation.
### Exact insertion rules
1. For insertion before an existing element, locate the block containing that element and convert the global index to its local offset. At a boundary, this chooses the following block at offset zero.
2. If that block has room, shift its suffix right and insert in place.
3. If it is full, create a following block containing its suffix from the local offset onward. Keep the prefix in the original block, append the new character there, and link the new block before the old successor.
4. At global `index == size`, append to the final block if it has room; otherwise append a new block containing the character. Inserting into an empty list creates its first block.
### Constraints & Assumptions
- `1 <= capacity <= 32`. Initial block lengths are from 1 through capacity.
- Values are single uppercase ASCII letters, so `|` cannot occur in a block.
- There are at most 2,000 operations, and the sequence contains at most 5,000 characters at any time.
- All get and insert indices are valid at the time of use. No deletion occurs.
- Do not rebalance into half-full blocks: splitting exactly at the insertion point is part of this task.
- Snapshot serialization and append/boundary rules are explicit practice details. Implement the block structure; its shape is observable in insertion results.
### Examples
```text
blocks = ["IOSAJ"], capacity = 5
operations = [["get","2"],["insert","2","X"],["get","3"]]
result = ["VALUE:S","BLOCKS:IOX|SAJ","VALUE:S"]
```
```text
blocks = ["AB","CD"], capacity = 2
operations = [["insert","2","X"],["insert","5","Y"]]
result = ["BLOCKS:AB|X|CD","BLOCKS:AB|X|CD|Y"]
```
Explain get and insert complexity in the number of blocks and block capacity. State why `O(N/B)` traversal is only an estimate when blocks actually contain around `B` elements, and include snapshot-output cost separately.
```hint The split point is the insertion point
The source rule can leave a one-element original block when insertion happens at offset zero. A balanced split would preserve sequence values but produce the wrong required block layout.
```
Overview: Implement unrolled-list get and insert with the exact insertion-point split rule, global indices, observable block snapshots, and honest complexity bounds.
Read the full Indeed Machine Learning Engineer interview experience this question came from
Implement indexed get and insert for an unrolled linked list. Each node holds a fixed-capacity character array, its occupied size, and a pointer to the next node. Preserve the reported splitting rule: when inserting inside a full node, move the suffix beginning at the insertion position into a new following node, then insert the new character into the original node.
Implement `edit_unrolled_list(blocks: string[], capacity: int, operations: string[][]) -> string[]`.
- Each initial block string contains that node's occupied characters in order. Unused slots are omitted.
- `["get", index]` returns `"VALUE:"` followed by the character at the global zero-based index.
- `["insert", index, value]` inserts one character and returns `"BLOCKS:"` followed by all occupied block strings joined with `|`.
- Numeric indices are valid decimal strings. Return one result per operation.
### Exact insertion rules
1. For insertion before an existing element, locate the block containing that element and convert the global index to its local offset. At a boundary, this chooses the following block at offset zero.
2. If that block has room, shift its suffix right and insert in place.
3. If it is full, create a following block containing its suffix from the local offset onward. Keep the prefix in the original block, append the new character there, and link the new block before the old successor.
4. At global `index == size`, append to the final block if it has room; otherwise append a new block containing the character. Inserting into an empty list creates its first block.
### Constraints & Assumptions
- `1 <= capacity <= 32`. Initial block lengths are from 1 through capacity.
- Values are single uppercase ASCII letters, so `|` cannot occur in a block.
- There are at most 2,000 operations, and the sequence contains at most 5,000 characters at any time.
- All get and insert indices are valid at the time of use. No deletion occurs.
- Do not rebalance into half-full blocks: splitting exactly at the insertion point is part of this task.
- Snapshot serialization and append/boundary rules are explicit practice details. Implement the block structure; its shape is observable in insertion results.
### Examples
```text
blocks = ["IOSAJ"], capacity = 5
operations = [["get","2"],["insert","2","X"],["get","3"]]
result = ["VALUE:S","BLOCKS:IOX|SAJ","VALUE:S"]
```
```text
blocks = ["AB","CD"], capacity = 2
operations = [["insert","2","X"],["insert","5","Y"]]
result = ["BLOCKS:AB|X|CD","BLOCKS:AB|X|CD|Y"]
```
Explain get and insert complexity in the number of blocks and block capacity. State why `O(N/B)` traversal is only an estimate when blocks actually contain around `B` elements, and include snapshot-output cost separately.
```hint The split point is the insertion point
The source rule can leave a one-element original block when insertion happens at offset zero. A balanced split would preserve sequence values but produce the wrong required block layout.
```
Constraints
- 1 <= capacity <= 32; every initial block has 1 through capacity uppercase ASCII characters.
- At most 2000 operations and at most 5000 occupied characters at any time.
- All decimal indices are valid when used; no deletion occurs.
- Insertion-point splits and following-block boundary selection are exact; do not rebalance.
Examples
Input: (['IOSAJ'], 5, [['get', '2'], ['insert', '2', 'X'], ['get', '3']])
Expected Output: ['VALUE:S', 'BLOCKS:IOX|SAJ', 'VALUE:S']
Explanation: The full block splits at offset two; the suffix starts with S.
Input: (['AB', 'CD'], 2, [['insert', '2', 'X'], ['insert', '5', 'Y']])
Expected Output: ['BLOCKS:AB|X|CD', 'BLOCKS:AB|X|CD|Y']
Explanation: A boundary selects the following block; appending after a full final block creates a node.