After almost brushing all the questions, I found these things… (second bullet)

Time:2022-5-11

A little digression

Last time, I did a small survey on my official account, which is “throw out the questions you want to explain the programming language ~”. The following are the results of the survey:

After almost brushing all the questions, I found these things... (second bullet)

For others, it is mostly go language.

After almost brushing all the questions, I found these things... (second bullet)

Since the proportion of Java and python has exceeded 60%, this time I try to write in both Java and python. Thank @ captainz for the Java code provided. At the same time forInstead of making the article smelly and long, I put all the code of this article (Java and python) on the official website of lijianga, website address: https://leetcode-solution.cn/…

If you don’t surf the Internet scientifically, it may be very slow to open.

text

After almost brushing all the questions, I found these things... (second bullet)

Hello, I’m Lucifer. Today, I bring you the topic of pile. First, let’s go over the outline of this article. This is a brain map I drew with mindmap. Then I will continue to improve and gradually improve other topics.

You can also use vscode blink mind to open the source file to view. There are some notes that you can click to view. The source files can be obtained by replying to the brain map on my official account “likuijiajia”, and the brain map will continue to update more content in the future. Vscode plugin address: https://marketplace.visualstu…

This series includes the following topics:

  • Almost finished all the linked list questions of Li Kou, and I found these things…
  • After almost brushing all the tree questions, I found these things…
  • After almost brushing all the questions, I found these things… (first bullet)

<!– more –>

This is the second part. Students who haven’t read the first part strongly recommend reading the first part first. I almost finished brushing all the questions. I found these things… (first bullet)

This is the second part. The later contents are more dry goods. They areThree skillsandFour applications。 These two topics are designed to teach you how to solve problems. After mastering it, most of the heap problems in the buckle are easy to talk about (of course, I only refer to the part of the topic related to the heap).

Warning: the topics in this chapter are basically the hard difficulty of force deduction. This is because there are many problems in the stack, and the marking difficulty is not small. This has also been introduced earlier.

A little explanation

Before serving the main course, let’s have an appetizer.

Here we introduce two concepts, namelytupleandSimulated large top reactor。 The reason for these instructions is to prevent you from not understanding it later.

tuple

Using heap can not only store a single value, for example, 1, 2, 3 and 4 of [1, 2, 3 and 4] are single values respectively. In addition to a single value, you can also store composite values, such as objects or tuples.

Here we introduce a way to store tuples. This skill will be widely used later. Please be sure to master it. For example [(1,2,3), (4,5,6), (2,1,3), (4,2,8)].

h = [(1,2,3), (4,5,6), (2,1,3),(4,2,8)]
heapq. Heappify (H) # heap (small top heap)

heapq. Heappop () # pop up (1,2,3)
heapq. Heappop () # pop up (2,1,3)
heapq. Heappop () # pop up (4,2,8)
heapq. Heappop () # pop up (4,5,6)

The heap structure is shown as follows:

After almost brushing all the questions, I found these things... (second bullet)

Briefly explain the execution result of the above code.

Using tuples, the first value of the tuple is compared as a key by default. If the first one is the same, continue to compare the second one. For example, for the above (4,5,6) and (4,2,8), because the first value is the same, continue to compare the latter one, and because 5 is larger than 2, so (4,2,8) comes out of the heap first.

Using this technique has two functions:

  1. Carry some additional information. For example, I want to find the k-th decimal in a two-dimensional matrix. Of course, I use the value as the key. However, the processing process needs to use its row and column information, so it is appropriate to use tuples, such as (Val, row, Col).
  2. You want to sort by two keys, a primary key and a secondary key. There are two typical uses,

    2.1 one is that both are in the same order, such as both in order or reverse order.

    2.2 the other is two different orders, that is, one is reverse order and the other is order.

Due to space reasons, the specific work will not be carried out here. You can pay attention to it in the process of doing questions at ordinary times. I will open a separate article to explain it when I have the opportunity.

If the programming language you use has no heap or the implementation of heap does not support tuples, you can also support it through simple transformation, mainly custom comparison logic.

Simulated large top reactor

Because Python does not have a large top heap. Therefore, I use a small top heap for simulation. That is, take all the original numbers as the opposite number. For example, if the original number is 5, put – 5 into the heap. After such treatment, the small top pile can be used as a large top pile. But it should be noted that when you pop out,Remember to reverse it and restore it backoh

Code example:

h = []
A = [1,2,3,4,5]
for a in A:
    heapq.heappush(h, -a)
-1 * heapq.heappop(h) # 5
-1 * heapq.heappop(h) # 4
-1 * heapq.heappop(h) # 3
-1 * heapq.heappop(h) # 2
-1 * heapq.heappop(h) # 1

As shown in the figure, it is as follows:

After almost brushing all the questions, I found these things... (second bullet)

That’s all. Let’s get to the point.

Three skills

Skill 1 – fixed reactor

This technique means that the size k of the fixed heap remains unchanged, and the code can be passedEvery pop goes out and pushes inTo achieve. Since the initial heap may be 0, we need to push into the heap one by one to achieve the heap size of K, so strictly speaking, it should beKeep the size of the heap no larger than k

A typical application of fixed heap is to find the smallest number K. In fact, the simplest way to find the k-th smallest number is to establish a small top pile and combine all numbersFirst put all into the heap, and then get out of the heap one by one. A total of K times。 The last one out of the heap is the number k smaller.

However, instead of putting them all into the heap first, we can establishLarge top pile(note that it is not the small top heap above), and maintain the size of the heap as K. If the size of the heap is larger than k after the new number is put into the heap, you need to compare the number at the top of the heap with the new number,And remove the larger ones。 This can guaranteeThe number in the heap is the smallest K of all numbers, and isn’t the largest of the smallest K (i.e. the top of the heap) the smallest K? This is why we choose to build a large top pile instead of a small top pile.

After almost brushing all the questions, I found these things... (second bullet)

In a simple sentence, the summary isFixing a large top heap with size k can quickly find the k-th smallest number, on the contrary, fixing a small top heap with size k can quickly find the k-th largest number。 For example, the third question of week 2020-02-24 is 5663 Finding the XOR coordinate value of the k-th largest can be realized by using the fixed small top heap technique (this problem allows you to find the number of the k-th largest).

So maybe your feeling is not strong. Next, I’ll give you two examples to help you deepen your impression.

295. Median data flow

Title Description
The median is the number in the middle of the sequence table. If the list length is even, the median is the average of the middle two numbers.

For example,

The median of [2,3,4] is 3

The median of [2,3] is (2 + 3) / 2 = 2.5

Design a data structure that supports the following two operations:

Void addnum (int Num) - adds an integer from the data stream to the data structure.
Double findmedian() - returns the median of all current elements.
Example:

addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3)
findMedian() -> 2
Advanced:

If all integers in the data stream are in the range of 0 to 100, how will you optimize your algorithm?
If 99% of the integers in the data stream are in the range of 0 to 100, how will you optimize your algorithm?
thinking

In fact, this problem can be seen as a special case of finding the smallest number K.

  • If the length of the list is odd, then K is (n + 1) / 2, and the median is the k-th number,. For example, if n is 5, K is (5 + 1) / 2 = 3.
  • If the list length is even, then K is (n + 1) / 2 and (n + 1) / 2 + 1, and the median is the average of the two numbers. For example, if n is 6, K is (6 + 1) / 2 = 3 and (6 + 1) / 2 + 1 = 4.

Therefore, our can maintain two fixed heaps. The size of the fixed heap is $(n + 1) \ div 2 $and $n – (n + 1) \ div2 $, that is, the size of the two heapsmostThe difference is 1. More specifically, $0 < = (n + 1) \ div 2 – (n – (n + 1) \ div 2) < = 1 $.

