Facebook Pixel

1649. Create Sorted Array through Instructions

Problem Description

You need to build a sorted array by inserting elements one by one from a given integer array instructions. Starting with an empty array nums, you process each element from instructions in order from left to right.

For each element you insert, there's a cost associated with the insertion. The cost is calculated as the minimum of:

  • The count of elements in nums that are strictly less than the current element
  • The count of elements in nums that are strictly greater than the current element

For example, if you're inserting 3 into nums = [1,2,3,5]:

  • Elements less than 3: 1 and 2 (count = 2)
  • Elements greater than 3: 5 (count = 1)
  • Cost = min(2, 1) = 1
  • After insertion: nums = [1,2,3,3,5]

The goal is to calculate the total cost of inserting all elements from instructions into nums. Since the final answer can be very large, return it modulo 10^9 + 7.

The solution uses a Binary Indexed Tree (Fenwick Tree) to efficiently track and query the count of elements. The BIT maintains frequencies of values, allowing us to:

  • Query how many elements are less than a value x using tree.query(x - 1)
  • Calculate elements greater than x as i - tree.query(x) where i is the total number of elements inserted so far
  • Update the frequency count when inserting a new element using tree.update(x, 1)

This approach achieves O(n log m) time complexity where n is the length of instructions and m is the maximum value in the array.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is recognizing that we need to efficiently count elements less than and greater than a given value as we build our array. If we were to use a simple sorted list and binary search for each insertion, we'd face O(n) insertion time, leading to O(n^2) overall complexity.

We observe that:

  1. We don't actually need to maintain the sorted array itself - we only need to track how many of each value we've seen
  2. For any value x, we need two pieces of information:
    • Count of all values strictly less than x
    • Count of all values strictly greater than x

This naturally leads us to think about frequency counting. If we maintain frequencies, then:

  • Count of elements less than x = sum of frequencies from 1 to x-1
  • Count of elements greater than x = total elements inserted - count of elements less than or equal to x

The challenge becomes: how do we efficiently update frequencies and calculate prefix sums? A naive approach would require O(m) time for each prefix sum query where m is the range of values.

This is where the Binary Indexed Tree (Fenwick Tree) becomes the perfect data structure. It allows us to:

  • Update a frequency in O(log m) time
  • Query a prefix sum (count of elements ≤ some value) in O(log m) time

The BIT works by storing partial sums in a clever way using binary representation. Each index in the BIT is responsible for a range of elements determined by the lowest set bit in its binary representation. This structure enables both updates and queries to traverse only O(log m) nodes.

By using the BIT to track frequencies and compute range sums efficiently, we reduce the overall complexity from O(n^2) to O(n log m), making the solution scalable for large inputs.

Learn more about Segment Tree, Binary Search, Divide and Conquer and Merge Sort patterns.

Solution Approach

The solution implements a Binary Indexed Tree (Fenwick Tree) to efficiently track element frequencies and compute range queries.

Binary Indexed Tree Implementation:

The BinaryIndexedTree class maintains an array c of size n+1 where:

  • c[x] stores partial sums based on the binary representation of index x
  • Each index is responsible for a range determined by its lowest set bit

Update Operation (update(x, v)):

while x <= self.n:
    self.c[x] += v
    x += x & -x
  • Adds value v to position x
  • x & -x extracts the lowest set bit (e.g., 12 & -12 = 4)
  • Updates all nodes that include position x in their range
  • Traverses up the tree by adding the lowest set bit

Query Operation (query(x)):

s = 0
while x:
    s += self.c[x]
    x -= x & -x
return s
  • Computes the prefix sum from positions 1 to x
  • Traverses down by removing the lowest set bit each iteration
  • Accumulates partial sums from responsible nodes

Main Algorithm:

  1. Initialize BIT: Create a tree with size equal to the maximum value in instructions

    m = max(instructions)
    tree = BinaryIndexedTree(m)
  2. Process each instruction: For each element x at position i:

    a. Calculate insertion cost:

    cost = min(tree.query(x - 1), i - tree.query(x))
    • tree.query(x - 1): Count of elements strictly less than x
    • tree.query(x): Count of elements less than or equal to x
    • i - tree.query(x): Count of elements strictly greater than x
    • The cost is the minimum of these two counts

    b. Update the tree:

    tree.update(x, 1)
    • Increment the frequency of value x by 1

    c. Accumulate total cost:

    ans += cost
  3. Return result modulo 10^9 + 7:

    return ans % mod

