九章算法第六课,链表和数组

450. K组翻转链表
把问题分步处理:1.考虑一个函数实现反转k个数,只要外面加个循环就能解决问题 2.反转k个数分三步A.记录首尾 B.翻转 C.首尾连接

class Solution {
public:
    ListNode * reverseKGroup(ListNode * head, int k) {
        if (k==0 || head==NULL) return head;
        
        ListNode* DummyNode=new ListNode(0);
        DummyNode->next=head;
        
        head=DummyNode;
        while (head->next!=NULL) {//这里的head是下一组待反转节点的前一个节点
            //需要明白反转第n,n+1,..n+k个节点,动的节点还包括n-1,因为n-1要链接n+k
            //构建一个helper函数,希望其能反转head->next接下来的k个节点,并返回下一组节点的头指针。
            head=helper(head,k);
            if (head==NULL) break;//处理不够K个数返回NULL的情况
        }
        return DummyNode->next;
    }
    ListNode* helper(ListNode * head, int k) {
        //定点,确认这一组k个数的头、尾方便连接
        ListNode* n1=head->next;
        ListNode* nk=head;
        for (int i=0;i<k;i++){
            nk=nk->next;
            if (nk==NULL) return NULL;
        }
        ListNode* nkp=nk->next;

        ListNode* prev=NULL;
        ListNode* cur=n1;
        while(cur!=nkp) {
            ListNode* tmp=cur->next;
            cur->next=prev;
            prev=cur;
            cur=tmp;
        }
        //连接
        n1->next=nkp;
        head->next=nk;
        
        return n1;
    }
};

96. 链表划分
使用两个链表头,将一个链表拆成两个。

class Solution {
public:
    ListNode * partition(ListNode * head, int x) {
        if (head==NULL) return head;
        
        ListNode* low=new ListNode(0);
        ListNode* high=new ListNode(0);
        ListNode* dummyl=low;
        ListNode* dummyh=high;
        
        ListNode* cur=head;
        while (cur!=NULL) {
            if (cur->val<x) {
                low->next=cur;
                low=low->next;
            }
            else {
                high->next=cur;
                high=high->next;
            }
            cur=cur->next;
        }
        high->next=NULL;//记得加这一句!!high后面要有结尾才不会报错!
        //处理low里面啥也没有的情况
        if (dummyl->next==NULL) return dummyh->next;
        else {
            low->next=dummyh->next;
            return dummyl->next;   
        }
    }
};

165. 合并两个排序链表

class Solution {
public:
    ListNode * mergeTwoLists(ListNode * l1, ListNode * l2) {
        // write your code here
        if (l1==NULL) return l2;
        if (l2==NULL) return l1;
        
        ListNode* dummy=new ListNode(0);
        ListNode* cur=dummy;
        while (l1!=NULL &&l2!=NULL) {
            if (l1->val<=l2->val) {
                cur->next=l1;
                l1=l1->next;
            }
            else {
                cur->next=l2;
                l2=l2->next;
            }
            cur=cur->next;
        }
        if (l1==NULL && l2!=NULL) cur->next=l2;
        if (l2==NULL && l1!=NULL) cur->next=l1;
        
        return dummy->next;
    }
};

36. 翻转链表 II
技巧:从dummy开始走几步,就到第几个节点。

class Solution {
public:
    ListNode * reverseBetween(ListNode * head, int m, int n) {
        if (head==NULL) return NULL;
        ListNode* dummy=new ListNode(0);
        dummy->next=head;
        
        ListNode* cur=dummy;
        for (int i=0;i<m-1;i++) 
            cur=cur->next;
        ListNode* nodemp=cur;
        ListNode* nodem=cur->next;
        
        cur=cur->next;
        ListNode * prev=NULL;
        int k=m;
        while (k<=n) {
            ListNode* tmp=cur->next;
            cur->next=prev;
            prev=cur;
            cur=tmp;
            k++;
        }
        nodemp->next=prev;
        nodem->next=cur;
        
        return dummy->next;
    }
};

511. 交换链表当中两个节点
注意两个点挨着的情况要特殊处理

