Skip to content

Commit 8fa2746

Browse files
committed
feat(book/array): sliding window pattern
1 parent 8c7b7be commit 8fa2746

File tree

4 files changed

+123
-8
lines changed

4 files changed

+123
-8
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ local.properties
8686
######################
8787
# Windows image file caches
8888
Thumbs.db
89+
*Zone.Identifier
8990

9091
# Folder config file
9192
Desktop.ini

book/content/part02/array.asc

+122-8
Original file line numberDiff line numberDiff line change
@@ -298,13 +298,13 @@ To sum up, the time complexity of an array is:
298298

299299
==== Array Patterns for Solving Interview Questions
300300

301-
Many programming problems involves manipulating arrays. Here are some patterns that can help you improve your problem solving skills.
301+
Many programming problems involve manipulating arrays. Here are some patterns that can help you improve your problem-solving skills.
302302

303303
===== Two Pointers Pattern
304304

305-
Usually we use one pointer to navigate each element in an array. However, there are times when having two pointers (left/right, low/high) comes handy. Let's do examples.
305+
Usually, we use one pointer to navigate each element in an array. However, there are times when having two pointers (left/right, low/high) comes in handy. Let's do some examples.
306306

307-
*AR-A*) _Given a sorted array of integers, find two numbers that add up to target t and return their values._
307+
*AR-A*) _Given a sorted `array` of integers, find two numbers that add up to a `target` and return their values._
308308

309309
.Examples
310310
[source, javascript]
@@ -316,7 +316,7 @@ twoSum([ -3, -2, -1, 1, 1, 3, 4], -4); // [-3, -1] // (-3 -1 = -4)
316316

317317
**Solutions:**
318318

319-
One naive solution would be use two pointers in a nested loop:
319+
One naive solution would be to use two pointers in a nested loop:
320320