Time Complexity: O(n log m) where n is the length of instructions and m is the maximum value

  • Each update and query operation takes O(log m)
  • We perform n iterations

Space Complexity: O(m) for the BIT array

The elegance of this solution lies in using the BIT to maintain a dynamic frequency array that supports efficient range sum queries, avoiding the need to maintain an actual sorted array.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with instructions = [1, 5, 6, 2].

Initial Setup:

  • Maximum value m = 6, so we create a BIT of size 6
  • BIT array c = [0, 0, 0, 0, 0, 0, 0] (index 0 unused)
  • Total cost = 0

Step 1: Insert 1 (i = 0)

  • Query elements < 1: tree.query(0) = 0
  • Query elements ≤ 1: tree.query(1) = 0
  • Elements > 1: 0 - 0 = 0
  • Cost = min(0, 0) = 0
  • Update BIT with value 1: tree.update(1, 1)
    • Update index 1: c[1] += 1c = [0, 1, 0, 0, 0, 0, 0]
    • Next index: 1 + (1 & -1) = 2
    • Update index 2: c[2] += 1c = [0, 1, 1, 0, 0, 0, 0]
    • Next index: 2 + (2 & -2) = 4
    • Update index 4: c[4] += 1c = [0, 1, 1, 0, 1, 0, 0]
  • Total cost = 0

Step 2: Insert 5 (i = 1)

  • Query elements < 5: tree.query(4)
    • At index 4: accumulate c[4] = 1
    • Total = 1
  • Elements > 5: 1 - tree.query(5) = 1 - 1 = 0
  • Cost = min(1, 0) = 0
  • Update BIT with value 5: tree.update(5, 1)
    • Update index 5: c[5] += 1c = [0, 1, 1, 0, 1, 1, 0]
    • Next index: 5 + (5 & -5) = 6
    • Update index 6: c[6] += 1c = [0, 1, 1, 0, 1, 1, 1]
  • Total cost = 0

Step 3: Insert 6 (i = 2)

  • Query elements < 6: tree.query(5)
    • At index 5: accumulate c[5] = 1
    • Next: 5 - (5 & -5) = 4
    • At index 4: accumulate c[4] = 1
    • Total = 2
  • Elements > 6: 2 - tree.query(6) = 2 - 2 = 0
  • Cost = min(2, 0) = 0
  • Update BIT with value 6: tree.update(6, 1)
    • Update index 6: c[6] += 1c = [0, 1, 1, 0, 1, 1, 2]
  • Total cost = 0

Step 4: Insert 2 (i = 3)

  • Query elements < 2: tree.query(1)
    • At index 1: accumulate c[1] = 1
    • Total = 1
  • Query elements ≤ 2: tree.query(2)
    • At index 2: accumulate c[2] = 1
    • Total = 1
  • Elements > 2: 3 - 1 = 2
  • Cost = min(1, 2) = 1
  • Update BIT with value 2: tree.update(2, 1)
    • Update index 2: c[2] += 1c = [0, 1, 2, 0, 1, 1, 2]
    • Next index: 2 + (2 & -2) = 4
    • Update index 4: c[4] += 1c = [0, 1, 2, 0, 2, 1, 2]
  • Total cost = 1

Final Result: Total cost = 1

The sorted array would be [1, 2, 5, 6] with insertion costs of [0, 0, 0, 1].

Solution Implementation

