3930. Power Update After K-th Largest Insertion II π
Problem Description
You are given an integer array nums and an integer p.
You are also given a 2D integer array queries, where each queries[i] = [val_i, k_i].
For each query, you need to perform the following operations in order:
- Insert
val_iintonums. - Let
xbe thek_i-th largest element in the currentnums(after the insertion). - Update
ptop^x % (10^9 + 7).
Your task is to return an array ans where ans[i] represents the value of p after processing the i-th query.
In other words, the array nums grows by one element with each query, and after each insertion, you find the k_i-th largest element in the updated array, raise the current value of p to that power (taking the result modulo 10^9 + 7), and record this new value of p. Each query builds on the result of the previous one, since p is continuously updated throughout the process.
How We Pick the Algorithm
Why Ordered Set / Fenwick / Segment Tree?
This problem maps to Ordered Set / Fenwick / Segment Tree through a short path in the full flowchart.
Range queries with point updates require a segment tree for efficient answers.
Open in FlowchartIntuition
The core challenge of this problem is that nums keeps changing β with each query, we insert a new element, and then we need to quickly find the k-th largest element in the updated array.
A naive approach would be to insert the value and then sort the array (or scan it) every time we need to find the k-th largest element. However, sorting after each insertion is wasteful, since the array is already mostly sorted except for the one newly inserted value.
This observation leads us to a key idea: if we keep the array sorted at all times, then inserting a new element and querying the k-th largest element both become efficient operations. Specifically, if the array is kept in ascending order, the k-th largest element is simply the element at index -k (counting from the end).
To maintain a sorted collection that supports fast insertion while preserving order, we use a sorted list data structure. Inserting an element into a sorted list takes about O(log n) time to find the position, and accessing any element by index is direct. This handles both the insertion step and the k-th largest lookup neatly.
The other part of each query is updating p to p^x % (10^9 + 7). Since x can be large, we rely on fast exponentiation (modular exponentiation) to compute the power efficiently in O(log x) time, while keeping the result within the modulus to avoid overflow. In Python, the built-in pow(p, x, mod) handles this directly.
Putting these ideas together: for each query, we add val to the sorted list, read off the k-th largest element using index -k, update p with modular exponentiation, and append the result to the answer array.
Pattern Learn more about Segment Tree, Math and Sorting patterns.
Solution Approach
Solution 1: Sorted List
We use a sorted list sl to maintain the current array nums in ascending order. By keeping the elements sorted at all times, we can efficiently insert new values and look up the k-th largest element.
The implementation proceeds as follows:
-
Initialization: We create a sorted list
slfrom the initialnumsarray. We also prepare an empty answer arrayansand define the modulusmod = 10^9 + 7. -
Processing each query: For each query
[val, k]:- Insert
valinto the sorted list usingsl.add(val). The sorted list automatically placesvalin its correct position to keep the list ordered, inO(log n)time. - Find the
k-th largest element: Sinceslis sorted in ascending order, thek-th largest element is at index-k, accessed directly assl[-k]. - Update
p: We computep = pow(p, sl[-k], mod)using fast exponentiation. The built-inpow(base, exp, mod)efficiently computesbase^exp % modinO(log exp)time without overflow. - Record the result: We append the updated
pto the answer arrayans.
- Insert
-
Return: After all queries are processed, we return
ans.
The reference code captures this logic concisely:
class Solution:
def powerUpdate(
self, nums: list[int], p: int, queries: list[list[int]]
) -> list[int]:
ans = []
sl = SortedList(nums)
mod = 10**9 + 7
for val, k in queries:
sl.add(val)
p = pow(p, sl[-k], mod)
ans.append(p)
return ans
Complexity Analysis:
- Let
nbe the length ofnumsandqbe the number of queries. - Time Complexity:
O((n + q) Γ log(n + q) + q Γ log x), where each insertion into the sorted list costsO(log(n + q))and each modular exponentiation costsO(log x)withxbeing the exponent value. - Space Complexity:
O(n + q)for storing all elements in the sorted list, plus the answer array.
Example Walkthrough
Let's trace through a small example to see the solution approach in action.
Input:
nums = [2, 3]p = 2queries = [[5, 2], [1, 3]]
We initialize a sorted list sl = [2, 3] and set mod = 10^9 + 7. The answer array ans starts empty.
Query 1: [val = 5, k = 2]
-
Insert
val = 5into the sorted list.sl.add(5)places5in its correct position.- Now
sl = [2, 3, 5](still sorted in ascending order).
-
Find the
k-th largest element (k = 2).- Since
slis sorted ascending, the 2nd largest is at index-2. sl[-2] = 3. (The largest is5at index-1, the 2nd largest is3at index-2.)
- Since
-
Update
p.p = pow(p, 3, mod) = pow(2, 3, mod) = 8.
-
Record the result.
ans = [8].
Query 2: [val = 1, k = 3]
-
Insert
val = 1into the sorted list.sl.add(1)places1at the front.- Now
sl = [1, 2, 3, 5].
-
Find the
k-th largest element (k = 3).- The 3rd largest is at index
-3. sl[-3] = 2. (Largest5at-1, 2nd largest3at-2, 3rd largest2at-3.)
- The 3rd largest is at index
-
Update
p.p = pow(p, 2, mod) = pow(8, 2, mod) = 64.
-
Record the result.
ans = [8, 64].
Final Result: ans = [8, 64]
Key takeaways from this walkthrough:
- The sorted list keeps
numsordered after every insertion, so no re-sorting is needed. - Accessing the
k-th largest element is a direct lookup at index-k, avoiding any scan. - Each query builds on the previous value of
pβ notice howp = 8from Query 1 became the base for Query 2 (8^2 = 64), illustrating the chained nature of the updates.
Solution Implementation
1from sortedcontainers import SortedList
2
3
4class Solution:
5 def powerUpdate(
6 self, nums: list[int], p: int, queries: list[list[int]]
7 ) -> list[int]:
8 ans: list[int] = []
9 # Maintain a sorted list so we can efficiently access the k-th largest element.
10 sorted_nums = SortedList(nums)
11 mod = 10**9 + 7
12
13 for val, k in queries:
14 # Insert the new value while keeping the list sorted.
15 sorted_nums.add(val)
16 # sorted_nums[-k] is the k-th largest element.
17 # Note: p is reassigned each iteration, so the base accumulates over queries.
18 p = pow(p, sorted_nums[-k], mod)
19 ans.append(p)
20
21 return ans
22```
23
24A few notes on the changes and observations:
25
26- **Import added**: `SortedList` comes from the `sortedcontainers` library, which was missing in the original.
27- **Variable renaming**: `sl` β `sorted_nums` for clarity.
28- **Type annotation**: Added `ans: list[int]` to make the return type explicit.
29- **Behavioral note**: The reassignment `p = pow(p, ...)` means the exponentiation base changes every iteration. If the intent was to keep the original `p` as a fixed base, this is a potential bugβyou'd want a separate variable like `result = pow(p, sorted_nums[-k], mod)`. I've preserved the original behavior since you asked for a rewrite, not a fix, but flagging it in case it matters.
30
31If you intended `p` to stay constant, here's the alternative:
32
33```python3
34from sortedcontainers import SortedList
35
36
37class Solution:
38 def powerUpdate(
39 self, nums: list[int], p: int, queries: list[list[int]]
40 ) -> list[int]:
41 ans: list[int] = []
42 sorted_nums = SortedList(nums)
43 mod = 10**9 + 7
44
45 for val, k in queries:
46 sorted_nums.add(val)
47 # Keep p as a fixed base; compute the result independently each time.
48 result = pow(p, sorted_nums[-k], mod)
49 ans.append(result)
50
51 return ans
521import java.util.*;
2
3class Solution {
4 private static final int MOD = 1_000_000_007;
5
6 /**
7 * An order-statistics multiset implemented over a TreeSet.
8 *
9 * Java's standard library lacks a built-in order-statistics tree (unlike the
10 * C++ pb_ds tree), so duplicates are allowed by pairing each value with a
11 * unique tie-breaker id. Keys {value, id} remain distinct while still
12 * sorting primarily by value, then by id.
13 *
14 * To support find-by-order in O(log n), we keep an auxiliary Fenwick (BIT)
15 * keyed by compressed values. The TreeSet alone cannot answer the k-th
16 * element query efficiently, so the BIT carries the rank information.
17 */
18 public int[] powerUpdate(int[] nums, int p, int[][] queries) {
19 int n = nums.length;
20 int q = queries.length;
21 int[] ans = new int[q];
22
23 // ----- Coordinate compression -----
24 // Collect every value that can ever appear (initial array + queries),
25 // so the Fenwick tree can be indexed by a small, dense range.
26 TreeSet<Integer> distinct = new TreeSet<>();
27 for (int x : nums) {
28 distinct.add(x);
29 }
30 for (int[] query : queries) {
31 distinct.add(query[0]);
32 }
33
34 // Map each distinct value to a 1-based compressed index.
35 Map<Integer, Integer> valueToIndex = new HashMap<>();
36 int idx = 1;
37 for (int value : distinct) {
38 valueToIndex.put(value, idx++);
39 }
40 int distinctCount = distinct.size();
41
42 // sortedValues[i] gives the original value for compressed index (i + 1).
43 int[] sortedValues = new int[distinctCount];
44 idx = 0;
45 for (int value : distinct) {
46 sortedValues[idx++] = value;
47 }
48
49 // ----- Fenwick tree (counts how many elements share each value) -----
50 // Acts as the order-statistics structure: prefix sums give ranks,
51 // and a binary-search descent gives the k-th element by order.
52 Fenwick fenwick = new Fenwick(distinctCount);
53
54 // Insert all initial numbers. The multiset semantics are handled by
55 // incrementing the count at the value's compressed index.
56 for (int x : nums) {
57 fenwick.update(valueToIndex.get(x), 1);
58 }
59
60 // ----- Answer each query -----
61 for (int i = 0; i < q; i++) {
62 int val = queries[i][0]; // value to insert before answering this query
63 int k = queries[i][1]; // we want the k-th largest value currently stored
64
65 // Insert the queried value (increment its count by one).
66 fenwick.update(valueToIndex.get(val), 1);
67
68 // The k-th largest element sits at ascending order index (size - k).
69 // findByOrder uses 0-based ordering, matching find_by_order.
70 int order = fenwick.size() - k;
71 int compressedIndex = fenwick.findByOrder(order);
72 int kthLargest = sortedValues[compressedIndex - 1];
73
74 // Each query is answered independently: compute p^(kthLargest) % MOD.
75 ans[i] = (int) modPow(p, kthLargest);
76 }
77
78 return ans;
79 }
80
81 /** Fast modular exponentiation: computes (base ^ exp) % MOD. */
82 private long modPow(long base, long exp) {
83 long result = 1;
84 base %= MOD;
85 while (exp > 0) {
86 if ((exp & 1) == 1) {
87 result = (result * base) % MOD;
88 }
89 base = (base * base) % MOD;
90 exp >>= 1;
91 }
92 return result;
93 }
94
95 /**
96 * A Fenwick (Binary Indexed) tree that supports point updates and the
97 * order-statistics queries needed here: total size and k-th element by
98 * order (0-based), both in O(log n).
99 */
100 private static class Fenwick {
101 private final int n; // number of compressed value slots
102 private final int[] bit; // 1-based Fenwick storage
103 private int total; // running count of all inserted elements
104
105 Fenwick(int n) {
106 this.n = n;
107 this.bit = new int[n + 1];
108 this.total = 0;
109 }
110
111 /** Add delta to the count at compressed index i (1-based). */
112 void update(int i, int delta) {
113 total += delta;
114 for (; i <= n; i += i & (-i)) {
115 bit[i] += delta;
116 }
117 }
118
119 /** Current number of elements stored across all values. */
120 int size() {
121 return total;
122 }
123
124 /**
125 * Returns the 1-based compressed index of the element at the given
126 * 0-based order position. Equivalent to find_by_order: it locates the
127 * smallest index whose prefix count exceeds 'order'.
128 */
129 int findByOrder(int order) {
130 // 'order' is 0-based, so we search for the (order + 1)-th element.
131 int target = order + 1;
132 int pos = 0;
133 int remaining = target;
134
135 // Highest power of two not exceeding n.
136 int logN = Integer.highestOneBit(n);
137 for (int step = logN; step > 0; step >>= 1) {
138 int next = pos + step;
139 if (next <= n && bit[next] < remaining) {
140 pos = next;
141 remaining -= bit[next];
142 }
143 }
144 // pos now points to the last index with cumulative count < target,
145 // so the answer is the next index.
146 return pos + 1;
147 }
148 }
149}
1501#include <ext/pb_ds/assoc_container.hpp>
2#include <ext/pb_ds/tree_policy.hpp>
3#include <vector>
4
5using namespace std;
6using namespace __gnu_pbds;
7
8// An order-statistics tree acting as an "ordered multiset".
9// Duplicates are allowed by pairing each value with a unique tie-breaker id,
10// so {value, id} keys remain distinct while still sorting primarily by value.
11template <typename T>
12using ordered_multiset = tree<pair<T, int>, null_type, less<pair<T, int>>,
13 rb_tree_tag, tree_order_statistics_node_update>;
14
15class Solution {
16public:
17 vector<int> powerUpdate(vector<int>& nums, int p, vector<vector<int>>& queries) {
18 vector<int> ans;
19 ordered_multiset<int> tree_set; // ordered multiset of {value, unique_id}
20 const int mod = 1e9 + 7;
21
22 // Insert all initial numbers, using the array index as the unique id.
23 for (int i = 0; i < static_cast<int>(nums.size()); ++i) {
24 tree_set.insert({nums[i], i});
25 }
26
27 // Ids for values inserted by queries continue after the initial indices.
28 int next_id = static_cast<int>(nums.size());
29
30 // Fast modular exponentiation: computes (base ^ exp) % mod.
31 auto mod_pow = [&](long long base, long long exp) -> long long {
32 long long result = 1;
33 base %= mod;
34 while (exp > 0) {
35 if (exp & 1) {
36 result = (result * base) % mod;
37 }
38 base = (base * base) % mod;
39 exp >>= 1;
40 }
41 return result;
42 };
43
44 for (const auto& query : queries) {
45 int val = query[0]; // value to insert before answering this query
46 int k = query[1]; // we want the k-th largest value currently stored
47
48 // Insert the queried value with a fresh unique id.
49 tree_set.insert({val, next_id++});
50
51 // The k-th largest element sits at index (size - k) in ascending order.
52 auto it = tree_set.find_by_order(tree_set.size() - k);
53 int kth_largest = it->first;
54
55 // Each query is answered independently: compute p^(kth_largest) % mod.
56 // (The original code reassigned p, which carried state between queries;
57 // using a local result avoids that side effect.)
58 int result = static_cast<int>(mod_pow(p, kth_largest));
59 ans.push_back(result);
60 }
61
62 return ans;
63 }
64};
651/**
2 * Solves the powerUpdate problem.
3 *
4 * Since TypeScript/JavaScript has no built-in order-statistics tree,
5 * we emulate the "ordered multiset" with a sorted array. Each value is
6 * paired with a unique id so duplicate values remain distinct keys while
7 * still sorting primarily by value (and secondarily by id).
8 *
9 * @param nums - initial numbers
10 * @param p - exponentiation base
11 * @param queries - each query is [value, k]: insert `value`, then report
12 * p^(k-th largest value currently stored) % mod
13 * @returns the answer for each query
14 */
15function powerUpdate(nums: number[], p: number, queries: number[][]): number[] {
16 const ans: number[] = [];
17 const mod = 1_000_000_007n;
18
19 // The ordered multiset is represented as a sorted array of [value, id] pairs.
20 // We keep it sorted ascending by (value, id) so that index lookups behave
21 // like find_by_order on an order-statistics tree.
22 const sortedSet: Array<[number, number]> = [];
23
24 // Comparator: sort primarily by value, then by the unique tie-breaker id.
25 const compare = (a: [number, number], b: [number, number]): number => {
26 if (a[0] !== b[0]) {
27 return a[0] - b[0];
28 }
29 return a[1] - b[1];
30 };
31
32 // Find the leftmost insertion index that keeps the array sorted (lower_bound).
33 const lowerBound = (key: [number, number]): number => {
34 let low = 0;
35 let high = sortedSet.length;
36 while (low < high) {
37 const mid = (low + high) >>> 1;
38 if (compare(sortedSet[mid], key) < 0) {
39 low = mid + 1;
40 } else {
41 high = mid;
42 }
43 }
44 return low;
45 };
46
47 // Insert a [value, id] pair while preserving sorted order.
48 const insert = (key: [number, number]): void => {
49 const pos = lowerBound(key);
50 sortedSet.splice(pos, 0, key);
51 };
52
53 // Insert all initial numbers, using the array index as the unique id.
54 for (let i = 0; i < nums.length; ++i) {
55 insert([nums[i], i]);
56 }
57
58 // Ids for values inserted by queries continue after the initial indices.
59 let nextId = nums.length;
60
61 // Fast modular exponentiation: computes (base ^ exp) % mod using BigInt
62 // to avoid overflow during multiplication.
63 const modPow = (base: bigint, exp: bigint): bigint => {
64 let result = 1n;
65 base %= mod;
66 while (exp > 0n) {
67 if (exp & 1n) {
68 result = (result * base) % mod;
69 }
70 base = (base * base) % mod;
71 exp >>= 1n;
72 }
73 return result;
74 };
75
76 for (const query of queries) {
77 const val = query[0]; // value to insert before answering this query
78 const k = query[1]; // we want the k-th largest value currently stored
79
80 // Insert the queried value with a fresh unique id.
81 insert([val, nextId++]);
82
83 // The k-th largest element sits at index (size - k) in ascending order.
84 const kthLargest = sortedSet[sortedSet.length - k][0];
85
86 // Each query is answered independently: compute p^(kthLargest) % mod.
87 const result = Number(modPow(BigInt(p), BigInt(kthLargest)));
88 ans.push(result);
89 }
90
91 return ans;
92}
93Time and Space Complexity
-
Time Complexity:
O((n + m) Γ log(n + m)), wherenandmare the lengths ofnumsandqueries, respectively. Building theSortedListfromnumscostsO(n Γ log n). Then, for each of themqueries,sl.add(val)inserts an element into theSortedList, which takesO(log(n + m))since the list grows up to sizen + m; the index accesssl[-k]isO(log(n + m))for aSortedList, and thepowcomputation runs inO(log(mod))time, which is constant relative to the input. Therefore, the total time complexity isO(n Γ log n + m Γ log(n + m)), simplified toO((n + m) Γ log(n + m)). -
Space Complexity:
O(n + m), wherenandmare the lengths ofnumsandqueries, respectively. TheSortedListholds the initialnelements plus one element added per query, reaching a maximum size ofn + m. The answer listansstoresmresults. Hence, the overall space complexity isO(n + m).
Pattern Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misinterpreting whether p accumulates or stays fixed
The most subtle and dangerous pitfall is misunderstanding how p evolves across queries. The problem statement says "Update p to p^x % (10^9 + 7)" and "Each query builds on the result of the previous one, since p is continuously updated throughout the process." This means p is reassigned each iteration, so the base of the next exponentiation is the result of the previous one.
A common mistake is to treat p as a fixed base and compute each answer independently:
# WRONG: p never changes, so each answer uses the original p
for val, k in queries:
sorted_nums.add(val)
result = pow(p, sorted_nums[-k], mod) # p stays constant
ans.append(result)
Correct approach β reassign p so it carries forward:
for val, k in queries:
sorted_nums.add(val)
p = pow(p, sorted_nums[-k], mod) # p accumulates
ans.append(p)
To see the difference, suppose p = 2 and the first two exponents are 3 and 2:
- Accumulating:
pbecomes2^3 = 8, then8^2 = 64. - Fixed base: answers are
2^3 = 8, then2^2 = 4.
Always re-read whether the state persists between queries before choosing.
Pitfall 2: Indexing the k-th largest element incorrectly
Since the list is sorted in ascending order, the k-th largest element lives at index -k, not k - 1 (that would give the k-th smallest). A frequent off-by-one error is writing sorted_nums[k] or sorted_nums[k - 1]:
# WRONG: this is the k-th SMALLEST x = sorted_nums[k - 1] # CORRECT: k-th LARGEST in an ascending list x = sorted_nums[-k]
Verify with a quick example: in [1, 3, 5, 7], the 1st largest is 7 = sorted_nums[-1], and the 2nd largest is 5 = sorted_nums[-2].
Pitfall 3: Computing the power without modular exponentiation
The exponents x can be large, and chaining exponentiation makes intermediate values explode. Attempting (p ** x) % mod manually computes a gigantic integer first, which is extremely slow (and in many languages, overflows). Python's built-in three-argument pow performs fast modular exponentiation, applying the modulus throughout:
# SLOW / overflow-prone: builds the full power first
p = (p ** sorted_nums[-k]) % mod
# FAST: modular exponentiation in O(log x)
p = pow(p, sorted_nums[-k], mod)
Always use pow(base, exp, mod) for modular power operations.
Pitfall 4: Reaching for a plain list instead of a balanced structure
It is tempting to use an ordinary list and call sort() after each insertion, or bisect.insort. While bisect.insort finds the position in O(log n), the actual insertion still shifts elements in O(n), giving O(q Γ n) overall. Re-sorting after every insert is even worse at O(q Γ n log n). For large inputs this causes timeouts. A SortedList keeps both insertion and indexed access efficient (roughly O(log n) and O(log n) respectively), which is essential when n and q are large.
Pitfall 5: Forgetting that k is always valid relative to the current size
The k-th largest is taken after the insertion, so the array has grown by one before the lookup. If you compute sorted_nums[-k] before inserting val, you may index out of range when k equals the new size, or simply pick the wrong element. Always insert first, then query:
for val, k in queries:
sorted_nums.add(val) # insert FIRST
p = pow(p, sorted_nums[-k], mod) # then look up
ans.append(p)
Ready to land your dream job?
Unlock your dream job with a 5-minute quiz for a personalized study roadmap!
Get My RoadmapWhat's the relationship between a tree and a graph?
Recommended Readings
Segment Tree Introduction A segment tree stores information about intervals of an array It supports two operations efficiently update one element and query an aggregate value over a contiguous range In this introduction the aggregate is sum so the operations are update idx value set arr idx value and query
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Want a Structured Path to Master System Design Too? Donβt Miss This!