Based on the knowledge mentioned above, we can:

  • Create a large top heap and store the minimum number of $(n + 1) \ div 2 $, so that the number at the top of the heap is the smaller number of $(n + 1) \ div 2 $, that is, the median of odd cases.
  • Create a small top heap and store the maximum number of N – $(n + 1) \ div 2 $, so that the number of top heap is the largest number of N – $(n + 1) \ div 2 $. Combined with the above large top heap, the median of even number can be obtained.

With this knowledge, all that remains is how to maintain the size of the two heaps.

  • If the number of large top piles is less than that of small top piles, the smallest of the small top piles will be transferred to the large top pile. Since the small top maintains the maximum number of K and the large top maintains the minimum number of K, the top of the small top must be greater than or equal to the top of the large top, and the two tops arehereMedian of.
  • If the number of large top piles is 2 more than the number of small top piles, then the largest of the large top piles will be transferred to the small top pile for the same reason.

At this point, you may have understood why you need to build two heaps respectively, and you need a large top heap and a small top heap. The reason for this is as described above.

The common application of fixed heap is more than that. Let’s continue to look at a problem.

code
class MedianFinder:
    def __init__(self):
        self.min_heap = []
        self.max_heap = []
    def addNum(self, num: int) -> None:
        if not self.max_heap or num < -self.max_heap[0]:
            heapq.heappush(self.max_heap, -num)
        else:
            heapq.heappush(self.min_heap, num)
        if len(self.max_heap) > len(self.min_heap) + 1:
            heappush(self.min_heap, -heappop(self.max_heap))
        elif len(self.min_heap) > len(self.max_heap):
            heappush(self.max_heap, -heappop(self.min_heap))
    def findMedian(self) -> float:
        if len(self.min_heap) == len(self.max_heap): return (self.min_heap[0] - self.max_heap[0]) / 2
        return -self.max_heap[0]

(code 1.3.1)

857. Minimum cost of employing K workers

Title Description
There are n# workers. The work quality of the , I , worker is , quality [i], and the minimum expected wage is , wave [i].

Now we want to hire , workers to form a wage group. When employing a group of K workers, we must pay them according to the following rules:

Each worker in the wage group shall be paid according to the proportion of the quality of his work to that of other workers in the same group.
Each worker in the wage group should receive at least their minimum expected wage.
Returns the minimum amount of money needed to form a wage group that meets the above conditions.

 

Example 1:

Input: quality = [10,20,5], wave = [70,50,30], k = 2
Output: 105.00000
Explanation: we pay 70 to worker 0 and 35 to worker 2.
Example 2:

Input: quality = [3,1,10,10,1], wave = [4,8,2,2,7], k = 3
Output: 30.66667
Explanation: we pay 4 to worker 0 and 13.33333 to worker 2 and 3 respectively.
 

Tips:

1 < = k < = n < = 10000, where n = quality length = wage. length
1 <= quality[i] <= 10000
1 <= wage[i] <= 10000
An answer that is within 10 ^ - 5 of the correct answer will be considered correct.
thinking

The topic requires us to select k individuals and pay wages according to the proportion of their work quality to the work quality of other workers in the same group, and each worker in the wage group should at least get their minimum expected wage.

In other words, for the K individuals in the same group, their work quality and wage ratio is a fixed value in order to pay the least wage. Please understand this sentence first. The following contents are based on this premise.

We might as well set an indexwork efficiency, whose value is equal to Q / W. As mentioned earlier, only when the Q / W of this k person is the same can we ensure the minimum wage, and this Q / W must be the lowest (short board) of this k person, otherwise someone will not get the minimum expected wage.

So we can write the following code:

class Solution:
    def mincostToHireWorkers(self, quality: List[int], wage: List[int], K: int) -> float:
        eff = [(q / w, q, w) for a, b in zip(quality, wage)]
        eff.sort(key=lambda a: -a[0])
        ans = float('inf')
        for i in range(K-1, len(eff)):
            h = []
            k = K - 1
            rate, _, total = eff[i]
            #Find K people who are more efficient than it, and the salary of these K people is as low as possible.
            #Because the work efficiency has been arranged in reverse order, the front ones are higher than it, and then use the heap to get k lowest paid ones.
            for j in range(i):
                heapq.heappush(h, eff[j][1] / rate)
            while k > 0:
                total += heapq.heappop(h)
                k -= 1
            ans = min(ans, total)
        return ans

(code 1.3.2)

This method pushes many times each time and does not pop K times, which does not make good use of the heapdynamicFeatures, but only itsSeeking extreme valueCharacteristics of.

A better approach is to useFixed heap technique

This problem can be considered from another angle. In fact, this question is not to let us choose k individuals, take the lowest work efficiency among them, calculate the total wage according to this minimum work efficiency, and find out the minimum total wage? Therefore, this problem can fix a large top pile with the size of K, and ensure that the top of the pile is the smaller K through certain operations (the operation is similar to the previous problem).

And in the previous solution, the heap uses triples (Q / W, Q, w). In fact, this is not necessary. Because we know two of them, we can deduce the other one, so we can store two, and because we needAccording to the work efficiency ratio, it is the key of the heapSo just choose any Q or W. here I choose q, that is, save (Q / 2, q) binary.

Specifically: the total salary of K individuals with rate as the minimum work efficiency ratio = $\ displaystyle \ sum_ {n=1}^{k}{q}_ {n} / rate $, where rate is the current Q / W and the minimum value of K individuals’ Q / W.

code
class Solution:
    def mincostToHireWorkers(self, quality: List[int], wage: List[int], K: int) -> float:
        effs = [(q / w, q) for q, w in zip(quality, wage)]
        effs.sort(key=lambda a: -a[0])
        ans = float('inf')
        h = []
        total = 0
        for rate, q in effs:
            heapq.heappush(h, -q)
            total += q
            if len(h) > K:
                total += heapq.heappop(h)
            if len(h) == K:
                ans = min(ans, total / rate)
        return ans

(code 1.3.3)

Technique 2 – multiway merging

This technique is actually mentioned earlierSuper ugly numberIt has been mentioned at the time of, but there is no one for this type of topicname

In fact, this technique, called multi pointer optimization, may be more appropriate, but the name is too simple and easy to be confused with double pointers, so I gave TA a unique name-multiway merge

  • Multiple routes are reflected in: there are multiple candidate routes. In code, we can use multiple pointers to represent.
  • Merging is reflected in: the result may be the longest or shortest among multiple candidate routes, or the k-th route, etc. Therefore, we need to compare the results of multiple routes and discard or select one or more routes according to the topic description.

This description is more abstract. Next, we will deepen our understanding through several examples.

Here I have carefully prepared for youThe difficulty of four courses is hardThe title of the. After mastering this routine, you can go to AC these four questions happily.

1439. The k-th smallest array sum in the ordered matrix

Title Description
Give you a matrix mat of M * N and an integer K. each row in the matrix is arranged in a non decreasing order.

You can select one element from each line to form an array. Returns the sum of the kth smallest array of all possible arrays.

 

Example 1:

Input: mat = [[1,3,11], [2,4,6]], k = 5
Output: 7
Explanation: select an element from each row. The first k and smallest arrays are:
[1,2], [1,4], [3,2], [3,4], [1,6]。 The sum of the fifth is 7.
Example 2:

Input: mat = [[1,3,11], [2,4,6]], k = 9
Output: 17
Example 3:

Input: mat = [[1,10,10], [1,4,5], [2,3,6]], k = 7
Output: 9
Explanation: select an element from each row. The first k and smallest arrays are:
[1,1,2], [1,1,3], [1,4,2], [1,4,3], [1,1,6], [1,5,2], [1,5,3]。 The sum of the seventh is 9.
Example 4:

Input: mat = [[1,1,10], [2,2,9]], k = 7
Output: 12
 

Tips:

