LeetCode HOT100 problem solving record - medium


2, Medium
No.1(#2)Add Two Numbers
No.2(#3)Longest Substring Without Repeating Characters
No.3(#5) Longest Palindromic Substring
No.4(#11)Container With Most Water
No.5(#15)3Sum
No.6(#17)Letter Combinations of a Phone Number
No.7(#19)Remove Nth Node From End of List
No.8(#22)Generate Parentheses
No.9(#31)Next Permutation
No.10(#33)Search in Rotated Sorted Array
No.11(#34) Find First and Last Position of Element in Sorted Array

2, Medium

No.1(#2)Add Two Numbers

Idea 1: first fill the length of the two linked lists to the same length, and use 0 as the value of the node. Synchronously traverse the two linked lists. If the carry is generated, use cf to record. If the last bit generates carry, first add a new node to the end.

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode *p1,*p2;
        int len1=find_length(l1);
        int len2=find_length(l2);
        if(len1>len2) {//p1 always points to a short linked list
            p1=l2;
            p2=l1;
        }
        else{
            p1=l1;
            p2=l2;
        }
        p1=complement_zero(p1,len1,len2);
        int cf=0;
        ListNode* head=new ListNode;
        head->next=NULL;//The value of the head node is null
        ListNode* r=head;
        while(p1!=NULL){
            ListNode* Node=new ListNode;
            if(p1->val+p2->val+cf>=10){//Two assignment cases with or without carry
                Node->val=p1->val+p2->val+cf-10;
                cf=1;
            }
            else{
                Node->val=p1->val+p2->val+cf;
                cf=0;
            }
            r->next=Node;
            Node->next=NULL;
            r=Node;
            p1=p1->next;
            p2=p2->next;
        }
        if(cf){
            ListNode* Node=new ListNode;
            Node->val=cf;
            r->next=Node;
            Node->next=NULL;
        }
        head=head->next;//The head originally points to the head node and needs to be moved back one bit
        return head;
    }
    int find_length(ListNode* head){//Calculate linked list length
        int count=0;
        while(head!=NULL){
            head=head->next;
            count++;
        }
        return count;
    }
    ListNode* complement_zero(ListNode* head,int len1,int len2){//Complement length with zero
        ListNode*p=head;
        while(p->next!=NULL) p=p->next;
        for(int i=0;i<abs(len1-len2);i++){
            ListNode*Node=new ListNode;
            Node->val=0;
            p->next=Node;
            Node->next=NULL;
            p=p->next;
        }
        return head;
    }
};

Idea 2: create a new linked list and add new nodes by head interpolation. The value of the new node has two cases: only one chain traverses to the empty node or both chains traverse to the empty node.

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        int val=(l1->val+l2->val)%10;
        int cf=l1->val+l2->val>=10?1:0;
        ListNode* l3=new ListNode(val);
        //First construct the header node
        l3->next=NULL;
        ListNode* pre=l3;
        l1=l1->next;
        l2=l2->next;
        while(l1!=NULL||l2!=NULL){
            if(l1!=NULL&&l2!=NULL){
            //Calculated value and carry
                val=(l1->val+l2->val+cf)%10;
                cf=l1->val+l2->val+cf>=10?1:0;
            }
            else if(l1==NULL){
                val=(l2->val+cf)%10;
                cf=l2->val+cf>=10?1:0;
            }
            else if(l2==NULL){
                val=(l1->val+cf)%10;
                cf=l1->val+cf>=10?1:0;
            }
            auto node=new ListNode(val);
            node->next=pre->next;
            pre->next=node;
            pre=node;
            if(l1!=NULL) l1=l1->next;
            if(l2!=NULL) l2=l2->next;
        }
        if(cf){
        //Finally, if there is carry, a new node must be added
            auto node=new ListNode(cf);
            node->next=pre->next;
            pre->next=node;
            pre=node;
        }
        return l3;
    }
};

Official solution code:

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* head=NULL,*tail=NULL;
        int carry=0;
        while(l1||l2){
            int n1=l1?l1->val:0;
            int n2=l2?l2->val:0;
            int sum=n1+n2+carry;
            if(!head){
                head=tail=new ListNode(sum%10);
            }else{
                tail->next=new ListNode(sum%10);
                tail=tail->next;
            }
            carry=sum/10;
            if(l1){
                l1=l1->next;
            }
            if(l2){
                l2=l2->next;
            }
        }
        if(carry){
            tail->next=new ListNode(carry);
        }
        return head;
    }
};

Idea 3: on the basis of idea 2, do not create a new linked list, but directly modify the value on one of the linked list nodes, and only create a new node when the carry is finally generated.

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* p1=l1,*p2=l2,*pre;
        int cf=0,flag=0;
        while(p1!=NULL||p2!=NULL){
            if(!flag){
            //Both chains are not empty
                int val=(p1->val+p2->val+cf)%10;
                cf=p1->val+p2->val+cf>=10?1:0;
                p1->val=val;
            }
            else{
            //When a chain traverses to null
                int val=(p1->val+cf)%10;
                cf=p1->val+cf>=10?1:0;
                p1->val=val;
            }
            if(p1->next==NULL&&p2->next!=NULL){
            //After traversing chain 1, connect the subsequent of chain 2 to chain 1
                p1->next=p2->next;
                flag=1;
            }
            if(p2->next==NULL&&p1->next!=NULL){
            //After traversing chain 2, p2 jumps to chain 1
                p2=p1;//This is different from the previous if because l1 is returned
                flag=1;
            }
            if(p1->next==NULL) pre=p1;
            //pre is used to connect the last carry node
            if(p1!=NULL) p1=p1->next;
            if(p2!=NULL) p2=p2->next;
        }
        if(cf){
        //Process last carry
            ListNode* node=new ListNode(cf);
            pre->next=node;
            node->next=NULL;
        }
        return l1;
    }
};

No.2(#3)Longest Substring Without Repeating Characters

Hash: scan in two layers. Scan in the second layer to check whether the element already exists in the hash table. If so, exit the loop of this layer. If not, add the element to the hash table and increase the length of the current subsequence by one. Finally, compare the length of the current subsequence with the longest length.

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        set<char> hash={};
        int max_len=0;
        for(int i=0;i<s.size();i++){
            int cur_len=0;
            hash.clear();
            for(int j=i;j<s.size();j++){
                if(hash.find(s[j])!=hash.end()){
                //Judgment max_len's statement can't be put here
                    break;
                }else{
                    hash.insert(s[j]);
                    cur_len++;
                }
            }
            if(max_len<cur_len) max_len=cur_len;
            //The reason why the judgment statement is put here is as follows
            //eg."abcd", when the second layer circularly scans the last element and the element does not repeat, it will
            //Exit this layer directly
        }
        return max_len;
    }
};

Idea 2 (sliding window): the size of the sliding window, that is, the length of the current subsequence, is constantly consistent with max_len comparison, you can get the final max_len.

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_set<char> win={};
        int max_len=0,left=0,cur_len;
        for(int i=0;i<s.size();i++){
            while(win.find(s[i])!=win.end()){
                win.erase(s[left]);
                left++;
            }
            win.insert(s[i]);
            cur_len=i-left+1;
            if(max_len<cur_len) max_len=cur_len;
        }
        return max_len;
    }
};

DP No.3(#5) Longest Palindromic Substring

DP skip

No.4(#11)Container With Most Water

Idea 1 (double pointer): to maximize the volume of water in the container, we should try to make the bottom of the container wider and the height of the short plate higher. Therefore, we can use the double pointer to point to the left and right ends respectively to get the maximum bottom, and record the current volume.

class Solution {
public:
    int maxArea(vector<int>& height) {
        int left=0,right=height.size()-;
        int max_area=0,cur_area;
        while(left<right){
            cur_area=(right-left)*min(height[left],height[right]);
            if(max_area<cur_area) max_area=cur_area;
            if(height[left]>height[right]) right--;
            //To increase the volume, we should "abandon" short boards and find longer boards
            else left++;
        }
        return max_area;
    }
};

Idea 2 (double pointer): optimize on the basis of idea 1, and last can be introduced_ Hei and cur_hei two variables, representing the container height recorded last time and the container height this time. We know that to maximize the volume of water in the container, we must maximize the height of the bottom and short plate of the container as much as possible. If the bottom of the container is smaller than before, we must make the short plate higher to make the volume larger than before. According to this idea, the following improvements are made.

class Solution {
public:
    int maxArea(vector<int>& height) {
        int left=0,right=height.size()-1;
        int max_area=0,cur_area;
        int cur_hei,last_hei=0;
        while(left<right){
            int cur_hei=min(height[left],height[right]);
            if(last_hei<cur_hei){
                cur_area=cur_hei*(right-left);
                if(max_area<cur_area) max_area=cur_area;
                last_hei=cur_hei;
            }
            if(height[left]<height[right]) left++;
            else right--;
        }
        return max_area;
    }
};

No.5(#15)3Sum

thinking ❌: First arrange the array in ascending order, and then make the left, right and mid pointers point to the left, right and middle of the array respectively.

Steps: ① calculate the value of num [left] + num [right]. If it is greater than 0, move mid to the left; If it is less than 0, move mid to the right; If it is equal to 0, find 0 according to the value of num [mid]. If num [mid] is greater than 0, move mid left; if it is less than 0, move mid right;
② Repeat the above operation until a set of qualified numbers is found, or mid meets left (right). At this time, if num [left] + num [right] is greater than 0, it will move left by right; If num [left] + num [right] is less than 0, move left to the right, and then reset the position of mid (mid=(left+right)/2);
③ Repeat operations ① and ② until left, mid and right at least meet.

❌ The above method is wrong, because when num [left] + num [right] is equal to 0, the above method is to shift right to the left first. At this time, if num [left + 1] + num [right] can also find the situation that meets the requirements, a group of answers will be omitted.

Idea 2: this topic is the sum of three numbers, which can be transformed into the sum of two numbers, that is, Num [a] + num [b] = - num [C]. Then we can use a pointer i to traverse the enumeration elements in the first layer and fix them, and then use the double pointer to find the match after i
The value of num [left] + num [right] = - num [i] is the answer.

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        if(nums.size()<3) return {};
        sort(nums.begin(),nums.end());
        vector<vector<int>> ans;
        for(int i=0;i<nums.size();i++){
            if(nums[i]>0) break;//If num [i] > 0, then num [left], Num [right] must be greater than 0
            if(i>0&&nums[i]==nums[i-1]) continue;
            //If it is the same as the previous element, a duplicate group will appear, so it will be skipped directly in case of repetition
            int target=-nums[i];
            int left=i+1,right=nums.size()-1;
            while(left<right){
                if(nums[left]+nums[right]==target){
                    ans.push_back({nums[i],nums[left],nums[right]});
                    //At this time, the value of the three pointers has met the requirements. If you only move left or right separately, the result is
                    //The sum of three numbers must not meet the requirements. Only when left and right lean towards the middle at the same time can they interact with each other
                    //Offset meets requirements
                    left++;
                    right--;
                    while(left<right&&nums[left]==nums[left-1]) left++;
                    //If nums[right] is the same as the previous group, there must be a duplicate group
                    while(left<right&&nums[right]==nums[right+1]) right--;
                }
                else if(nums[left]+nums[right]>target){
                    right--;
                    while(left<right&&nums[right]==nums[right+1]) right--;
                }
                else{
                    left++;
                    while(left<right&&nums[left]==nums[left-1]) left++;
                }
            }
        }
        return ans;
    }
};

No.6(#17)Letter Combinations of a Phone Number

Idea 1 (queue): using the queue, each first element out of the queue is put into the queue again after extending its suffix, waiting for the next suffix extension.

class Solution {
    map<char,string> num={
        {'2',"abc"},
        {'3',"def"},
        {'4',"ghi"},
        {'5',"jkl"},
        {'6',"mno"},
        {'7',"pqrs"},
        {'8',"tuv"},
        {'9',"wxyz"},
        };
public:
    vector<string> letterCombinations(string digits) {
        queue<string> q;
        vector<string> ans={};
        for(int i=0;i<digits.size();i++){
            if(i==0){
                //First, line up all the letters of the first number
                for(int j=0;j<num[digits[0]].size();j++){
                    string s;
                    s=num[digits[0]][j];
                    q.push(s);
                }
            }
            else{
                int cur=digits[i];
                ans.clear();
                //Use ans to temporarily store the results obtained this time
                while(!q.empty()){
                    string s;
                    s=q.front();
                    q.pop();
                    //Suffixes are added to each team head element, and then temporarily stored in ans
                    for(int j=0;j<num[cur].size();j++){
                        ans.push_back(s+num[cur][j]);
                    }
                }
                //Queue the elements in ans for the next suffix extension
                for(int j=0;j<ans.size();j++){
                    q.push(ans[j]);
                }
            }
        }
        if(digits.size()==1){
            //Special judgment of size==1
            while(!q.empty()){
                ans.push_back(q.front());
                q.pop();
            }
        }
        return ans;
    }
};

Train of thought 2 (backtracking): problems involving all combinations can generally use backtracking method. Backtracking method is recursive in nature, so our main task of writing code is divided into two parts: ① end condition part; ② Recursive part, so you can write the following modules first:

def back_track(){
	if(End condition): 
		Return to the selected path;
		return;
	
	for choice in Selection list:
		Make a choice;
		back_track();
		Revoke the selection;
}

① End condition: obviously, when the selected path length is digits.size(), we have traversed to the end of digits, that is, there is a complete selected path. Save the path in string list and add it to vector < string > ans as a sub answer, and then return;
② Recursive part: the recursive part is divided into three tasks. First, make a choice, that is, we need to make a choice for the next step of the existing path, and all letters of the current number are optional paths. There is no need to prune, so we can use a for loop to traverse all letters of the current number. Second, continue recursion. Set parameters and continue recursion. Third, withdraw the choice. For example, for sequence "23", we have selected the first letter a of 2 and the first letter D of 3, and have returned "ad" in the end condition. At this time, the next sequence we are looking for is "ae", so we need to delete d at the end of the list before we can continue to add e in the next loop.

class Solution {
    map<char,string> num={
        {'2',"abc"},
        {'3',"def"},
        {'4',"ghi"},
        {'5',"jkl"},
        {'6',"mno"},
        {'7',"pqrs"},
        {'8',"tuv"},
        {'9',"wxyz"},
        };
    vector<string> ans;
public:
    vector<string> letterCombinations(string digits) {
        if(digits.size()==0) return {};
        string list;
        //Save subsequence with list
        DFS(digits,0,list);
        return ans;
    }
    void DFS(string digits,int index,string list){
        if(index==digits.size()){
        //index changes dynamically and digits.size remains unchanged
            ans.push_back(list);
            //Add subsequence to answer ans
            return;
        }
        for(int i=0;i<num[digits[index]].size();i++){
        //Traverse each letter of the current number
            list+=num[digits[index]][i];//Make a choice
            DFS(digits,index+1,list);
            list.erase(list.end()-1);//Undo selection
        }
    }
};

//Another way of backtracking:
void dfs(string digits,int index){
        if(index == digits.size()){
            ans.push_back(digits);
            return;
        }
        switch(digits[index]){
            case '2': digits[index] = 'a'; dfs(digits,index+1);
                      digits[index] = 'b'; dfs(digits,index+1);  
                      digits[index] = 'c'; dfs(digits,index+1);
                      break;
            case '3': digits[index] = 'd'; dfs(digits,index+1);
                      digits[index] = 'e'; dfs(digits,index+1);  
                      digits[index] = 'f'; dfs(digits,index+1);
                      break;  
            case '4': digits[index] = 'g'; dfs(digits,index+1);
                      digits[index] = 'h'; dfs(digits,index+1);  
                      digits[index] = 'i'; dfs(digits,index+1);
                      break;  
            case '5': digits[index] = 'j'; dfs(digits,index+1);
                      digits[index] = 'k'; dfs(digits,index+1);  
                      digits[index] = 'l'; dfs(digits,index+1);
                      break; 
            case '6': digits[index] = 'm'; dfs(digits,index+1);
                      digits[index] = 'n'; dfs(digits,index+1);  
                      digits[index] = 'o'; dfs(digits,index+1);
                      break;  
            case '7': digits[index] = 'p'; dfs(digits,index+1);
                      digits[index] = 'q'; dfs(digits,index+1);  
                      digits[index] = 'r'; dfs(digits,index+1);
                      digits[index] = 's'; dfs(digits,index+1);
                      break;  
            case '8': digits[index] = 't'; dfs(digits,index+1);
                      digits[index] = 'u'; dfs(digits,index+1);  
                      digits[index] = 'v'; dfs(digits,index+1);
                      break;  
            case '9': digits[index] = 'w'; dfs(digits,index+1);
                      digits[index] = 'x'; dfs(digits,index+1);  
                      digits[index] = 'y'; dfs(digits,index+1);
                      digits[index] = 'z'; dfs(digits,index+1);
                      break;   
        }
    }

No.7(#19)Remove Nth Node From End of List

Idea 1 (two scans): to find the penultimate node, you can use double pointers. The first pointer points to the first node and the second pointer points to the nth node. The two pointers traverse at the same time. When the second pointer reaches the end, the first pointer just points to the penultimate node.

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        if(head->next==NULL) return NULL;
        ListNode*new_head=new ListNode;
        new_head->next=head;//Set a header node
        ListNode* pre=new_head,*p=head,*q=head;
        for(int i=0;i<n;i++) q=q->next;//Move the second pointer to the initial position
        while(q!=NULL){//Make p point to the penultimate pointer
            q=q->next;
            pre=p;
            p=p->next;
        }
        pre->next=p->next;
        head=new_head->next;//If you do not add this sentence and delete the first node, an error will occur
        return head;
    }
};

hash: you can set up an array to store pointers. When traversing the pointers, point the pointers to the corresponding nodes one by one, and then find the corresponding pointers in the array according to the location of the nodes to be deleted.

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        vector<ListNode*>ps;
        ListNode*new_head=new ListNode;
        new_head->next=head;//Add a header node
        ListNode*p=new_head;
        while(p!=NULL){//Give each node a corresponding pointer
            ps.push_back(p);
            p=p->next;
        }
        ps[ps.size()-n-1]->next=ps[ps.size()-n]->next;//Find the corresponding pointer and delete the node
        head=new_head->next;//Ensure that the head will not be empty after deleting the first node
        return head;
    }
};