class Solution {
public:
    ListNode * swapNodes(ListNode * head, int v1, int v2) {
        if (head==NULL) return NULL;
        
        ListNode* dummy=new ListNode(0);
        dummy->next=head;
        ListNode* cur=head;
        ListNode* prev=dummy;
        
        ListNode *prev1=NULL,*n1=NULL,*aft1=NULL;
        ListNode *prev2=NULL,*n2=NULL,*aft2=NULL;
        int k=0;
        while (cur!=NULL) {
            if (cur->val==v1) 
            {
                prev1=prev;
                n1=cur;
                aft1=n1->next;
                k++;
            }
            if (cur->val==v2) 
            {
                prev2=prev;
                n2=cur;
                aft2=n2->next;
                k++;
            }
            prev=cur;
            cur=cur->next;
        }
        if (k==2) {
            if (n1->next==n2) {
                prev1->next=n2;
                n2->next=n1;
                n1->next=aft2;
            }
            else if (n2->next==n1) {
                prev2->next=n1;
                n1->next=n2;
                n2->next=aft1;
            }
            else {
             prev2->next=n1;
             n1->next=aft2;
             prev1->next=n2;
             n2->next=aft1;   
            }
        }
        return dummy->next;
    }
};

99. 重排链表
注意掌握链表找中点的方法。

class Solution {
public:
    void reorderList(ListNode * head) {
        if (head==NULL || head->next==NULL) return;
        
        ListNode* mid=findMid(head);//找到中间节点;
        ListNode* head2=reverse(mid->next);//把后面的一半翻转;        
        mid->next=NULL;//彻底分成两个链表
        head=merge(head,head2);//前一半和后一半融合;
    }
    ListNode* findMid(ListNode* head) {
        ListNode* slow=head;
        ListNode* fast=head->next;
        //慢的一次走一步,快的一次走两步,快的走到头,慢的正好到中间
        //注意条件,考虑到奇偶,使得中间节点是靠前的那个
        while (fast!=NULL && fast->next!=NULL) {
            slow=slow->next;
            fast=fast->next->next;
        }
        return slow;
    }
    ListNode* reverse(ListNode* head) {
        ListNode* prev=NULL;
        ListNode* cur=head;
        while (cur!=NULL) {
            ListNode* tmp=cur->next;
            cur->next=prev;
            prev=cur;
            cur=tmp;
        }
        return prev;
    }
    ListNode* merge(ListNode *n1,ListNode*n2) {
        ListNode* dummy=new ListNode(0);
        ListNode* cur=dummy;
        
        int flag=0;
        while (n1!=NULL && n2!=NULL){
            if (flag%2==0) {
                cur->next=n1;
                n1=n1->next;
            }
            else {
                cur->next=n2;
                n2=n2->next;
            }
            cur=cur->next;
            flag++;
        }
        if (n1!=NULL) cur->next=n1;
        if (n2!=NULL) cur->next=n2;
        return dummy->next;
    }
};

170. 旋转链表
考虑使三步旋转法做这道题,只要写一个旋转接口调用三次就可以。注意k有可能大于n。

class Solution {
public:
    ListNode * rotateRight(ListNode * head, int k) {
        int n=getLength(head);
        if (n==0) return NULL;
        if (k>=n) k=k%n;
        if (k==0||n==1) return head;
        
        head=reverse(1,n-k,head);
        head=reverse(n-k+1,n,head);
        head=reverse(1,n,head);
        return head;
    }
    int getLength(ListNode* head) {
        int i=0;
        while (head!=NULL) {
            head=head->next;
            i++;
        }
        return i;
    }
    ListNode* reverse(int start, int end, ListNode* head) {
        if (start==end) return head;
        ListNode* dummy=new ListNode(0);
        dummy->next=head;
        
        ListNode* cur=dummy;
        for (int i=0;i<start-1;i++) cur=cur->next;//找到起始位置的前一个位置
        ListNode* start_prev=cur;
        cur=cur->next;
        ListNode* startn=cur;
        ListNode* prev=NULL;
        
        int k=start;
        while (k<=end) {
            ListNode* tmp=cur->next;
            cur->next=prev;
            prev=cur;
            cur=tmp;
            k++;
        }

        startn->next=cur;
        start_prev->next=prev;
     
        return dummy->next;   
    }
};

