Algorithm Clearance Village——Finally learned the wonderful use of double pointer thinking

Directory of articles in the Algorithm Clearance Village series

  1. Level 2 - Bronze Challenge - Finally learned how to reverse the linked list
  2. Level 2 - Silver Challenge - Extended Questions on Linked List Reversal


Preface

本系列文章是针对于鱼皮知识星球——编程导航中的算法通关村中的算法进行归纳和总结。 该篇文章讲解的是第三关中的白银挑战———双指针思想

In the continuous space where elements are stored, such as arrays and strings, if the following elements want to move forward, in order to maintain the continuity of the following elements, the following elements must move as a whole. Similarly, if an element is inserted in the middle, behind it The elements must also be moved backward as a whole, which will cause some algorithms to require multiple rounds. Moving a large number of elements will be less efficient. To solve this problem, let's look at a method for this - double pointer


1. Introduction to double pointers

Double pointers are not necessarily really pointers. In more cases, they are two variables. Because we often use two variables to point to the elements we want to operate, we just call the variables double pointers. . Since it is called a double pointer, there must be two pointers. In double pointers, the direction of the pointers may have many situations. For example, both pointers start from the beginning. This situation is called fast and slow double pointers; If two pointers start from both ends of the array and go to the inside of the array, this situation is called Colliding double pointers.
No matter what type of double pointer it is, the core is that there are qualified elements in front or behind a pointer. A pointer traverses the array elements and changes these elements through some changes. It is a qualified element, and the pointer that identifies the qualified element is moved, and all these qualified elements are divided into its "ruling name". The last two pointers meeting means that all elements in the array have become qualified elements. .

Next, we will analyze the content of double pointers through the LeetCode question.


2. Remove all array elements equal to val in place

LeetCode 27 question:
Given an array nums and a value val, you need to remove all elements with a value equal to val in place and return the new length of the array after removal. .
Do not use extra array space, you must use only O(1) extra space and modify the input array in-place.
The order of elements can be changed. You don't need to consider elements in the array beyond the new length.

Input: nums = [0,1,2,2,3,0,4,2], val = 2
Output: 5, nums = [0,1, 4,0,3]

Let’s analyze the usage of two double pointers on this question

2.1 Fast and slow double pointers

In the preface, we introduced that no matter what kind of double pointer, there will be a pointer to mark the elements that meet the conditions, and a pointer to traverse the elements to make the elements that do not meet the conditions meet the conditions. Increase the number of qualifying elements.
In the fast and slow dual pointers, we call the fast pointer fast and the slow pointer slow. And the slow pointer is used to mark elements that meet the conditions, and the fast pointer is used to traverse

step:

  1. Both slow and fast are initialized to 0, pointing to the first element of the array. Because the array here is not a special sequence such as ascending or descending order, it cannot be judged whether the first element meets the conditions, so it is assigned a value of 0 directly.
  2. Fast needs to traverse the array. Every time it traverses an element, it must be judged. This question is to delete the element with the value val. When fast determines that the element meets the conditions, it does not matter. It assigns the element pointed by fast to the element pointed by slow, because the meaning of slow is that the elements before slow-1 all meet the conditions, and the element pointed by slow is the element to be processed. , and fast makes a judgment. Fast judges that 0 meets the conditions and assigns 0 to the element pointed to by slow, so that slow can move forward one bit.
  3. When fast determines that the element does not meet the conditions, the slow pointer does not move. After all, this element does not meet the conditions and should not be included. The fast pointer continues to move until it moves to an element that meets the conditions, and uses the value of the element to overwrite the value pointed by slow, so that the value pointed by slow meets the conditions, and then slow can move.
    Insert image description here
    Insert image description here
    public static int removeElement(int[] nums, int val) {
    
    
        int slow = 0;
        for (int fast = 0; fast < nums.length; fast++) {
    
    
            if (nums[fast] != val) nums[slow++] = nums[fast];
        }
        return slow;
    }

2.2 Colliding double pointers

Having seen the fast and slow double pointers moving in the same direction, let's look at the colliding double pointers in different directions. The colliding double pointers require both sides to move at the same time.

step:

  1. The left pointer starts from 0, the right pointer starts from the end of the array, and the elements before the left pointer are qualified elements, and the elements after the right pointer are all unqualified elements.
  2. When the element pointed to by left is not equal to val and meets the condition, left moves one position backward.
  3. When the element pointed by right is equal to val and does not meet the conditions, right moves forward one position.
  4. When the element pointed by left is equal to val, it does not meet the condition. At the same time, the element pointed by right is not equal to val, which meets the condition. At this time, the elements pointed by left and right are exchanged, so that the left side of left meets the condition and the right side of right does not. Meet the criteria

Insert image description here

