Stacks & Queues · 33 of 38
First negative integer in every window of size k
Slide a frame of size k along a row of numbers and, for every position, name the first negative number inside the frame.
- Simple way
- time O(n*k)
room O(1) - Clever way
- time O(n)
room O(k)
The problem, simply
You get an array and a number k. Look at every block of k numbers in a row: the first k, then shift one step right, and so on. For each block, give the first negative number in it, reading left to right. If a block has no negative number, give 0.
There are n - k + 1 blocks, so the answer has that many numbers.
A story to remember it
Picture it
The window covers indices 2 to 4. The line of negative numbers seen so far, still inside the window, is -7 then -15. The front, -7, is the answer for this window.
- checking
- answer
Examples
A normal case
- In
arr = [12, -1, -7, 8, -15, 30, 16, 28], k = 3- Out
[-1, -1, -7, -15, -15, 0]
The last window, 30, 16, 28, has no negative number, so it gives 0.
Window of one
- In
arr = [-8, 2, 3, -6, 10], k = 1- Out
[-8, 0, 0, -6, 0]
Each window is a single number. It is its own answer if negative, otherwise 0.
No negatives anywhere
- In
arr = [1, 2, 3], k = 2- Out
[0, 0]
The waiting line stays empty the whole time.
One big window
- In
arr = [5, -3, -9], k = 3- Out
[-3]
There is only one window. The first negative in it is -3, not the smallest one, -9.
Way 1: the simple way
For each window start i, look at the k numbers from i and stop at the first negative.
- For
ifrom0ton - k, set the answer to0. - Scan
jfromitoi + k - 1. At the firstarr[j] < 0, take it and stop.
Way 1 on the last window. All three numbers are checked and none is negative. Most of them were already checked for the windows before it.
- checking
class Solution {
public int[] firstNegative(int[] arr, int k) {
int n = arr.length;
int[] ans = new int[n - k + 1];
for (int i = 0; i + k <= n; i++) {
for (int j = i; j < i + k; j++) {
if (arr[j] < 0) { // first negative wins
ans[i] = arr[j];
break;
}
} // stays 0 if none was found
}
return ans;
}
}How long it takes: up to k checks for each of about n windows, O(n * k). Extra room: O(1) besides the answer.
Way 2: the clever way
Keep a waiting line, a queue, of the indices of negative numbers, in the order they appear.
- Move the right end
rfrom0ton - 1. - If
arr[r]is negative, addrto the back of the line. - Once the window is full (
r >= k - 1), its left end isl = r - k + 1. Remove indices smaller thanlfrom the front, since they slid out. - The answer for this window is
arr[front]if the line is not empty, else0.
The line keeps left to right order, so its front is always the first negative in the window.
Sliding a window of 3 with a line of negative indices
01 / 08
12 is not negative, so the line does not change. The window is not full yet.
- looking now
- answer
- path
- just added
- ignored
class Solution {
public int[] firstNegative(int[] arr, int k) {
int n = arr.length;
int[] ans = new int[n - k + 1];
Deque<Integer> line = new ArrayDeque<>(); // indices of negatives
for (int r = 0; r < n; r++) {
if (arr[r] < 0) line.addLast(r); // join at the back
int l = r - k + 1; // left end of the window
if (l < 0) continue; // window not full yet
while (!line.isEmpty() && line.peekFirst() < l) {
line.pollFirst(); // slid out on the left
}
ans[l] = line.isEmpty() ? 0 : arr[line.peekFirst()];
}
return ans;
}
}How long it takes: each index joins the line at most once and leaves at most once, O(n). Extra room: the line only holds indices inside the window, at most k, so O(k).
Watch out for
- Remove from the front only while the front index is less than
l. Anifinstead ofwhileworks here because the window moves one step at a time, butwhileis the safe habit. - The answer array has
n - k + 1entries. Write toans[l], notans[r]. - If
k > n, there are no windows. Check that first to avoid a negative array size. - "First" means leftmost, not smallest. Do not use a heap or a minimum here.
What interviewers ask next
- Can you do it in
O(1)extra room? Yes. Keep one indexp. For each window, setp = max(p, l), then movepright whilep <= randarr[p]is not negative. Ifp <= r, the answer isarr[p], else 0.pnever moves back, so it is stillO(n). - Maximum of every window of size
k. Use a deque that keeps values in decreasing order, and drop smaller values from the back. That is the "sliding window maximum" pattern. - Count the negatives in every window instead. Keep a running count: add one when a negative enters, take one away when it leaves.
- What if the numbers arrive as a stream and you cannot look back? This method already works on a stream, because it only remembers the indices in the line.
Remember this
Grown-up words: the picture frame is a sliding window, the line of burnt cookies is a queue (or a deque used as one), and "each index joins once and leaves once" is why the whole thing is O(n).