The sliding window algorithm is an algorithm for more introductory topics. It is generally the optimal solution to some regular array problems. That is to say, if an array problem can be solved by dynamic programming, but it can be solved by sliding window, then the efficiency of sliding window is often higher.
The double pointer is not limited to the array problem. The "fast or slow pointer" in the linked list scene also belongs to the double pointer scene. The sliding process of the fast and slow pointer itself will generate a window. For example, when the window shrinks to a certain extent, you can get some in conclusion.
Therefore, mastering the sliding window is very basic and important. Next, I will introduce this algorithm to you based on my experience.
intensive reading
Sliding windows use double pointers to solve the problem, so it is generally called the double pointer algorithm because a window is formed between two pointers.
What situation is suitable for dual pointers? Generally double pointer is an optimized version of brute force algorithm, so:
- If the problem is relatively simple, and it is an array or linked list problem, you can often try whether the double pointer is solvable.
- If there are patterns in the array, you can try double pointers.
- If the linked list problem is more restrictive, such as requiring O(1) space complexity to be solved, perhaps only double pointers can be solved.
In other words, when a problem is more regular, simple, or clever, you can try the dual pointer (sliding window) solution.
Let's take an example to illustrate, the first is the sum of two numbers.
Sum of two numbers
The sum of two numbers is a simple question, in fact it has nothing to do with the sliding window, but in order to draw the sum of three numbers, let’s talk about it first. The topic is as follows:
Given an integer array
nums
and an integer target valuetarget
, please find in the array and the target valuetarget
two integers, and return their array subscripts.You can assume that each input will only correspond to one answer. However, the same element in the array cannot be repeated in the answer.
The violent solution is to exhaust the sum of all two numbers and find that the sum is target
. Obviously, this approach is a bit slow. Let's change our thinking.
Because you can use space for time, but only two numbers, we can be the subject of transformation, namely through the next iteration of the nums
each of which is subtracted target
, then find the value behind any of the previous results, it means that they The sum is target
.
map
can be used to speed up the query, that is, each item target - num
used as the key. If any of the following num
as the key can be found in map
, the solution is obtained, and the original value of the previous number can be map
in the value of 060c810bd018ff. This only needs to be traversed once, and the time complexity is O(n).
I say this question because it is a single pointer, that is, only one pointer moves in the array and is solved quickly with the hash table. For a slightly more complicated problem, a single pointer is not enough, and you need to use a double pointer to solve it (generally, three or more pointers are not used). The more complicated problem is the sum of three.
Sum of three
The sum of three numbers is a middle-level question. Don't think it is just an enhanced version of the sum of two numbers. The idea is completely different. The topic is as follows:
Give you a containedn
array of integersnums
, judgenums
whether there are three elementsa
,b
,c
, makinga + b + c = 0
? Please find all0
sum is 060c810bd0196f and does not repeat.
Since there are more than two numbers, it cannot be solved like a double pointer, because even if a hash table is used for storage, it will encounter the "sum of two numbers" problem during traversal, and the hash table scheme cannot continue to nest Use, that is unable to further reduce the complexity.
In order to reduce the time complexity, we hope to traverse the array only once, which requires the array to meet certain conditions before we can use the sliding window, so we sort the array. The time complexity of using fast sort is O(nlogn), and the time complexity is already More than the sum of two numbers, but because of the complexity of the topic, this sacrifice is inevitable.
Assuming sorting from small to large, then we get an increasing array, and the classic sliding window method is now available! How to slide it? First create two pointers, called left
and right
respectively. By continuously modifying left
and right
, let them slide between the arrays. The size of this window meets the requirements of the title. When the sliding is completed, return all the windows that meet the conditions, record It's actually very simple, just record it during the sliding process.
right + 1
too small, and then for the normal situation, we use a global variable to store the sum of the current window number, so that 060c810bd019ca only needs to accumulate nums[right+1]
, and left + 1
only nums[left]
to subtract 060c810bd019cd to quickly get the sum.
Because all situations need to be considered, an array traversal is required. For the starting point i
each traversal, if nums[i] > 0
is skipped directly, because the array is sorted, the sum will always be greater than 0; otherwise, the window will slide. First form three points [i, i+1, n-1]
i
place, and continue to clamp the next two numbers. As long as their sum is greater than 0, move the third point to the left (the number will become smaller), otherwise the second Click to move to the right (the number will become larger), in fact, the second and third numbers are sliding windows.
In this case, the time complexity is O(n²), because there are two traversals, ignoring the smaller time complexity of fast sorting.
What about the sum of four numbers and the sum of five numbers?
Sum of four
This question is exactly the same as the sum of three numbers, except that it requires four numbers.
Sort first, and then double recursion, that is, to determine that the first two numbers remain unchanged, and the last two numbers are i+1
. The last two numbers are 060c810bd01a38 and n-1
. The algorithm is the same as the sum of the three numbers, so the final time complexity is O( n³).
Then the sum of N numbers (N> 2) can be solved by this idea.
Why is there no better way? I think it might be because:
- Regardless of the sum of the numbers, the time complexity of a quick sort is fixed, so the scheme of using the sum of three numbers actually takes advantage of the sorting algorithm.
- The sliding window can only be moved with two pointers, and there is no window sliding algorithm that has three pointers but keeps the time complexity unchanged.
So for the sum of N numbers, after paying O(nlogn) time complexity through sorting, you can use a sliding window to optimize the time complexity of 2 numbers to O(n), so the overall time complexity is O(N-2 + 1 n), that is O(N-1 n), and the minimum time complexity O(n²) is larger than O(nlogn), so the time complexity of fast sorting is always ignored, so the sum of three times The complexity is O(n²), the time complexity of the sum of four numbers is O(n³), and so on.
It can be seen that we have crossed the threshold of sliding window from the simplest sum of two numbers to the sum of three numbers and the sum of four numbers. essentially uses the ordered characteristics of the sorted array, so that we don’t need to On the premise of traversing the array, the window can be sliding , which is the core idea of the sliding window algorithm.
In order to strengthen this understanding, look at a similar topic, the longest substring without repeated characters.
The longest substring without repeated characters
The longest substring without repeated characters is a medium question, the question is as follows:
Given a string, please find out the length of the longest substring
Since the longest substring is continuous, it is obvious that the sliding window solution can be considered. In fact, after determining the sliding window solution, the problem is very simple, just set left
and right
, and use a hash Set to record which elements have existed, record the maximum length in the process, and try to right
right. If it is found during the process of shifting to the right, If you repeat the character, left
right until the repeated character is eliminated.
The solution is not difficult, but the question is, we have to think clearly, why traversing once with a sliding window? 160c810bd01b57 is not too heavy and not missing That is, the time complexity of this question is only O(n)?
Just want to understand two questions:
- Since the substrings are continuous, since there is no jumping situation, as long as all solutions can be contained in a sliding window, all situations are covered.
- What is not included in a sliding window? Since we only move
right
right, and try to moveleft
right after the repetitionright
then 060c810bd01bad continues to move to the right, which ignores theright
left after the repetition occurs.
We focus on two issues look, obviously, if abcd
four consecutive characters are not repeated, then left
the right, bcd
also clearly not repeated, so if this time can be right
right form bcda
windows keep looking down, There is no need to try bc
, because although this case is not repeated, it must not be the optimal solution.
Well, through this example, we can see that it is not difficult to reduce the scope of the sliding window, but we should pay more attention to the thinking behind why the sliding window can be used, whether the sliding window is not heavy and not leaking, if not Think clearly, maybe the whole idea is wrong.
So the application of the sliding window has been fully explained? Actually no, we only talked about the relatively single brain circuit of shrinking the window above. In fact, the sliding window composed of two pointers may not always slide normally. An interesting scene is the speed of the pointer, that is, the window is determined by the relative speed. How to slide.
Regarding fast and slow pointers, classic problems include circular linked lists and deleting duplicates in ordered arrays.
Circular linked list
Circular linked list is a simple question, the topic is as follows:
Given a linked list, determine whether there is a ring in the linked list.
If the space complexity is not O(1) for advanced requirements, we can slightly "pollute" the original linked list during traversal, so that we can always find out whether we have gone back.
However, the space overhead must be constant, and we have to consider fast and slow pointers. To be honest, when I first saw this problem, if I could think of a quick and slow pointer solution, I would definitely be quite smart, because I must have the ability to transfer knowledge. How to migrate? Imagine that the school is holding a sports meeting. I believe that every time there is a student who ran the slowest, he was so slow that he was chased by the fastest student.
Wait, isn't the playground just a circular linked list? As long as someone runs slowly, they will be caught up quickly. Isn't it an encounter if you catch up? So the fast and slow pointers run separately, as long as they meet, it is judged as a circular linked list, otherwise it is not a circular linked list, and one pointer must finish first.
So the minutiae is to optimize efficiency, how much slower is the slow pointer?
Some people would say that in sports meets, if slow runners want to be caught up by fast runners, it is best not to run. Yes, but in the circular linked list problem, the linked list is not a playground. Maybe only a certain segment is a loop, that is, a slow runner must at least run into the loop to meet a fast runner, but not a slow runner. Knowing where to start the loop is the difficulty.
Have you ever wondered why the dichotomy is used for quick sorting instead of the rule of thirds? Why is it possible to finish the line as quickly as possible every time a knife comes in the middle? The reason is that dichotomy can use the smallest "depth" to cut the array into the smallest granularity. In the same way, in the fast and slow pointers, if the slow pointer wants to be caught up as quickly as possible, the speed may best be half of the fast pointer. So logically, why?
Intuitively, if the slow pointer is too slow, it may spend most of the time in the position before entering the ring. Although the fast pointer is fast, it will always run in the ring, so it is always impossible to encounter the slow pointer. This enlightenment to us is , The slow pointer cannot be too slow; if the slow pointer is too fast, almost the speed is the same as the fast pointer, just like two athletes fighting for the first place, they really want to meet each other, I guess they have to run for a few hours. , So the slow pointer cannot be too fast. So analyzed in this way, the slow pointer can only take half the speed of the compromise.
But is it really the fastest to meet at half the slow speed? Not necessarily. For an example, suppose the linked list is a perfect ring, with a total of 6 nodes in [1,6], then the slow pointer moves 1 step at a time, and the fast pointer moves 2 steps at a time, so the total is 2,3 3,5 4,1 5,3 6,5 1,1
a total of 6 steps, but What if the fast pointer takes 3 steps at a time? A total of 2,4 3,1 4,4
3 steps. So the general speed is not necessarily the best? In fact, it is not. When the computer is addressing the linked list, the consumption of node access should also be taken into consideration. Although the latter seems to be faster, it actually accesses the linked list next
more times. For the computer, it is not as fast as the first one. .
So to be precise, it is not that the fast pointer is twice as fast as the slow pointer, but the slow pointer moves one step at a time, and the fast pointer moves two steps at a time, because when they meet, the total number of moves is the least.
Let's talk about a simple problem, that is, use the speed pointer to determine the kth node from the bottom of the linked list or the midpoint of the linked list.
Determine the midpoint of the linked list
The fast pointer is twice the speed of the slow pointer. When the fast pointer reaches the end, the position of the slow pointer is the midpoint of the linked list.
The kth node from the bottom in the linked list
The kth node from the bottom of the linked list is a simple question. The question is as follows:
Input a linked list, and output the 060c810bd01d5d node fromk
In order to conform to the habits of most people, this question1
, that is, the end node of the linked list is the1
node from the bottom.
This question is to judge the variant of the point in the linked list. As long as the slow pointer is k
nodes slower than the fast pointer, when the fast pointer reaches the end, the slow pointer will point to the last k+1
node. For this question, just pay attention to the count and don't count it wrong.
Next, I finally talk about another classic usage question type of fast and slow pointers, which is to delete duplicate items in an ordered array.
Remove duplicates in an ordered array
Deleting duplicates in an ordered array is a simple question, the topic is as follows:
Give you an ordered array nums
, please place delete the repeated elements so that each element only appears once, and return the new length of the deleted array.
In this question, you need to delete the duplicate elements in place and return the length, so you can only use the speed pointer. But how to use it? How fast is it slow?
In fact, how fast or slow this question is is not preset like the previous question, but is judged based on the actual numbers encountered.
We assume that the slow pointer is slow
fast pointer is fast
. Note that variable naming is also interesting. It is also a double pointer problem. Some are slow right
, and some are slow fast
. The focus is on how to move the pointer.
We only need to let fast
scan the complete table and move all the non-duplicated ones together, so that the time complexity is O(n). The specific method is:
- Let
slow
andfast
initially point to index 0. - Because it is ordered array , so even if there are duplicate must also together, so you can make
fast
directly next scan, and only encounteredslow
different values, and only then itslow+1
exchange, thenslow
increment, recursively , Untilfast
comes to the end of the array.
After finishing this set of operations, the subscript value of slow
You can see, This question is for how slow pointer to slow, in fact, is a value to determine if fast
the value of slow
the same, then slow
has been waiting for, because the same value is to be ignored, let fast
away for Repeated values are skipped.
After talking about the common double pointer usage, let's look at some special problems that are more difficult to gnaw. Here are two main most water, and rainwater .
The container with the most water
The container with the most water is a middle-level question, the topic is as follows:
Give youn
non-negative integersa1,a2,...,an
, each number represents a point(i, ai)
in the coordinates.n
vertical lines in the coordinates. The two end points of thei
(i, ai)
and(i, 0)
respectively. Find out the two lines sox
shaft can hold the most water.
<img width=400 src="https://z3.ax1x.com/2021/06/12/25WZZt.png">
It is recommended to read the topic carefully before continuing, as this topic is relatively complicated.
Okay, why is this a double pointer problem? Because we see how to calculate the volume of water contained? In fact, this question is simplified to length multiplying width.
The length is the distance between the two selected pillars, and the width is the height of the shortest pillar. The problem is that although the farther the distance between the pillars is, the greater the length, but the width is not necessarily the largest. It is impossible to see the optimal solution at a glance.
So we still have to try many times, so how can we use the least number of attempts, but not too much? Defined left
right
two pointers point to 0
and n-1
that is, first and last position, this time length is the largest (inter-pillar distance is the farthest), then try the other column, which try it?
- The longer one? If the new one is shorter and shorter, then the width is shorter; if the new one is shorter and longer, it is useless, because the shorter one determines the water level.
- The shorter one? If the new one is longer, then there is a chance that the overall volume will be larger.
So we move the shorter one and calculate the volume each time. Finally, it ends when the two columns meet. The maximum volume in the process is the global maximum volume.
The moving rule of the double pointer in this question is more ingenious. It is different from the ordinary question above. The point is not whether the sliding window algorithm will be used, but whether the rule of moving the pointer can be found.
Of course, you might say, why should the two pointers be defined at the extreme ends and not elsewhere? Because this makes it impossible to control variables.
If the pointer is selected in the middle position, then when the pointer moves out, the distance between the pillars and the length of the pillars change at the same time, it is difficult to find a perfect route. For example, we move a shorter column because the shorter column determines the lowest water level. Changing it may make the lowest water level higher, but the problem is that the distance between the two columns is also increasing, so the movement is shorter or longer. It is impossible to say which column is better.
To be honest, this method is not easy to think of, you need to find a few more options to try to find out. Of course, if the algorithm can be derived according to a fixed routine, there will be no difficulty, so accept this kind of thinking jump.
Next, let’s look at a more special sliding window problem. It is even divided into multi-segment sliding windows when it receives rainwater.
Catch rain
Receiving rain is a difficult question, the topic is as follows:
Givenn
non-negative integers representing1
, calculate how much rain water can be received by the columns arranged in this way.
<img width=400 src="https://z3.ax1x.com/2021/06/12/25OejP.png">
Different from Sheng Yushui, this rain catching water looks at the whole, and we have to calculate the amount of all the water that can be catched.
In fact, compared to the previous question, this question is relatively easy to get into because we can calculate from left to right. Thinking and discovering that rainwater can only be received when a "groove" is generated, and the groove is determined by the tallest pillars on both sides of it. So what range is a groove?
Obviously grooves can be clearly grouped, and a groove cannot be divided into multiple grooves, just like you see puddles, no matter how many and how deep the pits are together, they can always be counted clearly, so we Just start counting from left to right.
How to count the grooves? Using the sliding window method, each window is a groove, then the starting point of the window left
is the first column on the left, there are the following situations:
- If the immediately adjacent right pillar is taller (or the same height), then looking to the right from it, it can't catch rain at all, so just throw it away,
left++
. If the immediately adjacent right column is shorter, there is a chance of grooves.
- Then continue to look to the right. If the right side is always shorter, it will not catch the rain.
If there is a taller one on the right, you can receive rainwater. The question is how much can be received, and where to find the end?
- As long as the height of the leftmost column is recorded, the end judgment condition of the right column is "encountered a column as high as the leftmost column", because how much water a groove can hold depends on the shortest column. Of course, if there is no pillar on the right side, although it is a little lower than the leftmost side, as long as it is higher than the deepest one, it is considered an end point.
In this question, once the groove end point is encountered, left
will be updated and a new round of groove calculation will start, so there are multiple sliding windows. It can be seen from this question that the sliding window question type is quite flexible. Not only does the judgment condition vary from question to question, but the number of windows may also be multiple.
to sum up
The sliding window is essentially a two-pointer gameplay. There are different routines for different topics, from the simplest double-folding according to the law, to fast and slow pointers, to special algorithms that vary from problem to problem without a fixed routine.
In fact, the routine of double-teaming according to the law belongs to the category of collision pointers. Generally, the sorted array can be judged step by step, or judged by dichotomy. In short, it is not necessary to judge based on the overall traversal, and the efficiency is naturally high.
There are also routines for fast and slow pointers, but how much faster or slower you actually need depends on the specific scene.
For sliding windows with no fixed routines, you must carefully taste them according to the topic. If all routines can be summarized, the algorithm will be less fun.
The discussion address is: Intensive Reading "Algorithm-Sliding Window" · Issue #328 · dt-fe/weekly
If you want to participate in the discussion, please click here , there are new topics every week, weekend or Monday release. Front-end intensive reading-to help you filter reliable content.
Follow front-end intensive reading WeChat public
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
Copyright statement: Freely reproduced-non-commercial-non-derivative-keep the signature ( Creative Commons 3.0 License )
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。