另一个方法:虽然链表移动了,但是本质上方向并没有改变,若看作一个环链,则只是改变了首尾的位置。可以利用这个特点找到新的头尾,重新连接即可。

class Solution {
public:
    ListNode * rotateRight(ListNode * head, int k) {
        int n=getLength(head);
        if (n==0) return NULL;
        if (k>=n) k=k%n;
        if (k==0||n==1) return head;
        
        ListNode* dummy=new ListNode(0);
        dummy->next=head;

        ListNode* tail=dummy;
        ListNode* old_tail=dummy;
        
        for (int i=0;i<k;i++)
            old_tail=old_tail->next;
        while (old_tail->next!=NULL) {
            tail=tail->next;
            old_tail=old_tail->next;
        }
        
        old_tail->next=dummy->next;
        dummy->next=tail->next;
        tail->next=NULL;
        
        return dummy->next;
    }
    int getLength(ListNode* head) {
        int i=0;
        while (head!=NULL) {
            head=head->next;
            i++;
        }
        return i;
    }
};

105. 复制带随机指针的链表
最常规的思路和拷贝图一样,第一步找到所有的node,第二步建立映射,第三步复制关系。

class Solution {
public:
    RandomListNode *copyRandomList(RandomListNode *head) {
        if (head==NULL) return NULL;
        
        map<RandomListNode*,RandomListNode*> f;
        set<RandomListNode*> s;
        RandomListNode *dummy=new RandomListNode(0);
        dummy->next=head;
        while (head!=NULL)
        {
            if (f.count(head)==0) {
                RandomListNode *tmp=new RandomListNode(head->label);
                s.insert(head);
                f[head]=tmp;
            }
            head=head->next;
        }
        for (auto node:s) {
            f[node]->next=f[node->next];
            f[node]->random=f[node->random];
        }
        return f[dummy->next];
    }
};

如果不使用额外空间,则考虑利用next指针,形成一个1->1'->2->2'.....的链表,再将其拆分。

class Solution {
public:
    RandomListNode *copyRandomList(RandomListNode *head) {
        if (head==NULL) return NULL;
        RandomListNode *dummy=new RandomListNode(0);
        RandomListNode* cur=head;
        //复制节点
        while (cur!=NULL){
            RandomListNode* copy_cur=new RandomListNode(cur->label);

            copy_cur->next=cur->next;
            cur->next=copy_cur;
            
            cur=cur->next->next;
        }
        //复制random关系
        cur=head;
        while (cur!=NULL) {
            if (cur->random==NULL) cur->next->random=NULL;
            else cur->next->random=cur->random->next;
            cur=cur->next->next;
        }
        //分开
        cur=head->next;
        dummy->next=cur;
        while(cur->next!=NULL) {
            cur->next=cur->next->next;
            cur=cur->next;
        }
        return dummy->next;
    }
};

102. 带环链表
牛逼算法,一个快结点,一个慢结点,要是快结点再次碰到慢接点而不是走出链表则说明有环。

class Solution {
public:
    bool hasCycle(ListNode * head) {
        if (head==NULL || head->next==NULL) return false;
        
        ListNode* slow=head;
        ListNode* fast=head;
        while (fast!=NULL&&fast->next!=NULL) {
            slow=slow->next;
            fast=fast->next->next;
            if (slow==fast) return true;
        }
        return false;
    }
};


380. 两个链表的交叉
接上个算法,将A的末尾和B的头连起来,如果有环则说明有交叉点。
若需要求交叉点在那,则需要证明:假如单链的长度是l1,相遇是慢点在环上走了x,环的长度是l2,则2(l1+x)=l1+l2+x,即l2-x=l1,也就是说一个点从当前位置开始,一个点从A头上开始走,相遇的点就是交叉点。

    ListNode * getIntersectionNode(ListNode * headA, ListNode * headB) {
        if (headA==NULL || headB==NULL) return NULL;
       
        ListNode* cur=headA;
        while (cur->next!=NULL)
           cur=cur->next;
        cur->next=headB;
        
        ListNode* slow=headA;
        ListNode* fast=headA;
        while (fast!=NULL || fast->next!=NULL) {
            slow=slow->next;
            fast=fast->next->next;
            if (slow==fast)
            {
                ListNode* count=headA;
                while (count!=fast)
                {
                    fast=fast->next;
                    count=count->next;
                }
                return fast;
            }
        }
        cur->next=NULL;
        return NULL;
    }
};

