title: Check Array Formation Through Concatenation slug: check-array-formation-through-concatenation difficulty: easy leetcode_id: 1640 leetcode_url: https://leetcode.com/problems/check-array-formation-through-concatenation/ categories: - arrays - hash-tables patterns: - greedy function_signature: "def can_form_array(arr: list[int], pieces: list[list[int]]) -> bool:" test_cases: visible: - input: { arr: [15, 88], pieces: [[88], [15]] } expected: true - input: { arr: [49, 18, 16], pieces: [[16, 18, 49]] } expected: false - input: { arr: [91, 4, 64, 78], pieces: [[78], [4, 64], [91]] } expected: true hidden: - input: { arr: [1], pieces: [[1]] } expected: true - input: { arr: [1, 2, 3], pieces: [[1], [2], [3]] } expected: true - input: { arr: [1, 2, 3], pieces: [[2], [1], [3]] } expected: true - input: { arr: [1, 2, 3], pieces: [[3, 2, 1]] } expected: false - input: { arr: [1, 2, 3, 4], pieces: [[1, 2], [3, 4]] } expected: true description: | You are given an array of **distinct** integers `arr` and an array of integer arrays `pieces`, where the integers in `pieces` are **distinct**. Your goal is to form `arr` by concatenating the arrays in `pieces` **in any order**. However, you are **not** allowed to reorder the integers in each array `pieces[i]`. Return `true` *if it is possible to form the array* `arr` *from* `pieces`. Otherwise, return `false`. constraints: | - `1 <= pieces.length <= arr.length <= 100` - `sum(pieces[i].length) == arr.length` - `1 <= pieces[i].length <= arr.length` - `1 <= arr[i], pieces[i][j] <= 100` - The integers in `arr` are **distinct** - The integers in `pieces` are **distinct** (if we flatten `pieces` into a 1D array, all integers are distinct) examples: - input: "arr = [15,88], pieces = [[88],[15]]" output: "true" explanation: "Concatenate [15] then [88] to form [15, 88]." - input: "arr = [49,18,16], pieces = [[16,18,49]]" output: "false" explanation: "Even though the numbers match, we cannot reorder pieces[0]. The piece [16,18,49] would place 16 first, but arr requires 49 first." - input: "arr = [91,4,64,78], pieces = [[78],[4,64],[91]]" output: "true" explanation: "Concatenate [91] then [4,64] then [78] to form [91, 4, 64, 78]." explanation: intuition: | Think of this problem like assembling a jigsaw puzzle where each piece has a fixed internal structure. You have pre-formed "chunks" of numbers that cannot be rearranged internally, but you can choose which chunk to place next. The key insight is that **each number is distinct**, which means every number in `arr` appears in exactly one piece. This is powerful because it means we can uniquely identify which piece should come next by looking at the **first element** of each piece. Imagine walking through `arr` from left to right. At each position, we ask: "Which piece starts with this number?" If we find such a piece, we verify that its subsequent elements match the next positions in `arr`. If they do, we've successfully placed that piece and can move forward. If no piece starts with the current number, or if the piece's elements don't match, it's impossible to form `arr`. The distinctness constraint transforms this from a complex permutation problem into a simple greedy matching problem. approach: | We solve this using a **Hash Map + Greedy Matching** approach: **Step 1: Build a lookup map** - Create a hash map that maps the **first element** of each piece to the entire piece - Since all integers are distinct, each first element uniquely identifies its piece - This allows O(1) lookup to find which piece should be placed next   **Step 2: Iterate through arr greedily** - Start at index `i = 0` in `arr` - Look up `arr[i]` in our hash map to find the piece that should start here - If no piece starts with `arr[i]`, return `false` — we can't form `arr` - If we find a piece, verify that **all elements** in that piece match the corresponding positions in `arr` - If any element doesn't match, return `false` - If all elements match, advance `i` by the length of the piece we just placed   **Step 3: Return the result** - If we successfully process all of `arr` (i.e., `i` reaches `len(arr)`), return `true` - The constraint `sum(pieces[i].length) == arr.length` ensures we'll use all pieces exactly once if successful common_pitfalls: - title: Trying to Reorder Pieces Internally description: | A common misunderstanding is thinking you can rearrange elements within a piece. The problem explicitly states that you **cannot** reorder integers within each `pieces[i]`. For example, with `arr = [49, 18, 16]` and `pieces = [[16, 18, 49]]`, you cannot rearrange the piece to `[49, 18, 16]`. The piece must be used as-is. wrong_approach: "Treating pieces as unordered sets" correct_approach: "Use pieces as fixed-order arrays that must match contiguously" - title: Linear Search for Each Element description: | Without a hash map, you might search through all pieces for each position in `arr`, resulting in O(n × m) complexity where m is the number of pieces. While this would still pass given the small constraints (`n <= 100`), using a hash map reduces lookups to O(1) and makes the solution cleaner and more scalable. wrong_approach: "Nested loops searching all pieces" correct_approach: "Hash map keyed by first element for O(1) lookup" - title: Not Validating the Entire Piece description: | It's tempting to only check if the first element matches, but you must verify that **all subsequent elements** in the piece match the corresponding positions in `arr`. For example, if `arr = [1, 3, 2]` and a piece is `[1, 2, 3]`, the first element `1` matches, but the rest doesn't. You must check the entire piece. wrong_approach: "Only checking the first element of each piece" correct_approach: "Verify all elements in the piece match contiguously in arr" key_takeaways: - "**Distinctness enables unique identification**: When elements are distinct, the first element of each piece uniquely identifies that piece" - "**Hash maps for fast lookup**: Mapping first elements to pieces allows O(1) identification of which piece belongs at each position" - "**Greedy works when there's no choice**: Since each position in `arr` can only match one piece, greedy matching finds the solution if one exists" - "**Constraint exploitation**: The `sum(pieces[i].length) == arr.length` constraint guarantees we'll use all pieces if successful" time_complexity: "O(n). We iterate through `arr` once and perform O(1) lookups. Each element in `pieces` is also checked exactly once during verification." space_complexity: "O(n). We store all pieces in a hash map, where n is the total number of elements across all pieces (which equals `arr.length`)." solutions: - approach_name: Hash Map Lookup is_optimal: true code: | def can_form_array(arr: list[int], pieces: list[list[int]]) -> bool: # Map first element of each piece to the entire piece piece_map = {piece[0]: piece for piece in pieces} i = 0 while i < len(arr): # Find the piece that should start at position i if arr[i] not in piece_map: # No piece starts with this element return False piece = piece_map[arr[i]] # Verify all elements in this piece match arr for val in piece: if i >= len(arr) or arr[i] != val: return False i += 1 return True explanation: | **Time Complexity:** O(n) — Each element in `arr` is visited exactly once. **Space Complexity:** O(n) — The hash map stores all pieces. We build a hash map keyed by the first element of each piece, then greedily match pieces to positions in `arr`. The distinctness constraint ensures each lookup is unambiguous. - approach_name: Brute Force with Linear Search is_optimal: false code: | def can_form_array(arr: list[int], pieces: list[list[int]]) -> bool: i = 0 while i < len(arr): # Find a piece that starts with arr[i] found = False for piece in pieces: if piece[0] == arr[i]: # Check if this piece matches for val in piece: if i >= len(arr) or arr[i] != val: return False i += 1 found = True break if not found: return False return True explanation: | **Time Complexity:** O(n × m) — For each position in `arr`, we potentially search all `m` pieces. **Space Complexity:** O(1) — No additional data structures used. This approach searches through all pieces to find one that starts with the current element. While correct, it's less efficient than the hash map approach. Given the small constraints (`n, m <= 100`), both solutions pass.