1409. Queries on a Permutation With Key
Problem Description
You are given an array queries
containing positive integers between 1
and m
, and you need to process each query in order.
Initially, you have a permutation P = [1, 2, 3, ..., m]
.
For each query queries[i]
:
- Find the current position (0-indexed) of the value
queries[i]
in permutationP
- Record this position as the result for this query
- Move the element
queries[i]
from its current position to the beginning of permutationP
The task is to return an array containing the positions found for all queries.
For example, if m = 5
and queries = [3, 1, 2]
:
- Initial
P = [1, 2, 3, 4, 5]
- Query 1: Find
3
at position2
, move it to front →P = [3, 1, 2, 4, 5]
, result =2
- Query 2: Find
1
at position1
, move it to front →P = [1, 3, 2, 4, 5]
, result =1
- Query 3: Find
2
at position2
, move it to front →P = [2, 1, 3, 4, 5]
, result =2
- Final answer:
[2, 1, 2]
The solution simulates this process directly by maintaining the permutation list, finding the index of each query value, recording it, then removing and reinserting the element at the front.
Intuition
The problem asks us to track positions of elements as they get moved to the front of a list. The key observation is that we're essentially simulating a "move-to-front" data structure, where recently accessed elements are prioritized by placing them at the beginning.
Since we need to:
- Find where an element currently is
- Move it to the front
- Track its position before moving
The most straightforward approach is to directly simulate these operations. We maintain the permutation as a list and perform the exact operations described: find the index, record it, remove the element, and insert it at position 0.
Why does simulation work here? Looking at the constraints (with m
likely being reasonably small), the cost of operations like index()
, pop()
, and insert()
on a list is acceptable. For each query, we perform:
O(m)
to find the element's positionO(m)
to remove it from its current positionO(m)
to insert it at the front
With n
queries, this gives us O(n * m)
time complexity, which is feasible for the problem's scale.
The beauty of this solution is its clarity - the code directly mirrors what the problem asks us to do, making it easy to understand and verify correctness. While more sophisticated data structures could optimize this further, the direct simulation is sufficient and keeps the implementation simple.
Solution Approach
The solution uses direct simulation to process each query, maintaining the permutation as a Python list.
Initial Setup:
- Create the initial permutation
p = [1, 2, 3, ..., m]
usinglist(range(1, m + 1))
- Initialize an empty result list
ans
to store positions
Processing Each Query:
For each value v
in queries
:
-
Find Position: Use
p.index(v)
to find the current position of valuev
in the permutation. This returns the 0-based index wherev
is located. -
Record Result: Append this position
j
to our answer listans.append(j)
. -
Move to Front:
- Remove the element from its current position using
p.pop(j)
- Insert it at the beginning using
p.insert(0, v)
- Remove the element from its current position using
Data Structure Choice:
The solution uses a Python list to represent the permutation. While lists have O(n)
complexity for index lookup, removal, and insertion operations, the simplicity of implementation makes this approach practical for the problem's constraints.
Step-by-step Example:
If m = 4
and queries = [3, 1]
:
- Start:
p = [1, 2, 3, 4]
- Query
3
: Found at index2
, move to front →p = [3, 1, 2, 4]
, record2
- Query
1
: Found at index1
, move to front →p = [1, 3, 2, 4]
, record1
- Return
[2, 1]
The simulation approach directly implements the problem's requirements without any optimization tricks, making it easy to understand and verify while being efficient enough for the given constraints.
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 concrete example with m = 5
and queries = [3, 1, 2, 1]
.
Initial Setup:
- Create permutation:
p = [1, 2, 3, 4, 5]
- Result array:
ans = []
Processing Query 1: value = 3
- Find position:
p.index(3)
returns2
(3 is at index 2) - Record result:
ans = [2]
- Remove 3 from index 2:
p.pop(2)
→p = [1, 2, 4, 5]
- Insert 3 at front:
p.insert(0, 3)
→p = [3, 1, 2, 4, 5]
Processing Query 2: value = 1
- Find position:
p.index(1)
returns1
(1 is at index 1) - Record result:
ans = [2, 1]
- Remove 1 from index 1:
p.pop(1)
→p = [3, 2, 4, 5]
- Insert 1 at front:
p.insert(0, 1)
→p = [1, 3, 2, 4, 5]
Processing Query 3: value = 2
- Find position:
p.index(2)
returns2
(2 is at index 2) - Record result:
ans = [2, 1, 2]
- Remove 2 from index 2:
p.pop(2)
→p = [1, 3, 4, 5]
- Insert 2 at front:
p.insert(0, 2)
→p = [2, 1, 3, 4, 5]
Processing Query 4: value = 1
- Find position:
p.index(1)
returns1
(1 is at index 1) - Record result:
ans = [2, 1, 2, 1]
- Remove 1 from index 1:
p.pop(1)
→p = [2, 3, 4, 5]
- Insert 1 at front:
p.insert(0, 1)
→p = [1, 2, 3, 4, 5]
Final Answer: [2, 1, 2, 1]
Notice how elements that were recently queried stay near the front of the permutation, while unqueried elements (like 4 and 5) gradually shift toward the back. This "move-to-front" behavior ensures frequently accessed elements require less searching in subsequent queries.
Solution Implementation
1class Solution:
2 def processQueries(self, queries: List[int], m: int) -> List[int]:
3 # Initialize permutation array with values from 1 to m
4 permutation = list(range(1, m + 1))
5
6 # Store the results for each query
7 result = []
8
9 # Process each query value
10 for query_value in queries:
11 # Find the index of the current query value in the permutation
12 current_index = permutation.index(query_value)
13
14 # Add the index to the result
15 result.append(current_index)
16
17 # Remove the value from its current position
18 permutation.pop(current_index)
19
20 # Move the value to the front of the permutation
21 permutation.insert(0, query_value)
22
23 return result
24
1class Solution {
2 public int[] processQueries(int[] queries, int m) {
3 // Initialize a list to represent the permutation array
4 // Initially contains values from 1 to m in ascending order
5 List<Integer> permutation = new LinkedList<>();
6 for (int i = 1; i <= m; i++) {
7 permutation.add(i);
8 }
9
10 // Array to store the result - position of each query value
11 int[] result = new int[queries.length];
12
13 // Process each query
14 for (int queryIndex = 0; queryIndex < queries.length; queryIndex++) {
15 int queryValue = queries[queryIndex];
16
17 // Find the current position of the query value in the permutation
18 int position = permutation.indexOf(queryValue);
19
20 // Store the position in the result array
21 result[queryIndex] = position;
22
23 // Move the query value to the front of the permutation
24 // First remove it from its current position
25 permutation.remove(position);
26 // Then add it at the beginning (index 0)
27 permutation.add(0, queryValue);
28 }
29
30 return result;
31 }
32}
33
1class Solution {
2public:
3 vector<int> processQueries(vector<int>& queries, int m) {
4 // Initialize permutation array with values 1 to m
5 vector<int> permutation(m);
6 iota(permutation.begin(), permutation.end(), 1);
7
8 // Store the result positions
9 vector<int> result;
10
11 // Process each query value
12 for (int queryValue : queries) {
13 // Find the position of current query value in permutation
14 int position = 0;
15 for (int i = 0; i < m; ++i) {
16 if (permutation[i] == queryValue) {
17 position = i;
18 break;
19 }
20 }
21
22 // Add the position to result
23 result.push_back(position);
24
25 // Move the found element to the front of permutation
26 // First, remove the element from its current position
27 permutation.erase(permutation.begin() + position);
28 // Then, insert it at the beginning
29 permutation.insert(permutation.begin(), queryValue);
30 }
31
32 return result;
33 }
34};
35
1function processQueries(queries: number[], m: number): number[] {
2 // Initialize permutation array with values from 1 to m
3 const permutation: number[] = [];
4 for (let i = 1; i <= m; i++) {
5 permutation.push(i);
6 }
7
8 // Store the result positions
9 const result: number[] = [];
10
11 // Process each query value
12 for (const queryValue of queries) {
13 // Find the position of current query value in permutation
14 let position = 0;
15 for (let i = 0; i < m; i++) {
16 if (permutation[i] === queryValue) {
17 position = i;
18 break;
19 }
20 }
21
22 // Add the position to result
23 result.push(position);
24
25 // Move the found element to the front of permutation
26 // First, remove the element from its current position
27 permutation.splice(position, 1);
28 // Then, insert it at the beginning
29 permutation.unshift(queryValue);
30 }
31
32 return result;
33}
34
Time and Space Complexity
Time Complexity: O(n * m)
where n
is the length of queries and m
is the size of the initial permutation.
- The outer loop iterates through all queries:
O(n)
- For each query, we perform:
p.index(v)
: Linear search through the list p, which can have up tom
elements:O(m)
p.pop(j)
: Removing an element at index j requires shifting all elements after it:O(m)
p.insert(0, v)
: Inserting at the beginning requires shifting all elements:O(m)
- Total:
O(n) * O(m) = O(n * m)
Space Complexity: O(m)
- The list
p
storesm
elements:O(m)
- The answer list
ans
storesn
elements:O(n)
, but this is considered output space and typically not counted in space complexity analysis - No additional data structures are used that scale with input size
- Total auxiliary space:
O(m)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Confusing 1-indexed Values with 0-indexed Positions
A frequent mistake is mixing up the fact that the permutation contains values from 1
to m
(1-indexed), while the positions we need to return are 0-indexed.
Incorrect approach:
# Wrong: Trying to use the value directly as an index position = permutation[query_value] # This would cause IndexError or wrong result
Correct approach:
# Right: Find the index (position) of the value position = permutation.index(query_value)
2. Modifying the List While Iterating Without Proper Handling
Some developers might try to optimize by combining operations incorrectly:
Problematic approach:
# Attempting to remove and insert in one line might lead to errors for query_value in queries: result.append(permutation.index(query_value)) permutation = [query_value] + [x for x in permutation if x != query_value] # This creates a new list each time, which is inefficient
Better approach:
# Use pop() and insert() for in-place modifications current_index = permutation.index(query_value) result.append(current_index) permutation.pop(current_index) permutation.insert(0, query_value)
3. Not Handling the Order of Operations Correctly
The order matters: you must find the index BEFORE moving the element.
Wrong order:
for query_value in queries: # Moving first, then finding index - completely wrong! permutation.remove(query_value) permutation.insert(0, query_value) result.append(permutation.index(query_value)) # Always returns 0!
Correct order:
for query_value in queries: # Find index first current_index = permutation.index(query_value) result.append(current_index) # Then move the element permutation.pop(current_index) permutation.insert(0, query_value)
4. Using remove() Instead of pop() Without Considering Performance
While both work functionally, there's a subtle difference:
Less efficient:
current_index = permutation.index(query_value) result.append(current_index) permutation.remove(query_value) # Searches again for the value permutation.insert(0, query_value)
More efficient:
current_index = permutation.index(query_value) result.append(current_index) permutation.pop(current_index) # Directly removes at known index permutation.insert(0, query_value)
The pop()
approach avoids searching for the element twice since we already know its index.
Which of the following is a min heap?
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!