Check Array Formation Through Concatenation
EasyPrompt
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.
Example 1:
Input: arr = [15,88], pieces = [[88],[15]]
Output: true
Explanation: Concatenate [15] then [88]Example 2:
Input: arr = [49,18,16], pieces = [[16,18,49]]
Output: false
Explanation: Even though the numbers match, we cannot reorder pieces[0].Example 3:
Input: arr = [91,4,64,78], pieces = [[78],[4,64],[91]]
Output: true
Explanation: Concatenate [91] then [4,64] then [78]
Constraints:
1 <= pieces.length <= arr.length <= 100sum(pieces[i].length) == arr.length1 <= pieces[i].length <= arr.length1 <= arr[i], pieces[i][j] <= 100- The integers in
arrare distinct. - The integers in
piecesare distinct (i.e., If we flatten pieces in a 1D array, all the integers in this array are distinct).
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves a straightforward, brute-force search. We iterate through the target array arr with a pointer. At each position, we search through the entire pieces array to find a piece that can start at the current position. If a potential piece is found, we verify if it perfectly matches the corresponding segment in arr. If it does, we advance our pointer and repeat the process. If no matching piece is found at any point, we conclude that arr cannot be formed.
Algorithm
- Initialize a pointer
ito 0, which will track the current position inarr. - Loop as long as
iis less than the length ofarr. - Inside the loop, initialize a boolean flag
foundMatchtofalse. - Iterate through each
piecein thepiecesarray.- If the first element of the current
piecematchesarr[i]:- Set
foundMatchtotrue. - Verify that the entire
piecematches the corresponding segment inarrstarting fromi. - If any element does not match, it's impossible to form
arr, so returnfalse. - If the entire
piecematches, advanceiby the length of thepieceand break the inner loop to move to the next segment ofarr.
- Set
- If the first element of the current
- After the inner loop, if
foundMatchis stillfalse, it means no piece starts witharr[i]. Returnfalse. - If the outer loop completes, it means
ihas reached the end ofarr, and the array has been successfully formed. Returntrue.
Walkthrough
The core idea is to build the arr from left to right. We use an index i to keep track of our progress in arr. While i hasn't reached the end of arr, we look for a piece that can be placed at arr[i]. We do this by iterating through all the arrays in pieces. If we find a piece whose first element is arr[i], we then check if the rest of that piece's elements match the subsequent elements in arr. Because all numbers are distinct, there can be at most one such starting piece. If the piece doesn't fully match the segment in arr, we can immediately return false. If it does match, we advance our index i by the length of the matched piece and continue our search for the next piece. If we ever fail to find a piece that starts with arr[i], we also return false. If we successfully reach the end of arr, we return true.
class Solution { public boolean canFormArray(int[] arr, int[][] pieces) { int i = 0; while (i < arr.length) { boolean foundPiece = false; for (int[] piece : pieces) { if (piece[0] == arr[i]) { foundPiece = true; // Check if the rest of the piece matches for (int j = 0; j < piece.length; j++) { if (i + j >= arr.length || arr[i + j] != piece[j]) { return false; } } i += piece.length; break; // Move to the next segment of arr } } if (!foundPiece) { return false; // No piece starts with arr[i] } } return true; }}Complexity
Time
O(M^2 + N), where N is the length of `arr` and M is the number of pieces. The main `while` loop runs M times in total (once for each piece that forms `arr`). In each iteration of the `while` loop, we might iterate through all M pieces to find the one that starts with `arr[i]`. This search contributes O(M^2) to the complexity. The element-wise comparisons across all successful matches sum up to N comparisons in total. Thus, the overall complexity is O(M^2 + N).
Space
O(1), as no additional data structures are used. The space required is limited to a few variables to keep track of the current index and state, which does not depend on the input size.
Trade-offs
Pros
It uses constant extra space, O(1), as it only requires a few variables for indexing and flags.
Cons
The time complexity is relatively high due to the nested iteration. For each segment of
arrthat needs to be matched, the algorithm scans the entirepiecesarray.
This optimized approach significantly improves the time efficiency by using a hash map. Instead of repeatedly searching the pieces array, we first pre-process it by storing all pieces in a hash map. The key for each piece is its first element. This allows us to find the correct piece for any given starting number in constant time on average. We then iterate through arr, and for each segment, we use the map to instantly find the corresponding piece and verify the match.
Algorithm
- Create a
HashMapto map the starting number of each piece to the piece itself. - Iterate through the
piecesarray. For eachpiece, add an entry to the map:map.put(piece[0], piece). - Initialize an index
i = 0to traversearr. - While
iis less thanarr.length:- Look for
arr[i]as a key in the map. If it's not present, returnfalse. - Retrieve the corresponding
piecefrom the map. - Compare the elements of the
piecewith the elements ofarrstarting from indexi. - If there is any mismatch, return
false. - If they match, advance
iby the length of thepiece.
- Look for
- If the loop completes, it means the entire
arrhas been successfully constructed. Returntrue.
Walkthrough
The bottleneck in the brute-force approach is the linear scan to find the right piece. We can eliminate this by using a hash map. We create a map where keys are the first elements of the pieces and values are the piece arrays themselves. This map can be built by iterating through pieces once.
After building the map, we iterate through arr using an index i. For each arr[i], we check if it exists as a key in our map. If not, no piece starts with this number, and we can't form arr, so we return false. If it does exist, we retrieve the piece and verify that it matches the subarray in arr starting at i. If it matches, we advance i by the length of that piece. If it doesn't match, we return false. If we successfully process the entire arr, we return true.
import java.util.HashMap;import java.util.Map; class Solution { public boolean canFormArray(int[] arr, int[][] pieces) { Map<Integer, int[]> map = new HashMap<>(); for (int[] piece : pieces) { map.put(piece[0], piece); } int i = 0; while (i < arr.length) { if (!map.containsKey(arr[i])) { return false; } int[] piece = map.get(arr[i]); for (int val : piece) { if (i >= arr.length || arr[i] != val) { return false; } i++; } } return true; }}Complexity
Time
O(N + M), where N is the length of `arr` and M is the number of pieces. Building the map takes O(M) time. The subsequent traversal of `arr` involves one lookup and one comparison for each element of `arr`. Since the total number of elements is N, this part takes O(N) time. Therefore, the total time complexity is O(N + M). Given that M <= N, this can be simplified to O(N).
Space
O(N), where N is the total number of elements in `pieces` (which equals `arr.length`). The hash map stores M key-value pairs, where M is the number of pieces. The keys and references take O(M) space, and the pieces themselves (which the map values refer to) contain N elements in total.
Trade-offs
Pros
Very efficient time complexity, as finding the correct piece is an O(1) operation on average.
The logic is straightforward once the hash map is built.
Cons
This approach requires extra space to store the hash map. The space complexity is proportional to the number of pieces and their total length.
Solutions
Solution
class Solution {public boolean canFormArray(int[] arr, int[][] pieces) { for (int i = 0; i < arr.length;) { int k = 0; while (k < pieces.length && pieces[k][0] != arr[i]) { ++k; } if (k == pieces.length) { return false; } int j = 0; while (j < pieces[k].length && arr[i] == pieces[k][j]) { ++i; ++j; } } return true; }}/** * @param {number[]} arr * @param {number[][]} pieces * @return {boolean} */ var canFormArray = function (arr, pieces) { const d = new Map(); for (const p of pieces) { d.set(p[0], p); } for (let i = 0; i < arr.length; ) { if (!d.has(arr[i])) { return false; } const p = d.get(arr[i]); for (const v of p) { if (arr[i++] != v) { return false; } } } return true; };class Solution {public: bool canFormArray(vector<int> &arr, vector<vector<int>> &pieces) { for (int i = 0; i < arr.size();) { int k = 0; while (k < pieces.size() && pieces[k][0] != arr[i]) { ++k; } if (k == pieces.size()) { return false; } int j = 0; while (j < pieces[k].size() && arr[i] == pieces[k][j]) { ++i; ++j; } } return true; }};class Solution: def canFormArray(self, arr: List[int], pieces: List[List[int]]) -> bool: i = 0 while i < len(arr): k = 0 while k < len(pieces) and pieces[k][0] != arr[i]: k += 1 if k == len(pieces): return False j = 0 while j < len(pieces[k]) and arr[i] == pieces[k][j]: i, j = i + 1, j + 1 return TrueVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.