98. 链表排序
数组排序中符合时间复杂度O(NlogN),原地排序的只有快速排序,但是由于链表的特殊性,归并排序也可以原地排序。

class Solution {
public:
    ListNode * sortList(ListNode * head) {
        //注意出口条件,只剩一个数了就返回
        if (head==NULL || head->next==NULL) return head;
        
        ListNode* mid=findMid(head);
        ListNode* right=sortList(mid->next);
        mid->next=NULL;
        ListNode* left=sortList(head);
        
        return merge(left,right);
    }
    ListNode* findMid(ListNode* head) {
        ListNode *slow=head;
        ListNode *fast=head->next;
        while (fast!=NULL && fast->next!=NULL) {
            slow=slow->next;
            fast=fast->next->next;
        }
        return slow;
    }
    ListNode * merge(ListNode *n1,ListNode *n2) {
        ListNode* dummy=new ListNode(0);
        ListNode* cur=dummy;
        while (n1!=NULL && n2!=NULL) {
            if (n1->val<=n2->val) {
                cur->next=n1;
                n1=n1->next;
            }else {
                cur->next=n2;
                n2=n2->next;
            }
            cur=cur->next;
        }
        if (n1!=NULL) cur->next=n1;
        if (n2!=NULL) cur->next=n2;
        return dummy->next;
    }
};

快排利用链表的性质,拉了三个链表,分别是小于、等于、大于标杆值的链表,然后拼在一起即可。并不是利用交换把小的往前放大的往后放。牛逼!注意注释中的小细节。

class Solution {
public:
    ListNode * sortList(ListNode * head) {
        //注意出口条件,只剩一个数了就返回
        if (head==NULL || head->next==NULL) return head;
        
        ListNode* mid=findMid(head);
        int pivot=mid->val;
        
        ListNode* leftDummy=new ListNode(0);
        ListNode* rightDummy=new ListNode(0);
        ListNode* midDummy=new ListNode(0);
        
        ListNode* leftcur=leftDummy;
        ListNode* rightcur=rightDummy;
        ListNode* midcur=midDummy;
        ListNode* cur=head;
        
        while (cur!=NULL) {
            if (cur->val<pivot) {
                leftcur->next=cur;
                leftcur=leftcur->next;
            }else if (cur->val>pivot) {
                rightcur->next=cur;
                rightcur=rightcur->next;
            }else {
                midcur->next=cur;
                midcur=midcur->next;
            }
            cur=cur->next;
        }
        //加尾巴!!!重点!!!
        leftcur->next=NULL;
        rightcur->next=NULL;
        midcur->next=NULL;
        
        ListNode* left=sortList(leftDummy->next);
        ListNode* right=sortList(rightDummy->next);
        
        return concat(left,midDummy->next,right);
    }
    ListNode* findMid(ListNode* head) {
        ListNode *slow=head;
        ListNode *fast=head->next;
        while (fast!=NULL && fast->next!=NULL) {
            slow=slow->next;
            fast=fast->next->next;
        }
        return slow;
    }
    ListNode* concat(ListNode* left, ListNode* mid,ListNode* right) {
        ListNode* dummy=new ListNode(0);
        //新建一个tail不停的找结尾并连接,不可以用left直接找,值会改变!!
        ListNode* tail=dummy;
        
        tail->next=left;
        tail=getTail(tail);
        tail->next=mid;
        tail=getTail(tail);
        tail->next=right;
        
        return dummy->next;
    }
    ListNode* getTail(ListNode* head) {
        if (head==NULL) return NULL;
        while (head->next!=NULL) head=head->next;
        return head;
    }
};

106. 有序链表转换为二分查找树
本质还是分治算法,但是注意mid的左子树链末尾要手动添加NULL,而且要判断左子树链表是不是空。

