leetcode單鏈表問題集


刷了幾天leetcode ,發現以前的很多解法都很冗餘,代碼不夠精簡,重新整理了一波,準備做個集合。
每一題解法爭取用最少的行數解決問題
以下是鏈表相關問題,持續更新中…

反轉鏈表

  1. 反轉過程中需要保存當前節點 curr 的下一個節點 next,當 next 空時,反轉結束,返回 curr
  2. 全程只需要關注 curr 是否爲空即可。
  3. 每一輪反轉結束,需要更新 prev,curr 的值,而 next 是在最開始就更新的。
struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* prev = NULL;
    struct ListNode* curr = head;
    struct ListNode* next = NULL;
    while(curr)
    {
        next = curr->next; //save next
        curr->next = prev; //reverse
        if (next==NULL){   //last node of list
            break;
        }
        prev = curr; //move forword
        curr = next; //move forword
    }
    return curr;
}

k個一組反轉鏈表

  1. 這題用遞歸解法會比較簡單
  2. 找到翻轉點很重要,代碼裏 kTail 是當前k個一組節點的尾節點,爲了找到 kTail ,需要從頭節點開始走 k-1 步。
  3. 通過 kTail 是否空來判斷是否需要翻轉,如果 kTail 爲空,說明剩下的節點數小於 K個了。
  4. 同時需要保存下一組 k 個節點的頭節點,也就是 nextHead
  5. 只關注當前一組節點的翻轉,翻轉代碼與上述翻轉單鏈表一樣,唯一不同的是,break 的地方變成了 next==nextHead
  6. 當前組的K個節點翻轉完畢之後,需要將尾節點(oldHead)的next 指向下一組節點的頭節點。
struct ListNode* reverseKGroup(struct ListNode* head, int k) {
    struct ListNode* kTail = head;   //k個節點的尾節點
    struct ListNode* nextHead = NULL;//下一組k個節點的頭節點

    int n = k-1;
    while(n-- && kTail){
        kTail = kTail->next;
    }
    if (kTail){
        nextHead = kTail->next;
    }
    
    if (kTail){ //滿足k個節點即可反轉
        struct ListNode* oldHead = head; //保存下舊的頭部
        struct ListNode* prev = NULL;
        struct ListNode* curr = head;
        struct ListNode* next = NULL;
        while(curr){
            next = curr->next;
            curr->next = prev;
            if(next == nextHead){ //翻轉到下一組的頭節點就截止
                break;
            }
            prev = curr;
            curr = next;
        }
        oldHead->next = reverseKGroup(next, k); //舊的頭節點(即翻轉後的尾節點)指向下一組的頭節點
        return curr; //返回翻轉後新的head
    }
    return head; //不滿足k個節點則直接返回 head
}

合併有序鏈表

  1. 合併之前需要判兩個鏈表是否有空鏈表
  2. 合併時需要一個頭指針和一個尾指針,尾指針不斷更新,最後返回的是頭指針
  3. 合併完之後需要再次判斷兩個鏈表剩餘的部分
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2) {
    if (l1 == NULL || l2 == NULL)
        return l1 != NULL? l1 : l2;
    
    struct ListNode head = {0, NULL}; //virtual head node
    struct ListNode *tail = &head;
    while(l1 && l2){
        if (l1->val < l2->val){
            tail->next = l1;
            l1=l1->next;
        }else{
            tail->next = l2;
            l2=l2->next;
        }
        tail=tail->next;
    }
    tail->next = l1==NULL ? l2:l1;
    return head.next;
}

合併k個有序鏈表

//找到K個鏈表中最小的頭節點
struct ListNode* findMinNode(struct ListNode** lists, int listsSize, int*index)
{
    int init=0;
    struct ListNode* min = NULL;
    for (int i=0;i<listsSize;i++){
        if (lists[i]==NULL) 
            continue;
    
        if (init==0){ //初始化第一個最小節點
            min=lists[i];
            *index = i;
            init=1;
        } else {
            if (lists[i]->val < min->val){
                *index = i;
                min=lists[i];
            }
        }
    }
    return min;
}
struct ListNode* mergeKLists(struct ListNode** lists, int listsSize) {
    struct ListNode head = {0, NULL};
    struct ListNode* tail = &head;
    struct ListNode* minNode = NULL;
    int i = 0;
    while(minNode = findMinNode(lists, listsSize, &i)){
        tail->next = minNode;
        tail = tail->next;    
        lists[i] = lists[i]->next;
    }
    return head.next;
}

環形鏈表

  1. 快慢指針找到相遇點