1class BinaryIndexedTree:
2    """Fenwick Tree (Binary Indexed Tree) for efficient prefix sum queries and updates."""
3  
4    def __init__(self, n: int) -> None:
5        """
6        Initialize a Binary Indexed Tree with size n.
7      
8        Args:
9            n: The maximum value that can be indexed (1-indexed)
10        """
11        self.size = n
12        self.tree = [0] * (n + 1)  # 1-indexed array for BIT
13
14    def update(self, index: int, delta: int) -> None:
15        """
16        Add delta to the value at index and update the tree accordingly.
17      
18        Args:
19            index: The position to update (1-indexed)
20            delta: The value to add at the position
21        """
22        while index <= self.size:
23            self.tree[index] += delta
24            # Move to next index by adding the lowest set bit
25            index += index & (-index)
26
27    def query(self, index: int) -> int:
28        """
29        Get the prefix sum from 1 to index (inclusive).
30      
31        Args:
32            index: The end position for prefix sum (1-indexed)
33      
34        Returns:
35            The sum of elements from position 1 to index
36        """
37        prefix_sum = 0
38        while index > 0:
39            prefix_sum += self.tree[index]
40            # Move to parent by removing the lowest set bit
41            index -= index & (-index)
42        return prefix_sum
43
44
45class Solution:
46    def createSortedArray(self, instructions: List[int]) -> int:
47        """
48        Calculate minimum cost to create sorted array using instructions.
49      
50        For each element, the cost is the minimum of:
51        - Count of strictly smaller elements already inserted
52        - Count of strictly larger elements already inserted
53      
54        Args:
55            instructions: List of integers to insert in order
56      
57        Returns:
58            Total minimum cost modulo 10^9 + 7
59        """
60        # Find maximum value to determine BIT size
61        max_value = max(instructions)
62      
63        # Initialize Binary Indexed Tree to track element frequencies
64        bit = BinaryIndexedTree(max_value)
65      
66        total_cost = 0
67        MOD = 10**9 + 7
68      
69        for position, value in enumerate(instructions):
70            # Count elements strictly less than current value
71            smaller_count = bit.query(value - 1)
72          
73            # Count elements strictly greater than current value
74            # Total elements so far minus elements less than or equal to current
75            greater_count = position - bit.query(value)
76          
77            # Add minimum cost for this insertion
78            cost = min(smaller_count, greater_count)
79            total_cost += cost
80          
81            # Update BIT to include current element
82            bit.update(value, 1)
83      
84        return total_cost % MOD
85
1/**
2 * Binary Indexed Tree (Fenwick Tree) for efficient range sum queries and updates
3 */
4class BinaryIndexedTree {
5    private int size;           // Size of the tree
6    private int[] tree;         // Tree array (1-indexed)
7
8    /**
9     * Constructor to initialize the Binary Indexed Tree
10     * @param n The maximum value that can be stored
11     */
12    public BinaryIndexedTree(int n) {
13        this.size = n;
14        this.tree = new int[n + 1];  // 1-indexed array
15    }
16
17    /**
18     * Updates the value at index x by adding delta
19     * @param x The index to update (1-indexed)
20     * @param delta The value to add at index x
21     */
22    public void update(int x, int delta) {
23        // Traverse upward through parent nodes
24        while (x <= size) {
25            tree[x] += delta;
26            x += x & -x;  // Move to next index by adding lowest set bit
27        }
28    }
29
30    /**
31     * Queries the prefix sum from index 1 to x
32     * @param x The ending index for the range sum (1-indexed)
33     * @return The sum of elements from index 1 to x
34     */
35    public int query(int x) {
36        int sum = 0;
37        // Traverse downward through parent nodes
38        while (x > 0) {
39            sum += tree[x];
40            x -= x & -x;  // Move to parent index by removing lowest set bit
41        }
42        return sum;
43    }
44}
45
46/**
47 * Solution class for creating sorted array with minimum cost
48 */
49class Solution {
50    /**
51     * Calculates minimum cost to create sorted array by inserting elements
52     * Cost of inserting an element = min(count of smaller elements, count of larger elements)
53     * @param instructions Array of integers to be inserted
54     * @return Total minimum cost modulo 10^9 + 7
55     */
56    public int createSortedArray(int[] instructions) {
57        // Find the maximum value in instructions array
58        int maxValue = 0;
59        for (int value : instructions) {
60            maxValue = Math.max(maxValue, value);
61        }
62      
63        // Initialize Binary Indexed Tree with range [1, maxValue]
64        BinaryIndexedTree fenwickTree = new BinaryIndexedTree(maxValue);
65      
66        int totalCost = 0;
67        final int MOD = (int) 1e9 + 7;
68      
69        // Process each instruction
70        for (int i = 0; i < instructions.length; ++i) {
71            int currentValue = instructions[i];
72          
73            // Count elements less than currentValue
74            int smallerCount = fenwickTree.query(currentValue - 1);
75          
76            // Count elements greater than currentValue
77            // Total elements inserted so far = i
78            // Elements <= currentValue = fenwickTree.query(currentValue)
79            // Elements > currentValue = i - fenwickTree.query(currentValue)
80            int greaterCount = i - fenwickTree.query(currentValue);
81          
82            // Choose minimum cost between smaller and greater count
83            int cost = Math.min(smallerCount, greaterCount);
84          
85            // Add cost to total with modulo to prevent overflow
86            totalCost = (totalCost + cost) % MOD;
87          
88            // Insert current value into the tree (increment count by 1)
89            fenwickTree.update(currentValue, 1);
90        }
91      
92        return totalCost;
93    }
94}
95
1class BinaryIndexedTree {
2public:
3    // Constructor: Initialize BIT with size n
4    BinaryIndexedTree(int size) 
5        : tree_size(size), tree_array(size + 1, 0) {}
6
7    // Update: Add delta to position index and propagate changes up the tree
8    void update(int index, int delta) {
9        // Traverse all affected nodes in the BIT
10        while (index <= tree_size) {
11            tree_array[index] += delta;
12            // Move to next node that this index affects
13            // Adding the lowest set bit moves to parent in BIT structure
14            index += index & (-index);
15        }
16    }
17
18    // Query: Calculate prefix sum from index 1 to index
19    int query(int index) {
20        int sum = 0;
21        // Traverse down the tree accumulating values
22        while (index > 0) {
23            sum += tree_array[index];
24            // Move to previous range by removing the lowest set bit
25            index -= index & (-index);
26        }
27        return sum;
28    }
29
30private:
31    int tree_size;           // Maximum index value the tree can handle
32    vector<int> tree_array;  // Internal array storing the BIT (1-indexed)
33};
34
35class Solution {
36public:
37    int createSortedArray(vector<int>& instructions) {
38        // Find the maximum value to determine BIT size
39        int max_value = *max_element(instructions.begin(), instructions.end());
40      
41        // Initialize Binary Indexed Tree with size equal to max value
42        BinaryIndexedTree fenwick_tree(max_value);
43      
44        const int MOD = 1e9 + 7;
45        int total_cost = 0;
46      
47        // Process each instruction sequentially
48        for (int i = 0; i < instructions.size(); ++i) {
49            int current_value = instructions[i];
50          
51            // Count elements strictly less than current_value
52            int smaller_count = fenwick_tree.query(current_value - 1);
53          
54            // Count elements strictly greater than current_value
55            // Total elements so far (i) minus elements <= current_value
56            int greater_count = i - fenwick_tree.query(current_value);
57          
58            // Choose minimum cost between removing smaller or greater elements
59            int min_cost = min(smaller_count, greater_count);
60          
61            // Add cost to total with modulo to prevent overflow
62            total_cost = (total_cost + min_cost) % MOD;
63          
64            // Mark that current_value has been added to the array
65            fenwick_tree.update(current_value, 1);
66        }
67      
68        return total_cost;
69    }
70};
71
1// Binary Indexed Tree (Fenwick Tree) for efficient range sum queries
2// Array to store the tree values
3let treeSize: number;
4let treeArray: number[];
5
6/**
7 * Initialize the Binary Indexed Tree with given size
8 * @param n - The size of the tree
9 */
10function initializeBIT(n: number): void {
11    treeSize = n;
12    // Create array with size n+1 (1-indexed for easier BIT operations)
13    treeArray = new Array(n + 1).fill(0);
14}
15
16/**
17 * Update the value at position x by adding v
18 * @param x - The position to update (1-indexed)
19 * @param v - The value to add
20 */
21function update(x: number, v: number): void {
22    // Traverse all affected nodes in the tree
23    while (x <= treeSize) {
24        treeArray[x] += v;
25        // Move to next node using lowbit operation
26        // x & -x gives the rightmost set bit
27        x += x & -x;
28    }
29}
30
31/**
32 * Query the prefix sum from index 1 to x
33 * @param x - The ending position for the sum (1-indexed)
34 * @returns The sum of elements from 1 to x
35 */
36function query(x: number): number {
37    let sum = 0;
38    // Traverse the tree backwards to calculate prefix sum
39    while (x > 0) {
40        sum += treeArray[x];
41        // Move to parent node by removing the rightmost set bit
42        x -= x & -x;
43    }
44    return sum;
45}
46
47/**
48 * Create a sorted array with minimum cost
49 * Cost is the minimum of elements less than current or greater than current
50 * @param instructions - Array of numbers to insert
51 * @returns The minimum total cost modulo 10^9 + 7
52 */
53function createSortedArray(instructions: number[]): number {
54    // Find the maximum value to determine tree size
55    const maxValue = Math.max(...instructions);
56  
57    // Initialize the Binary Indexed Tree
58    initializeBIT(maxValue);
59  
60    let totalCost = 0;
61    const MOD = 10 ** 9 + 7;
62  
63    // Process each instruction
64    for (let i = 0; i < instructions.length; ++i) {
65        const currentValue = instructions[i];
66      
67        // Count elements less than current value
68        const elementsLessThan = query(currentValue - 1);
69      
70        // Count elements greater than current value
71        // Total elements inserted so far minus elements less than or equal to current
72        const elementsGreaterThan = i - query(currentValue);
73      
74        // Choose minimum cost between shifting left or right
75        const minCost = Math.min(elementsLessThan, elementsGreaterThan);
76      
77        // Add cost to total with modulo operation
78        totalCost = (totalCost + minCost) % MOD;
79      
80        // Mark this value as inserted by incrementing its count
81        update(currentValue, 1);
82    }
83  
84    return totalCost;
85}
86

