[Algorithm series (four)]: double pointer

table of Contents

1. Common algorithms for left and right pointers

 1.1, binary search

35. Search for Insertion Location

1.2, the sum of two numbers

1. the sum of two numbers

15. Sansanowa

 1.3, sliding window algorithm

76. Minimum Covering Substring

438. Find all letter dysphoric words in the string

3. The longest substring without repeated characters

2. Common algorithms for fast and slow pointers

206. Reverse Linked List

19. Delete the Nth node from the bottom of the linked list

141. Circular Linked List

142. Circular Linked List II

 148. Sort Linked List


General dual pointer algorithms are mainly divided into two categories: (1) fast and slow pointers (2) left and right pointers . The former solution mainly solves the problems in the linked list, such as the typical determination of whether the linked list contains rings; the latter mainly solves the problems in the array (or string), such as binary search.

  • Fast and slow pointers: Generally, the fast and slow pointers are initialized to point to the head node of the linked list. When moving forward, the fast pointer is fast in the front and the slow pointer is slow in the back. This skillfully solves some problems in the linked list.
  • Left and right pointers: The left and right pointers actually refer to two index values ​​in the array. Generally, the left pointer points to the first address of the array and the tail pointer points to the end of the array.

 In addition, another advanced usage of the left and right pointers is called the sliding window algorithm . This is the highest level of the dual pointer technique. This type of algorithm mainly solves a large class of substring matching problems, but the "sliding window" is more complicated than the left and right pointer algorithm.

1. Common algorithms for left and right pointers

 1.1, binary search

Search is very common in algorithmic questions, but how to maximize the efficiency of search and write bugfree code is the hard part. The general search methods include sequential search, binary search and double pointer. It is recommended to use sequential search directly at the beginning. If you encounter TLE, consider the remaining two. After all, AC is the most important.

Generally, the objects of binary search are ordered or changed from ordered parts (may not be understood temporarily, just look at the sample questions), but there is also a place where binary search by value can be used, which will be introduced later.

  • Basic framework
int binarySearch(int[] nums, int target) {
    int left = 0, right = ...;

    while(...) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            ...
        } else if (nums[mid] < target) {
            left = ...
        } else if (nums[mid] > target) {
            right = ...
        }
    }
    return ...;
}
  • while(left<right): Is it <or <=

When we write binary search, we will definitely be very confused, whether it is "<" or should be written as "<=". To understand this concept, one needs to understand the concept of interval [left, right]. If it is written as "<", then the ending condition is: right=left ; if it is written as "<=", then the ending condition is: [right+1,right]. That is to say, if it is to find whether there is a certain value, then under the condition of "<", an interval equal value may be missed. If it is "<=", then all the traversal is just completed. So, if right=nuns.size(), then we need to use "<", for right=nuns.size()-1, we need to use "<=".

  • Should left be added by 1, right should be subtracted by 1, and when should it be added or subtracted by 1?

This is mainly related to the settings of left and right. If right=nums.size(), that is to say, the range of the search interval is: [left, right); if right=nums.size()-1, the search range is: [ left,right]. If it is the first case, then when we judge the mid, the new search interval is: [right, mid) and [mid+1, right). It can also be seen here that we need to compare left when <mid =mid+1, and >mid, for right=mid, there is no need to add 1. If it is the second case, then we need to perform mid+1 for left and mid-1 for right.

  • Precautions for binary search with left boundary and boundary,

If there is an array like [1,2,2,2,3], ordinary binary search may return 2. If you want to find the target value of the leftmost or rightmost position, you need to make certain modifications under ordinary binary search . If we want to find the left boundary, we need to set right=mid-1 to continue searching when nums[mid]==target. Similarly, to find the right boundary, we need to set left=mid+1 to continue searching.

The final binary search template is summarized as follows:

int binary_search(int[] nums, int target) {
    int left = 0, right = nums.length - 1; 
    while(left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid - 1; 
        } else if(nums[mid] == target) {
            // 直接返回
            return mid;
        }
    }
    // 直接返回
    return -1;
}

