Algorithm - Double Pointer

content

I. Introduction

2. The core idea of ​​the algorithm

3. Algorithm model

1. Collision pointer

2. Fast and slow pointer

3. Sliding window

4. Merge Sort

4. Summary


I. Introduction

Double pointer is a widely used and basic algorithm. Strictly speaking, double pointer is not an algorithm but more like an idea. The "pointer" in the double pointer is not only the well-known address pointer in C/C++, but also an index and a cursor. This article will briefly introduce dual pointers and four common models of dual pointers for reference learning of dual pointers.

2. The core idea of ​​the algorithm

Double pointer refers to the use of two or more pointers for traversal and corresponding operations when traversing an object. Mostly used for array operations, which take advantage of the sequential nature of arrays. Double pointers are often used to reduce the time complexity of algorithms, because using two pointers can avoid multiple loops.

Three key points of double pointer

  • Selection of the starting position of the pointer
  • pointer movement direction
  • pointer movement speed

These three key points are the core and difficulty of the double pointer algorithm.

3. Algorithm model

1. Collision pointer

Colliding pointers: The left and right pointers move closer to the middle.

1.

Leetcode https://leetcode-cn.com/problems/binary-search/

704. Binary search

Given an n-element sorted (ascending) integer array nums and a target value target , write a function to search for target in nums , returning the subscript if the target value exists, and -1 otherwise.

Example :

Input:  Output: 4
 Explanation: 9 appears in nums and has subscript 4nums= [-1,0,3,5,9,12], target= 9

On the premise that the array is ordered, use double pointers for binary search. The function of double pointers is to "binary" . First, the left and right pointers lr point to the first element and the tail element of the array respectively , and determine the size relationship between the array value corresponding to the array subscript mid between the left and right pointers and the target value. There are three cases as follows:

  1. nums[mid] == target   find the target value, record the index of the array, end
  2. The value in the middle of nums[mid] > target  is greater than the target value, and should continue to search in the interval  [ l, mid-1 ] 
  3. nums[mid] < target The median value is less than the target value, and should  continue to search in the interval [ mid+1 , r ]

3 < 9   l = mid + 1

 find target value

code show as below:

class Solution {
    public int search(int[] nums, int target) {
        //数组长度
    int len = nums.length;
    int l = 0 ,r = len - 1;
    //记录目标值下标
    int index = -1;

    while(l <= r){
        //求出中间下标 (数据类型)
        int mid = (r-l) / 2 + l;
        //大于目标值 移动右指针
        if(nums[mid] > target){
            r = mid - 1;
        }
        else if(nums[mid] < target){
            //小于目标值 移动左指针
            l = mid + 1;
        }else{
            //等于目标值 记录下标值 结束循环
            index = mid;
            break;
        }
    }
    return index;
    }
}

2.

Leetcode https://leetcode-cn.com/problems/3sum/

15. The sum of three numbers

Moderate difficulty 3979

Given an  n array of integers  nums, determine  nums if there are three elements  a, b, c such that  a + b + c =  0? Please find all  0 triples that sum to and do not repeat.

Note: Answers cannot contain duplicate triples.
 

Example:

Input: nums = [-1,0,1,2,-1,-4]
 Output: [[-1,-1,2],[-1,0,1]]

First, sort the nums array. The problem requires to find three groups of elements whose sum is 0 and do not repeat. We can first select an element a, and then use double pointers to find the eligible elements b and c, (pay attention to deduplication) Assuming sum = a+b+c, there are the following three cases:

  1. sum == 0 and the condition, add these three numbers to the set
  2. sum > 0    moves the right pointer left to make the element c smaller
  3. sum < 0   , move right as a pointer to make element b larger

code show as below:

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
      List<List<Integer>> res = new ArrayList<List<Integer>>();
      int n = nums.length;
      //对数组进行排序
      Arrays.sort(nums);
      //记录上一个元素,用于去重 
      int index = -1;

         for(int i = 0; i < n;i++ ) {
             //去重
             if(index != -1 && nums[index] == nums[i])
             continue;

             //左右指针
        	 int l = i + 1, r = n - 1;
        	 while(l < r) {
                 // 三个数的和
        		 int num = nums[i] + nums[l]+nums[r];
                 //和为0 符合题意 记录在集合中
        		 if(num == 0) {
        			 List<Integer> list = new ArrayList<Integer>();
        			  list.add(nums[i]);
        			  list.add(nums[l]);
        			  list.add(nums[r]);
        			 res.add(list);
                     //二次去重
        			 int temp = nums[l];
        			 while(l < r && temp == nums[l]) l++;
        		 }else if(num < 0) {
                     //和小于0 右移左指针
        			 l++;
        		 }else {
                     //和大于0 左移右指针
        			 r--;
        		 }	 
        	 } 
             //记录上一次第一个元素的下标
             index = i;  	 
         }
      
      return res;
    }
}