Time and Space Complexity

Time Complexity: O(n * log m) where n is the length of the instructions array and m is the maximum value in the instructions array.

  • The main loop iterates through all n instructions
  • For each instruction, we perform:
    • Two query operations: tree.query(x - 1) and tree.query(x), each taking O(log m) time
    • One update operation: tree.update(x, 1), which takes O(log m) time
  • The Binary Indexed Tree operations (update and query) have O(log m) complexity because:
    • In update, we traverse up the tree by adding x & -x (the least significant bit), which takes at most log m steps
    • In query, we traverse down by subtracting x & -x, which also takes at most log m steps
  • Finding the maximum value initially takes O(n) time
  • Total: O(n) + n * O(log m) = O(n * log m)

Space Complexity: O(m) where m is the maximum value in the instructions array.

  • The Binary Indexed Tree uses an array c of size m + 1, requiring O(m) space
  • The instructions input array is given and not counted as extra space
  • Other variables (ans, mod, i, x, cost) use O(1) space
  • Total: O(m)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Off-by-One Error in Counting Strictly Less/Greater Elements

A frequent mistake is incorrectly calculating the count of elements strictly less than or strictly greater than the current value.

Incorrect Implementation:

# Wrong: This counts elements less than OR EQUAL to value
smaller_count = bit.query(value)  # Should be value - 1