m == mat.length
n == mat.length[i]
1 <= m, n <= 40
1 <= k <= min(200, n ^ m)
1 <= mat[i][j] <= 5000
Mat [i] is a non decreasing group
thinking

In fact, this problem is to give you m one-dimensional arrays with the same length. Let’s choose a number from these m arrays, that is, select m numbers in total, and find the sum of these m numbers isAll selection possibilitiesAnd the k-th smallest.

After almost brushing all the questions, I found these things... (second bullet)

A simple idea is to use multiple pointers to solve. For this problem, m pointers are used to point to m one-dimensional arrays respectively. The position of the pointer indicates that the current selection is the number of one-dimensional arrays.

In the titlemat = [[1,3,11],[2,4,6]], k = 5For example.

  • First initialize the two pointers P1 and P2, respectively pointing to the beginning of two one-dimensional arrays. The code indicates that they are all initialized to 0.
  • At this time, the sum of the numbers pointed to by the two pointers is 1 + 2 = 3, which is the first small sum.
  • Next, we move one of the pointers. At this point, we can move P1 or P2.
  • Then the second smallest value must be the smaller value of moving P1 and moving P2. Here, moving P1 and P2 will actually get 5, that is, the sum of the second and third smallest is 5.

It has forked here. There are two situations (pay attention to the position in bold, which indicates the position of the pointer):

  1. [1,3,11],[2, 4,6] and 5
  2. [1,3,11],[2,4, 6] and 5

Next, these two situations shouldGo hand in hand and carry on together

For case 1, there are two cases of moving next.

  1. [1,3,11],[2, 4,6] and 13
  2. [1,3,11],[2,4, 6] and 7

For case 2, there are two cases of moving next.

  1. [1,3,11],[2,4, 6] and 7
  2. [1,3,11],[2,4,6]The sum is 7

By comparing these four cases, we come to the conclusion that the small numbers of 4, 5 and 6 are all 7. But the seventh smallest number is not necessarily 13. The reason is similar to the above. Maybe the seventh one is hidden in the new situation after the division of the previous seven. In fact, it is true. Therefore, we need to continue to implement the above logic.

Further, we can expand the above ideas to the general situation.

The problem mentioned above requires the smallest sum of K, and the smallest one is easy to know, that is, the first sum of all one-dimensional arrays. We also found that according to the smallest, we can deduce the second smallest. The way of derivation is to move one of the pointers, which splits a total of N cases, where n is the length of one-dimensional array, the second smallest is in the N cases in the split, and the way of screening is the sum of these n casesminimumYes, the latter situation is similar. It is not difficult to see that the extreme value also changes after each split. Therefore, this is an obvious signal for dynamic extreme value calculation. Using heap is a good choice.

How should the code be written?

As mentioned above, we first need to initialize m pointers and assign a value of 0. Corresponding pseudo code:

#Initialize heap
h = []
#Sum (VEC [0] for VEC in mat) is the first term and sum of M one-dimensional arrays
#[0] * m initializes an array with length m and all filled with 0.
#We assemble the above two information into Yuanzu cur for easy use
cur = (sum(vec[0] for vec in mat), [0] * m)
#Put it in the heap
heapq.heappush(h, cur)

Next, we move the pointer one at a time to form a new branch. The smallest one is ejected from the heap every time, and the k-th one is the smallest one. Pseudo code:

for 1 to K:
    #ACC current and pointers are pointers.
    acc, pointers = heapq.heappop(h)
    #Brutally move a pointer in the pointer array every time. Every time you move a pointer, it forks. The total possible movement is n, where n is the length of one-dimensional array.
    for i, pointer in enumerate(pointers):
        #If pointer = = len (mat [0]) - 1, it means the end is reached and cannot be moved
        if pointer != len(mat[0]) - 1:
            #The meaning of the following two sentences is to change the pointer of pointers [i] to pointers [i] + 1
            new_pointers = pointers.copy()
            new_pointers[i] += 1
            #Reload the updated ACC and pointer array into the heap
            heapq.heappush(h, (acc + mat[i][pointer + 1] - mat[i][pointer], new_pointers))

This ismultiway merge The core code of the problem, please remember.

The code looks a lot, but it’s only seven lines without comments.

There is a problem with the pseudo code above. For example, there are two one-dimensional arrays, and the pointers are initialized to 0. Move the pointer of the first one-dimensional array for the first time and the pointer of the second array for the second time. At this time, the pointer array is [1,1], that is, all pointers point to the elements with subscript 1. If the pointer of the second one-dimensional array is moved for the first time and the pointer of the first array is moved for the second time, the pointer array is still [1,1]. This is actually a situation. If it is not controlled, it will be calculated twice and cause errors.

One possible solution is to use HashSet to record all pointers, so as to avoid the problem that the same pointer is calculated multiple times. To do this, we need to fine tune the use of pointer arrays, that is, use tuples instead of arrays. The reason is that arrays cannot be hashed directly. Please refer to the code area for details.

multiway merge The title, idea and code are similar. In order to understand the following questions better, please be sure to solve this question. We won’t analyze it in detail later.

code
class Solution:
    def kthSmallest(self, mat, k: int) -> int:
        h = []
        cur = (sum(vec[0] for vec in mat), tuple([0] * len(mat)))
        heapq.heappush(h, cur)
        seen = set(cur)

        for _ in range(k):
            acc, pointers = heapq.heappop(h)
            for i, pointer in enumerate(pointers):
                if pointer != len(mat[0]) - 1:
                    t = list(pointers)
                    t[i] = pointer + 1
                    tt = tuple(t)
                    if tt not in seen:
                        seen.add(tt)
                        heapq.heappush(h, (acc + mat[i][pointer + 1] - mat[i][pointer], tt))
        return acc

(code 1.3.4)

719. Find the k-th smallest distance pair

Title Description
Given an integer array, returns the k-th minimum distance between all pairs. The distance between a pair (a, b) is defined as the absolute difference between a and B.

Example 1:

Input:
nums = [1,3,1]
k = 1
Output: 0
Explanation:
All pairs are as follows:
(1,3) -> 2
(1,1) -> 0
(3,1) -> 2
Therefore, the number pair of the first minimum distance is (1,1), and the distance between them is 0.
Tips:

2 <= len(nums) <= 10000.
0 <= nums[i] < 1000000.
1 <= k <= len(nums) * (len(nums) - 1) / 2.
thinking

It is not difficult to see that all pairs may total $C_ N ^ 2 $, that is, $n \ times (n-1) \ div2 $.

Therefore, we can use two loops to find all pairs, sort them in ascending order, and then take the k-th.

In fact, we can use the fixed heap technique to maintain a large top heap with the size of K, so that the element at the top of the heap is the smallest K. This has been mentioned in the previous fixed heap and will not be repeated.

class Solution:
    def smallestDistancePair(self, nums: List[int], k: int) -> int:
        h = []
        for i in range(len(nums)):
            for j in range(i + 1, len(nums)):
                a, b = nums[i], nums[j]
                #Keep the heap size no more than k
                if len(h) == k and -abs(a - b) > h[0]:
                    heapq.heappop(h)
                if len(h) < k:
                    heapq.heappush(h, -abs(a - b))

        return -h[0]

(code 1.3.5)

However, this optimization is of little significance, because the bottleneck of the algorithm lies in the enumeration of the $n ^ 2 $part, which we should try to optimize.

If we sort the number pairs, the minimum number pair distance must be in num [i] – num [I – 1], where I is an integer from 1 to N, which depends on who is smaller. Next, we can use the above idea of multi-channel merging to solve it.

If the difference between num [i] – num [I – 1] is the smallest, the second smallest must be the remaining N – 1 case and the new case of num [i] – num [I – 1] splitting. About how to split, similar to the above, we just need to move the pointer of I to I + 1. The length of the pointer array here is fixed to 2, not m in the above title. Here, I name the two pointers FR and to respectively, representing from and to respectively.

