力扣刷题笔记:链表(21)

2. 两数相加(模拟)

两个链表从头加到尾,边加边创建新链表的节点,最后有进位则再创建一个节点

  ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
     ListNode* head=new ListNode(-1);
     ListNode* h=head;
     int flag=0;
     while(l1!=NULL||l2!=NULL){
         int sum=0;
         if(l1!=NULL) {
             sum+=l1->val;
             l1=l1->next;
         }
         if(l2!=NULL){
             sum+=l2->val;
             l2=l2->next;
         }
         sum+=flag;
         h->next=new ListNode(sum%10);
         h=h->next;
         flag=sum/10;    
     }   
     if (flag) {
         h->next=new ListNode(1);
         h=h->next;
     }
     return head->next;
    }

445. 两数相加 II(栈)

碰到逆序操作的题,考虑先全部入栈再相加

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        stack<int> s1, s2;
        while (l1) {
            s1.push(l1 -> val);
            l1 = l1 -> next;
        }
        while (l2) {
            s2.push(l2 -> val);
            l2 = l2 -> next;
        }
        int carry = 0;
        ListNode* ans = nullptr;
        while (!s1.empty() or !s2.empty() or carry != 0) {
            int a = s1.empty() ? 0 : s1.top();
            int b = s2.empty() ? 0 : s2.top();
            if (!s1.empty()) s1.pop();
            if (!s2.empty()) s2.pop();
            int cur = a + b + carry;
            carry = cur / 10;
            cur %= 10;
            auto curnode = new ListNode(cur);
            curnode -> next = ans;
            ans = curnode;
        }
        return ans;
    }
};

19. 删除链表的倒数第 N 个结点(快慢指针)

1、用快慢指针,找倒数第n个,等于让快指针先走n步,再和慢指针同时走到尾节点
2、此时慢指针指向第n个节点,删除该节点即可

public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        //链表题常用技巧,定义哑节点指向头结点
        ListNode* dummy = new ListNode(-1, head);
        ListNode* first = dummy;ListNode* second = dummy;
        //由于不知道链表总体长度,定义快慢指针
        for (int i = 0; i < n; i++)
            first = first->next;
        while (first->next) {
            first = first->next;
            second = second->next;
        }
        second->next = second->next->next;
        return head;
    }

剑指 Offer 22. 链表中倒数第k个节点(快慢指针)

    ListNode* getKthFromEnd(ListNode* head, int k) {
    //指针模板体型,双指针,用dummy开头,返回dummy->next
    ListNode* dummy=new ListNode(-1);
    dummy->next=head;
    ListNode* first=dummy;
    ListNode* second=dummy;
    for(int i=0;i<k;i++)
        second=second->next;
    while(second->next!=NULL){
        first=first->next;
        second=second->next;
    }
    return first->next;
    }

21. 合并两个有序链表(递归)

利用函数返回为链表节点,进行递归调用。

        ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {      
            if (l1 == NULL)
                return l2;
            if (l2 == NULL)
                return l1;
            if (l1->val < l2->val){
                l1->next = mergeTwoLists(l1->next, l2);
                return l1;
            }
            else{
                l2->next = mergeTwoLists(l1, l2->next);
                return l2;
            }
        }

23. 合并K个升序链表(顺序合并)

写好一个合并函数,然后调用n次合并操作即可

class Solution {
public:
    ListNode* mergeTwoLists(ListNode *a, ListNode *b) {
        if ((!a) || (!b)) return a ? a : b;
        ListNode head, *tail = &head, *aPtr = a, *bPtr = b;
        while (aPtr && bPtr) {
            if (aPtr->val < bPtr->val) {
                tail->next = aPtr; aPtr = aPtr->next;
            } else {
                tail->next = bPtr; bPtr = bPtr->next;
            }
            tail = tail->next;
        }
        tail->next = (aPtr ? aPtr : bPtr);
        return head.next;
    }
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        ListNode *ans = nullptr;
        for (size_t i = 0; i < lists.size(); ++i) {
            ans = mergeTwoLists(ans, lists[i]);
        }
        return ans;
    }
};