int left_bound(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid - 1;
        } else if (nums[mid] == target) {
            // 别返回,锁定左侧边界
            right = mid - 1;
        }
    }
    // 最后要检查 left 越界的情况
    if (left >= nums.length || nums[left] != target)
        return -1;
    return left;
}


int right_bound(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid - 1;
        } else if (nums[mid] == target) {
            // 别返回,锁定右侧边界
            left = mid + 1;
        }
    }
    // 最后要检查 right 越界的情况
    if (right < 0 || nums[right] != target)
        return -1;
    return right;
}

35. Search for Insertion Location

Given a sorted array and target value, if the target is found, the index is returned. If it is not, it returns the index of the position where the index is inserted in order. You can assume that there are no duplicates in the array.

Example 1:
Input: [1,3,5,6], 5
Output: 2

Example 2:
Input: [1,3,5,6], 2
Output: 1

Example 3:
Input: [1,3,5,6], 7
Output: 4

Example 4:
Input: [1,3,5,6], 0
Output: 0

Analysis: The  point to note here is that the reason why high should be set to len(nums) is that as in the third example, it will exceed the maximum value of the array, so lo can reach this subscript.

  • C++ algorithm implementation
int searchInsert(vector<int>& nums, int target) {
	int l = 0, r = nums.size();
	while (l < r) {
		int mid = l + (l - r) / 2;
		if (nums[mid] == target) return mid;
		else if (nums[mid] > target) r = mid;
		else if (nums[mid] < target) l = mid + 1;
	}

	return l;
}

1.2, the sum of two numbers

Generally, for binary search or the sum of two numbers, and the subsequent three or four numbers, the most important point is to see whether the array is in order. If the array is disordered, it needs to be converted to order. Therefore, in general, we The unordered array will be sorted. Secondly, the general routine for the sum of two numbers is:

  • Left and right pointers point to both sides of the array
  • while iterating over the array
    • ==target, return directly
    • >target, the right pointer moves forward
    • <target, the left pointer moves backward

In short, the template for the sum of two numbers is as follows:

# 对撞指针套路
l,r = 0, len(nums)-1
while l < r:
    if nums[l] + nums[r] == target:
        return nums[l],nums[r]
    elif nums[l] + nums[r] < target:
        l += 1
    else:
        r -= 1

1. the sum of two numbers

  • Title description

Given an integer array nums, return the index values ​​i and j of the two numbers in this array, so that nums[i] + nums[j] is equal to a given target value, and the two indexes cannot be equal. Such as: nums= [2,7,11,15], target=9 returns [0,1]

  • Problem-solving ideas

Need to consider:

  1. Whether the starting array is in order;
  2. Does the index start from 0 or 1?
  3. What should I do if there is no solution?
  4. What if there are multiple solutions? Ensure that there is a unique solution.

Before sorting, use an additional array to copy the original array. For the index problem of two identical elements, use a bool variable to assist in finding both indexes. The total time complexity is O(n)+O (nlogn) = O(nlogn)

  • C++ algorithm implementation
vector<int> twoSum(vector<int>& nums, int target) {
    vector<pair<int,int>> dict;
    for(int i=0;i<nums.size();++i){
        dict.push_back(pair<int,int>(nums[i],i));
    }

    sort(dict.begin(),dict.end(),
         [](pair<int,int> &a,pair<int,int>&b){return a.first<b.first;});

    vector<int> res;
    int i=0,j=dict.size()-1;
    while(i<j){
        if(dict[i].first+dict[j].first>target){
            j--;
        }else if(dict[i].first+dict[j].first<target){
            i++;
        }else if(dict[i].first+dict[j].first==target){
            res.push_back(dict[i].second);
            res.push_back(dict[j].second);
            return res;
        }
    }

    return res;
}

15. Sansanowa

  • Title description

Given an integer array, find all the different triples (a, b, c) in it, so that a+b+c=0

Note: The answer cannot contain repeated triples.

For example: nums = [-1, 0, 1, 2, -1, -4],

The result is: [[-1, 0, 1],[-1, -1, 2]]

  • Problem-solving ideas

Review questions

  1. The array is not ordered;
  2. The returned result is all solutions. Does the order of multiple solutions need to be considered? --No need to consider the order
  3. What are different triples? Different indexes mean different, or different values? --The definition of the title is that different values ​​are different triples
  4. How to return when there is no solution? --Empty list

