{x}
blog image

Queries on a Permutation With Key

Given the array queries of positive integers between 1 and m, you have to process all queries[i] (from i=0 to i=queries.length-1) according to the following rules:

  • In the beginning, you have the permutation P=[1,2,3,...,m].
  • For the current i, find the position of queries[i] in the permutation P (indexing from 0) and then move this at the beginning of the permutation P. Notice that the position of queries[i] in P is the result for queries[i].

Return an array containing the result for the given queries.

 

Example 1:

Input: queries = [3,1,2,1], m = 5
Output: [2,1,2,1] 
Explanation: The queries are processed as follow: 
For i=0: queries[i]=3, P=[1,2,3,4,5], position of 3 in P is 2, then we move 3 to the beginning of P resulting in P=[3,1,2,4,5]. 
For i=1: queries[i]=1, P=[3,1,2,4,5], position of 1 in P is 1, then we move 1 to the beginning of P resulting in P=[1,3,2,4,5]. 
For i=2: queries[i]=2, P=[1,3,2,4,5], position of 2 in P is 2, then we move 2 to the beginning of P resulting in P=[2,1,3,4,5]. 
For i=3: queries[i]=1, P=[2,1,3,4,5], position of 1 in P is 1, then we move 1 to the beginning of P resulting in P=[1,2,3,4,5]. 
Therefore, the array containing the result is [2,1,2,1].  

Example 2:

Input: queries = [4,1,2,2], m = 4
Output: [3,1,2,0]

Example 3:

Input: queries = [7,5,5,8,3], m = 8
Output: [6,5,0,7,5]

 

Constraints:

  • 1 <= m <= 10^3
  • 1 <= queries.length <= m
  • 1 <= queries[i] <= m

Solution Explanation for Queries on a Permutation With Key

This problem involves processing a series of queries, each requiring finding the index of a specific number in a permutation and then moving that number to the beginning of the permutation. We'll explore two approaches: simulation and using a Binary Indexed Tree (BIT).

Approach 1: Simulation

This approach directly simulates the process described in the problem statement. For each query:

  1. We find the index of the queried number within the current permutation.
  2. This index is the result for the query, so we store it.
  3. We remove the queried number from its current position.
  4. We insert the queried number at the beginning of the permutation.

This approach is straightforward and easy to understand. However, its time complexity is not optimal.

Time Complexity: O(m*q), where 'm' is the size of the permutation and 'q' is the number of queries. This is because finding the index in a list takes O(m) in the worst case, and this operation is repeated for each query.

Space Complexity: O(m), the space used to store the permutation.

Code Examples (Simulation):

The provided code examples in Python, Java, C++, and Go all implement this simulation approach. They differ slightly in syntax and data structure usage (lists vs. vectors vs. arrays), but the core logic remains the same. The Python example is shown below for illustration:

class Solution:
    def processQueries(self, queries: List[int], m: int) -> List[int]:
        p = list(range(1, m + 1))  # Initialize permutation
        ans = []
        for v in queries:
            j = p.index(v)         # Find index of v
            ans.append(j)         # Store index as result
            p.pop(j)              # Remove v from its position
            p.insert(0, v)        # Insert v at the beginning
        return ans

Approach 2: Binary Indexed Tree (BIT)

This approach utilizes a Binary Indexed Tree (BIT) to optimize the process of finding the index of the queried number. A BIT allows for efficient prefix sum queries and updates. We can represent the permutation as a BIT, where each element's value represents its position in the permutation.

Here's how it works:

  1. Initialization: We initialize a BIT with size m + q (m is the size of the initial permutation and q is the number of queries). We assign initial positions to each element in the permutation within the BIT.
  2. Query Processing: For each query:
    • We use the BIT's query() method to efficiently find the current position (index) of the queried number. This is the result for the query.
    • We update the BIT to reflect the change in the permutation (moving the queried number to the front). This involves removing the element from its old position and adding it at the new position (index 0).
  3. Result: The accumulated results from each query form the final output array.

Time Complexity: O(q*log(m+q)), where 'q' is the number of queries and 'm' is the size of the initial permutation. This is because each query involves a query() and an update() operation on the BIT, each taking O(log(m+q)) time.

Space Complexity: O(m+q), the space used to store the BIT and auxiliary data structures.

Code Examples (Binary Indexed Tree):

The provided code examples demonstrate the BIT implementation in Python, Java, C++, and Go. The code is more complex due to the BIT's implementation, but it achieves better performance for larger inputs. A simplified illustration in Python is shown below:

class BinaryIndexedTree:  # ... (BIT implementation as shown in the original response) ...
 
class Solution:
    def processQueries(self, queries: List[int], m: int) -> List[int]:
        # ... (Initialization and query processing logic using BIT as explained above) ...

The full BIT implementations (including the BinaryIndexedTree class) are in the original response. They handle the details of BIT updates and queries. Note that the implementation uses a slightly modified approach where positions are tracked and updated within the BIT, directly resulting in the index.

In summary, the simulation approach is simpler but less efficient, while the Binary Indexed Tree approach offers better time complexity for larger inputs, at the cost of increased code complexity. The choice between approaches depends on the expected input size and the prioritization of code readability versus performance.