24. 两两交换链表中的节点(递归)

递归方法修改,每次只修改当前两个节点

    ListNode* swapPairs(ListNode* head) {
        if(head==nullptr||head->next==nullptr)
            return head;
        ListNode* node=head->next;
        head->next=swapPairs(node->next);
        node->next=head;
        return node;
    }

25. K 个一组翻转链表(分组翻转)

1、k个一组进行翻转链表,写好一个翻转链表函数,移动head进行判断
2、使用伪头结点hair避免边界判断,最终答案无视head,返回hair->next
3、遍历时记录head的前一个节点pre和tail后一个节点nex,翻转完接回去
3、若剩余节点不足k个,则直接返回hair->next

    // 翻转一个子链表,并且返回新的头与尾
    pair<ListNode*, ListNode*> myReverse(ListNode* head, ListNode* tail) {
        ListNode* prev = tail->next;
        ListNode* p = head;
        while (prev != tail) {
            ListNode* nex = p->next;
            p->next = prev;
            prev = p;
            p = nex;
        }
        return {tail, head};
    }
    ListNode* reverseKGroup(ListNode* head, int k) {
        ListNode* hair = new ListNode(0);
        hair->next = head;
        ListNode* pre = hair;
        while (head) {
            ListNode* tail = pre;
            // 查看剩余部分长度是否大于等于 k
            for (int i = 0; i < k; ++i) {
                tail = tail->next;
                if (!tail) {
                    return hair->next;
                }
            }
            ListNode* nex = tail->next;
            pair<ListNode*, ListNode*> result = myReverse(head, tail);
            head = result.first;
            tail = result.second;
            // 把子链表重新接回原链表
            pre->next = head;
            tail->next = nex;
            pre = tail;
            head = tail->next;
        }
        return hair->next;
    }

简易递归版

class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        if (head == NULL) return NULL;
        ListNode *a = head;
        ListNode *b = head;
        for (int i = 0; i < k; i++) {
            if (b == NULL) return head;
            b = b->next;
        }
        ListNode *newNode = reverseOperator(a,b);
        a->next = reverseKGroup(b,k);
        return newNode;
    }
    ListNode* reverseOperator(ListNode* n,ListNode *b) {
        ListNode *pre, *cur, *nxt;
        pre = NULL; cur = n; nxt = n;
        while (cur != b) {
            nxt = cur->next;
            cur->next = pre;
            pre = cur;
            cur = nxt;
        }
        return pre;
    }
};

61. 旋转链表(快慢指针)

1、用快慢指针,找到链表尾节点接上头节点,找到旋转后的头节点,切掉前面的连接,返回该头节点即可
2、链表为空时返回,注意k有可能比链表长度还大,先遍历一遍链表求出长度并接上原头结点,k对长度求余
3、将第二个节点移动num-k-1个节点,切断连接,返回头结点即可

   ListNode* rotateRight(ListNode* head, int k) {
        if(head==NULL)
            return NULL;
        ListNode* first=head;
        ListNode* second=head;
        int num=1;
        while(first->next!=NULL)
        {
            first=first->next;
            num++;
        }
        k=k%num;
        first->next=head;
        for(int i=0;i<num-k-1;i++)
            second=second->next;
        ListNode* ans=second->next;
        second->next=NULL;
        return ans;    
    }

82. 删除排序链表中的重复元素 II(一次遍历)

由于头结点可能被删掉,所以用一个dummy指向头结点,循环遍历,如果cur下两个节点相等,则删除两次

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if (!head) 
            return head;
        ListNode* dummy = new ListNode(0, head);
        ListNode* cur = dummy;
        while (cur->next && cur->next->next) {
            if (cur->next->val == cur->next->next->val) {
                int x = cur->next->val;
                while (cur->next && cur->next->val == x) 
                    cur->next = cur->next->next;
            }
            else 
                cur = cur->next;
        }
        return dummy->next;
    }
};

