Quick Overview

Implement unrolled-list get and insert with the exact insertion-point split rule, global indices, observable block snapshots, and honest complexity bounds.

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.

Loading coding console...

Show the approach

Approach

Each node owns a capacity-sized character array, an occupied size, and a next link. Track the head, tail, and total size. For an existing index, subtract whole occupied block sizes until the residual is strictly smaller than the current size; equality advances to the following block, enforcing the boundary rule. A get reads that slot. A nonfull insert shifts only the occupied suffix. A full insert copies the suffix beginning at the local offset into a new immediate successor, preserves the old successor link, truncates the original occupied prefix, and appends the new character there. End insertions use the tail or create it. These updates preserve the concatenated sequence and exactly the required block layout; induction over operations proves every later lookup and snapshot correct. Each insert then serializes only occupied slots. With K actual blocks, capacity C, and N occupied characters, get takes O(K), insertion work takes O(K+C), and its snapshot adds O(N+K) time. Appending with the tail costs O(C) on node allocation and O(1) otherwise, before the snapshot. O(N/B) traversal is only an occupancy-based estimate when blocks actually hold around B elements; insertion-point splits can leave very sparse nodes. Initialization costs O(KC); node storage is O(KC), with O(N+K) temporary serialization space plus all returned strings.

Time complexity:
Get: O(K); insert: O(K+C) plus O(N+K) snapshot output; initialization: O(KC).
Space complexity:
O(KC) node storage, O(N+K) snapshot workspace, plus returned output.