321321
.Solution 1: Brute force
322322
[source, javascript]
@@ -331,9 +331,9 @@ function twoSum(arr, target) {
331331

332332
The runtime of this solution would be `O(n^2)`. Because of the nested loops. Can we do better? We are not using the fact that the array is SORTED!
333333

334-
We can use two pointers but this time we will traverse the array only once. One starting from the left side and the other from the right side.
334+
We can use two pointers: one pointer starting from the left side and the other from the right side.
335335

336-
Depending on if the the sum is bigger or smaller than target, we move right or left pointer. If the sum is equal to target we return the values at the current left or right pointer.
336+
Depending on whether the sum is bigger or smaller than the target, we move right or left. If the sum is equal to the target, we return the current left and right pointer's values.
337337

338338
.Solution 1: Two Pointers
339339
[source, javascript]
@@ -352,11 +352,125 @@ function twoSum(arr, target) {
352352

353353
These two pointers have a runtime of `O(n)`.
354354

355-
REMEMBER: This technique only works for sorted arrays. If the array was not sorted, you would have to sort it first or choose another approach.
355+
WARNING: This technique only works for sorted arrays. If the array was not sorted, you would have to sort it first or choose another approach.
356356

357357
===== Sliding Windows Pattern
358358

359-
TBD
359+
The sliding window pattern is similar to the two pointers. The difference is that the distance between the left and right pointer is always the same. Also, the numbers don't need to be sorted. Let's do an example!
360+
361+
*AR-B*) _Find the max sum of an array of integers, only taking `k` items from the right and left side sequentially._
362+
_*Constraints*: `k` won't exceed the number of elements `n`: `1 <= k <= n`._
363+
364+
.Examples
365+
[source, javascript]
366+
----
367+
maxSum([1,2,3], 3); // 6 // (1 + 2 + 3 = 6)
368+
maxSum([1,1,1,1,200,1], 3); // 202 // (1 + 200 + 1 = 202)
369+
maxSum([3, 10, 12, 4, 7, 2, 100, 1], 3); // 104 // (3 + 1 + 100 = 104)
370+
maxSum([1,200,1], 1); // 6 // (1 + 2 + 3 = 6)
371+
----
372+
373+
Let's take `[3, 10, 12, 4, 7, 2, 100, 1], k=3` as an example.
374+
375+
There are multiple ways to solve this problem. Before applying the sliding window, let's consider this other algorithm:
376+
377+
*Backtracking algorithm*
378+
379+
- We have two initial choices: going left with `3` or right with `1`.
380+
- We can take the first element from the left side `3`; from there, we can keep going left with `10` or right `1`.
381+
- If we go right with `1` on the right side, next, we have two options from the right side `100` or `10`.
382+
- If we go with `100`, then we compute the sum `3 + 1 + 100 = 104`.
383+
- Repeat with other combinations and keep track of the max sum.
384+
385+
How many combinations can we form? 2^k, since in the worst-case k is n, then we have a runtime of `2^n`!
386+
387+
// image::max-sum-backtracking.png[max sum with backtracking]
388+
389+
We can also visualize all the options as follows. If you add up the numbers from the top to bottom, you get the result for all combinations:
390+
391+
[graphviz, max-sum-sliding-window-red, png]
392+
....
393+
graph G {
394+
0 -- 3
395+
0 -- 1
396+
397+
3 -- 10
398+
3 -- a1
399+
400+
10 -- 12
401+
10 -- b1
402+
403+
a1 -- a10
404+
a1 -- 100
405+
406+
1 -- a3
407+
1 -- a100
408+
409+
a3 -- b10
410+
a3 -- b100
411+
412+
a100 -- b3
413+
a100 -- 2
414+
415+
1, a1, b1 [label = 1 color = red]
416+
10, a10, b10 [label = 10 color = red]
417+
3, a3, b3 [label = 3 color = red]
418+
100, a100, b100 [label = 100 color = red]
419+
420+
12 -- res1 [color = gray]
421+
b1 -- res2 [color = gray]
422+
a10 -- res3 [color = gray]
423+
100 -- res4 [color = gray]
424+
b10 -- res5 [color = gray]
425+
b100 -- res6 [color = gray]
426+
b3 -- res7 [color = gray]
427+
2 -- res8 [color = gray]
428+
429+
res1 [label = "Sum: 25", shape=plaintext, fontcolor=gray]
430+
res2 [label = "Sum: 14", shape=plaintext, fontcolor=gray]
431+
res3 [label = "Sum: 14", shape=plaintext, fontcolor=gray]
432+
res4 [label = "Sum: 104", shape=plaintext, fontcolor=gray]
433+
res5 [label = "Sum: 14", shape=plaintext, fontcolor=gray]
434+
res6 [label = "Sum: 104", shape=plaintext, fontcolor=gray]
435+
res7 [label = "Sum: 104", shape=plaintext, fontcolor=gray]
436+
res8 [label = "Sum: 103", shape=plaintext, fontcolor=gray]
437+
}
438+
....
439+
440+
441+
Notice that many elements on the middle branches (in red color) have the same numbers but in a different order, so the sums oscillate between 104 and 14. That's why this algorithm is not very optimal for this problem.
442+
443+
*Sliding window algorithm*
444+
445+
Another approach is using sliding windows. Since the sum always has `k` elements, we can compute the cumulative sum for k first elements from the left. Then, we slide the "window" to the right and remove one from the left until we cover all the right items. In the end, we would have all the possible combinations without duplicated work.
446+
447+
Check out the following illustration:
448+
449+
image::max-sum-sliding-window.png[sliding window for arrays]
450+
451+
Here's the implementation:
452+
453+
[source, javascript]
454+
----
455+
function maxSum(arr, k) {
456+
let left = k - 1;
457+
let right = arr.length -1;
458+
let sum = 0;
459+
for (let i = 0; i < k; i++) sum += arr[i];
460+
let max = sum;
461+
462+
for (let i = 0; i < k; i++) {
463+
sum += arr[right--] - arr[left--];
464+
max = Math.max(max, sum);
465+
}
466+
467+
return max;
468+
};
469+
----
470+
471+
The difference between the two pointers pattern and the sliding windows, it's that we move both pointers at the same time to keep the length of the window the same.
472+
473+
The runtime for this code is: `O(k)`. We move the window k times. Since `k <= n`, the final runtime is `O(n)`.
360474

361475
==== Practice Questions
362476
(((Interview Questions, Arrays)))

book/images/max-sum-backtracking.png

27.8 KB
Loading
5.77 KB
Loading

0 commit comments

Comments
 (0)