code
class Solution(object):
    def smallestDistancePair(self, nums, k):
        nums.sort()
        #N candidate answers
        h = [(nums[i+1] - nums[i], i, i+1) for i in range(len(nums) - 1)]
        heapq.heapify(h)

        for _ in range(k):
            diff, fr, to = heapq.heappop(h)
            if to + 1 < len(nums):
                heapq.heappush((nums[to + 1] - nums[fr], fr, to + 1))

        return diff

(code 1.3.6)

Since the time complexity is related to K, and K may reach the order of $n ^ 2 $at most, this method will actually timeout.However, this proves the correctness of this idea. If the topic changes slightly, it may be used

This problem can be solved by dichotomy. Because it deviates from the heap theme, let’s talk about it briefly here.

It’s easy to think of heap and dichotomy to find the smallest number K. The reason for dichotomy is to find the smallest K, which is essentially to find the number with K – 1 that is not greater than itself. This problem is often monotonous, so dichotomy can be used to solve it.

For this problem, the maximum number difference is the maximum minimum value of the array, which can be recorded as max_ diff。 We can ask this question:

  • The number pair difference is less than max_ How many diffs are there?
  • The number pair difference is less than max_ How many diff – 1?
  • The number pair difference is less than max_ How many diff – 2?
  • The number pair difference is less than max_ How many diff – 3?
  • The number pair difference is less than max_ How many diff – 4?
  • 。。。

As we know, the answer to the question is not strictly decreasing, so the use of two points should be thought of. We kept asking questions until we askedK – 1 less than xJust. However, such questions also have problems. There are two reasons:

  1. There may be more than one number with K – 1 less than x
  2. We cannot be sure that the number of K – 1 less than x must exist. For example, if the number pair differences are [1,1,1,1,2], let you find the third largest, then there are two numbers less than X that don’t exist at all.

Our thinking can be adjusted to demandLess than or equal to XThere are k. Next, we can use the leftmost template of dichotomy to solve it. For the leftmost template, please refer to my binary search topic

code:

class Solution:
    def smallestDistancePair(self, A: List[int], K: int) -> int:
        A.sort()
        l, r = 0, A[-1] - A[0]

        def count_ngt(mid):
            slow = 0
            ans = 0
            for fast in range(len(A)):
                while A[fast] - A[slow] > mid:
                    slow += 1
                ans += fast - slow
            return ans

        while l <= r:
            mid = (l + r) // 2
            if count_ngt(mid) >= K:
                r = mid - 1
            else:
                l = mid + 1
        return l

(code 1.3.7)

632. Minimum interval

Title Description
You have k# a list of non decreasing integers. Find a minimum interval so that at least one number in each of the K # lists is included.

We define that if , B-A < d-c , or , a < C when , B-A = = d-c , the interval [a, b] is smaller than [C, D].

 

Example 1:

Input: num = [[4,10,15,24,26], [0,9,12,20], [5,18,22,30]]
Output: [20,24]
Explanation:
List 1: [4, 10, 15, 24, 26], 24 in interval [20, 24].
List 2: [0, 9, 12, 20], 20 in interval [20, 24].
List 3: [5, 18, 22, 30], 22 in interval [20, 24].
Example 2:

Input: num = [[1,2,3], [1,2,3], [1,2,3]]
Output: [1,1]
Example 3:

Input: num = [[10,10], [11,11]]
Output: [10,11]
Example 4:

Input: nums = [11], []
Output: [10,11]
Example 5:

Input: num = [[1], [2], [3], [4], [5], [6], [7]]
Output: [1,7]
 

Tips:

nums.length == k
1 <= k <= 3500
1 <= nums[i].length <= 50
-105 <= nums[i][j] <= 105
Nums [i] in non decreasing order
thinking

This problem is essentiallyTake out a number from each of the M one-dimensional arrays and re form a new array a to minimize the difference (diff) between the maximum value and the minimum value in the new array a

This question is a little similar to the above one, but slightly different. This question is a matrix, and the above question is a one-dimensional array. However, we can see the two-dimensional matrix as one-dimensional array, so we can follow the above idea.

The idea above is that the smallest diff must be generated between adjacent elements after sorting. For this problem, we can’t directly sort the two-dimensional array, and even if we sort, it’s not easy to determine the principle of sorting.

In fact, we can continue to use the ideas of the previous two questions. Specifically, useSmall top heap gets the minimum value in the heap, and then throughA variable records the maximum value in the heapIn this way, you can know the diff. each time you update the pointer, a new diff will be generated. Just repeat this process and maintain the global minimum diff.

The premise of this algorithm is that K lists are arranged in ascending order. Here, the principle of array ascending order is the same as the above topic. After ordering, you can maintain a pointer for each list, and then use the above idea to solve it.

Take num = [[1,2,3], [1,2,3], [1,2,3]] in the title as an example:

  • [1,2,3]
  • [1,2,3]
  • [1,2,3]

Let’s first select the minimum value of all rows, that is [1,1,1]. At this time, the diff is 0, the global maximum value is 1, and the minimum value is 1. Next, continue to look for a spare tire to see if there is a better spare tire for us to choose from.

The next spare wheel may occur in case 1:

  • [1,2,3]
  • [1,2,3]
  • [1,2, 3] moved the pointer of this line, moving it one unit from the original 0 to 1.

Or case 2:

  • [1,2,3]
  • [1,2, 3] moved the pointer of this line, moving it one unit from the original 0 to 1.
  • [1,2,3]

。。。

These situations continue to split, and there are more situations. This is the same as the above topic, so I won’t repeat it.

code
class Solution:
    def smallestRange(self, martrix: List[List[int]]) -> List[int]:
        l, r = -10**9, 10**9
        #Put the smallest of each row into the heap, and record its row number and column number at the same time. A total of n go hand in hand
        h = [(row[0], i, 0) for i, row in enumerate(martrix)]
        heapq.heapify(h)
        #Maintenance maximum
        max_v = max(row[0] for row in martrix)

        while True:
            min_v, row, col = heapq.heappop(h)
            # max_ v - min_ V is the current maximum and minimum difference, and R - L is the global maximum and minimum difference. Because if the current is smaller, we update the global results
            if max_v - min_v < r - l:
                l, r = min_v, max_v
            if col == len(martrix[row]) - 1: return [l, r]
            #Update the pointer and continue to move back one bit
            heapq.heappush(h, (martrix[row][col + 1], row, col + 1))
            max_v = max(max_v, martrix[row][col + 1])

(code 1.3.8)

1675. Minimum offset of array

Title Description
Give you an array of n positive integers, nums.

You can perform two types of operations on any element of the array any number of times:

If the element is even, divide by 2
For example, if the array is [1,2,3,4], you can do this on the last element to make it [1,2,3,2]
If the element is odd, multiply by 2
For example, if the array is [1,2,3,4], you can do this on the first element to make it [2,2,3,4]
The offset of the array is the maximum difference between any two elements in the array.

Returns the minimum offset an array can have after performing some operation.

Example 1:

Input: num = [1,2,3,4]
Output: 1
Explanation: you can convert the array to [1,2,3,2] and then to [2,2,3,2]. The offset is 3 - 2 = 1
Example 2:

Input: num = [4,1,5,20,3]
Output: 3
Explanation: after two operations, you can convert the array to [4,2,5,5,3], and the offset is 5 - 2 = 3
Example 3:

Input: num = [2,10,8]
Output: 3

Tips:

n == nums.length
2 <= n <= 105
1 <= nums[i] <= 109
thinking

The title says that you can perform any operation on each item in the array, but in fact, the operation is limited.

  • We can only double the odd number once, because after twice, it becomes an even number.
  • We can divide an even number by 2 several times until it is equal to an odd number. It is not difficult to see that this is also a finite operation.