Double pointer correctness argument: First, the array can use double pointers. After sorting, first select an element a and use i to point to this element, then use pointer l to point to element b, and pointer r to point to element c. sum is the sum of three elements, sum has only the above three cases,

If the first case is satisfied immediately sum = 0;

When sum > 0 , move the right pointer r to the left, instead of moving r to the left and l  to the right , the reason is that at this time sum > 0 , in any case, the sum of the pointer l to the right will be greater than 0, and the element pointed to by r is useless. The element pointed to by l may be eligible for an element in the interval [l,r-1] . If lr moves, some possible situations will be lost. It is even more impossible to go back because it is repeating the previous operation.

sum < 0时,右移左指针l ,而不是后退右指针r,原因在于首先指针移动到这一步说明之前遍历过的元素是不符合条件的,那上一步可能是两种可能 sum > 0 sum < 0,而右指针r 左移到这一步的位置一定是sum > 0 ,所以只有一种可能就是sum > 0,如果是sum > 0 那么是左移右指针r得到的这一步,再后退就是在重复操作。


2.快慢指针

快慢指针:左右两个指针,一块一慢

1.

876. 链表的中间结点

给定一个头结点为 head 的非空单链表,返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

示例 :

输入:[1,2,3,4,5]

输出:此列表中的结点 3 (序列化形式:[3,4,5]) 返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。 注意,我们返回了一个 ListNode 类型的对象 ans,这样: ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.

寻找单链表的中间结点,我们可以简单的进行循环遍历求出单链表的结点数量,然后再循环遍历找到中间的结点。但是如果用双指针的思路该怎么办?

还记得双指针的三个关键点吗,双指针初始位置的选取,因为我们不知道单链表的尾部,因此首尾双指针显然是行不通的,只能一端进行,方向从头到尾,那么应用双指针的价值何在?

At this time, we need to look at the third key point, the speed of the double pointer movement. Let us imagine that if two people start from the same position in a straight line, and one person is twice as fast as the other, then the distance of this person is twice that of the other in the same time. We set up two pointers, one moves twice as fast as the other, so the first one reaches the end point, and the second one just reaches the middle position, right?

The structure is defined as:

 public class ListNode {
     int val;
      ListNode next;
      ListNode() {}
      ListNode(int val) { this.val = val; }
      ListNode(int val, ListNode next) { this.val = val; this.next = next; }
  }

code show as below:

class Solution {
    public ListNode middleNode(ListNode head) {
         ListNode p = head, q = head;
     while(q != null && q.next != null){
        q = q.next.next;
         p = p.next;
     }
     return p;
    }
}

2.

392. Judging subsequence https://leetcode-cn.com/problems/is-subsequence/

Difficulty Easy 545

Given strings  s  and  t  , determine whether  s  is   a subsequence of t .

A subsequence of a string is a new string formed by removing some (or not) characters from the original string without changing the relative positions of the remaining characters. (e.g. a subsequence of "ace"yes , not)"abcde""aec"

Example:

Input: s = "abc", t = "ahbgdc"
 Output: true

To determine whether a string s is a subsequence of a string t, first of all, it is necessary to clarify the definition of a subsequence, that is, the characters in the string t can take out a string that is the same as s without changing the relative position , then s is t subsequence. We traverse t to see if there is the first character of s. If there is, continue to traverse to find the second character of s based on this position, and proceed in turn. If all s are found, it means that s is the word sequence of t.

We set two pointers i, j, i starts from the first character of s, j starts from the first character of t, when they are equal, both i and j are shifted to the right , if they are not equal, only j is shifted to the right , and finally if i is equal to the length of s , then the complete match is successful, and s is a subsequence of t.

code show as below:

class Solution {
    public boolean isSubsequence(String s, String t) {
   int n = s.length(), m =t.length();
         if (n > m ) {
			return false;
		}
            int i = 0 ,j = 0;
            while(i< n && j<m ) {
            	if(s.charAt(i) == t.charAt(j))  
            		i++;         	
            		j++;
            	
            }
         return i == n;
    }
}

3. Sliding window

Sliding window: The left and right pointers form a "window", the right pointer expands continuously, and the left pointer shrinks according to conditions.

1004. Maximum Consecutive Ones III https://leetcode-cn.com/problems/max-consecutive-ones-iii/

Given an   array of sums  0 ,  we can change  at most  sums from 0 to 1.1AK