Idea 3 (stack): you can use the first in first out feature of the stack to put all the nodes on the stack, and then take the last n nodes out of the stack one by one, so that you can find the nodes to be deleted in one traversal.

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        if(head->next==NULL) return NULL;
        stack<ListNode*> st;
        ListNode* p=head,*q;
        while(p!=NULL){
            st.push(p);
            p=p->next;
        }
        if(n==st.size()) return head->next;
        //If you delete the head node, you can directly return to the second node
        for(int i=0;i<n;i++){
            st.pop();
        }
        st.top()->next=st.top()->next->next;
        return head;
    }
};

SKIP No.8(#22)Generate Parentheses

thinking ❌ (how do you know there are no other combinations?): assign - 1 to the left bracket and 1 to the right bracket respectively. When n=2, according to the validity of brackets, the left bracket can be added only when the bracket sequence value sum=0,-1, and the right bracket can be added when sum=-1, - 2. We found that when sum=-1, both left and right parentheses can be added, so we can record the number of left parentheses c_left, when sum=-1 and C_ When left < n, we give priority to adding the left bracket; When sum=-1 and C_ When left = n, we can only add right parentheses. Continue to add parentheses according to the above rules until sum=0 and c_left=n.

skip

No.9(#31)Next Permutation

Idea 1: to find the smallest next larger sort, we should give priority to the low order operation, and give priority to the small number forward. Let the sequence x=0,1,2,..., i-1,i,i+1,... N-1, scan from right to left to find the first two numbers satisfying nums[i-1] < nums [i], then find the smallest number in i,i+1,..., n-1 and satisfying nums[j] > nums[i-1], exchange the positions of nums[i-1] and nums[j], and get a number y1 > x, but y1 is not the next ranking greater than x. i~n-1 should also be arranged into a decreasing sequence, y2 is the next sequence greater than x.

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int i;
        for(i=nums.size()-1;i>0;i--){
            if(nums[i-1]<nums[i]){
                int min=nums[i],min_pos=i;
                for(int j=i+1;j<nums.size();j++){
                    if(min>nums[j]&&nums[j]>nums[i-1]){
                    //Find the number that can minimize the number of exchanged bits nums[i-1] and meet the requirements
                        min=nums[j];
                        min_pos=j;
                    }
                }
                swap(nums[i-1],nums[min_pos]);
                break;
            }
        }
        sort(nums.begin()+i,nums.end());
    }
};