Take [1,2,3,4] in the title. We can:

  • Change 1 to 2 (can also remain unchanged)
  • Change 2 to 1 (it can also be unchanged)
  • Change 3 to 6 (it can also be unchanged)
  • Change 4 to 2 or 1 (it can also be unchanged)

It is shown as follows:

After almost brushing all the questions, I found these things... (second bullet)

Isn’t this equivalent to selecting a number from each row of a two-dimensional array such as [[1,2], [1,2], [3,6], [1,2,4]] and minimizing the difference? Isn’t this the same as the above topic?

Here, I directly encapsulate the solution of the above topic into an API call. See the code for details.

code
class Solution:
    def smallestRange(self, martrix: List[List[int]]) -> List[int]:
        l, r = -10**9, 10**9
        #Put the smallest of each row into the heap, and record its row number and column number at the same time. A total of n go hand in hand
        h = [(row[0], i, 0) for i, row in enumerate(martrix)]
        heapq.heapify(h)
        #Maintenance maximum
        max_v = max(row[0] for row in martrix)

        while True:
            min_v, row, col = heapq.heappop(h)
            # max_ v - min_ V is the current maximum and minimum difference, and R - L is the global maximum and minimum difference. Because if the current is smaller, we update the global results
            if max_v - min_v < r - l:
                l, r = min_v, max_v
            if col == len(martrix[row]) - 1: return [l, r]
            #Update the pointer and continue to move back one bit
            heapq.heappush(h, (martrix[row][col + 1], row, col + 1))
            max_v = max(max_v, martrix[row][col + 1])
    def minimumDeviation(self, nums: List[int]) -> int:
        matrix = [[] for _ in range(len(nums))]
        for i, num in enumerate(nums):
            if num & 1 == 1:
                matrix[i] += [num, num * 2]
            else:
                temp = []
                while num and num & 1 == 0:
                    temp += [num]
                    num //= 2
                temp += [num]
                matrix[i] += temp[::-1]
        a, b = self.smallestRange(matrix)
        return b - a

(code 1.3.9)

Skill 3 – little Zhuge after the event

After almost brushing all the questions, I found these things... (second bullet)

This technique refers to: when traversing from left to right, we don’t know what the right is. We don’t know until you get to the right.

If you want to know what is on the right, a simple way is to traverse twice. The first traverse records the data. When the second traverse, use the data recorded in the last traverse. This is the way we use most. However, sometimes we can also go back after traversing the specified element, so that we can store while traversing and use one traversal. Specifically, collect all the data from left to right, and select one from the inside when it is needed. If we all want to take the maximum or minimum value and the extreme value will change, we canUsing heap acceleration。 Intuitively, it is to use the time machine to go back to the front and achieve the purpose of hindsight.

Say soYou must not understand what you mean。 It doesn’t matter. Let’s talk about it through a few examples. When you have read these examples, look back at this sentence.

871. Minimum refueling times

Title Description
The car starts from the starting point and drives to the destination, which is located at the target mile to the east of the starting position.

There are gas stations along the way. Each station [i] represents a gas station. It is located at [i] [0] miles east of the starting position, and there are [i] [1] liters of gasoline.

Suppose that the capacity of the car's fuel tank is unlimited, in which there is , startfuel and , liters of fuel at first. It uses a liter of gasoline every mile it travels.

When the car arrives at the gas station, it may stop to refuel and transfer all the gasoline from the gas station to the car.

What is the minimum number of times a car needs to refuel in order to reach its destination? If the destination cannot be reached, - 1 is returned.

Note: if the remaining fuel is 0 when the car arrives at the gas station, it can still refuel there. If the remaining fuel is 0 when the car reaches its destination, it is still considered to have reached its destination.

 

Example 1:

Input: target = 1, startfuel = 1, stations = []
Output: 0
Explanation: we can reach our destination without refueling.
Example 2:

Input: target = 100, startfuel = 1, stations = [[10100]]
Output: - 1
Explanation: we can't reach our destination or even the first gas station.
Example 3:

Input: target = 100, startfuel = 10, stations = [[10,60], [20,30], [30,30], [60,40]]
Output: 2
Explanation:
We had 10 litres of fuel when we set out.
We drove to the gas station 10 miles from the starting point and consumed 10 litres of fuel. Increase the gasoline from 0 liters to 60 liters.
Then we drove from the gas station 10 miles to the gas station 60 miles (consuming 50 litres of fuel),
And increase the gasoline from 10 liters to 50 liters. Then we drove to our destination.
We stopped at two gas stations along the way, so we returned to 2.
 

Tips:

1 <= target, startFuel, stations[i][1] <= 10^9
0 <= stations.length <= 500
0 < stations[0][0] < stations[1][0] < ... < stations[stations.length-1][0] < target
thinking

In order to getMinimum refueling times, we certainly hope not to refuel without refueling. When do we have to refuel? The answer should beIf you don’t refuel, you won’t be able to reach your next destination

Pseudo code description is:

Cur = startfuel # just started with startfuel litres of gasoline
Last = 0 # last position
for i, fuel in stations:
    Cur - = I - last # the fuel consumption of two stations is the distance between two stations, that is, I - last
    if cur < 0:
        #We must refuel in front, or we can't get here
        #But which station in front of it?
        #Intuition tells us that we should greedily choose the station I that can add the most gasoline. If the gasoline added with I is still cur < 0, continue to add the second largest station J until there is no more gasoline to add or cur > 0

It is said to choose the station I that can add the most gasoline. If it is not enough, continue to choose the second station. This dynamic extreme value scenario is very suitable for using heap.

Specifically:

  • After each station, the oil is added to the stack.
  • Drive forward as far as possible, and continue to drive as long as the oil is not less than 0.
  • If the oil quantity is less than 0, take the maximum oil from the stack and add it to the oil tank. If the oil quantity is still less than 0, continue to take the maximum oil quantity in the stack.
  • If the oil volume is greater than 0 after filling, continue to open and repeat the above steps. Otherwise, – 1 is returned, indicating that the destination cannot be reached.

How does this algorithm reflectLittle Zhuge afterwardsWhat about the? You can substitute yourself into the topic for simulation. Imagine yourself driving. Your goal is the requirements in the topic:Minimum refueling times。 When you drive to a station, you don’t know whether your fuel is enough to support the next station, and even if you can’t support the next station, it may be better to refuel at the last station. thereforeIn realityYou can do it anywayI can’t know whether I should refuel or not at the current station, because there is too little information.

After almost brushing all the questions, I found these things... (second bullet)

What would I do? If I’m driving, I can only refuel every time. In this way, I can’t reach my destination, so I can’t reach my destination. But if I can reach my destination in this way, I can sayIf we refuel at that station, this station can reach the destination with the least number of refuelings if we choose not to refuel。 Why didn’t you say it earlier? Isn’t that hindsight?

This hindsight is reflected inWe waited until we ran out of gas to think we should refuel at a previous station

Therefore, the essence of this hindsight is that we cannot obtain the optimal solution based on the current information. We must trace back after mastering all the information. For this problem, we can first traverse one station, and then record the oil volume of each station into an array. Every time we “foresee” that we can’t reach the next station, we will take the largest… From this array. Based on this, we can consider using the process of heap optimization to get the extreme value, rather than using the array.

code
class Solution:
    def minRefuelStops(self, target: int, startFuel: int, stations: List[List[int]]) -> int:
        stations += [(target, 0)]
        cur = startFuel
        ans = 0

        h = []
        last = 0
        for i, fuel in stations:
            cur -= i - last
            while cur < 0 and h:
                cur -= heapq.heappop(h)
                ans += 1
            if cur < 0:
                return -1
            heappush(h, -fuel)

            last = i
        return ans

(code 1.3.10)

1488. Avoid flooding

Title Description
Your country has countless lakes, all of which are empty at first. When the nth Lake rains, if the nth lake is empty, it will be filled with water, otherwise the lake will flood. Your goal is to avoid any flood.