class Solution {
public:
    TreeNode * sortedListToBST(ListNode * head) {
        if (head==NULL) return NULL;
        if (head->next==NULL) {
             TreeNode* root=new TreeNode(head->val);
             return root;
        }
        
        ListNode* mid=findMid(head);
        TreeNode* root=new TreeNode(mid->val);
        
        root->right=sortedListToBST(mid->next);
        
        if (mid==head) root->left=NULL;
        else {
            ListNode* cur=new ListNode(0);
            cur->next=head;
            while (cur->next!=mid) cur=cur->next;
            cur->next=NULL;
            root->left=sortedListToBST(head);   
        }
        return root;
    }
    ListNode* findMid (ListNode* head) {
        ListNode* slow=head;
        ListNode* fast=head->next;
        while (fast!=NULL && fast->next!=NULL) {
            slow=slow->next;
            fast=fast->next->next;
        }
        return slow;
    }
};

378. 将二叉查找树转换成双链表
注意处理左右子树为空的情况,尤其是左子树为空,应该返回mid指针。

class Solution {
public:
    DoublyListNode * bstToDoublyList(TreeNode * root) {
        if (root==NULL) return NULL;
        
        DoublyListNode* mid=new DoublyListNode(root->val);
        if (root->left==NULL && root->right==NULL) {
            return mid;
        }
        
        DoublyListNode* leftlist=bstToDoublyList(root->left);
        if (leftlist!=NULL) {
             DoublyListNode* cur=leftlist;
             while (cur->next !=NULL) cur=cur->next;
             mid->prev=cur;
             cur->next=mid;    
        }
       
        DoublyListNode* rightlist=bstToDoublyList(root->right);
        if (rightlist!=NULL){
            mid->next=rightlist;
            rightlist->prev=mid;   
        }
        
        return leftlist==NULL?mid:leftlist;
    }
};


41. 最大子数组
使用前缀和的思想,区间和sum(a,b)是两个前缀和subsum[b]和subsum[a]的差,要想得到最大区间和,要求subsum[a]最小,subsum[b]最大。用三个变量分别记录当前前缀和、最小前缀和以及最大结果即可。

class Solution {
public:
    int maxSubArray(vector<int> &nums) {
        if (nums.size()==1) return nums[0];
        
        int subsum=0;
        int minsubsum=0;
        int res=INT_MIN;
        for (int i=0;i<nums.size();i++)
           {
               subsum=subsum+nums[i];
               res=max(res,subsum-minsubsum);
               minsubsum=min(minsubsum,subsum);
           }
        return res;
    }
};

138. 子数组之和
还是利用子数组,如果两个前缀和子数组相等,说明这两个之间的数加起来是0。用map判断是不是有重复的前缀和了,不需要全部记录下来再用两重for循环。注意处理第1个数的前缀数组(0)。

class Solution {
public:
    vector<int> subarraySum(vector<int> &nums) {
        if (nums.size()==0) return vector<int>();
        
        vector<int> res;
        unordered_map<int,int> subSum;
        int sum=0;
        subSum[0]=-1;
        for (int i=0;i<nums.size();i++) {
            sum+=nums[i];
            if (subSum.find(sum)==subSum.end()) {
                subSum[sum]=i;
            }else{
                res.push_back(subSum[sum]+1);
                res.push_back(i);
                return res;
            }
        }
    }
};

139. 最接近零的子数组和
思路和上面的一样,不同的是要记录每个前缀子数组的和、位置,然后排序,判断相邻和的差哪个最小。

class Solution {
public:
    vector<int> subarraySumClosest(vector<int> &nums) {
        if (nums.size()==0) return vector<int>();
        
        vector<int> res(2,0);
        vector<pair<int,int>> subSum;
        
        subSum.push_back(make_pair(0,-1));
        int sum=0;
        //计算前缀子数组和
        for (int i=0;i<nums.size();i++) {
            sum+=nums[i];
            subSum.push_back(make_pair(sum,i));
        }
        sort(subSum.begin(),subSum.end());//重新排序
        
        //找最小差值
        int minSum=INT_MAX;
        int begin=0;
        int end=0;
        for (int i=0;i<subSum.size()-1;i++) {
            if (subSum[i+1].first-subSum[i].first<minSum) {
                minSum=subSum[i+1].first-subSum[i].first;
                begin=min(subSum[i+1].second,subSum[i].second)+1;
                end=max(subSum[i+1].second,subSum[i].second);
            }
            if (minSum==0) break;
        }
        res[0]=begin;
        res[1]=end;
        return res;
    }
};

