2657. Find the Prefix Common Array of Two Arrays
Problem Description
You are given two permutations A
and B
, both containing integers from 1 to n exactly once. These are 0-indexed arrays of length n
.
Your task is to find the prefix common array C
for these two permutations.
For each position i
in the result array C
:
C[i]
represents the count of numbers that appear in bothA[0...i]
andB[0...i]
- In other words,
C[i]
counts how many distinct numbers are present in both arrays up to and including indexi
Example walkthrough:
If A = [3, 1, 2]
and B = [2, 3, 1]
:
- At index 0:
A[0] = 3
,B[0] = 2
. No common numbers yet, soC[0] = 0
- At index 1:
A[0...1] = [3, 1]
,B[0...1] = [2, 3]
. Number 3 appears in both, soC[1] = 1
- At index 2:
A[0...2] = [3, 1, 2]
,B[0...2] = [2, 3, 1]
. All three numbers (1, 2, 3) appear in both, soC[2] = 3
The result would be C = [0, 1, 3]
.
The solution uses two counters to track the frequency of elements seen so far in each array. At each position i
, it counts elements by finding the minimum occurrence of each number between the two counters - if a number has appeared in both arrays up to position i
, the minimum will be at least 1, contributing to the count.
Intuition
The key insight is that we need to track which numbers have appeared in both arrays up to each position. Since we're dealing with permutations (each number from 1 to n appears exactly once), we know that each number will eventually appear in both arrays.
Think of it this way: as we traverse both arrays simultaneously, we're gradually "collecting" numbers from each array. A number contributes to our count only when it has been seen in both arrays.
This naturally leads us to use counting/frequency tracking. For each position i
:
- We keep track of all numbers we've seen so far in array
A
- We keep track of all numbers we've seen so far in array
B
- A number is "common" at position
i
if it has appeared at least once in both tracking structures
The clever part is using min(cnt1[x], cnt2[x])
for each number x
. Since we're dealing with permutations where each number appears exactly once:
- If a number hasn't appeared in one array yet,
min
will be 0 - If a number has appeared in both arrays,
min
will be 1 - The sum of all these minimums gives us the total count of common numbers
For example, if by position i
, array A
has seen {1, 3, 5}
and array B
has seen {2, 3, 5}
, then:
- Number 1:
min(1, 0) = 0
(only in A) - Number 2:
min(0, 1) = 0
(only in B) - Number 3:
min(1, 1) = 1
(in both) - Number 5:
min(1, 1) = 1
(in both) - Total common = 0 + 0 + 1 + 1 = 2
This approach efficiently solves the problem in a single pass through both arrays.
Solution Approach
The implementation uses a counting approach with two hash maps (Counter objects in Python) to track the occurrences of elements in both arrays.
Step-by-step implementation:
-
Initialize data structures:
- Create an empty result array
ans
to store the prefix common counts - Initialize two Counter objects
cnt1
andcnt2
to track element frequencies for arraysA
andB
respectively
- Create an empty result array
-
Traverse both arrays simultaneously:
- Use
zip(A, B)
to iterate through both arrays in parallel, getting elementsa
fromA
andb
fromB
at each position
- Use
-
Update counters at each position:
- Increment
cnt1[a]
to record that elementa
has been seen in arrayA
- Increment
cnt2[b]
to record that elementb
has been seen in arrayB
- Increment
-
Calculate common count:
- For each element
x
that exists incnt1
, computemin(cnt1[x], cnt2[x])
- This gives us 1 if element
x
has appeared in both arrays, 0 otherwise - Sum all these minimum values:
t = sum(min(v, cnt2[x]) for x, v in cnt1.items())
- For each element
-
Store and continue:
- Append the calculated count
t
to the result arrayans
- Continue this process for all positions in the arrays
- Append the calculated count
-
Return the result:
- After processing all positions, return the complete
ans
array
- After processing all positions, return the complete
Why this works:
- Since we're dealing with permutations, each number appears exactly once in each array
- At any position
i
, a number contributes to the count if and only if it has appeared in both arrays up to that position - The
min
operation effectively checks if a number exists in both counters (returning 1 if yes, 0 if no) - The sum aggregates all common numbers at the current position
Time Complexity: O(n²)
in the worst case, as for each of the n
positions, we might iterate through up to n
elements in cnt1
Space Complexity: O(n)
for storing the counters and result array
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with A = [2, 3, 1]
and B = [3, 1, 2]
.
We'll track two counters and build our result array step by step:
Initial state:
cnt1 = {}
(tracks elements seen in A)cnt2 = {}
(tracks elements seen in B)ans = []
Position 0:
- Process:
a = 2
,b = 3
- Update counters:
cnt1 = {2: 1}
,cnt2 = {3: 1}
- Calculate common count:
- Element 2:
min(1, 0) = 0
(2 is in A but not in B) - Total = 0
- Element 2:
- Append to result:
ans = [0]
Position 1:
- Process:
a = 3
,b = 1
- Update counters:
cnt1 = {2: 1, 3: 1}
,cnt2 = {3: 1, 1: 1}
- Calculate common count:
- Element 2:
min(1, 0) = 0
(only in A) - Element 3:
min(1, 1) = 1
(in both A and B!) - Total = 0 + 1 = 1
- Element 2:
- Append to result:
ans = [0, 1]
Position 2:
- Process:
a = 1
,b = 2
- Update counters:
cnt1 = {2: 1, 3: 1, 1: 1}
,cnt2 = {3: 1, 1: 1, 2: 1}
- Calculate common count:
- Element 2:
min(1, 1) = 1
(now in both!) - Element 3:
min(1, 1) = 1
(still in both) - Element 1:
min(1, 1) = 1
(now in both!) - Total = 1 + 1 + 1 = 3
- Element 2:
- Append to result:
ans = [0, 1, 3]
Final result: [0, 1, 3]
Notice how at each position, we only count elements that have appeared in BOTH arrays up to that point. The min operation elegantly handles this - returning 1 only when an element exists in both counters, and 0 otherwise.
Solution Implementation
1class Solution:
2 def findThePrefixCommonArray(self, A: List[int], B: List[int]) -> List[int]:
3 """
4 Find the prefix common array where each element represents the count of
5 common elements between prefixes of arrays A and B at each index.
6
7 Args:
8 A: First list of integers
9 B: Second list of integers
10
11 Returns:
12 List where result[i] is the count of common elements between
13 A[0:i+1] and B[0:i+1]
14 """
15 result = []
16
17 # Counter to track frequency of elements seen so far in array A
18 frequency_a = Counter()
19
20 # Counter to track frequency of elements seen so far in array B
21 frequency_b = Counter()
22
23 # Process both arrays element by element
24 for element_a, element_b in zip(A, B):
25 # Update the frequency counters for current elements
26 frequency_a[element_a] += 1
27 frequency_b[element_b] += 1
28
29 # Count common elements by taking minimum frequency for each element
30 # that appears in array A's prefix
31 common_count = sum(
32 min(freq_in_a, frequency_b[element])
33 for element, freq_in_a in frequency_a.items()
34 )
35
36 # Append the count of common elements for current prefix
37 result.append(common_count)
38
39 return result
40
1class Solution {
2 public int[] findThePrefixCommonArray(int[] A, int[] B) {
3 int n = A.length;
4 int[] result = new int[n];
5
6 // Frequency arrays to track occurrences of each number (1 to n)
7 // in prefixes of A and B respectively
8 int[] frequencyA = new int[n + 1]; // Index 0 unused, elements are from 1 to n
9 int[] frequencyB = new int[n + 1]; // Index 0 unused, elements are from 1 to n
10
11 // Process each position to build prefix arrays
12 for (int i = 0; i < n; i++) {
13 // Increment frequency for current elements
14 frequencyA[A[i]]++;
15 frequencyB[B[i]]++;
16
17 // Count common elements in current prefixes
18 // An element is common if it appears in both prefix arrays
19 for (int value = 1; value <= n; value++) {
20 // Add minimum occurrence count to get common elements
21 result[i] += Math.min(frequencyA[value], frequencyB[value]);
22 }
23 }
24
25 return result;
26 }
27}
28
1class Solution {
2public:
3 vector<int> findThePrefixCommonArray(vector<int>& A, vector<int>& B) {
4 int n = A.size();
5 vector<int> result(n);
6
7 // Frequency arrays to track occurrences of each number (1 to n)
8 // in prefixes of A and B respectively
9 vector<int> frequencyA(n + 1, 0); // Index 0 unused, 1 to n for values
10 vector<int> frequencyB(n + 1, 0);
11
12 // Process each position from 0 to n-1
13 for (int i = 0; i < n; ++i) {
14 // Increment frequency for current elements
15 ++frequencyA[A[i]];
16 ++frequencyB[B[i]];
17
18 // Count common elements in current prefixes
19 // A number is common if it appears in both prefixes
20 // The count of common occurrences is the minimum of its frequencies
21 for (int value = 1; value <= n; ++value) {
22 result[i] += min(frequencyA[value], frequencyB[value]);
23 }
24 }
25
26 return result;
27 }
28};
29
1/**
2 * Finds the prefix common array between two permutations A and B.
3 * For each prefix of length i+1, counts how many numbers appear in both prefixes.
4 *
5 * @param A - First permutation array of numbers from 1 to n
6 * @param B - Second permutation array of numbers from 1 to n
7 * @returns Array where ans[i] represents the count of common elements in prefixes A[0..i] and B[0..i]
8 */
9function findThePrefixCommonArray(A: number[], B: number[]): number[] {
10 const arrayLength: number = A.length;
11
12 // Frequency count arrays for elements in A and B (1-indexed to match element values)
13 const frequencyArrayA: number[] = Array(arrayLength + 1).fill(0);
14 const frequencyArrayB: number[] = Array(arrayLength + 1).fill(0);
15
16 // Result array to store common element counts for each prefix
17 const result: number[] = Array(arrayLength).fill(0);
18
19 // Process each position in the arrays
20 for (let i = 0; i < arrayLength; ++i) {
21 // Update frequency counts for current elements
22 ++frequencyArrayA[A[i]];
23 ++frequencyArrayB[B[i]];
24
25 // Calculate common elements count for current prefix
26 // Check all possible values from 1 to n
27 for (let value = 1; value <= arrayLength; ++value) {
28 // Add minimum frequency of each value (represents common occurrences)
29 result[i] += Math.min(frequencyArrayA[value], frequencyArrayB[value]);
30 }
31 }
32
33 return result;
34}
35
Time and Space Complexity
The time complexity is O(n²)
, and the space complexity is O(n)
.
Time Complexity Analysis:
- The outer loop iterates through both arrays
A
andB
simultaneously usingzip
, which runsn
times wheren
is the length of the arrays. - Inside each iteration:
- Updating
cnt1[a]
andcnt2[b]
takesO(1)
time. - The key operation is
sum(min(v, cnt2[x]) for x, v in cnt1.items())
, which iterates through all items incnt1
. - In the worst case, after processing
i
elements,cnt1
containsi
unique elements. - Therefore, at iteration
i
, this sum operation takesO(i)
time.
- Updating
- The total time complexity is
O(1) + O(2) + O(3) + ... + O(n) = O(n(n+1)/2) = O(n²)
.
Space Complexity Analysis:
- Two Counter objects
cnt1
andcnt2
are created to store the frequency of elements. - In the worst case, all elements in arrays
A
andB
are unique, so each Counter can store up ton
key-value pairs. - The
ans
list storesn
results. - Therefore, the total space complexity is
O(n) + O(n) + O(n) = O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Inefficient Iteration Over All Counter Elements
The Pitfall:
The current solution iterates through all elements in frequency_a
at each position, even though most elements haven't appeared yet. Since we're processing permutations of numbers 1 to n, early in the iteration, most counter entries are zero, making the iteration unnecessarily expensive.
Why It's Problematic:
- At position i, we're iterating through up to i+1 elements in the counter
- This leads to O(n²) time complexity even though we could optimize further
- We're checking elements that we know haven't appeared in array A yet
Optimized Solution: Instead of iterating through the entire counter, we can track common elements more efficiently by checking only when an element appears in both arrays:
class Solution:
def findThePrefixCommonArray(self, A: List[int], B: List[int]) -> List[int]:
result = []
seen_a = set()
seen_b = set()
common_count = 0
for element_a, element_b in zip(A, B):
# Add current elements to their respective sets
seen_a.add(element_a)
seen_b.add(element_b)
# Check if the new elements create new common elements
if element_a in seen_b:
common_count += 1
if element_b in seen_a and element_a != element_b:
common_count += 1
result.append(common_count)
return result
2. Using Counter When Sets Would Suffice
The Pitfall: Since we're dealing with permutations where each element appears exactly once, using Counters adds unnecessary overhead. The frequency will always be either 0 or 1.
Why It's Problematic:
- Counter objects have more overhead than sets
- The
min
operation is redundant since values are always 0 or 1 - We're essentially using Counters as expensive sets
Better Approach: Use sets to track which elements have been seen, making the logic clearer and more efficient:
class Solution:
def findThePrefixCommonArray(self, A: List[int], B: List[int]) -> List[int]:
result = []
seen_a = set()
seen_b = set()
for element_a, element_b in zip(A, B):
seen_a.add(element_a)
seen_b.add(element_b)
# Count intersection size directly
common_count = len(seen_a & seen_b)
result.append(common_count)
return result
This approach:
- Reduces time complexity to O(n) average case for set operations
- Uses less memory with sets instead of Counters
- Makes the intent clearer - we're finding intersection of two sets
What does the following code do?
1def f(arr1, arr2):
2 i, j = 0, 0
3 new_arr = []
4 while i < len(arr1) and j < len(arr2):
5 if arr1[i] < arr2[j]:
6 new_arr.append(arr1[i])
7 i += 1
8 else:
9 new_arr.append(arr2[j])
10 j += 1
11 new_arr.extend(arr1[i:])
12 new_arr.extend(arr2[j:])
13 return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2 int i = 0, j = 0;
3 List<Integer> newArr = new ArrayList<>();
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.add(arr1[i]);
8 i++;
9 } else {
10 newArr.add(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.add(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.add(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
1function f(arr1, arr2) {
2 let i = 0, j = 0;
3 let newArr = [];
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.push(arr1[i]);
8 i++;
9 } else {
10 newArr.push(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.push(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.push(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
Recommended Readings
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!