Give you an integer array, {rains, where:

Trains [i] > 0 , indicates that it will rain in the [i] th lake on the I , th day.
Rains [i] = = 0 , indicates that it will rain if there is no lake on the I , day. You can select a , lake and drain the water of , the lake.
Please return an array {ans, which meets the following requirements:

ans.length == rains.length
If "rains [i] > 0, then ans [i] = = - 1.
If , rains [i] = = 0, ans [i] is the lake you choose to drain on , I , day.
If there are multiple feasible solutions, please return any of them. If there is no way to stop the flood, please return an empty array.

Please note that if you choose to drain a lake full of water, it will become an empty lake. But if you choose to drain an empty lake, nothing will happen (see example 4 for details).

 

Example 1:

Input: rains = [1,2,3,4]
Output: [- 1, - 1, - 1, - 1]
Explanation: after the first day, lakes filled with water include [1]
After the second day, the lakes filled with water include [1,2]
After the third day, the lakes filled with water include [1,2,3]
After the fourth day, the lakes filled with water include [1,2,3,4]
No day can you drain the water of any lake, and no lake will flood.
Example 2:

Input: rains = [1,2,0,0,2,1]
Output: [- 1, - 1,2,1, - 1, - 1]
Explanation: after the first day, lakes filled with water include [1]
After the second day, the lakes filled with water include [1,2]
After the third day, we drained the lake 2. So the remaining lakes full of water include [1]
After the fourth day, we drained the lake 1. So there are no lakes full of water for the time being.
After the fifth day, lakes filled with water include [2].
After the sixth day, the lakes filled with water include [1,2].
It can be seen that there will be no flood under this scheme. At the same time, [- 1, - 1,1,2, - 1, - 1] is another feasible scheme without flood.
Example 3:

Input: rains = [1,2,0,1,2]
Output: []
解释:After the second day, the lakes filled with water include [1,2]。我们可以在第三天抽干一个湖泊的水。
But after the third day, lakes 1 and 2 will rain again, so no matter which Lake we drain on the third day, another lake will flood.
Example 4:

Input: rains = [69,0,0,0,69]
Output: [- 1,69,1,1, - 1]
Explanation: any form such as [- 1,69, x, y, - 1], [- 1, x, 69, y, - 1] or [- 1, x, y, 69, - 1] is a feasible solution, where 1 < = x, y < = 10 ^ 9
Example 5:

Input: rains = [10,20,20]
Output: []
Explanation: since Lake 20 will rain for two consecutive days, there is no way to stop the flood.
 

Tips:

1 <= rains.length <= 10^5
0 <= rains[i] <= 10^9
thinking

If the above question is usedWise after the eventIf the description is far fetched, the latter two questions can be said to be very suitable.

The title explains that we can drain a lake when it doesn’t rain. If there are multiple lakes full of rain, which lake should we drain? Obviously, it should be to drain the lake that is about to be flooded recently. But in reality, in any case, we can’t know which lake will rain in the future. Even if there is a weather forecast, it’s not 100% reliable.

But the code is OK. We can traverse the rain array first to know which Lake rained the next day. With this information, we can be wise afterwards.

“The weather is fine today. I opened my eyes. Lake 2 will be flooded tomorrow. Let’s drain it today, otherwise it will be flooded.”.

After almost brushing all the questions, I found these things... (second bullet)

Like the above topic, we can not traverse the rain array first, and then simulate the daily changes, but directly simulate. Even if it is sunny, we will not drain any lakes. Then in the process of simulationRecord sunny days, when the flood happens, we’ll think about the frontWhich sunny dayWhich lake should be drained. Therefore, this hindsight is reflected inWe didn’t think about what we should do one day before the flood

Algorithm:

  • Traverse the rain and simulate the daily changes
  • If rain is currently 0, it means it is sunny, and we will not drain any lakes. But we record the current day into the sunny array.
  • If the rain is greater than 0, it means that it is raining in a lake. Let’s go to see if the lake that is raining is flooded. In fact, it is to see if there is water before it rains. This suggests that we use a data structure lakes to record the situation of each lake. We can use 0 to indicate that there is no water and 1 to indicate that there is water. In this way, when lake I rains and lakes [i] = 1, flooding will occur.
  • If the current lake is flooded, go to sunny array to find a sunny day to drain it, so that it will not flood. Next, just keep lakes [i] = 1.

I didn’t use the heap for this problem. I did it on purpose. The reason for this is to let everyone understandWise after the eventThis technique is not unique to the heap. In fact, it is a common algorithm idea, just like traversing from back to front. But many times, weWise after the eventIn this scenario, the maximum and minimum values need to be dynamically taken. At this time, we should consider using heap, which actually goes back to the beginning of the articleA centerTherefore, we must use these skills flexibly and not copy them mechanically.

The next question is a complete oneWise after the event + Heap optimizationThe title of the.

code
class Solution:
    def avoidFlood(self, rains: List[int]) -> List[int]:
        ans = [1] * len(rains)
        lakes = collections.defaultdict(int)
        sunny = []

        for i, rain in enumerate(rains):
            if rain > 0:
                ans[i] = -1
                if lakes[rain - 1] == 1:
                    if 0 == len(sunny):
                        return []
                    ans[sunny.pop()] = rain
                lakes[rain - 1] = 1
            else:
                sunny.append(i)
        return ans

(code 1.3.11)

1642. Farthest accessible building

Title Description
Give you an integer array heights, which represents the height of the building. There are also some brick bricks and ladder ladders.

You start your journey from building 0 and continue to move towards the buildings behind you, during which you may use bricks or ladders.

When moving from building I to building I + 1 (subscript starts from 0):

If the height of the current building is greater than or equal to the height of the next building, ladders or bricks are not required
If the height of the current building is less than the height of the next building, you can use a ladder or (H [i + 1] - H [i]) bricks
If you use a given ladder and brick in the best way, return the subscript of the farthest building you can reach (starting from 0).

After almost brushing all the questions, I found these things... (second bullet)

Example 1:


Input: heights = [4,2,7,6,9,14,12], bricks = 5, ladders = 1
Output: 4
Explanation: starting from building 0, you can complete the journey according to this scheme:
-Do not use bricks or ladders to reach Building 1 because 4 > = 2
-Use 5 bricks to reach Building 2. You must use bricks or ladders because 2 < 7
-Do not use bricks or ladders to reach building 3 because 7 > = 6
-Use a unique ladder to reach building 4. You must use bricks or ladders because 6 < 9
Building 4 cannot be crossed because there are no more bricks or ladders.
Example 2:

Input: heights = [4,12,2,7,3,18,20,3,19], bricks = 10, ladders = 2
Output: 7
Example 3:

Input: heights = [14,3,19,3], bricks = 17, ladders = 0
Output: 3
 

Tips:

1 <= heights.length <= 105
1 <= heights[i] <= 106
0 <= bricks <= 109
0 <= ladders <= heights.length
thinking

We can see that the ladder is an infinite brick, but it can only be used once. Of course, we hope to use a good ladder on the blade. As above, if it is real life, we can’t know when to use ladders and bricks.

It doesn’t matter. We continue to use the hindsight method, which can be completed in one traversal. Similar to the previous idea, that is, I have no brain to use the ladder. When the ladder is not enough, we will start to make wise suggestions after the event,If only bricks were used in front。 When would it be better to use bricks? Obviously, the height difference when the ladder was used was smaller than the current height difference.

The straight white point is that I climbed a 5-meter wall with a ladder. Now there is a 10 meter wall. I have no ladder and can only use 10 bricks. If five bricks had been used before, wouldn’t it be possible to use a ladder now, saving five bricks?

This prompts us to save the height difference of the building crossed by the ladder in front. When the ladder in the back runs out, we will “exchange” the ladder used in front into bricks to continue to use. In the above example, we can exchange 10 bricks and then use up 5 bricks, which is equivalent to an increase of 5 bricks.

If the ladder is used many times, which time do we give priority to “exchange”? It’s obviously a priority exchangeHeight differenceBig ones. Only in this way can you exchange the most bricks. This prompts you to select the largest height difference from the previously stored height difference each time and remove it after “Redemption”. suchDynamic extremumWhat data structure is appropriate for the scenario? Of course it’s a pile.

code
class Solution:
    def furthestBuilding(self, heights: List[int], bricks: int, ladders: int) -> int:
        h = []
        for i in range(1, len(heights)):
            diff = heights[i] - heights[i - 1]
            if diff <= 0:
                continue
            if bricks < diff and ladders > 0:
                ladders -= 1
                if h and -h[0] > diff:
                    bricks -= heapq.heappop(h)
                else:
                    continue
            bricks -= diff
            if bricks < 0:
                return i - 1
            heapq.heappush(h, -diff)
        return len(heights) - 1

(code 1.3.12)

Four applications

The following is the last part of this article, “four applications”, which aims to help you consolidate the previous knowledge through these examples.

1. topK

Solving TOPK is a very important function of heap. This is actually already in the frontFixed heapI introduced some of them to you.

Here we directly quote the previous words:

“In fact, the simplest way to find the k-th smallest number is to build a small top pile. First put all the numbers into the pile, and then get out of the pile one by one. A total of K times. The last time we get out of the pile is the k-th smallest number. However, instead of putting all the numbers into the pile, we can build a large top pile (note that it is not the small top pile above) , and maintain the heap size of K. If the size of the heap is larger than k after the new number is put into the heap, you need to compare the number at the top of the heap with the new number and remove the larger one. In this way, we can ensure that the number in the heap is the smallest K of all numbers, and the largest of the smallest K (i.e. the top of the heap) is not the smallest K? This is why we chose to build a large top pile instead of a small top pile. “

In fact, in addition to the k-th smallest number, we can also collect all the middle numbers, which can find the smallest numberNumber of K。 The only difference from the number k above is that you need to collect all the numbers from Popp.

It should be noted that sometimes the weight is not the size of the original array value itself, but also the distance, occurrence frequency, etc.

Related topics:

  • Interview question 17.14 Minimum number of K
  • 347. Top k high frequency elements
  • 973. K points closest to the origin

Many questions about the K in the force buckle are heaps. In addition to the heap, there are actually some problems in the topic of KFind rulesFor this kind of topic, you can passDivide and conquer + recursionTo solve the problem, the details will not be carried out here. Those who are interested can leave a message and discuss with me.

2. Shortest distance with weight

In fact, I mentioned this in the previous part, but I just passed by at that time. For example, for the shortest path problem with weight graph, if you use queue as BFS, you need priority queue, because there is a gap between pathsWeight differenceYes, this is the original intention of the priority queue.The typical implementation of BFS using priority queue is Dijkstra algorithm。”

Dijkstra algorithm mainly solves the shortest distance of any two points in the graph.

The basic idea of the algorithm is greedy. It traverses all neighbors every time and finds the one with the smallest distance. In essence, it is a breadth first traversal. Here, with the help of the heap data structure, we can find the point with the lowest cost in the time of $logn $, where n is the size of the heap.

Code template:

def dijkstra(graph, start, end):
    #The data in the heap is the binary ancestor of (cost, I), which means "the distance from start to I is cost".
    heap = [(0, start)]
    visited = set()
    while heap:
        (cost, u) = heapq.heappop(heap)
        if u in visited:
            continue
        visited.add(u)
        if u == end:
            return cost
        for v, c in graph[u]:
            if v in visited:
                continue
            next = cost + c
            heapq.heappush(heap, (next, v))
    return -1

(code 1.4.1)

It can be seen that the code template and BFS are basically similar. If you set the heap key to steps, you can also simulate the implementation of BFS. This has been mentioned earlier and will not be repeated here.

For example, a diagram is as follows:

E -- 1 --> B -- 1 --> C -- 1 --> D -- 1 --> F
 \                                         /\
  \                                        ||
    -------- 2 ---------> G ------- 1 ------

We use adjacency matrix to construct:

G = {
    "B": [["C", 1]],
    "C": [["D", 1]],
    "D": [["F", 1]],
    "E": [["B", 1], ["G", 2]],
    "F": [],
    "G": [["F", 1]],
}

shortDistance = dijkstra(G, "E", "C")
print(shortDistance)  # E -- 3 --> F -- 3 --> C == 6

You can use this algorithm 743 Network delay time.

Full code:

class Solution:
    def dijkstra(self, graph, start, end):
        heap = [(0, start)]
        visited = set()
        while heap:
            (cost, u) = heapq.heappop(heap)
            if u in visited:
                continue
            visited.add(u)
            if u == end:
                return cost
            for v, c in graph[u]:
                if v in visited:
                    continue
                next = cost + c
                heapq.heappush(heap, (next, v))
        return -1
    def networkDelayTime(self, times: List[List[int]], N: int, K: int) -> int:
        graph = collections.defaultdict(list)
        for fr, to, w in times:
            graph[fr - 1].append((to - 1, w))
        ans = -1
        for to in range(N):
            #Call the encapsulated Dijkstra method
            dist = self.dijkstra(graph, K - 1, to)
            if dist == -1: return -1
            ans = max(ans, dist)
        return ans

(code 1.4.2)

Have you learned?

The above algorithm is not the optimal solution, I just want to reflectEncapsulating Dijkstra as an API callMy thoughts. A better approach is to record all the distance information in one traverse instead of repeating the calculation every time. The time complexity will be greatly reduced. This is of great significance in calculating the distance from a point to all points in the graph. In order to achieve this goal, what adjustments will our algorithm have?

Tip: you can use a dist hash table to record the shortest distance from the starting point to each point. If you think of it, you can deduct 882 questions to verify it~

In fact, you only need to make a small adjustment. Because the adjustment is very small, it will be better to look at the code directly.

code:

class Solution:
    def dijkstra(self, graph, start, end):
        heap = [(0, start)]  # cost from start node,end node
        dist = {}
        while heap:
            (cost, u) = heapq.heappop(heap)
            if u in dist:
                continue
            dist[u] = cost
            for v, c in graph[u]:
                if v in dist:
                    continue
                next = cost + c
                heapq.heappush(heap, (next, v))
        return dist
    def networkDelayTime(self, times: List[List[int]], N: int, K: int) -> int:
        graph = collections.defaultdict(list)
        for fr, to, w in times:
            graph[fr - 1].append((to - 1, w))
        ans = -1
        dist = self.dijkstra(graph, K - 1, to)
        return -1 if len(dist) != N else max(dist.values())

(code 1.4.3)

It can be seen that we just replaced visitd with dist, and the others remain unchanged. In addition, dist is actually a visited with a key. It also plays the role of visited here.

If you need to calculate the shortest path from one node to all other nodes, you can use a dist (a HashMap) to record the shortest path information from the starting point to all points, rather than using visited (a HashSet).

There are many similar topics. Let me give you another 787 The cheapest flight in K station. Title Description:

There are n cities connected by M flights. Each flight starts from city u and arrives at V at price W.

Now, given all cities and flights, as well as the departure city SRC and destination DST, your task is to find the cheapest transfer price from SRC to DST through k# station at most. If there is no such route, output - 1.

 

Example 1:

Input:
n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]
src = 0, dst = 2, k = 1
Output: 200
Explanation:
The city flight chart is as follows

After almost brushing all the questions, I found these things... (second bullet)

The cheapest transfer price from city 0 to city 2 within 1 station is 200, as shown in red in the figure.
Example 2:

Input:
n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]
src = 0, dst = 2, k = 0
Output: 500
Explanation:
The city flight chart is as follows

