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
and2
(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
usingtree.query(x - 1)
- Calculate elements greater than
x
asi - tree.query(x)
wherei
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.
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:
- We don't actually need to maintain the sorted array itself - we only need to track how many of each value we've seen
- 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
- Count of all values strictly less than
This naturally leads us to think about frequency counting. If we maintain frequencies, then:
- Count of elements less than
x
= sum of frequencies from1
tox-1
- Count of elements greater than
x
= total elements inserted - count of elements less than or equal tox
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 indexx
- 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 positionx
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
tox
- Traverses down by removing the lowest set bit each iteration
- Accumulates partial sums from responsible nodes
Main Algorithm:
-
Initialize BIT: Create a tree with size equal to the maximum value in
instructions
m = max(instructions) tree = BinaryIndexedTree(m)
-
Process each instruction: For each element
x
at positioni
:a. Calculate insertion cost:
cost = min(tree.query(x - 1), i - tree.query(x))
tree.query(x - 1)
: Count of elements strictly less thanx
tree.query(x)
: Count of elements less than or equal tox
i - tree.query(x)
: Count of elements strictly greater thanx
- 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
-
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 EvaluatorExample 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] += 1
→c = [0, 1, 0, 0, 0, 0, 0]
- Next index:
1 + (1 & -1) = 2
- Update index 2:
c[2] += 1
→c = [0, 1, 1, 0, 0, 0, 0]
- Next index:
2 + (2 & -2) = 4
- Update index 4:
c[4] += 1
→c = [0, 1, 1, 0, 1, 0, 0]
- Update index 1:
- Total cost = 0
Step 2: Insert 5 (i = 1)
- Query elements < 5:
tree.query(4)
- At index 4: accumulate
c[4] = 1
- Total = 1
- At index 4: accumulate
- 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] += 1
→c = [0, 1, 1, 0, 1, 1, 0]
- Next index:
5 + (5 & -5) = 6
- Update index 6:
c[6] += 1
→c = [0, 1, 1, 0, 1, 1, 1]
- Update index 5:
- 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
- At index 5: accumulate
- 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] += 1
→c = [0, 1, 1, 0, 1, 1, 2]
- Update index 6:
- Total cost = 0
Step 4: Insert 2 (i = 3)
- Query elements < 2:
tree.query(1)
- At index 1: accumulate
c[1] = 1
- Total = 1
- At index 1: accumulate
- Query elements ≤ 2:
tree.query(2)
- At index 2: accumulate
c[2] = 1
- Total = 1
- At index 2: accumulate
- Elements > 2:
3 - 1 = 2
- Cost =
min(1, 2) = 1
- Update BIT with value 2:
tree.update(2, 1)
- Update index 2:
c[2] += 1
→c = [0, 1, 2, 0, 1, 1, 2]
- Next index:
2 + (2 & -2) = 4
- Update index 4:
c[4] += 1
→c = [0, 1, 2, 0, 2, 1, 2]
- Update index 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)
andtree.query(x)
, each takingO(log m)
time - One
update
operation:tree.update(x, 1)
, which takesO(log m)
time
- Two
- The Binary Indexed Tree operations (
update
andquery
) haveO(log m)
complexity because:- In
update
, we traverse up the tree by addingx & -x
(the least significant bit), which takes at mostlog m
steps - In
query
, we traverse down by subtractingx & -x
, which also takes at mostlog m
steps
- In
- 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 sizem + 1
, requiringO(m)
space - The
instructions
input array is given and not counted as extra space - Other variables (
ans
,mod
,i
,x
,cost
) useO(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)
Which of the following is a min heap?
Recommended Readings
Segment Tree Faster Range Queries For this article we want to introduce the idea of a Segment Tree Segment Trees allow us to quickly perform range queries as well as range updates Suppose we have an array and we want to know the sum of a particular range of numbers as well
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
https assets algo monster cover_photos dnd svg Divide and Conquer The main idea of divide and conquer is to split the main problem into two smaller components assuming that each one of the components had already been solved recursively and then try to solve the bigger problem using the solutions
Want a Structured Path to Master System Design Too? Don’t Miss This!