//Optimized version:
class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        if(nums.size()==1) return;
        int pos,flag=0;
        for(int i=nums.size()-2;i>=0;i--){
            if(nums[i]<nums[i+1]){
                pos=i;
                flag=1;
                break;
            }
        }
        if(!flag){
            sort(nums.begin(),nums.end());
            return;
        }
        //flag==0 indicates that num is in reverse order, that is, Num is the maximum value
        swap(nums[pos],nums[find_min(pos+1,nums)]);
        //Swap pos and the smallest element behind pos
        sort(nums.begin()+pos+1,nums.end());
        //Arrange the elements after pos in ascending order
        return;
    }
    int find_min(int start,vector<int>& nums){
        int min_val=nums[start],min_pos=start;
        for(int i=start;i<nums.size();i++){
            if(nums[i]<min_val&&nums[i]>nums[start-1]){
                //You must also satisfy the condition that num [i] is greater than num [POS]
                min_val=nums[i];
                min_pos=i;
            }
        }
        return min_pos;
    }
};

No.10(#33)Search in Rotated Sorted Array

Idea 1 (divide and conquer): cut a knife in the middle of the rotated array to get two sequences. Maybe both sequences are orderly, or one sequence is orderly and the other sequence is disordered, that is, at least one sub sequence is orderly. At this time, we can judge whether the target is in the ordered subsequence. If so, we can directly binary search; If not, it must be in another unordered subsequence. At this time, repeat the above operation until the target is found.

class Solution {
public:
    int search(vector<int>& nums, int target) {
    //Special cases with lengths of 1 and 2 are listed separately
        if(nums.size()==1&&nums[0]!=target) return -1;
        if(nums.size()==2){
            if(nums[0]==target) return 0;
            else if(nums[1]==target) return 1;
            else return -1;
        }
        int left=0,right=nums.size()-1;
        while(left<=right){
            int mid=(left+right)/2;
            if(nums[mid]==target) return mid;
            if(nums[left]<=nums[mid]){//If left sequence is ordered
            //If the num [mid] in the upper and lower sentences is replaced by num [mid-1], overflow will occur
                if(target>=nums[left]&&target<=nums[mid]){//The target number is in the left sequence
                    right=mid-1;
                }
                else{//The target number is in the right sequence
                    left=mid+1;
                }
            }
            else{//If the left sequence is out of order, the right sequence must be in order
                if(target>=nums[mid]&&target<=nums[right]){//The target number is in the right sequence
                    left=mid+1;
                }
                else{//The target number is in the left sequence
                    right=mid-1;
                }
            }
        }
        return -1;
    }
};

//For ordered subsequences, binary search can be used directly
int search(vector<int>& nums, int target) {
        if(nums.size()==1) return nums[0]==target?0:-1;
        int left=0,right=nums.size()-1;
        while(left<=right){
            int mid=(left+right)/2;
            if(nums[mid]==target) return mid;
            if(nums[left]<=nums[mid]){//Left order
                if(target>=nums[left]&&target<=nums[mid]){
                    return binary_search(nums,left,mid,target);
                }
                else{//Not on the left
                    left=mid+1;
                }
            }
            else if(nums[mid+1]<=nums[right]){//Right order
                if(target>=nums[mid+1]&&target<=nums[right]){
                    return binary_search(nums,mid+1,right,target);
                }
                else{
                    right=mid;
                }
            }
        }
        return -1;  
    }

Idea 2 (divide and conquer): Based on idea 1, divide and conquer the array in half. If the left is disordered, the right must be orderly. If the left is orderly, the right can be orderly (the whole array is orderly) or disordered. For ordered subsequences, we can easily know whether there is a target, which can be found by dichotomy. For unordered subsequences, we only need to repeat the above operation of splitting array.

class Solution {
    int tar_pos=-1;//If the search is successful, the corresponding subscript is returned; otherwise, - 1 is returned
    int flag=0;//flag==1 indicates that the search is successful
public:
    int search(vector<int>& nums, int target) {
        if(nums.size()==1) return nums[0]==target?0:-1;
        find_tar(0,nums.size()-1,target,nums);
        return tar_pos;
    }
    void find_tar(int left,int right,int tar,vector<int>& nums){
        if(flag) return;//If the search is successful, it will be returned directly
        if(left==right){
            if(nums[left]==tar){
                flag=1;
                tar_pos=left;
            }
            return;
        }
        int mid=(left+right)/2;
        if(nums[left]<=nums[mid]){
            //If left is ordered
            if(tar>=nums[left]&&tar<=nums[mid]){
                //The element is on the left
                tar_pos=binary_search(nums,left,mid,tar);
                flag=1;
                return;
            }
            //If the element exists, it can only be on the right
            else find_tar(mid+1,right,tar,nums);//Then go to the right
        }
        else{//Since the left is disordered, the right must be orderly
            if(tar>=nums[mid+1]&&tar<=nums[right]){
                tar_pos=binary_search(nums,mid+1,right,tar);
                flag=1;
                return;
            }
            else find_tar(left,mid,tar,nums);
        }
    }
    int binary_search(vector<int>& nums,int left,int right,int tar){
        int mid;
        while(left<=right){
            mid=(left+right)/2;
            if(nums[mid]==tar) return mid;
            else if(nums[mid]>tar) right=mid-1;
            else left=mid+1;
        }
        return -1;
    }//Binary search
};

No.11(#34) Find First and Last Position of Element in Sorted Array

Idea 1: use binary search to find the first number greater than or equal to target, and then find the first number greater than target.

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if(nums.size()==0) return {-1,-1};
        int left=0,right=nums.size()-1;
        int left_pos;
        while(left<=right){
            int mid=(left+right)/2;
            if(left==right){
                if(nums[left]==target){
                    left_pos=left;
                    break;
                }
                return {-1,-1};
            }
            if(nums[mid]>=target){//There may be elements on the left that meet the requirements
                right=mid;
            }
            else if(nums[mid]<target){//Neither mid nor the left is possible. You have to go to the right of mid
                left=mid+1;
            }
        }
        left=0;
        right=nums.size()-1;
        //If this can be done, there must be a right bound
        while(left<=right){
            int mid=(left+right)/2;
            if(left==right){
            //Finally, the right bound must be greater than or equal to target
                if(nums[left]>target) return {left_pos,right-1};
                if(nums[left]==target) return {left_pos,right};
            }
            if(nums[mid]<=target){
                left=mid+1;
            }
            else right=mid;
        }
        return {-1,-1};
    }
};