64. 合并排序数组
这个题的特别之处在于A的大小为m+n,要把B合并进A。由于A的后半部分是空的,所以采用从大到小、从前往后的排法。

class Solution {
public:
    void mergeSortedArray(int A[], int m, int B[], int n) {
        int index=m+n-1;
        int ia=m-1;
        int ib=n-1;
        
        while (ia>=0 && ib>=0) {
            if (A[ia]>=B[ib]) {
                A[index--]=A[ia--];
            }else {
                A[index--]=B[ib--];
            }
        }
        while (ib>=0) {A[index--]=B[ib--];}
    }
};

547. 两数组的交集
三种方法:A.HashMap B.排序&归并 C.排序&二分查找。
注意结果数组也要去重:

 auto last = unique(intersect.begin(), intersect.end());
 intersect.erase(last, intersect.end());

65. 两个排序数组的中位数
可以进一步简化为寻找两个排序数组的第K大的数。
问题的对数时间复杂度算法为:每次排除A,B数组较小的K/2个数(可以用数学算法证明,若A[K/2-1]<B[K/2-1],那么A的前K/2个数中一定不含邮第K大的数)。
具体实现的时候要注意:1.第K大的数是A[K-1] ,注意脚标问题;
                                        2.每次排除K/2个数,剩下K-K/2个数,而且当K=1的时候就要停止,不然会死循环。
                                        3.扔掉不是真的删除,是通过脚标移动进行的,要注意判断脚标是否越界。如果数组内的数不足K/2个,就不扔(直接设置为INT_MAX)。

class Solution {
public:
    double findMedianSortedArrays(vector<int> &A, vector<int> &B) {
        int m=A.size();
        int n=B.size();
        //处理奇偶问题,偶数的情况找两次即可。
        if ((m+n)%2==0) {
            int res1=findk(A,B,(m+n)/2);
            int res2=findk(A,B,(m+n)/2+1);
            return (res1+res2)/2.0;
        }else return findk(A,B,(m+n)/2+1)/1.0;
    }
    
    int findk(vector<int> &A, vector<int> &B,int k) {
        
        int m=A.size()-1;
        int n=B.size()-1;
        int i=0; int j=0;
        while (k>1) {//注意,k==1的时候就退出,手动比较当前脚标。
            //判断脚标是否越界,越界说明不够扔了,不够扔就扔另外一个。
            //用INT_MAX避免被扔掉。
            int ia=i+k/2-1>m?INT_MAX:A[i+k/2-1];
            int ib=j+k/2-1>n?INT_MAX:B[j+k/2-1];
            if (ia<ib) {
                i=i+k/2;//移动到被扔的下一个数的位置
            } else {
                j=j+k/2;
            }
            k=k-k/2;
        }
        
        //处理NULL或者最后坐标加出界的情况
        if (A.size()==0 || i>m) return B[j];
        if (B.size()==0 || j>n) return A[i];
        
        return (A[i]<B[j]?A[i]:B[j]);
    }
};  

烙饼排序
首先我们知道有n个烙饼,对其进行排序,移动的次数最多为2n-3。使用汉诺塔的思想,每次最多使用两次操作将最大的饼放在最下面(先把最大的饼翻到最上面,然后再整个翻下来)。
想要优化这个方法,只能通过搜索+减枝。搜索即是枚举所有操作,记录排好序的所有次数。剪枝可以采用以下几个条件:
1.操作次数大于当前最少的操作次数;
2.已经排好序了;
3.当前已有的操作次数+预计的操作次数大于当前最少的操作次数。由于每次操作只能使两个烙饼相邻有序,所以统计当前烙饼数组,统计相邻确无序的烙饼组数,即为预计的最小操作次数;
4.当前烙饼如果上一次操作刚反转过则不考虑;
5.当前状态若之前之前出现过则也不考虑;将已经出现过的状态转换成字符串或者一个数保存到栈内,退出当前状态的时候记得要把状态出栈。

睡眠排序、面条排序、猴子排序
其中猴子排序使用到了洗牌算法,其他算法不用掌握。