bool hasCycle(struct ListNode *head) {
    struct ListNode *slow = head;
    struct ListNode *fast = head;
    while(slow && fast && fast->next){
        slow = slow->next;
        fast = fast->next->next;
        if (fast == slow)
            return true;
    }
    return false;
}

環形鏈表入口

  1. 先快慢指針找到相遇點
  2. 再2個指針分別從起點和相遇點開始,直到再次相遇則爲入口
struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode *slow = head;
    struct ListNode *fast = head;
    struct ListNode *meet = NULL;
    //find meet node
    while(slow && fast && fast->next){
        slow = slow->next;
        fast = fast->next->next;
        if (slow==fast){
            meet = slow;
            break;
        }
    }
    slow = head;
    while(slow && meet){
        if (meet==slow){ //find entry point
            return meet;
        }
        slow = slow->next;
        meet = meet->next;
    }
    return NULL;
}

刪除倒數第N個節點

  1. 快指針先向前走n步,注意是n步,不是n-1步
  2. 快慢指針同時走,直到 fast 或 fast->next 爲空
  3. 如果 fast 爲空,表示要刪除的是第一個節點,此時 slow 指向的是 head
  4. 如果 fast !=NULL && fast->next==NULL,表示要刪除的是head節點之後的節點,此時slow 指向的是被刪節點的前置節點。
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while(fast && n--){ 
        fast=fast->next;
    }
    while(slow && fast && fast->next){ 
        slow = slow->next;
        fast = fast->next;
    }
    
    if (fast==NULL){ //刪除的是第一個節點
        head = head->next;
    }else if (slow && slow->next){ //刪除的不是第一個節點
        slow->next = slow->next->next;
    }
    return head;
}

相交鏈表的交點

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    if (headA==NULL || headB==NULL){
        return NULL;
    }
    //先找到其中一個鏈表的尾節點
    struct ListNode *tailA = headA;
    while(tailA->next){
        tailA = tailA->next;
    }
    //將尾節點和頭結點連起來形成環
    tailA->next = headA;
    
    //問題轉化爲求環的入口
    struct ListNode *slow = headB;
    struct ListNode *fast = headB;
    struct ListNode *meet = NULL;
    while(slow && fast && fast->next){
        slow = slow->next;
        fast = fast->next->next;
        if (slow==fast){
            meet = slow;
            break;
        }
    }
    slow = headB;
    while(slow && meet){
        if (slow==meet){
            break;
        }
        slow = slow->next;
        meet = meet->next;
    }
    ////將尾節點和頭結點解開
    tailA->next = NULL;
    return meet;
}

移除鏈表指定元素

struct ListNode* removeElements(struct ListNode* head, int val) {
    //移除最開始相等的節點,直到第一個不相等的
    while(head && head->val == val){
        head = head->next;
    }
    //head 一定是不相等的節點,此時如果下一個節點爲空可以直接返回了
    if (head==NULL || head->next==NULL)
        return head;
    
    //2個指針一前一後
    struct ListNode* slow = head;
    struct ListNode* fast = head->next;
    while(slow && fast){
        if (fast->val==val){ //快指針相等的話,慢指針直接刪除該節點
            slow->next = fast->next;
        }else{
            slow = slow->next;
        }
        fast = fast->next;
    }
    return head;
}

迴文鏈表

以下面2個鏈表爲例

list1: 1->2->3->2->1
list2: 1->2->3->2
  1. 首先需要處理特殊情況,即鏈表長度爲0,1,2 的時候。
  2. 接下來用快慢指針,直到 fast 或 fast->next 爲空
  3. 如果fast 爲空則表示鏈表長度爲偶數,此時 slow 指向鏈表第 n/2 +1 個節點,也就是list1的第3 個節點
  4. 如果fast 不爲空則 fast->next 爲空,鏈表長度爲奇數,此時 slow 指向鏈表中間節點,也就是 list2 的第3個節點
  5. 針對偶數鏈表做翻轉,需要反轉 1->2 的部分,其下一個節點正是 slow
  6. 針對奇數鏈表做反轉,也只需要反轉 1->2 的部分,其下一個節點正是 slow
  7. 所以當 next==slow 的時候反轉停止,返回反轉後的頭節點,也就是 slow 的上一個節點
  8. 接下來同時反向遍歷比較,這裏針對奇偶有區別,奇數鏈表的右半部分從 slow->next 開始,偶數鏈表右半部分從 slow 開始。