Returns the length of the longest (contiguous) subarray containing only 1s.

Example:

Input: A = [1,1,1,0,0,0,1,1,1,1,0], K = 2
Output: 6
Explanation: 
[1,1,1,0,0, 1 ,1 ,1,1,1,1 ] Bold numbers flip from 0 to 1, the longest subarray length is 6 .

This question finds the maximum number of consecutive 1s, that is, on the basis of less than or equal to k 0s, to find the length of the longest eligible interval. We use the double pointer lr to represent both sides of the window, and traverse the array from the beginning. If the value is 1 or the number of 0s in the interval  [ l, r ]  <= k, we move the pointer r to the right to expand the window, and Update the number of 0s. When the number of 0s is greater than k, we need to update the maximum eligible interval length. Then move l to the right to shrink the window, update the number of 0s, and continue to repeat until the right pointer exceeds the subscript limit of the array, and the traversal ends. At this time, the value of the maximum interval length is recorded.

 

 Shrink the window

 

 

code show as below:

class Solution {
    public int longestOnes(int[] nums, int k) {
     int n = nums.length;
     int l = 0, r = 0;
     int count = 0;  //记录0的个数
     int res = 0;  //记录结果

     while(r < n){
         //当nums[r]为1 或者 子区间零的个数少于k
         if(nums[r] == 1 || count < k){
             //是否为0,若为0则count++
             count += nums[r] == 0 ? 1 : 0;
             r++;
              continue;   
         }

         res = Math.max(res,r-l);

         //nums[r] 为 0
         count++;
         r++;

         //如果子区间0的个数 大于 k ,缩小窗口 l < n防止数组下标越界
          while(count > k ){
             count -= nums[l] == 0 ? 1 : 0;
           l++;
          }
     }
      res = Math.max(res,r-l);
return res;

    }
}

4. Merge Sort

88. Merge two sorted arrays https://leetcode-cn.com/problems/merge-sorted-array/

You are given two integer arrays nums1 and nums2 in non-decreasing order, and two more integers m and n, representing the number of elements in nums1 and nums2, respectively.

Please merge nums2 into nums1 so that the merged arrays are also arranged in non-decreasing order.

Note: Ultimately, the merged array should not be returned by the function, but stored in the array nums1. To cope with this situation, the initial length of nums1 is m + n, where the first m elements represent the elements that should be merged, and the last n elements are 0 and should be ignored. The length of nums2 is n.

Example:

Input: nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
Output: [1,2,2,3,5,6]
Explanation: Need to combine [1,2,3] and [2,5,6].
The combined result is [1,2,2,3,5,6].

To merge two sorted arrays, no additional array space can be used, and the merged array is nums1. Similar to the subsequence of the previous string, the difference is that we cannot traverse and compare the two array elements from the beginning, because the comparison is stored in nums1 at this time, so the original element of nums1 may need to be shifted back one bit, similar to The time complexity of the addition operation of the array is O(N). If the smallest element of nums2 is larger than the largest element of nums1, then the element of nums1 has been moved backward and the number of elements of nums2 is performed for O(N) operations, which will be O( N^2), how to optimize?

The reason we encounter the above problem is that we start from the end where valid data has been stored. At this time, the movement will definitely affect the left and right elements. Then we change our thinking and start the comparison from the tail, that is to say, the largest number can be compared. , stored at the end of nums1.

code show as below:

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
//l指向nums1尾部元素,r指向nums2尾部元素 ,index指向nums1要存储比较后数据的位置
     int l = m  - 1, r = n -1 , index = m + n - 1;
                          
             while(l >= 0 && r >= 0){
           //如果l指向的元素值大 左移l
                  if(nums1[l] > nums2[r]){

                      nums1[index] = nums1[l];
                      --l;     
                  }else{
                  //如果r指向的元素值大 左移r
                      nums1[index] = nums2[r];
                      --r; 
                  }
            //存储一个元素,左移index指向下一个待存储元素的位置
                   --index;
             }
              // 如果此时 nums2还有元素没有比较,就全部依次存储在nums1中
              while(r >= 0) nums1[index--] = nums2[r--];
    }
}

4. Summary

Double pointer is one of the common bases for solving complex arithmetic problems, and it is widely used. It is relatively simple to examine double pointer problems alone. As an important algorithm basis, double pointer is not only a method to reduce the time complexity of the algorithm, but also can provide a new idea for solving the problem. To master double pointers proficiently, not only need to understand the common models of double pointers, but also practice related algorithm problems and apply the knowledge learned in practice.

Guess you like

Origin blog.csdn.net/qq_52595134/article/details/121385996