607. 两数之和 III-数据结构设计
这里使用unordered_multiset作为存储结构。有一点需要注意的是,set和map的底层都是红黑树,可以保证有序;而unordered_set 和unordered_map的底层才是哈希表。muti指可以支持相同key值。

class TwoSum {
public:
    unordered_multiset<int> hash;
    void add(int number) {
        hash.insert(number);
    }
    bool find(int value) {
        for (auto n:hash) {
            if (value==2*n) {
                if (hash.count(n)>=2) return true;
            }
            else if (hash.count(value-n)>=1) return true;
        }
        return false;
    }
};

608. 两数和 II-输入已排序的数组
已经排好序情况可以使用两根指针。

class Solution {
public:
    vector<int> twoSum(vector<int> &nums, int target) {
        vector<int> res;
        int i=0; int j=nums.size()-1;
        while (i<j) {
            if (nums[i]+nums[j]==target) {
                res.push_back(i+1);
                res.push_back(j+1);
                return res;
            }
            if (nums[i]+nums[j]<target) i++;
            else j--;
        }
    }
};

587. 两数之和 - 不同组成
不可以先去重。

class Solution {
public:
    int twoSum6(vector<int> &nums, int target) {
        sort(nums.begin(),nums.end());
        int res=0;
        int i=0; int j=nums.size()-1;
        while (i<j) {
            if (nums[i]+nums[j]==target) {
                res++;
                while (i<j && nums[i+1]==nums[i]) i++;
                while (i<j && nums[j-1]==nums[j]) j--;
            }
            if (nums[i]+nums[j]<target) i++;
            else j--;
        }
        return res;
    }
};

57. 三数之和
注意三个数都要处理重复情况。

class Solution {
public:
    vector<vector<int>> threeSum(vector<int> &numbers) {
        vector<vector<int>> res;
        sort(numbers.begin(),numbers.end());
        
        for (int i=0;i<=numbers.size()-3;i++) {
            if (i!=0 && numbers[i]==numbers[i-1]) continue;
            int target=-numbers[i];
            int left=i+1; int right=numbers.size()-1;
            while (left<right) {
                if (numbers[left]+numbers[right]==target) {
                    vector<int> tmp;
                    tmp.push_back(numbers[i]);
                    tmp.push_back(numbers[left]);
                    tmp.push_back(numbers[right]);
                    res.push_back(tmp);
                    while (left<right && numbers[left+1]==numbers[left]) left++;
                    while (left<right && numbers[right-1]==numbers[right]) right--;
                    left++; right--;
                }
                else if (numbers[left]+numbers[right]<target) left++;
                else right--;
            }
        }
        return res;
    }
};

609. 两数和-小于或等于目标值
最快的方法还是两个指针不回头,应该想明白一个问题:nums[i]+nums[j+1]>target && nums[i]+nums[j]<=target 则nums[i+1]的解最多只能从j开始,因此ij都不回头。

class Solution {
public:
    int twoSum5(vector<int> &nums, int target) {
        if (nums.size()<2) return 0; 
        sort(nums.begin(),nums.end());
        int res=0;
        
        int i=0;
        int j=nums.size()-1;
        while (i<j) {
            if (nums[i]+nums[j]<=target) {
                res=res+(j-i);
                i++;
            }
            if (nums[i]+nums[j]>target) j--;
        }
        return res;
    }
};

443. 两数之和 II
总个数减去上面那个题即可,或者把ij的移动换过来。

class Solution {
public:
    int twoSum2(vector<int> &nums, int target) {
        if (nums.size()<2) return 0; 
        sort(nums.begin(),nums.end());
        int res=0;
        
        int i=0;
        int j=nums.size()-1;
        while (i<j) {
            if (nums[i]+nums[j]>target) {
                res=res+(j-i);
                j--;
            }
            if (nums[i]+nums[j]<=target) i++;
        }
        return res;
    }
};

533. 两数和的最接近值

class Solution {
public:
    int twoSumClosest(vector<int> &nums, int target) {
        sort(nums.begin(),nums.end());
        int res=INT_MAX;
        int i=0; int j=nums.size()-1;
        while (i<j) {
            if (nums[i]+nums[j]==target) {
                return 0;
            }
            res=min(res,abs(nums[i]+nums[j]-target));
            if (nums[i]+nums[j]<target) i++;
            else j--;
        }
        return res;
    }
};