83. 删除排序链表中的重复元素(一次遍历)

一次遍历,判断当前节点是否和下一个节点相等,相等则删除继续判断,不相等才往后挪

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if (!head) 
            return head;
        ListNode* cur = head;
        while (cur->next) {
            if (cur->val == cur->next->val) 
                cur->next = cur->next->next;
            else 
                cur = cur->next;
        }
        return head;
    }
};

86. 分隔链表(一次遍历+双链表)

题目要求把所有小于指定元素的值放到前面,所以可以维护两个链表,一个存小值,一个存大值,遍历一遍,最后把大值接到小值链表后面即可符号要求

class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        ListNode* small = new ListNode(0);
        ListNode* smallHead = small;
        ListNode* large = new ListNode(0);
        ListNode* largeHead = large;
        while (head != nullptr) {
            if (head->val < x) {
                small->next = head;
                small = small->next;
            } else {
                large->next = head;
                large = large->next;
            }
            head = head->next;
        }
        large->next = nullptr;
        small->next = largeHead->next;
        return smallHead->next;
    }
};

92. 反转链表 II(双指针)

反转指定范围的链表,首先移动头结点h到left前一个,开始反转r-l次链表,

  ListNode* reverseBetween(ListNode* head, int left, int right) {
        auto dummyNode = new ListNode(-1),h = dummyNode;
        dummyNode->next = head;
        for(int i = 0; i < left - 1; i++,h = h->next);
        auto p = h->next; 
        for(int i = 0; i < right - left; i++){
            auto q = p->next;
            p->next = q->next;
            q->next = h->next;
            h->next = q;
        }
        return dummyNode->next;
    }

141. 环形链表(快慢指针)

1、用快慢指针,快指针移动两格,慢指针移动一格,如果快指针能追上慢指针,则存在环。
2、快指针起始值大于慢指针一格,方便进入循环。
3、如果有节点等于NULL,一定不存在环
4、快指针先判断有没有到表尾再移动,慢指针不用判断

    bool hasCycle(ListNode *head) {
    if(head==NULL||head->next==NULL) return false;
    ListNode* first=head;
    ListNode* second=head->next;
    while(first!=second){
        if(second==NULL||second->next==NULL) return false;
        first=first->next;
        second=second->next->next;
    }
    return true;
    }

142. 环形链表 II(哈希表或快慢指针)

方法1:哈希表
一个非常直观的思路是:我们遍历链表中的每个节点,并将它记录下来;一旦遇到了此前遍历过的节点,就可以判定链表中存在环。借助哈希表可以很方便地实现。

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        unordered_set<ListNode *> visited;
        while (head != nullptr) {
            if (visited.count(head)) {
                return head;
            }
            visited.insert(head);
            head = head->next;
        }
        return nullptr;
    }
};

方法2:双指针法
初始化:快指针fast指向头结点, 慢指针slow指向头结点
让fast一次走两步, slow一次走一步,第一次相遇在C处,停止
然后让fast指向头结点,slow原地不动,让后fast,slow每次走一步,当再次相遇,就是入口结点。
NC3 链表中环的入口结点

class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        ListNode *fast = pHead;
        ListNode *slow = pHead;
        while (fast && fast->next) {
            fast = fast->next->next;
            slow = slow->next;
            if (fast == slow) break;
        }
        if (!fast || !fast->next) return nullptr;
        fast = pHead;
        while (fast != slow) {
            fast = fast->next;
            slow = slow->next;
        }
        return fast;
    }
};

143.重排链表(数组)