After almost brushing all the questions, I found these things... (second bullet)

The cheapest transfer price from city 0 to city 2 within 0 station is 500, as shown in blue in the figure.
 

Tips:

The N range is [1, 100], and the city tag is from 0 to N - 1
The number of flights ranges from [0, n * (n - 1) / 2]
Format of each flight (SRC, DST, price)
The price range of each flight is [1, 10000]
The K range is [0, N - 1]
There are no duplicate flights and there is no self loop

This question is not fundamentally different from the above one. I still encapsulate it into an API to use. It depends on the code.

The only special point of this question is that if the number of transfers is greater than k, it is also considered unreachable. This is actually very easy. We just need to use tuples in the heapCarry one more stepThat is, this step is the distance in the weighted BFS. If the steps of pop are greater than k, it is considered illegal. We can skip and continue processing.

class Solution:
    #After modification, add parameter K and carry one more step in the heap
    def dijkstra(self, graph, start, end, K):
        heap = [(0, start, 0)]
        visited = set()
        while heap:
            (cost, u, steps) = heapq.heappop(heap)
            if u in visited:
                continue
            visited.add((u, steps))
            if steps > K: continue
            if u == end:
                return cost
            for v, c in graph[u]:
                if (v, steps) in visited:
                    continue
                next = cost + c
                heapq.heappush(heap, (next, v, steps + 1))
        return -1
    def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, K: int) -> int:
        graph = collections.defaultdict(list)
        for fr, to, price in flights:
            graph[fr].append((to, price))
         #Call the encapsulated Dijkstra method
        return self.dijkstra(graph, src, dst, K + 1)