//Reuse the code and make the following improvements:
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if(nums.size()==0) return {-1,-1};
        int left_pos=function(0,nums.size()-1,nums,target,false);
        int right_pos=function(0,nums.size()-1,nums,target,true);
        if(left_pos>=0&&right_pos<nums.size()&&
        nums[left_pos]==target&&nums[right_pos]==target) return {left_pos,right_pos};
        //Judge whether the found value is correct
        return {-1,-1};
    }
    int function(int left,int right,vector<int>& nums,int tar,bool flag){
        while(left<right){
            //Except for the following two conditional statements, the two search codes are basically the same,
            //Therefore, code reuse should be considered
            int mid=(left+right)/2;
            //flag=true indicates that the first element larger than target is found this time
            //flag=false indicates that the first element greater than or equal to target is searched this time
            if(flag&&nums[mid]<=tar||nums[mid]<tar) 
                left=mid+1;
            else if(flag&&nums[mid]>tar||nums[mid]>=tar) 
                right=mid;
        }
        if(flag&&nums[right]==tar) return right;
        //case1: the first element larger than target does not exist
        else if(flag&&nums[right]>tar) return right-1;
        //case2: the first element larger than target exists
        else return left;
    }
};

No.12(#39)Combination Sum

Idea 1 (backtracking): use the backtracking method to check every possibility. According to the backtracking template of NO.7 (#17), you can write the following code. See the notes for details.

class Solution {
    vector<vector<int>> ans={};
    vector <int> temp;
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        DFS(candidates,target,0);
        //After obtaining ans, it is de duplicated
        for(int i=0;i<ans.size();i++){
            sort(ans[i].begin(),ans[i].end());
        }
        sort(ans.begin(),ans.end());
        ans.erase(unique(ans.begin(),ans.end()),ans.end());
        return ans;
    }
    void DFS(vector<int>& can, int tar,int cur_sum){
        if(cur_sum>tar){//prune
            return;
        }
        else if(cur_sum==tar){
            //Get the sub answers that meet the conditions
            ans.push_back(temp);
            return;
        }
        for(int i=0;i<can.size();i++){
            temp.push_back(can[i]);//Select next
            DFS(can,tar,cur_sum+can[i]);
            temp.pop_back();//Undo this step
        }
    }
};

Idea 2 (backtracking): the de duplication efficiency of idea 1 is too low. Consider whether the de duplication can be completed in the recursive process? Or don't add duplicate answers at all? We can sort the array first, and then each new element added must be greater than or equal to the previous element, so as to ensure that the sub answers added to ans are not repeated.

class Solution {
    vector<vector<int>> ans={};
    vector <int> temp;
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        sort(candidates.begin(),candidates.end());
        DFS(candidates,target,0,0);
        return ans;
    }
    void DFS(vector<int>& can, int tar,int cur_sum,int pos){
        if(cur_sum>tar){//prune
            return;
        }
        else if(cur_sum==tar){
            //Get the sub answers that meet the conditions
            ans.push_back(temp);
            return;
        }
        for(int i=pos;i<can.size();i++){
    	//Use pos to record the subscript of the current element, so that the next element can traverse from this subscript
            temp.push_back(can[i]);//Select next
            DFS(can,tar,cur_sum+can[i],i);
            temp.pop_back();//Undo this step
        }
    }
};