们利用线性表存储该链表,然后利用线性表可以下标访问的特点,直接按顺序访问指定元素,重建该链表即可。

    void reorderList(ListNode *head) {
       vector<ListNode*> vec;
       ListNode* node=head;
       while(node!=NULL){
           vec.push_back(node);
            node=node->next;
       }
       int i=0,j=vec.size()-1;
       while(i<j){
           vec[i++]->next=vec[j];
           if(i==j)
                break;
            vec[j]->next=vec[i];
            j--;
       }
       vec[i]->next = NULL;
    }

原地重排链表,先利用快慢指针找到链表中点,砍断两个链表,对后半段链表进行翻转,然后对两个链表进行合并

class Solution {
public:
    void reorderList(ListNode* head) {
        ListNode* p=head,*q=head,*r,*s=head;
        if(!head)            //head为空,则直接退出
            return ;         
        while(q->next){      //寻找中间结点
            q=q->next;       //p走一步
            p=p->next;
            if(q->next)
              q=q->next;     //q走两步
        }
        q=p->next;           //p所指结点为中间结点,q为后半段链表的首结点
        p->next=nullptr;
        while(q){            //将链表后半段逆置
            r=q->next;
            q->next=p->next;
            p->next=q;
            q=r;
        }
        q=p->next;            //q指向后半段的第一个数据结点
        p->next=nullptr;
        while(q){             //将链表后半段的结点插入到指定位置
            r=q->next;        //r指向后半段的下一个结点
            q->next=s->next;  //将q所指结点插入到s所指结点(head结点)之后
            s->next=q;        
            s=q->next;        //s指向前半段的下一个插入点
            q=r;
        }
    }
};

148. 排序链表(归并排序)

归并排序
中间归并之前需要把两个链表相连的地方砍断,再归并返回新链表的头结点

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        if (head == nullptr) 
            return head;
        int length = 0;
        ListNode* node = head;
        while (node != nullptr) {
            length++;
            node = node->next;
        }
        ListNode* dummyHead = new ListNode(0, head);
        for (int subLength = 1; subLength < length; subLength <<= 1) {
            ListNode* prev = dummyHead, *curr = dummyHead->next;
            while (curr != nullptr) {
                ListNode* head1 = curr;
                for (int i = 1; i < subLength && curr->next != nullptr; i++) 
                    curr = curr->next;
                ListNode* head2 = curr->next;
                //必须砍断两个链表之间的连接
                curr->next = nullptr;
                curr = head2;
                for (int i = 1; i < subLength && curr != nullptr && curr->next != nullptr; i++) 
                    curr = curr->next;
                ListNode* next = nullptr;
                if (curr != nullptr) {
                    next = curr->next;
                    //砍断第二个链表和后面的链接
                    curr->next = nullptr;
                }
                //将砍断的链表归并排序,返回合并完的头结点
                ListNode* merged = merge(head1, head2);
                //将合并完的接回去,再把prev节点后移
                prev->next = merged;
                while (prev->next != nullptr) 
                    prev = prev->next;
                curr = next;
            }
        }
        return dummyHead->next;
    }
    ListNode* merge(ListNode* head1, ListNode* head2) {
        ListNode* dummyHead = new ListNode(0);
        ListNode* temp = dummyHead, *temp1 = head1, *temp2 = head2;
        while (temp1 != nullptr && temp2 != nullptr) {
            if (temp1->val <= temp2->val) {
                temp->next = temp1;
                temp1 = temp1->next;
            } else {
                temp->next = temp2;
                temp2 = temp2->next;
            }
            temp = temp->next;
        }
        if (temp1 != nullptr) 
            temp->next = temp1;
        else if (temp2 != nullptr) 
            temp->next = temp2;
        return dummyHead->next;
    }
};