(code 1.4.4)

3. Factorization

With the above two applications, I also mentioned this in the previous “313. Super ugly numbers”.

Review the definition of ugly number:Ugly number is a positive integer whose prime factor contains only 2, 3 and 5.Therefore, the essence of ugly number is a number processFactorizationThen there are only integers of 2, 3 and 5, without other factors.

There are many problems about ugly numbers, and most of them can also be solved from the perspective of heap. However, sometimes the number of factors is limited, and it is easy to solve without heap. For example: 264 Ugly number II can be recorded with three pointers. This technique has been mentioned earlier and will not be repeated.

Some topics are not ugly numbers, but they explicitly mention similar onesfactorAnd let you ask for XX with the largest K. at this time, priority should be given to using heap to solve it. If the title contains some other information, such asOrderly, dichotomy can also be considered. The specific method to be used should be analyzed in detail, but before that, we should be familiar with both methods.

4. Heap sorting

The above three applications have been mentioned more or less earlier. andHeap sortBut not mentioned earlier.

There are few topics to directly investigate heap sorting. However, the interview may be investigated. In addition, learning heap sorting is of great significance for you to understand important algorithmic thinking such as divide and conquer. Personally, heap sorting, constructing binary tree, constructing line segment tree and other algorithms are very similar. If you master one, you can learn from others.

In fact, after the previous heap learning, we can encapsulate a heap sorting method, which is very simple.

Here I put a simple example code of heap sorting using heap API:

h = [9,5,2,7]
heapq.heapify(h)
ans = []

while h:
    ans.append(heapq.heappop(h))
print(ans) # 2,5,7,9

Understand the example, which is encapsulated intoGeneral heap sortIt’s not difficult.

def heap_sort(h):
    heapq.heapify(h)
    ans = []
    while h:
        ans.append(heapq.heappop(h))
    return ans

This method is simple enough. If you understand the principle of the previous heap, it is not difficult for you to sort a heap by hand. But this method has a drawback, it is notIn situ algorithmIn other words, you must use additional space to undertake the result, and the space complexity is $o (n) $. But in fact, after calling the heap sorting method, the original array memory can be released, so in theory, there is no waste of space, but when we calculate the space complexity, we take the time when we use the most memory, so it is undoubtedly better to use the in-situ algorithm. If you are really unhappy with this implementation, you can also modify it in situ. It’s not difficult, just slightly modify the implementation of the heap in front. Due to space constraints, I won’t talk about it here.

summary

Piles and queues are inextricably linked. I think about many problems first and use heap to complete them. Then it is found that each heap entry is + 1 without skipping updates. For example, the next one is + 2, + 3 and so on. Therefore, it is better to use the queue to complete the task. For example, 649 Dota2 Senate and 1654 The minimum number of jumps at home, etc.

There is only one in the center of the pile, that isDynamic extremum

The extreme value is nothing more than the maximum or minimum value, which is not difficult to see. If the maximum value is obtained, we can use the large top heap. If the minimum value is obtained, we can use the minimum heap. In fact, if there is no dynamic word, there is no need to use heap in many cases. For example, you can directly traverse once to find the largest. The dynamic point is not easy to see, which is the difficulty of the topic. This requires you to analyze the problem and work out the problem firstIn fact, it is dynamic extremum, then using heap to optimize should be thought of.

There are many implementations of heap. For example, the implementation of jump table based on linked list, binary heap based on array and red black tree. Here we introduceTwo main implementationsIt also describes the implementation of binary heap in detail, which is not only simple, but also performs well in many cases. It is recommended that you focus on the implementation of binary heap.

For the implementation of binary heap,The core point is just a little, that is to always maintain the nature of the heap unchanged. What is the specific nature? That’s itThe weight of the parent node is not greater than that of the son (small top heap)。 In order to achieve this goal, we need to use floating and sinking operations when entering and leaving the heap, and properly complete the element exchange. Specifically, the floating process is to exchange with the larger parent node, and the sinking process is to exchange with the smaller of the two child nodes. Of course, the premise is that it has child nodes and the child nodes are smaller than it.

We have not made a detailed analysis on heap. However, if you understand the heap loading operation in this article, it is actually very easy. Therefore, heaping itself is a continuous process of heaping, butTurn discrete operations in time into one-time operationsnothing more.

In addition, I introduced you to the problem-solving skills of three piles, which are:

  • Fixed heap can not only solve the k-th problem, but also make effective use of the calculated results and avoid repeated calculation.
  • Multiway merging is essentially a violent solution, which is not fundamentally different from violent recursion. If you convert it into recursion, it is also a recursion that cannot be memorized. So it’s more likeBacktracking algorithm
  • After that, little Zhuge. Some information can be stored in a data structure when we can’t get it at present, so that we can check it later when the “east window incident” happens. This kind of data solution can be many, including hash table and heap. You can also regard this technique asRegret afterwards, some people are more receptive to this term, but no matter how it is called, it refers to this meaning.

Finally, I will introduce four applications. In addition to heap sorting, these four applications have been mentioned more or less earlier. They are:

  • topK
  • Weighted shortest path
  • Factorization
  • Heap sort

These four applications actually revolve around one center of the heapDynamic extremum, these four applications just use this feature flexibly. Therefore, when you do questions, you just have to memorize themDynamic extremumJust. If you can analyze that this problem is related to dynamic extremum, please be sure to consider heap. Next, we have to go through the complexity in our mind. By comparing the subject data range, we can probably estimate whether it is feasible.

If you have any opinions on this, please leave me a message. I will check and answer one by one when I have time. For more algorithm routines, please visit my leetcode problem solving warehouse: https://github.com/azl3979858… 。 It’s already 39K star. You can also follow my official account Li Kou Jia Jia to take you to the hard bone of algorithm.

After almost brushing all the questions, I found these things... (second bullet)

Recommended Today

Net6 configuration & Options source code analysis part2 options model usage and source code analysis

Net6 configuration & Options source code analysis part2 options Part II main recordsOptions modelThe optionsconfigurationservicecollectionextensions class provides a pair ofOptions modelAndConfiguration systemExtension of the configure method of 1. Use options directly Use options directly In the startup configservice, you often see a ramda registered as a configuration item, for example:.Configure(it ->it.age = 18), we call […]