No.13(#46)Permutations

Idea 1 (backtracking): use the backtracking method to enumerate all possible elements. Pay attention not to add duplicate elements in the recursive process. You can use the hash table to avoid adding duplicate elements. The following code can also be written according to the backtracking module of No.6 (#17).

class Solution {
    vector<vector<int>> ans;
    vector<int> temp;
    bool flag[21]={false};//hash
    //0~10 for -1~-10
    //1~20 for 0~10
public:
    vector<vector<int>> permute(vector<int>& nums) {
        DFS(nums);
        return ans;
    }
    void DFS(vector<int>& nums){
        if(temp.size()==nums.size()){
            ans.push_back(temp);
            return;
        }
        for(int i=0;i<nums.size();i++){
            if(!flag[nums[i]+10]){
                //flag is false, indicating that the element is not added
                flag[nums[i]+10]=true;//Select the next element and modify the flag
                temp.push_back(nums[i]);
                DFS(nums);
                temp.pop_back();//Undo this selection and modify the flag
                flag[nums[i]+10]=false;
            }
        }
    }
};

No.14(#48)Rotate Image

Idea 1: rotating the matrix clockwise by 90 ° is equivalent to transposing the matrix first and then making left-right symmetry.

class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n=matrix.size();
        get_matrixT(matrix,n);
        get_symmetry(matrix,n);
        return;
    }
    void get_matrixT(vector<vector<int>>& matrix,int n){
        for(int i=0;i<n;i++){
            for(int j=i+1;j<n;j++){
                if(i==j) continue;
                swap(matrix[i][j],matrix[j][i]);
            }
        }
        return;
    }
    void get_symmetry(vector<vector<int>>& matrix,int n){
        for(int i=0;i<n;i++){
            for(int j=0;j<n/2;j++){
                swap(matrix[i][j],matrix[i][n-j-1]);
            }
        }
        return;
    }
};

Idea 2: divide each layer of the matrix into two steps: ① rotate the main and auxiliary diagonal elements; ② Rotate the edges divided by the four diagonals, as shown in the following figure,

class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n=matrix.size();
        for(int i=0;i<n/2;i++){//Rotate four corners layer by layer
            rotate1(matrix,n,i);
        }
        for(int i=0;i<n/2;i++){//Rotate four edges layer by layer (remove four corners)
            rotate2(matrix,n,i);
        }
        return;
    }
    void rotate1(vector<vector<int>>& matrix,int n,int i){
        //Rotation angle
        int temp=matrix[i][n-1-i];
        matrix[i][n-1-i]=matrix[i][i];
        matrix[i][i]=matrix[n-1-i][i];
        matrix[n-1-i][i]=matrix[n-1-i][n-1-i];
        matrix[n-1-i][n-1-i]=temp;
    }
    void rotate2(vector<vector<int>>& matrix,int n,int i){
        //Rotating edge
        //i can be regarded as the coordinate of the diagonal, and pos is the next element under the diagonal,
        //That is, the coordinates of the first element of the edge to be rotated
        vector<int> temp;
        int pos=i+1;
        for(int x=pos;x<n-pos;x++){//Temporary right
            temp.push_back(matrix[x][n-pos]);
        }
        for(int x=pos;x<n-pos;x++){//Top cover to right
            matrix[x][n-pos]=matrix[pos-1][x];
        }
        for(int x=pos;x<n-pos;x++){//Left overlay
            matrix[pos-1][x]=matrix[n-x-1][pos-1];
        }
        for(int x=pos;x<n-pos;x++){//Lower cover left
            matrix[n-x-1][pos-1]=matrix[n-pos][n-x-1];
        }
        for(int x=n-pos-1;x>=pos;x--){//Under temp cover
            int t=temp[temp.size()-1];
            temp.pop_back();
            matrix[n-pos][n-x-1]=t;
        }
        return;
    }
};

