Double pointer topic summary

Keywords: Algorithm data structure leetcode

(Kago double pointer chapter punch in)

1. Remove element

thinking

  • It can be solved directly. But the time complexity will be very large. A simpler way is to speed the pointer. The idea of this fast and slow pointer is that the slow pointer points to the position of the inserted character (treat the array as a new array), and the fast pointer points to the character to be verified.
  • It is equivalent to fast verifying whether the character is equal to val
    1. If so, fast goes back, slow does not need to insert val and go back.
    2. If not, fast still goes back, but slow needs to insert val and go back
class Solution {
    public 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. Reverse string

thinking

Left and right pointers. Left and right pointers are placed at the beginning and end of the array. And traverse through while (left < right), that is, as long as left==right or left > right, end the loop and exchange s[left] and s[right]. The time complexity is n

class Solution {
    public void reverseString(char[] s) {
       int left=0;
       int right=s.length-1;
       while(left<right){
           char temp=s[left];
           s[left]=s[right];
           s[right]=temp;
           left++;
           right--;
       }
    }
}

3. Replace spaces

thinking

Traverse the entire string and create a StringBuilder. If you find that the traversed characters are spaces, add% 20 into StringBuilder. If not, add the original characters into StringBuilder. The time complexity is n and the space complexity is n (because the original character is basically assigned to the new string)

expand

The reason to create a StringBuilder is that String is immutable and a new String is needed to receive the modified String.

class Solution {
    public String replaceSpace(String s) {
        StringBuilder sb=new StringBuilder();
        for(int i=0;i<s.length();i++){
            if(" ".equals(String.valueOf(s.charAt(i)))){
                sb.append("%20");
            }else{
                sb.append(s.charAt(i));
            }
        }
        return sb.toString();
    }
}

4. Reverse the words in the string

thinking

① Remove the spaces first. Using the leading and trailing pointers, if a space is found, start + + and end –. Then continue to traverse the string in this range and add it to StringBuilder. If it is found that the last character of StringBuilder is not a space, then a space can be added. Other characters are added normally.

② Then, after reversing the string, reverse the words of all strings again.

③ The inverted string is directly inverted with the beginning and end pointers (remember that the length passed in is StringBuilder, not the length of the original string)

④ Finally, reverse the word and use the speed pointer. The end pointer detects spaces. If it is found, start to end is a word, which can be reversed by the previously written inversion string method, and switched to the next traversal. That is, start goes to end+1, that is, the word after the space, and end continues to traverse later.

class Solution {
    public String reverseWords(String s) {
        StringBuilder sb=removeSpace(s);
        reverseString(sb, 0, sb.length() - 1);
        reverseEachWord(sb);
        return sb.toString();
    }

//1. Remove redundant spaces
    private StringBuilder removeSpace(String s){
        int start=0;
        int end=s.length()-1;
        //1. Remove the leading and trailing spaces
        while(s.charAt(start)==' ') start++;
        while(s.charAt(end)==' ') end--;
        //2. Remove the space in the middle, sb the last one is not a space can be added, because there is only one space between words
        StringBuilder sb=new StringBuilder();
        while(start<=end){
            char c=s.charAt(start);
            if(c!=' '||sb.charAt(sb.length()-1)!=' '){
                sb.append(c);
            }
            start++;
        }
        return sb;
        
    }
    //2. Reverse string (leading and trailing pointers)
    private void reverseString(StringBuilder sb,int start,int end){
        while (start < end) {
            char temp = sb.charAt(start);
            sb.setCharAt(start, sb.charAt(end));
            sb.setCharAt(end, temp);
            start++;
            end--;
        }
    }

//3. Reverse the words in the string
    private void reverseEachWord(StringBuilder sb){
        int start = 0;
        int end = 1;
        int n = sb.length();
        while (start < n) {
            while (end < n && sb.charAt(end) != ' ') {
                end++;
            }
           reverseString(sb, start, end - 1);
            start = end + 1;
            end = start + 1;
        }
    }
}

5. Reverse linked list

Double finger needling

thinking

A cur points to the current node and pre points to the previous node. Then save the next of the original cur through temp, and then modify the next of cur to point to pre. It is equivalent to that temp first arrives at the next node after cur, and then cur completes the pointing of the next pointer. The essence is to modify the next point of each node.

class Solution {
    public ListNode reverseList(ListNode head) {
       ListNode cur=head;
       ListNode temp;
       ListNode pre=null;
       while(cur!=null){
           temp=cur.next;
           cur.next=pre;
           pre=cur;
           cur=temp;
       }
       return pre;
    }
}

Recursive method

thinking

Essentially as like as two peas. The difference is that there is no assignment exchange. Instead, the cur of the current recursion is treated as pre, and the temp is treated as a new cur. Then it is handed over to the next recursive processing, and the pointer points to

class Solution {

