1764. Form Array by Concatenating Subarrays of Another Array
Problem Description
You are given two arrays:
groups: a 2D integer array containingnsubarraysnums: a 1D integer array
Your task is to determine if you can find n disjoint subarrays within nums that match the following conditions:
- The 
i-thsubarray found innumsmust be exactly equal togroups[i](matching element by element) - The subarrays must appear in 
numsin the same order as they appear ingroups- meaning if subarraygroups[i]is found at positionpinnums, then subarraygroups[i+1]must be found at some position afterp + len(groups[i]) - The subarrays must be disjoint - no element in 
numscan belong to more than one matched subarray 
A subarray is defined as a contiguous sequence of elements within an array.
Example visualization:
- If 
groups = [[1,2], [3,4]]andnums = [1,2,5,3,4] - We can find 
[1,2]at indices 0-1 and[3,4]at indices 3-4 - These subarrays are disjoint (don't overlap) and appear in order
 - Therefore, return 
true 
The solution uses a greedy approach with two pointers:
- Pointer 
itracks which group we're currently trying to match - Pointer 
jtracks the current position innums - For each group, we check if it matches the subarray starting at position 
jinnums - If it matches, we move 
jforward by the length of the group and move to the next group - If it doesn't match, we just move 
jforward by 1 and try again - We return 
trueif we successfully match all groups (i == n) 
Intuition
The key insight is that we need to find subarrays in a specific order without overlapping. This naturally suggests a greedy approach - once we find a match for the first group, we should take it immediately and move on to find the next group.
Why does greedy work here? Consider what happens if we skip a valid match for groups[i]:
- We'd have to find another match for 
groups[i]later innums - But this would only push our search position further right
 - Since all subsequent groups must appear after 
groups[i], delaying our match forgroups[i]can never help us - it only reduces the remaining space to find later groups 
The sequential nature of the problem (groups must appear in order) means we can process nums from left to right in a single pass. At each position in nums, we only need to consider:
- Can the current group start here?
 - If yes, consume it and move to the next group
 - If no, try the next position
 
This leads to the two-pointer approach:
- We maintain our position in 
groups(which group we're looking for) - We maintain our position in 
nums(where we're currently searching) - When we find a match, we "consume" the entire subarray by jumping past it
 - The disjoint requirement is automatically satisfied because we jump past each matched subarray
 
The algorithm terminates successfully when we've matched all groups, or fails when we've exhausted nums without finding all groups. The time complexity is O(n * m) in the worst case, where we might need to check each position in nums against each group.
Learn more about Greedy and Two Pointers patterns.
Solution Approach
The implementation uses a two-pointer greedy algorithm to efficiently find matching subarrays:
Variables:
i: pointer tracking which group ingroupswe're currently trying to match (starts at 0)j: pointer tracking our current position innums(starts at 0)n: total number of groups to matchm: length of thenumsarray
Main Algorithm:
while i < n and j < m:
We continue searching as long as we have groups to match (i < n) and positions to check in nums (j < m).
Matching Logic:
g = groups[i]
if g == nums[j : j + len(g)]:
- Extract the current group we're looking for
 - Use Python's slice notation to check if the subarray starting at position 
jmatches the current group - The slice 
nums[j : j + len(g)]extracts exactlylen(g)elements starting from indexj 
Success Case:
j += len(g)
i += 1
When we find a match:
- Jump 
jforward by the length of the matched group (ensuring disjoint subarrays) - Move to the next group by incrementing 
i 
Failure Case:
else: j += 1
When the current position doesn't match:
- Simply move 
jforward by 1 to check the next possible starting position 
Final Check:
return i == n
- If 
i == n, we successfully matched all groups - If 
i < n, we ran out of positions innumsbefore matching all groups 
Example Walkthrough:
For groups = [[1,2], [3,4]] and nums = [1,2,5,3,4]:
- Start: 
i=0, j=0, check if[1,2]matchesnums[0:2]→ Yes! Movejto 2,ito 1 i=1, j=2, check if[3,4]matchesnums[2:4]→ No ([5,3]≠[3,4]), movejto 3i=1, j=3, check if[3,4]matchesnums[3:5]→ Yes! Movejto 5,ito 2i=2equalsn=2, returntrue
The algorithm efficiently handles edge cases like empty groups or insufficient remaining elements in nums through the slice comparison and loop conditions.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a detailed example to understand how the greedy two-pointer approach works.
Example: groups = [[1,2,3], [4,5]] and nums = [1,2,7,1,2,3,4,5,6]
Initial Setup:
i = 0(looking for first group[1,2,3])j = 0(starting at beginning ofnums)n = 2(we have 2 groups to match)m = 9(length ofnums)
Step 1: i=0, j=0
- Current group: 
[1,2,3] - Check: Does 
nums[0:3]=[1,2,7]match[1,2,3]? - No! (third element differs: 7 ≠ 3)
 - Action: Move 
jforward by 1 →j=1 
Step 2: i=0, j=1
- Current group: 
[1,2,3] - Check: Does 
nums[1:4]=[2,7,1]match[1,2,3]? - No! (first element differs: 2 ≠ 1)
 - Action: Move 
jforward by 1 →j=2 
Step 3: i=0, j=2
- Current group: 
[1,2,3] - Check: Does 
nums[2:5]=[7,1,2]match[1,2,3]? - No! (first element differs: 7 ≠ 1)
 - Action: Move 
jforward by 1 →j=3 
Step 4: i=0, j=3
- Current group: 
[1,2,3] - Check: Does 
nums[3:6]=[1,2,3]match[1,2,3]? - Yes! Perfect match!
 - Action:
- Jump 
jforward by length of group (3) →j=6 - Move to next group → 
i=1 
 - Jump 
 
Step 5: i=1, j=6
- Current group: 
[4,5] - Check: Does 
nums[6:8]=[4,5]match[4,5]? - Yes! Perfect match!
 - Action:
- Jump 
jforward by length of group (2) →j=8 - Move to next group → 
i=2 
 - Jump 
 
Final Check:
i = 2which equalsn = 2- We've successfully matched all groups!
 - Return 
true 
Visual representation of the matching process:
nums: [1, 2, 7, 1, 2, 3, 4, 5, 6] ^ ^--------^ ^-----^ | group[0] group[1] | matched matched | at j=3 at j=6 | skipped positions (j=0,1,2)
The key insights from this walkthrough:
- We skip past non-matching positions one by one until we find a match
 - Once we find a match, we "consume" the entire matched subarray by jumping past it
 - The disjoint property is automatically maintained because we never revisit consumed positions
 - The order is preserved because we search for groups sequentially
 
Solution Implementation
1from typing import List
2
3
4class Solution:
5    def canChoose(self, groups: List[List[int]], nums: List[int]) -> bool:
6        """
7        Check if all groups can be found as disjoint subarrays in nums in order.
8      
9        Args:
10            groups: List of integer arrays to find in nums
11            nums: Target array to search within
12          
13        Returns:
14            True if all groups can be chosen from nums in order, False otherwise
15        """
16        num_groups = len(groups)
17        nums_length = len(nums)
18      
19        # Pointer for current group we're trying to match
20        group_index = 0
21        # Pointer for current position in nums array
22        nums_index = 0
23      
24        # Continue while we have groups to match and positions to check
25        while group_index < num_groups and nums_index < nums_length:
26            current_group = groups[group_index]
27            group_length = len(current_group)
28          
29            # Check if current group matches the subarray starting at nums_index
30            if nums[nums_index:nums_index + group_length] == current_group:
31                # Match found: move past this group in nums
32                nums_index += group_length
33                # Move to next group
34                group_index += 1
35            else:
36                # No match: try next position in nums
37                nums_index += 1
38      
39        # All groups matched if we've processed all of them
40        return group_index == num_groups
411class Solution {
2    /**
3     * Determines if we can choose all groups from the groups array as disjoint subarrays from nums array
4     * in the same order they appear in groups.
5     * 
6     * @param groups 2D array where each element is a group to be found in nums
7     * @param nums   The array in which we need to find all groups
8     * @return true if all groups can be found in order as disjoint subarrays, false otherwise
9     */
10    public boolean canChoose(int[][] groups, int[] nums) {
11        int groupCount = groups.length;
12        int numsLength = nums.length;
13      
14        int currentGroupIndex = 0;  // Track which group we're currently trying to match
15        int numsIndex = 0;          // Current position in nums array
16      
17        // Try to find each group in nums
18        while (currentGroupIndex < groupCount && numsIndex < numsLength) {
19            // Check if current group matches at current position in nums
20            if (isGroupMatchAtPosition(groups[currentGroupIndex], nums, numsIndex)) {
21                // Move past this group in nums array
22                numsIndex += groups[currentGroupIndex].length;
23                // Move to next group
24                currentGroupIndex++;
25            } else {
26                // Current position doesn't match, try next position in nums
27                numsIndex++;
28            }
29        }
30      
31        // Return true if we successfully matched all groups
32        return currentGroupIndex == groupCount;
33    }
34  
35    /**
36     * Checks if a group array matches the nums array starting at the given position.
37     * 
38     * @param group         The group array to match
39     * @param nums          The array to match against
40     * @param startPosition Starting position in nums array
41     * @return true if the entire group matches starting at startPosition, false otherwise
42     */
43    private boolean isGroupMatchAtPosition(int[] group, int[] nums, int startPosition) {
44        int groupLength = group.length;
45        int numsLength = nums.length;
46      
47        int groupIndex = 0;
48        int numsIndex = startPosition;
49      
50        // Compare each element of group with corresponding position in nums
51        while (groupIndex < groupLength && numsIndex < numsLength) {
52            if (group[groupIndex] != nums[numsIndex]) {
53                return false;  // Mismatch found
54            }
55            groupIndex++;
56            numsIndex++;
57        }
58      
59        // Return true only if we matched the entire group
60        return groupIndex == groupLength;
61    }
62}
631class Solution {
2public:
3    bool canChoose(vector<vector<int>>& groups, vector<int>& nums) {
4        // Lambda function to check if a group matches at a specific position in nums
5        auto isGroupMatchAtPosition = [&](vector<int>& group, vector<int>& nums, int startPos) {
6            int groupSize = group.size();
7            int numsSize = nums.size();
8            int groupIdx = 0;
9          
10            // Try to match each element of the group with nums starting from startPos
11            while (groupIdx < groupSize && startPos < numsSize) {
12                if (group[groupIdx] != nums[startPos]) {
13                    return false;  // Mismatch found
14                }
15                groupIdx++;
16                startPos++;
17            }
18          
19            // Return true only if we matched the entire group
20            return groupIdx == groupSize;
21        };
22      
23        int totalGroups = groups.size();
24        int numsSize = nums.size();
25        int currentGroupIdx = 0;  // Index of the current group we're trying to match
26      
27        // Iterate through nums array to find all groups in order
28        for (int numsIdx = 0; currentGroupIdx < totalGroups && numsIdx < numsSize;) {
29            // Check if current group matches at current position in nums
30            if (isGroupMatchAtPosition(groups[currentGroupIdx], nums, numsIdx)) {
31                // Match found, move past this group in nums array
32                numsIdx += groups[currentGroupIdx].size();
33                // Move to next group
34                currentGroupIdx++;
35            } else {
36                // No match, try next position in nums
37                numsIdx++;
38            }
39        }
40      
41        // Return true if all groups were successfully matched
42        return currentGroupIdx == totalGroups;
43    }
44};
451function canChoose(groups: number[][], nums: number[]): boolean {
2    // Helper function to check if a group matches starting at a specific position in nums
3    const isGroupMatchAtPosition = (group: number[], nums: number[], startPos: number): boolean => {
4        const groupSize = group.length;
5        const numsSize = nums.length;
6        let groupIdx = 0;
7      
8        // Try to match each element of the group with nums starting from startPos
9        while (groupIdx < groupSize && startPos < numsSize) {
10            if (group[groupIdx] !== nums[startPos]) {
11                return false;  // Mismatch found
12            }
13            groupIdx++;
14            startPos++;
15        }
16      
17        // Return true only if we matched the entire group
18        return groupIdx === groupSize;
19    };
20  
21    const totalGroups = groups.length;
22    const numsSize = nums.length;
23    let currentGroupIdx = 0;  // Index of the current group we're trying to match
24  
25    // Iterate through nums array to find all groups in order
26    for (let numsIdx = 0; currentGroupIdx < totalGroups && numsIdx < numsSize;) {
27        // Check if current group matches at current position in nums
28        if (isGroupMatchAtPosition(groups[currentGroupIdx], nums, numsIdx)) {
29            // Match found, move past this group in nums array
30            numsIdx += groups[currentGroupIdx].length;
31            // Move to next group
32            currentGroupIdx++;
33        } else {
34            // No match, try next position in nums
35            numsIdx++;
36        }
37    }
38  
39    // Return true if all groups were successfully matched
40    return currentGroupIdx === totalGroups;
41}
42Time and Space Complexity
Time Complexity: O(n * m * k) where n is the number of groups, m is the length of the nums array, and k is the average length of each group.
The outer while loop can iterate at most m times (through all elements in nums). For each iteration, when we perform the slice comparison g == nums[j : j + len(g)], this operation takes O(len(g)) time to compare the elements. In the worst case, we might need to check almost every position in nums before finding matches for all groups. Therefore, the overall time complexity is O(n * m * k).
Space Complexity: O(k) where k is the average length of each group.
The space complexity comes from the slice operation nums[j : j + len(g)] which creates a new list of size len(g) for comparison. No other auxiliary data structures are used, and the variables i, j, n, m, and g only use constant space. Therefore, the space complexity is O(k).
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Boundary Check When Slicing
A common mistake is not properly handling the case when the remaining elements in nums are fewer than the current group's length. While Python's slice notation handles out-of-bounds indices gracefully (returning a shorter list), comparing a shorter slice with the group will correctly return False. However, developers might try to "optimize" by adding an explicit length check that could introduce bugs.
Incorrect approach:
# Trying to add premature optimization
if nums_index + len(current_group) > nums_length:
    break  # This might exit too early!
Why it's wrong: This breaks out of the loop entirely, preventing the algorithm from checking if we've matched all groups. The correct behavior is to let the comparison fail naturally and continue incrementing nums_index.
2. Using == vs Element-wise Comparison for Lists
Python's == operator for lists works correctly for our use case, but developers coming from other languages might overthink this and try to implement manual element-wise comparison, introducing unnecessary complexity and potential bugs.
Overcomplicated approach:
# Unnecessary manual comparison
def matches(group, nums_slice):
    if len(group) != len(nums_slice):
        return False
    for i in range(len(group)):
        if group[i] != nums_slice[i]:
            return False
    return True
3. Forgetting to Skip the Entire Matched Group
A critical mistake is incrementing nums_index by only 1 when a match is found, instead of by the group's length. This would violate the disjoint requirement.
Incorrect approach:
if nums[nums_index:nums_index + group_length] == current_group: nums_index += 1 # Wrong! Should be += group_length group_index += 1
Solution: Always increment by group_length to ensure the matched elements aren't reused for subsequent groups.
4. Not Handling Empty Groups Correctly
If a group is empty ([]), the slice comparison nums[j:j+0] returns an empty list, which correctly equals the empty group. However, developers might add special handling that breaks this natural behavior.
Incorrect special case handling:
if len(current_group) == 0:
    continue  # Wrong! This skips the group without proper index updates
Solution: Trust the slice comparison to handle empty groups naturally. The algorithm will correctly match an empty group and move forward.
5. Off-by-One Errors in Final Check
Some developers might check group_index >= num_groups or group_index == num_groups - 1 in the return statement.
Solution: The correct check is group_index == num_groups because group_index represents the number of successfully matched groups, and we need to match exactly num_groups groups.
Which algorithm is best for finding the shortest distance between two points in an unweighted graph?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!