No.15(#49)Group Anagrams

Hash: take the increasing order of each word as the key of hash, and the word of hash saves the group number (subscript) of letter ectopic words in ans.

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        if(strs.size()==0) return {};
        vector<vector<string>> ans={};
        map<string,int> hash;
        int count=0;
        for(int i=0;i<strs.size();i++){
            string temp=strs[i];
            sort(strs[i].begin(),strs[i].end());
            //Sort strings and find
            if(hash.find(strs[i])==hash.end()){
                //If there is no such string in the hash, you need to add an item
                //And add a new group in ans
                vector<string> tempV;
                tempV.push_back(temp);
                hash[strs[i]]=count;
                ans.push_back(tempV);
                count++;
            }
            else{
                //If a string with the same increasing order can be found in the hash, that is, an alphabetic word can be found
                //Then put the word in the corresponding position of ans
                ans[hash[strs[i]]].push_back(temp);
            }
            strs[i]=temp;//Restore modified strs[i]
        }
        return ans;
    }
};

Idea 2: use some combination of numbers to represent a word, considering the sum of squares and other representations, but there will always be repetition. Considering the characteristics of prime numbers, we can use prime numbers to represent letters and multiply prime numbers to uniquely represent a word.

class Solution {
    int prime[26]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,
    71,73,79,83,89,97,101};
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        if(strs.size()==0) return {};
        map<double,int> hash;
        vector<vector<string>> ans;
        int count=0;
        for(int i=0;i<strs.size();i++){
            double sum=1;
            for(int j=0;j<strs[i].size();j++){
                sum*=prime[strs[i][j]-'a'];
            }
            if(hash.find(sum)==hash.end()){
                vector<string> tempV;
                tempV.push_back(strs[i]);
                hash[sum]=count++;
                ans.push_back(tempV);
            }
            else{
                ans[hash[sum]].push_back(strs[i]);
            }
        }
        return ans;
    }
};