59. 最接近的三数之和

class Solution {
public:
    int threeSumClosest(vector<int> &numbers, int target) {
        if (numbers.size()<3) return -1;
        
        sort(numbers.begin(),numbers.end());
        int res=INT_MAX;
        
        for (int i=0;i<numbers.size()-2;i++) {
            int j=i+1;
            int k=numbers.size()-1;
            while (j<k) {
                int sum=numbers[i]+numbers[j]+numbers[k];
                if (sum==target) return target;
                if (abs(sum-target)<abs(res-target)) res=sum;
                if (sum<target) j++;
                else k--;
            }
        }
        return res;
    }
};

58. 四数之和
也可以沿用上面的方法,固定两个数找另外两个数。下面的代码是尝试使用hash表做的。

class Solution {
public:
    vector<vector<int>> fourSum(vector<int> &numbers, int target) {
        sort(numbers.begin(),numbers.end());
        vector<vector<int>> res;
        if (numbers.size()<4) return res;
        unordered_map<int,vector<pair<int,int>>> twosum;
        //构建两和,并记录两数和的组合
        for (int i=0;i<numbers.size()-1;i++)
            for (int j=i+1;j<numbers.size();j++) {
                if (twosum.find(numbers[i]+numbers[j])==twosum.end()) {
                    vector<pair<int,int>> tmp;
                    tmp.push_back(make_pair(i,j));
                    twosum[numbers[i]+numbers[j]]=tmp;
                }
                else {
                    int flag=0;
                    for (auto sum:twosum[numbers[i]+numbers[j]])
                        if (numbers[sum.first]==numbers[i] && numbers[sum.second]==numbers[j]
                           || numbers[sum.first]==numbers[j] && numbers[sum.second]==numbers[i]){
                            flag=1;
                            break;
                        }
                    if (flag==0) twosum[numbers[i]+numbers[j]].push_back(make_pair(i,j));
                }
            }

        for (int i=0;i<numbers.size()-1;i++){
            //不可以用if (i!=0 && nums[i]==nums[i-1]) continue去重
            //因为有可能第一个nums[i]被用过了是不符合条件的
            for (int j=i+1;j<numbers.size();j++) {
                if (twosum.find(target-numbers[i]-numbers[j])!=twosum.end()) {
                    for (auto sum:twosum[target-numbers[i]-numbers[j]]) {
                        if (i>sum.first && i>sum.second) {//防止找到镜像解(a+b+c+d)和(c+d+a+b)
                            vector<int> tmp;
                            tmp.push_back(numbers[sum.first]);
                            tmp.push_back(numbers[sum.second]);
                            tmp.push_back(numbers[i]);
                            tmp.push_back(numbers[j]);
                            
                            //最后答案去重
                            int flag=0;
                            for (int k=0;k<res.size();k++)
                               if (res[k]==tmp) {flag=1; break;}
                            if (flag==0) res.push_back(tmp);
                        }
                    }
                }
            }
        }
        return res;
    }
};

610. 两数和 - 差等于目标值
这个题可以用同向指针做。但是注意两个问题:
1.题目要求输出index,所以排序时要记录index的变化;
2.差值,没有说正负,判断的时候用abs;
3.输出要求index从小到大,输出的时候要检查一下。
当然也可以用hashmap做,应该会简单一些。

class Solution {
public:
    vector<int> twoSum7(vector<int> &nums, int target) {
        vector<int> res;
        if (nums.size()==0) res;
        vector<pair<int,int>> m;
        for (int i=0;i<nums.size();i++) {
            m.push_back(make_pair(nums[i],i));
        }
        
        sort(m.begin(),m.end());
        int i=0;
        int j=1;
        while (j<m.size()) {
            if (i==j) j++;
            else if (abs(m[j].first-m[i].first)==abs(target)) {
                res.push_back(min(m[i].second+1,m[j].second+1));
                res.push_back(max(m[i].second+1,m[j].second+1));
                return res;
            }else if (abs(m[j].first-m[i].first)>abs(target)) i++;
            else j++;
        }
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值