# Wrong: This counts elements greater than OR EQUAL to value  
greater_count = position - bit.query(value - 1)  # Should be position - bit.query(value)

Correct Implementation:

# Count elements STRICTLY less than value
smaller_count = bit.query(value - 1)

# Count elements STRICTLY greater than value
greater_count = position - bit.query(value)

2. Using 0-Indexed Values with 1-Indexed BIT

Binary Indexed Trees typically use 1-based indexing, but the problem values might include 0 or use 0-based indexing.

Problem Scenario:

instructions = [0, 1, 2, 3]  # Contains 0
bit = BinaryIndexedTree(max(instructions))  # Size = 3
bit.update(0, 1)  # Error: BIT doesn't handle index 0

Solution: Shift all values by 1 when working with the BIT:

class Solution:
    def createSortedArray(self, instructions: List[int]) -> int:
        # Shift values to handle 0s
        max_value = max(instructions)
        bit = BinaryIndexedTree(max_value + 1)  # Extra space for shifting
      
        total_cost = 0
        MOD = 10**9 + 7
      
        for position, value in enumerate(instructions):
            # Shift value by 1 for BIT operations
            shifted_value = value + 1
          
            smaller_count = bit.query(shifted_value - 1)
            greater_count = position - bit.query(shifted_value)
          
            total_cost += min(smaller_count, greater_count)
            bit.update(shifted_value, 1)
      
        return total_cost % MOD

3. Integer Overflow Before Taking Modulo

Forgetting to apply modulo during accumulation can cause overflow in languages with fixed integer sizes.

Potential Issue:

total_cost = 0
for i in range(len(instructions)):
    cost = min(smaller_count, greater_count)
    total_cost += cost  # Can overflow for large inputs

return total_cost % MOD  # Modulo applied too late

Better Practice:

total_cost = 0
for i in range(len(instructions)):
    cost = min(smaller_count, greater_count)
    total_cost = (total_cost + cost) % MOD  # Apply modulo during accumulation

return total_cost

4. Incorrect BIT Size Allocation

Not allocating enough space for the maximum value can cause out-of-bounds errors.

Wrong:

# If instructions = [1, 5, 6, 2]
bit = BinaryIndexedTree(len(instructions))  # Size = 4, but max value is 6!
bit.update(6, 1)  # Index out of bounds

Correct:

max_value = max(instructions)
bit = BinaryIndexedTree(max_value)  # Size based on max value, not array length

5. Confusion Between Element Count and Element Value

Mixing up the position index (number of elements inserted) with the actual values being inserted.

Common Mistake:

for i, value in enumerate(instructions):
    # Wrong: Using value instead of i for total element count
    greater_count = value - bit.query(value)  # Should use i, not value

Correct:

for i, value in enumerate(instructions):
    # i represents how many elements have been inserted so far
    greater_count = i - bit.query(value)
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following is a min heap?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More