struct ListNode* reverse(struct ListNode* head, struct ListNode* givenNode){
    struct ListNode* prev = NULL;
    struct ListNode* curr = head;
    struct ListNode* next = NULL;
    while(curr){
        next = curr->next;
        curr->next = prev;
        if (next == givenNode){
            break;
        }
        prev = curr;
        curr = next;
    }
    return curr;
}

bool isPalindrome(struct ListNode* head) {
    //1個節點
    if (head && head->next==NULL){
        return true;
    }
    //2個節點
    if (head && head->next && head->next->next==NULL){
        if (head->val == head->next->val){
            return true;
        }else{
            return false;
        }
    }
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while (slow!=NULL && fast!=NULL && fast->next!=NULL) {
        slow = slow->next;
        fast = fast->next->next;
    }

    struct ListNode* left = reverse(head, slow);
    struct ListNode* right = NULL;
    if (fast){  //奇數鏈表
        right = slow->next;
    }else{      //偶數鏈表,此時slow指向第len/2+1個節點
        right = slow;
    }
    while(left && right){
        if (left->val != right->val){
            return false;
        }else{
            left  = left->next;
            right = right->next;
        }
    }
    return true;
}

奇偶鏈表

  1. 用4個指針分別指向奇數鏈表的首尾節點和偶數鏈表的首尾節點
  2. 最後再把偶數鏈表和奇數鏈表連起來
struct ListNode* oddEvenList(struct ListNode* head) {
    if (head==NULL || head->next==NULL)
        return head;
    struct ListNode* oddHead = head; //奇數節點的頭
    struct ListNode* oddTail = oddHead; //奇數節點的尾
    
    struct ListNode* eventHead = head->next; //偶數節點頭
    struct ListNode* eventTail = eventHead; //偶數節點尾
    
    bool isOdd = true;
    head = head->next->next; //從第3個節點開始
    while(head){
        printf("%d\n", head->val);
        if (isOdd){
            oddTail->next = head;
            oddTail = oddTail->next;
            isOdd = false;
        }else{
            eventTail->next = head;
            eventTail = eventTail->next;
            isOdd = true;
        }
        head = head->next;
    }
    if (!isOdd) //一定要將偶數節點的最後一個節點置爲空
        eventTail->next = NULL;
    
    oddTail->next = eventHead;
    return oddHead;
}

兩數相加

給出兩個 非空 的鏈表用來表示兩個非負的整數。其中,它們各自的位數是按照 逆序 的方式存儲的,並且它們的每個節點只能存儲 一位 數字。
輸入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
輸出:7 -> 0 -> 8
原因:342 + 465 = 807

  1. 和合併有序鏈表比較相似,只是多了一個進位的操作。同時要考慮其中一個鏈表更長的情況。
struct ListNode*newNode(int val)
{
    struct ListNode* node = malloc(sizeof(struct ListNode));
    node->val = val;
    node->next = NULL;
    return node;
}

struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2) {
    struct ListNode head = {0,NULL};
    struct ListNode* tail = &head;

    struct ListNode* p1 = l1;
    struct ListNode* p2 = l2;
    int carry = 0; //進位
    while(p1 && p2){
        int sum = p1->val + p2->val + carry;
        carry = sum/10;
        tail->next = newNode(sum%10);
        tail = tail->next;
        p1 = p1->next;
        p2 = p2->next;
    }
    if (carry != 0){
        while(p1){
            int sum = p1->val + carry;
            carry = sum/10;
            tail->next = newNode(sum%10);
            tail = tail->next;
            p1 = p1->next;
        }
        while(p2){
            int sum = p2->val + carry;
            carry = sum/10;
            tail->next = newNode(sum%10);
            tail = tail->next;
            p2 = p2->next;
        }
        if (carry!=0){
            tail->next = newNode(carry);
            tail = tail->next;
        }
    }else{
        tail->next = p1==NULL?p2:p1;
    }
    return head.next;    
}

旋轉鏈表

  1. 2個指針分別找到鏈表解除鏈接的點,要注意是否是原地旋轉
struct ListNode* rotateRight(struct ListNode* head, int k) {
    if (head==NULL || k<=0)
        return head;

    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while(k--){
        fast = fast->next;
        if(fast == NULL){ //k>len 的時候從頭開始
            fast = head;
        }
    }
    if (slow==fast){ // 相等表示 k%len==0
        return head;
    }
    while(slow && fast && fast->next){
        fast = fast->next;
        slow = slow->next;
    }
    struct ListNode* newHead = slow->next;
    slow->next = NULL;
    fast->next = head;
    return newHead;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章