    public ListNode reverse(ListNode pre,ListNode cur){
        if(cur==null) return pre;
        ListNode temp=cur.next;
        cur.next=pre;
        return reverse(cur,temp);
    }


    public ListNode reverseList(ListNode head) {
         return reverse(null,head);
    }
}

Conclusion: whether recursive method or double finger needle method, their essential goal is to change the direction of the pointer. ① Save the next node in advance ② change the direction ③ assign values to the new pre and cur to enter the next round of direction modification.

6. Delete the penultimate node

thinking

Speed pointer. The essence is that fast takes n steps first, so the distance between fast and slow is n. At this time, only when fast is equal to null, the position of slow is fast-2. Fast is at the end of the list, that is, slow is above the penultimate in the list. However, in order to slow, the penultimate n can be deleted, so fast takes step n+1. Let slow go to the previous node of the node to be deleted

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
          ListNode dummy=new ListNode(-1);
          dummy.next=head;

          ListNode fast=dummy;
          ListNode slow=dummy;

          //Let fast move n+1 first, that is, the distance between slow and fast is n+1, which is convenient for slow to delete the next node
          int l=n+1;
          
          while(l-->0){
              fast=fast.next;
          }

          while(fast!=null){
              slow=slow.next;
              fast=fast.next;
          }

          slow.next=slow.next.next;
          
          return dummy.next;

    }
}

7. Linked list intersection

thinking

① Romantic encounters can be used. It means that if a and B have intersecting nodes, they have a common distance c. The distance from a to the intersection is a, and the distance from B to the intersection is B. If they take another section of the distance from each other to the intersection is b+c+a and a+c+b, that is, they will go to the intersection at the same time or go to null without intersection. That is, they will meet.

② Another solution is that the intersection of the two linked lists must be the second half of the shortest linked list and the second half of the long linked list (because the pointers are the same, then the next must be the same), so you can first move the pointer of the long linked list to the length + 1 of the short linked list. Then start the traversal of the two linked lists and compare them. If you find the same, it is the intersection. This solution is very troublesome. You need to find the length of both sides, then find gap, move the long linked list, and finally compare. It's not concise compared with the first one.

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode h1=headA;
        ListNode h2=headB;
        while(h1!=h2){
            //If you don't finish your own, continue to walk. If you finish, go to someone else's way.
            h1=h1==null?headB:h1.next;
            h2=h2==null?headA:h2.next;
        }
        return h1;
    }
}

8. Ring linked list 2

thinking

First use the speed pointer to judge whether it is a circular linked list. If not, the fast pointer will go to null and terminate the loop. If it is a circular linked list, it will go to the time when fast==slow. You need to find the circular entrance at this time. The circular entrance is actually based on. The distance from the point where the fast and slow pointers meet to the ring entrance is equal to the distance from the chain header node to the entrance. This calculation is actually that the distance from the chain header to the ring entrance is x, and the distance from the ring entrance to the encounter is y. The point of meeting and then to the circular entrance is z. (x+y)*2 the distance traveled by the fast pointer is equal to the distance traveled by the slow pointer multiplied by 2. The distance traveled by the fast pointer itself is x+y+ n * (y+z). At this time, x=nz+(n-1)y. When n=1, x=z, when n > 1, you will find that only one more z+y, that is, one more circle, has no effect on x=z.