递归方法

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        return sortList(head, nullptr);
    }
    ListNode* sortList(ListNode* head, ListNode* tail) {
        if (head == nullptr) 
            return head;
        if (head->next == tail) {
            head->next = nullptr;
            return head;
        }
        ListNode* slow = head, *fast = head;
        while (fast != tail) {
            slow = slow->next;
            fast = fast->next;
            if (fast != tail) {
                fast = fast->next;
            }
        }
        ListNode* mid = slow;
        return merge(sortList(head, mid), sortList(mid, tail));
    }
    ListNode* merge(ListNode* head1, ListNode* head2) {
        ListNode* dummyHead = new ListNode(0);
        ListNode* temp = dummyHead, *temp1 = head1, *temp2 = head2;
        while (temp1 != nullptr && temp2 != nullptr) {
            if (temp1->val <= temp2->val) {
                temp->next = temp1;
                temp1 = temp1->next;
            } else {
                temp->next = temp2;
                temp2 = temp2->next;
            }
            temp = temp->next;
        }
        if (temp1 != nullptr) 
            temp->next = temp1;
        else if (temp2 != nullptr) 
            temp->next = temp2;
        return dummyHead->next;
    }
};

160. 相交链表(双指针)

1、用双指针,每次移动一格判断是否相交,如果两个都为空返回空
2、同一链表的移动要用ifelse,先判断再移动避免重复移动

    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
     ListNode* node1=headA;
     ListNode* node2=headB;
     while(node1!=node2) {
         //先进行条件判断,再node1->next,否则可能对node1操作两次造成死循环
         if(node1!=NULL)
            node1=node1->next;
         else node1=headB;
         if(node2!=NULL)
            node2=node2->next;
         else node2=headA;
     }   
     return node1;
    }

206. 反转链表(双指针)

1、用双指针法遍历链表,遍历的过程中修改链表的指向。
2、用temp指针暂存second指针指向的下一个节点。
3、最后返回的是first指针,second指针存的是NULL。

    ListNode* reverseList(ListNode* head) {
    //使用双指针,不用dummy开头
    ListNode* first=NULL;
    ListNode* second=head;
    //判断结束不用next
    while(second!=NULL) {
        //用temp暂存下一个节点
        ListNode* temp=second->next;
        second->next=first;
        first=second;
        second=temp;
    }
    //first里存着的是倒转的链表,不是second
    return first;
    }

234. 回文链表(数组、递归、快慢指针)

方法1:将值复制到数组中后用双指针法

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        vector<int> vals;
        while (head != nullptr) {
            vals.emplace_back(head->val);
            head = head->next;
        }
        for (int i = 0, j = (int)vals.size() - 1; i < j; ++i, --j) {
            if (vals[i] != vals[j]) {
                return false;
            }
        }
        return true;
    }
};

方法2:递归

class Solution {
    ListNode* frontPointer;
public:
    bool recursivelyCheck(ListNode* currentNode) {
        if (currentNode != nullptr) {
            if (!recursivelyCheck(currentNode->next)) 
                return false;
            if (currentNode->val != frontPointer->val) 
                return false;
            frontPointer = frontPointer->next;
        }
        return true;
    }
    bool isPalindrome(ListNode* head) {
        frontPointer = head;
        return recursivelyCheck(head);
    }
};

方法3:快慢指针
整个流程可以分为以下五个步骤:
找到前半部分链表的尾节点。
反转后半部分链表。
判断是否回文。
恢复链表。
返回结果。

328. 奇偶链表重排(双指针)

双指针一个指奇数队列,一个指偶数队列,遍历改next指针,最后把偶接到奇数后面返回

    ListNode* oddEvenList(ListNode* head) {
        if(head==NULL||head->next==NULL||head->next->next==NULL)
            return head; 
        ListNode* first=head;
        ListNode* second=head->next;
        ListNode* p=second;
        while(second!=NULL&&second->next!=NULL){
            first->next=second->next;
            first=second->next;
            second->next=second->next->next;
            second=first->next;
        }
        first->next=p;
        return head;
    }

你可能感兴趣的:(力扣刷题,链表,指针,数据结构)