At the beginning, the nums array is sorted. After sorting, when the pointer k that is traversed for the first time encounters the next and previous value duplication, it will be skipped. In order to facilitate the calculation, in the second layer of loop, you can use the double pointer routine.

It should be noted that in the inner loop, the case of repeated values ​​should also be considered. Therefore, when the values ​​are equal, when the pointer is moved again, it is necessary to ensure that the value pointed to by it does not repeat the value pointed to by the previous time.

  • C++ algorithm implementation
vector<vector<int>> threeSum(vector<int>& nums) {
    vector<vector<int>> res;
    if(nums.size()<3) return res;

    sort(nums.begin(),nums.end());

    for(int i=0;i<nums.size()-2;++i){
        if(nums[i]>0) break;
        if(i>0&&nums[i]==nums[i-1]) continue;

        int l=i+1,r=nums.size()-1;
        while(l<r){
            int sum=nums[i]+nums[l]+nums[r];
            if(sum==0){
               vector<int> tmp={nums[i],nums[l],nums[r]};
               res.push_back(tmp);
               l+=1;
               r-=1;
               while(l<r&& nums[l]==nums[l-1]) l++;
               while(l<r&&nums[r]==nums[r+1]) r--;
            }else if(sum<0){
                l+=1;
            }else if(sum>0){
                r-=1;
            }
        }
    }

    return res;
}

 1.3, sliding window algorithm

The idea of ​​sliding window algorithm is this:

  1. Using the left and right pointer technique in the double pointer, initialize left = right = 0, and call the index closed interval [left, right] a "window".
  2. We first continue to increase the right pointer to expand the window [left, right] until the window meets the requirements.
  3. At this point, we stop increasing right, and continue to increase the left pointer to shrink the window [left, right] until the window no longer meets the requirements (not including all the characters in T). At the same time, every time left is increased, we have to update the results of a round.
  4. Repeat steps 2 and 3 until right reaches the end.

76. Minimum Covering Substring

  • Title description

Give you a string S and a string T. Please design an algorithm that can be found in the string S within O(n) time complexity: the smallest substring that contains all the characters of T.

示例:
输入:S = "ADOBECODEBANC", T = "ABC"
输出:"BANC"
  • Problem-solving ideas

The idea of ​​sliding window algorithm is this:

1. We use the left and right pointer technique in the double pointer in the string S, initialize left = right = 0, and call the index closed interval [left, right] a "window".

2. We first continue to increase the right pointer to expand the window [left, right] until the string in the window meets the requirements (including all characters in T).

3. At this time, we stop increasing right, and continue to increase the left pointer to shrink the window [left, right] until the string in the window no longer meets the requirements (not including all the characters in T). At the same time, every time left is increased, we have to update the results of a round.

4. Repeat steps 2 and 3 until right reaches the end of the string S.

This idea is actually not difficult. The second step is equivalent to looking for a "feasible solution", and then the third step is to optimize this "feasible solution", and finally find the optimal solution. **The left and right pointers move forward in turn, the window size increases or decreases, and the window continues to slide to the right.

  • C++ algorithm implementation
string minWindow(string s, string t){
    int left=0,right=0,start=0;
    int min=INT_MAX;

    unordered_map<char,int> windows;
    unordered_map<char,int> needs;

    for(int i=0;i<t.size();++i) needs[t[i]]++;

    int match=0;
    while(right<s.size()){
        char c1=s[right];
        if(needs.count(c1)){
            windows[c1]++;
            if(windows[c1]==needs[c1]) match++;
        }
        ++right;

        while(match==needs.size()){
            if(right-left<min){
                min=right-left;
                start=left;
            }
            char c2=s[left];
            if(needs.count(c2)){
                windows[c2]--;
                if(windows[c2]<needs[c2]){
                    match--;
                }
            }
            ++left;
        }
    }

    return min == INT_MAX ? "" : s.substr(start, min);
}

438. Find all letter dysphoric words in the string

  • Title description

Given a string s and a non-empty string p, find all substrings of the letter dyslexia of p in s, and return the starting index of these substrings.