No.16(#55)Jump Game

Idea 1 (backtracking): backtracking + greed, timeout!

class Solution {
    bool flag=false;
public:
    bool canJump(vector<int>& nums) {
        if(nums.size()==1) return true;
        DFS(nums,0);
        return flag;
    }
    void DFS(vector<int>& nums,int start){
        if(start==nums.size()-1){
            flag=true;
            return;
        }
        if(start>nums.size()-1) return;
        for(int i=nums[start];i>=1&&!flag;i--){
            DFS(nums,start+i);
        }
        return;
    }
};

Idea 2: regard the element value as the number of steps that can be taken, such as [4,4,2,1,0]. When i=0, the number of steps that can be taken is 4; When i=1, step is reduced by one step to 3. Compared with the array element value at this time, the array element value is larger, then step is updated, that is, step=4; When i=2, step=3, which is smaller than the current element value, so it is not updated, and so on.

class Solution {
public:
    bool canJump(vector<int>& nums) {
        if(nums.size()==1) return true; 
        int step=nums[0];
        for(int i=0;i<nums.size();i++){
            if(step==0&&nums[i]==0) break;
            if(nums[i]>step) step=nums[i];
            if(i+step>=nums.size()-1) return true;
            step--;
        }
        return false;
    }
};

Idea 3 (greed): set value max_reach records the farthest reachable position when max_reach > = n-1 indicates reachable; When i moves to max_reach without updating max_reach, that is, the array element scanned by i is 0, which means it is unreachable.

class Solution {
public:
    bool canJump(vector<int>& nums) {
        if(nums.size()==1) return true; 
        int max_reach=nums[0];
        for(int i=1;i<=max_reach;i++){
            max_reach=max(max_reach,i+nums[i]);
            if(max_reach>=nums.size()-1) return true;
        }
        return false;
    }
};

No.17(#56)Merge Intervals

Idea 1 (double pointer): after sorting intetvs, the groups are arranged in order, and there are only the following three cases in the front and back adjacent groups:
Set the first pointer to cur for the current packet and the second pointer to temp for the scanned packet;
① Cur [1] < temp [0], such as (1,3), (4,5), the group is merged in disorder;
② Temp [1] < = cur [1], such as (1,3), (2,3). At this time, temp is included by cur, and change temp to (- 1, - 1) to indicate that temp has been covered by cur;
③ Temp [1] > cur [1] & & Temp [0] < = cur [1], such as (1, 3), (2, 5). At this time, temp and cur intersect. The merged result is (cur[0],temp[1]). Changing temp to (- 1, - 1) indicates that temp and cur have been merged successfully.
Repeat the above operations for intervals to get a sequence composed of the merged groups and (- 1, - 1). We only need to pick out the merged groups.

class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        vector<vector<int>> ans;
        sort(intervals.begin(),intervals.end());
        for(int i=0;i<intervals.size();i++){
            //No longer process (- 1, - 1)
            if(intervals[i][0]==-1) continue;
            for(int j=i+1;j<intervals.size();j++){
                //Three different situations
                if(intervals[j][1]<=intervals[i][1]){
                    //-1 is not within the range of element values. It is used to represent the processed groups
                    intervals[j][0]=intervals[j][1]=-1;
                }
                else if(intervals[j][1]>intervals[i][1]&&intervals[j][0]<=intervals[i][1]){
                    intervals[i][1]=intervals[j][1];
                    intervals[j][0]=intervals[j][1]=-1;
                }
                else if(intervals[j][0]>intervals[i][1]){
                    continue;
                }
            }
        }
        //Pick out (- 1, - 1)
        for(int i=0;i<intervals.size();i++){
            if(intervals[i][0]==-1) continue;
            ans.push_back(intervals[i]);
        }
        return ans;
    }
};