Code:

    public static int removeElementCash(int[] nums, int val) {
    
    
        int left = 0;
        int right = nums.length - 1;
        for (left = 0; left <= right; ) {
    
    
            // 没找到
            if ((nums[left] == val) && (nums[right] != val)) {
    
    
                // 这里一定是交换值,而不是简单的把nums[right]赋值给nums[left],因为当right检测到一个不是val的值时
                // 如果不交换,right就一直停留在这个值,不会继续向前检索
                int temp = nums[left];
                nums[left] = nums[right];
                nums[right] = temp;
            }
            if (nums[left] != val) left++;
            if (nums[right] == val) right--;
        }
        return left;
    }

3. Odd-even movement of elements

LeetCode 905
Given an integer array nums, move all the even elements in nums to the front of the array, followed by all the odd elements.
Return any array that satisfies this condition as the answer:

Input: nums = [3,1,2,4]
Output: [2,4,3,1]
Explanation: [ 4,2,3,1], [2,4,1,3] and [4,2,1,3] are also considered correct answers.

This type is divided into two types, which is very suitable for writing colliding double pointers. The writing method is similar to the above colliding double pointers. Then I won’t analyze it here, just post the code directly.
Insert image description here

    public static int[] sortArrayByParity(int[] nums){
    
    
        int left=0;
        int right=nums.length-1;
        for (left=0;left<=right;){
    
    
            if((nums[left]%2!=0&&nums[right]%2==0)){
    
    
                int temp=nums[right];
                nums[right]=nums[left];
                nums[left]=temp;
            }
            if(nums[left]%2==0) left++;
            if(nums[right]%2!=0) right--;
        }
        return nums;
    }

4. Interval problem of arrays

LeetCode 228:
Given an ordered integer array nums without duplicate elements.
Returns the smallest ordered range list that exactly covers all numbers in the array. That is, every element of nums is covered by exactly some interval range, and there is no number x that belongs to a range but not nums .

Input: nums = [0,1,2,4,5,7]
Output: ["0->2", "4->5", "7"]
Explanation: The interval range is:
[0,2] --> "0->2"
[4,5] --> “4->5”
[7,7] --> “7”

We use fast and slow double pointers to solve this problem

step:

  1. The elements between slow and mi are qualified, and fast is responsible for finding elements that meet the conditions. This is equivalent to slow being the left endpoint of a certain interval, and mi being the right endpoint of a certain interval.
  2. When the element pointed to by mi + 1 is not equal to the element pointed by fast, that is to say, mi and fast are not continuous at this time, which does not meet the conditions, so the elements between slow and fast must be picked out, and let slow Both mi and mi are equal to fast now.
  3. When fast traverses the last element, there will definitely be one element or a range left that has not been picked out. At this time, there is a need for finishing work.

Three pointer code:

    public List<String> summaryRanges(int[] nums) {
    
    
        
        List<String> strings = new ArrayList<>();
        if(nums.length==0) return strings ;
        int slow=0;
        int fast=0;
        int mi=0;
        for ( fast = 1; fast < nums.length; fast++) {
    
    
            if(nums[mi]+1!=nums[fast]){
    
    
                if (nums[slow]==nums[mi]) {
    
    
                    strings.add(nums[slow]+"");
                }else {
    
    
                    strings.add(nums[slow]+"->"+nums[mi]);
                }
                slow=fast;
                mi=fast;
            }else {
    
    
                mi++;
            }
        }
        if(slow!= nums.length-1) strings.add(nums[slow]+"->"+nums[nums.length-1]);
        if(slow== nums.length-1) strings.add(nums[nums.length-1]+"");
        return strings;
    }

After researching here, I found that fast can also do mi. We regard fast here as mi and fast+1 as the original fast. We can also use the elements between slow and fast. It meets the conditions, so it is clearer
Double pointer code:

    public static List<String> summaryRanges(int[] nums){
    
    
        List<String> strings = new ArrayList<>();
        int slow=0;
        int fast=0;
//        int mi=0;
        // 这个fast既起到了我们的mi又相当于fast
        for ( fast = 0; fast < nums.length; fast++) {
    
    
            if(fast+1== nums.length||nums[fast]+1!=nums[fast+1]){
    
    
                StringBuilder stringBuilder = new StringBuilder();
                stringBuilder.append(nums[slow]);
                if (nums[slow]!=nums[fast]) {
    
    
                    stringBuilder.append("->"+nums[fast]);
                }
                strings.add(stringBuilder.toString());
                slow=fast+1;
//                mi=fast;
            }
//            else {
    
    
//                mi++;
//            }
        }
//        if(slow!= nums.length-1) strings.add(nums[slow]+"->"+nums[nums.length-1]);
//        if(slow== nums.length-1) strings.add(nums[nums.length-1]+"");
        return strings;
    }

Summarize

This is the end of the simple use of double pointers. The essence is that a pointer divides qualified elements and a pointer searches for qualified elements. It is relatively easy to understand. I will list the golden challenges of this level later. That is to say, some of the more difficult questions about double pointers, but the idea is still the same. It’s just that there are more things to consider during the processing. After careful study, they can still be connected.

Guess you like

Origin blog.csdn.net/aaaaaaaa273216/article/details/133165385