The string contains only lowercase English letters, and the length of the strings s and p are not more than 20100.

Note: Aliphatic words refer to strings with the same letters but different arrangements.
The order in which answers are output is not considered.

示例 1:
输入:
s: "cbaebabacd" p: "abc"
输出:
[0, 6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的字母异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的字母异位词。
  • Problem-solving ideas
  • C++ algorithm implementation
vector<int> findAnagrams(string s, string p) {
    int left=0,right=0;
    vector<int> res;

    unordered_map<char,int> windows;
    unordered_map<char,int> needs;

    for(int i=0;i<p.size();++i) needs[p[i]]++;

    int match=0;
    while(right<s.size()){
        char c1=s[right];
        if(needs.count(c1)){
            windows[c1]++;
            if(windows[c1]==needs[c1]) match++;
        }
        right++;

        while(match==needs.size()){
            if((right-left)==p.size()) res.push_back(left);

            char c2=s[left];
            if(needs.count(c2)){
                windows[c2]--;
                if(needs[c2]>windows[c2])match--;
            }
            left++;
        }
    }

    return res;
}

3. The longest substring without repeated characters

  • Title description

Given a string, please find out the length of the longest substring that does not contain repeated characters.

示例 1:
输入: "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
  • Problem-solving ideas
  • C++ algorithm implementation
int lengthOfLongestSubstring(string s) {
    int left=0,right=0,res=0;

    unordered_map<char,int> windows;
    while(right<s.size()){
        char c1=s[right];
        windows[c1]++;
        right++;

        while(windows[c1]>1){
            char c2=s[left];
            windows[c2]--;
            left++;
        }

        res=max(res,right-left);
    }

    return res;
}

2. Common algorithms for fast and slow pointers

The fast and slow pointers are generally initialized to point to the head node head of the linked list. When moving forward, the fast pointer fast is in the front and the slow pointer is slow in the back, which cleverly solves some problems in the linked list.

206. Reverse Linked List

  • Title description

Reverse a singly linked list.

示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
  • Problem-solving ideas

p1As the pointer in the front to explore the way, p2as the pointer in the back to follow up, run a circle along the linked list to solve the problem.

  • C++ algorithm implementation
ListNode* reverseList(ListNode* head) {
    ListNode *p1, *p2;
    p1 = head;
    p2 = NULL;

    while (p1 != NULL) {
        ListNode *tmp=p1->next;
        p1->next = p2;
        p2 = p1;
        p1 = tmp;
    }

    return p2;
}

19. Delete the Nth node from the bottom of the linked list

  • Title description

Given a linked list, delete the nth node from the bottom of the linked list, and return the head node of the linked list.

示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
  • Problem-solving ideas

Using two pointers, the p1first pointer nmoves first , and then the rear pointer moves p2synchronously to the end point, that is, to the position of the node to be deleted.p1p1p2

  • C++ algorithm implementation
ListNode* removeNthFromEnd(ListNode* head, int n) {
    ListNode *p1 = head;
    ListNode *p2 = head;

    int j = n;
    while (p1 != NULL && j>=0) {
        p1 = p1->next;
        j--;
    }
    if (p1 == NULL && j==0) {
        head = head->next;
    }
    else {
       while (p1 != NULL) {
           p1 = p1->next;
           p2 = p2->next;
       }

       ListNode *tmp = p2->next;
       p2->next = p2->next->next;
   }

    return head;
}

141. Circular Linked List

  • Title description

Given a linked list, determine whether there is a ring in the linked list.

In order to represent the rings in a given linked list, we use the integer pos to indicate the position where the end of the linked list is connected to the linked list (the index starts from 0). If pos is -1, then there is no ring in the linked list.

示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
  • Problem-solving ideas

Under normal circumstances, we use Hashthe method to determine whether it contains repeated elements . For this scenario of a singly linked list, we can also use the double pointer approach.

The first pointer p1moves two timers at a time, and the second pointer p2moves one timer at a time. If there is a ring in the linked list, the first pointer will definitely touch the second pointer again, otherwise, there is no ring. .

  • C++ algorithm implementation
bool hasCycle(ListNode *head) {
    ListNode *p1=head;
    ListNode *p2=head;
    
    while(p1!=NULL&& p1->next!=NULL&& p1->next->next!=NULL){
        p1=p1->next->next;
        p2=p2->next;
        
        if(p1==p2){
            return true;
        }
    }
    
    return false;
}

142. Circular Linked List II

  • Title description

Given a linked list, return the first node where the linked list starts to enter the loop. If the linked list has no rings, null is returned.

In order to represent the rings in a given linked list, we use the integer pos to indicate the position where the end of the linked list is connected to the linked list (the index starts from 0). If pos is -1, then there is no ring in the linked list.

Note: It is not allowed to modify the given linked list.

示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:tail connects to node index 1
解释:链表中有一个环,其尾部连接到第二个节点。
  • Problem-solving ideas

At the first encounter, assuming that the slow pointer slow has taken k steps, then the fast pointer fast must have taken 2k steps, which means that it has taken k steps more than slow (a multiple of the loop length).

 

Suppose the distance between the meeting point and the starting point of the ring is m, then the distance between the starting point of the ring and the head node head is k-m, that is to say, if you advance k-m steps from head, you can reach the starting point of the ring. Coincidentally, if you continue k-m steps from the meeting point, you will also just reach the starting point of the ring.

Therefore, as long as we repoint either of the fast and slow pointers to head, and then the two pointers move forward at the same speed, they will meet after k-m steps, and the meeting point is the starting point of the ring.

  • C++ algorithm implementation
ListNode *detectCycle(ListNode *head) {
    ListNode* fast = head;
     ListNode* slow = head;
     while(fast != NULL && fast->next != NULL) {
         slow = slow->next;
         fast = fast->next->next;
         // 快慢指针相遇,此时从head 和 相遇点,同时查找直至相遇
         if (slow == fast) {
             ListNode* index1 = fast;
             ListNode* index2 = head;
             while (index1 != index2) {
                 index1 = index1->next;
                 index2 = index2->next;
             }
             return index2; // 返回环的入口
         }
     }
     return NULL;
}

148. Sort Linked List

  • Title description

Under O(n log n) time complexity and constant level space complexity, the linked list is sorted.

示例 1:
输入: 4->2->1->3
输出: 1->2->3->4

示例 2:
输入: -1->5->3->4->0
输出: -1->0->3->4->5
  • Problem-solving ideas

Imitate the idea of ​​merging and sorting, a typical backtracking algorithm.

If the elements to be sorted are stored in an array, we can use concatenation sorting. And these elements are stored in the linked list, we can not directly use the merge sort, we can only learn from the idea of ​​merge sort to modify the algorithm.

The idea of ​​union sorting is to group the sequence to be sorted until it contains one element, and then merge the two ordered sequences back and forth, and finally get the sorted sequence.

For the linked list, we can recursively divide the current linked list into two segments, and then merge. The method of dividing the two segments is to use the double pointer method. The p1pointer moves two steps at a p2time , and the pointer moves one step p1at a time until it reaches the end, p2where it is. It is the middle position, so it is divided into two sections.

  • C++ algorithm implementation
 ListNode* sortList(ListNode* head) {
       if(!head||!head->next) return head;
        ListNode *pre=head,*slow=head,*fast=head;
        while(fast&&fast->next)
        {
            pre=slow;
            slow=slow->next;
            fast=fast->next->next;
        }
        pre->next=NULL;
        return merge(sortList(head),sortList(slow));
    }
    ListNode* merge(ListNode* l1,ListNode* l2)
    {
        ListNode* dummy=new ListNode(-1);
        ListNode* cur=dummy;
        while(l1&&l2)
        {
            if(l1->val>l2->val)
            {
                cur->next=l2;
                l2=l2->next;
            }
            else 
            {
                cur->next=l1;
                l1=l1->next;
            }
            cur=cur->next;
        }
        if(l1) cur->next=l1;
        if(l2) cur->next=l2;
        return dummy->next;
    }

Reference link:

Summary of Dual Pointer Techniques

The Application of Double Pointer Technology in Solving Algorithmic Problems

Guess you like

Origin blog.csdn.net/wxplol/article/details/108267441