DP No.18(#62) Unique Paths

Idea 1 (recursion): timeout!!

class Solution {
    int count_path=0;
public:
    int uniquePaths(int m, int n) {
        DFS(0,0,m-1,n-1);
        return count_path;
    }
    void DFS(int cur_x,int cur_y,int fin_x,int fin_y){
        if(cur_x==fin_x&&cur_y==fin_y){
            //Reach the end
            count_path++;
            return;
        }
        //Don't go if you cross the border
        if(cur_x+1<=fin_x) DFS(cur_x+1,cur_y,fin_x,fin_y);
        if(cur_y+1<=fin_y) DFS(cur_x,cur_y+1,fin_x,fin_y);
        return;
    }
};

DP No.19(#64)Minimum Path Sum

No.20(#75)Sort Colors

Train of thought 1 (double pointer): twice. The first pass divides the array into two halves bounded by zero and non-zero. The second pass starts with the first non-zero element and is bounded by one and two.

class Solution {
public:
    void sortColors(vector<int>& nums) {
        int left=0,right=nums.size()-1;
        //Divide zero and non-zero
        partition(nums,1,2,0,left,right);
        //Move left to the position of the first non-zero element
        while(left<nums.size()&&nums[left]==0) left++;
        //Division 1 and 2
        partition(nums,2,2,1,left,right);
        return;
    }
    void partition(vector<int>& nums,int x,int y,int z,int left,int right){
        while(left<right){
            if((nums[left]==x||nums[left]==y)&&nums[right]==z){
                swap(nums[left],nums[right]);
                left++;
                right--;
            }
            else if(nums[left]==z&&(nums[right]==x||nums[right]==y)){
                left++;
                right--;
            }
            else if(nums[left]==z&&nums[right]==z){
                left++;
            }
            else if((nums[left]==x||nums[left]==y)&&(nums[right]==x||nums[right]==y)){
                right--;
            }
        }
        return;
    }
};

No.21(#78)Subsets

Idea 1: first add an empty set to the two-dimensional array ans, and then start traversing the num array. For each element traversed, add the current element to all sets in ANS. The specific operation is shown in the figure below.
The yellow is the original array, and the red is the newly added element in this traversal.

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int>> ans;
        vector<int> temp;
        ans.push_back({});//If ans[0] = {} is used for assignment, null pointer error will occur
        for(int i=0;i<nums.size();i++){
            int last_ans_size=ans.size();
            //The second for cannot use ans.size() as an indicator to exit the loop, because it changes dynamically, so its length must be recorded in advance
            for(int j=0;j<last_ans_size;j++){
                temp=ans[j];//Save the original collection
                temp.push_back(nums[i]);//Expand the original set
                ans.push_back(temp);//Adds a new collection to the ans array
            }
        }
        return ans;
    }
};

Idea 2 (backtracking): obviously, the problem can be solved by distribution, that is, continue to add new elements to the solution in the previous step to get another solution, then the solution of this idea can be done by backtracking method.
First, you can apply the template of backtracking method as follows:

void DFS(){
        if(End condition){
            //Recursion to the next position of the last element returns
            return;
        }
        //Enumeration one by one by backtracking method
        v.push_back();//Join selection
        DFS();//Continue recursion
        v.pop_back();//Undo selection
    }

We further improve this template:
① Recursive parameters. Original array nums; Save the sequence v obtained in the middle; Pointer to the currently accessed element index.
② Return condition. Obviously, when index recurses to the last element, it has to be returned. Therefore, the recursive condition is index = = num.size (), and we don't need to do additional operations at the end of recursion, so we can return directly.
③ Recursive operation. Our recursive operation can be seen as adding new elements to all groups one by one on the basis of the previous solution, so as to form a new solution. As shown in the figure below, when the index of this recursion points to 1, just add 1 to the Group [] in ans to get [1], which is all the solutions obtained by this recursion; When our recursive index points to 2, as long as we add 2 to the grouping [], [1] of ANS one by one, we get [2], [1,2], which is the solution obtained by this recursion. When we add the solution of this recursion to ans, we get [], [1], [2], [1,2], which is all the solutions recursive to 2; The same goes for recursion to 3.

According to this idea, we have to use a loop to traverse all array elements. At this point, we can update the template according to the existing information, as follows:

void DFS(vector<int>& nums,int index,vector<int>& v){
        if(index==nums.size()){
            //Recursion to the next position of the last element returns
            return;
        }
        //Enumeration one by one by backtracking method
        for(int i=index;i<nums.size();i++){
        //Note that i must start with index, otherwise the element will be traversed repeatedly
            v.push_back(nums[i]);//Join selection
            DFS(nums,i+1,v);//Continue recursion
            v.pop_back();//Undo selection
        }
    }

Of course, we still lack the most important step - adding the current solution to ANS. The operation is very simple, that is, ans.push_back(v). But when should this statement be executed? We can first define the approximate position, that is, after adding the selection, before canceling the selection, and before traversing the next element, we have the following four choices.
First, feasible!
The second is basically equivalent to the first and feasible!
Third, each solution will be added to ans repeatedly, which is not feasible;
Fourth, if recursion is performed first, it is equivalent to recursion to the end condition without doing anything. It is not feasible.

So far, we can write the code completely, as follows:

class Solution {
    vector<vector<int>> ans={{}};
    //If the first method is adopted, the initial value of ANS is different, ans = {}
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<int> v={};
        DFS(nums,0,v);
        return ans;
    }
    void DFS(vector<int>& nums,int index,vector<int>& v){
        //Then go back to v.push_ After back (Num [i]),
        //The current sequence should be added directly to ans
        //If the first method is adopted, ans.push_back, put it here
        if(index==nums.size()){
            //Recursion to the next position of the last element returns
            return;
        }
        //Enumeration one by one by backtracking method
        for(int i=index;i<nums.size();i++){
            v.push_back(nums[i]);//Join selection
            ans.push_back(v);//The second method is adopted
            DFS(nums,i+1,v);//Continue recursion
            v.pop_back();//Undo selection
        }
    }
};

No.22(#79)Word Search

Tags: Algorithm leetcode linked list

Posted on Sat, 16 Oct 2021 02:22:36 -0400 by jimmyt1988