public class Solution {
    public ListNode detectCycle(ListNode head) {
       ListNode slow=head;
       ListNode fast=head;
       
       while(fast!=null&&fast.next!=null){
           slow=slow.next;
           fast=fast.next.next;

           if(fast==slow){
               fast=head;
               while(fast!=slow){
                   fast=fast.next;
                   slow=slow.next;
               }
               return fast;
           }
           
       }
       return null;
        
    }
}

9. Sum of three

Idea (sort + double pointer)

Can be calculated by double pointers. Sort the array first, and then traverse for once. If it is found that the initial value of num [i] must be > 0, then the subsequent values must be greater than 0, and the result can be returned directly. If it is nums[i]==nums[i-1], it needs to be de duplicated, because the later combinations will coincide with the previous ones. Finally, the double pointers left and right are used to de iterate one end at a time. If the calculated num [i] + num [left] + num [right] is sum, if it is greater than 0, right should be moved left. The reason is that the array is sorted, and right should be moved left to reduce sum. The opposite is to move left to the right. If you find that the next one of left and right is the same, skip the loop (de duplication).

The time complexity is nlogn (sort) + n (traverse array) * n (double pointer)

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res=new ArrayList<>();
        
        Arrays.sort(nums);
        
        for(int i=0;i<nums.length;i++){
            //If the minimum values are > 0, the result will be returned directly
            if(nums[i]>0) return res;
            //If it is the same as the previous one, skip de duplication
            if(i>0&&nums[i]==nums[i-1]) continue;

            int left=i+1;
            int right=nums.length-1;
            //Then it is calculated through left and right pointers, de duplication, and value finding. If yes, put res and check whether the next left or right is repeated. If the calculated value is large, then right shifts to the left; if it is small, then left shifts to the right
            while(left<right){
                int sum=nums[i]+nums[left]+nums[right];
                if(sum>0){
                    right--;
                }else if(sum<0){
                    left++;
                }else{
                    res.add(Arrays.asList(nums[i],nums[left],nums[right]));
                    
                    while(right>left&&nums[right]==nums[right-1]) right--;
                    while(right>left&&nums[left]==nums[left+1]) left++;

                    right--;
                    left++;
                }
            }
        }
        return res;
    }
}

10. Sum of four numbers

thinking

The idea of the sum of four numbers is basically the same as that of the sum of three numbers. It's just another cycle. Double pointers are used here to facilitate de duplication and reduce the complexity of the violent cycle. The essence is to determine two numbers (de duplication), and then traverse through double pointers (instead of for, or you can directly use violence). The key to de duplication is sorting.

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> res=new ArrayList<>();
        Arrays.sort(nums);
        
        for(int k=0;k<nums.length;k++){
            if(k>0&&nums[k]==nums[k-1]) continue;

            for(int j=k+1;j<nums.length;j++){
                if(j>k+1&&nums[j]==nums[j-1]) continue;

                int left=j+1;
                int right=nums.length-1;
                while(left<right){
                    int sum=nums[k]+nums[j]+nums[left]+nums[right];
                    if(sum>target){
                        right--;
                    }else if(sum<target){
                        left++;
                    }else{
                        res.add(Arrays.asList(nums[k],nums[j],nums[left],nums[right]));
                        
                        while(right>left&&nums[right]==nums[right-1]) right--;
                        while(right>left&&nums[left]==nums[left+1]) left++;
                        
                        right--;
                        left++;
                    }
                }
            }
        }
        return res;
    }
}

Summary: fast and slow pointers and head and tail pointers are often used. The fast and slow pointer can solve the problems of ring, countdown and removing some values. The first pointer can solve the problems of inversion, de duplication and so on.

Posted by markuatcm on Thu, 30 Sep 2021 11:55:40 -0700