Linked List的復習總結

Single Linked List

相比較另一個基本的數據結構array,linked list有幾個優勢:尺寸是可以動態分配,insert或者delete更加方便。缺點就是不可以ramdom access,并且在結構中需要分配額外空間來指示下一個pointer。

基本操作

作為data strucutre里面基本的數據結構Linked list有很多特殊的性質值得我們去注意,由于它是線性結構大多數linked list問題還是相對其他數據結構(如tree, graph)較容易的。由于Linked List是由一個個節點(node)相聯組成的,因此首先看一下一個node一般怎么寫吧(這里主要先討論的單向的鏈表,雙向鏈表或者其他變種會在之后章節討論):

struct Node{
       int val;
       Node *next;
       Node(int x) : val(x), next(nullptr){}
  }

從以上可以看出在c++中Node一般是用struct來生成,并且可以使用constructor來初始化Node,因此初始化node就跟普通的class一樣。了解了Node的結構之后,下面就看看怎樣insert, delete one node on the linked list.
首先來看insertion, 我們需要考慮兩種情況:1:如果原來的linked list是空的,insert需要更新第一個node(header),一般linked list由the first node (header)來表示,一旦有了第一個node的地址其他操作都可以從這里進行; 2:如果原來的linked list是非空,insert需要更新previous node和next node的聯結關系。在這里我們來介紹一個小技巧,對于第一個node需要update的情形(即linked list可能空的),一般可以使用dummy node來避免使用if來分情況討論,具體方法就是建立一個dummy node使得它next指向linked list的第一個node,但是需要注意的是此時函數返回值必須為Node *即pointer to Node.因此在函數的signature給定的情況下,可以寫一個wrapper函數在wrapper function中call這個函數來完成。以下是具體的insertion函數,注意這里返回Node pointer指向header,這里插入的位置是插入節點的值大于前一個節點并且小于后一個節點。

Node * insert(Node *node, Node *top){
  Node dummy(-1);
  dummy.next = top;
  if(top == nullptr){
     dummy.next = node;
     return dummy.next;
   }
 
Node *prev = &dummy, *cur = prev->next;
 while(cur->val < node->val && cur){
          prev = cur;
          cur = cur ->next;
    }
 
 if(cur == nullptr){
    prev->next = node;
  }else{
    Node *next = cur->next;
    prev->next = node;
    node->next = next;
 }

 return dummy.next;
}

其實這里可以修改一下prev和cur的起始位置(即向前在移一位),這樣可以避免討論head節點是否為空。并且需要注意此時dummy node的初始化值需要改為INT_MIN:

 Node *insert(Node *node, Node *top){
    Node dummy(INT_MIN);
    dummy.next = top;
    Node *prev = nullptr, cur = &dummy;
    while(cur && cur ->val < node->val){
           prev = cur;
           cur = cur->next;
      }
     prev->next = node;
     node->next = cur;
  return dummy.next;
 }

這里需要注意while里面首先判斷cur是否存在,再比較cur-val和node->val。

對應insertion的就是delete了,這里同樣還是用dummy node的方法來解決,這里刪除的是節點值等于key的所有節點,需要注意的是得一直跟蹤記錄需要刪除的前一個node:
Remove Linked List Elements

void delete(Node *head, int key){
  Node dummy(-1);
  dummy.next = head;
  Node *prev = &dummy, *cur= dummy.next;
  while(cur){
    if(cur->val == key){
       Node *tmp = cur;
       prev->next = cur->next;
       delete tmp;
     }else{
       prev = prev->next;
     }
      cur = prev->next;
    }
 }

以上的方法是通過改變前后的節點關系,然后刪除節點的方法特別注意刪除節點之后前驅節點不需要更新。另外注意prev和cur兩個指針需要同步更新, 如果給定要刪除的節點,可以通過copy value的方法來刪除節點:

void delete(Node *n){
      if(n->next){
        n->val= n->next->val;
        Node *tmp = n->next;
        n->next = n->next->next;
        delete tmp;
       }else{
           Node *tmp = n;
           n = nullptr;
           delete tmp;
        }
    }         

這里需要注意討論下一個節點是否是空的,然后分情況進行處理。類似的方法,刪除整個linked lis如下(不需要dummy node):

 void delete(Node * head){
       Node *cur = head;
         while(cur){
             Node *tmp = cur;
             cur = cur->next;
             delete tmp;
          }
     }

經典的刪除問題: 刪除重復的節點

ListNode *deleteDuplicates(ListNode *head){
      if(head == nullptr)
          return head;
      ListNode dummy(-1);
      dummy.next = head;
      ListNode *cur = head;
      while(cur){
           while(cur->next && cur->next->val == cur->val){
                ListNode *tmp = cur->next;
                cur->next = cur->next->next;
                delete tmp;
             }
          cur = cur->next;
      }
     return dummy.next;
 }

這里需要注意一個地方在while循環里面判斷是否有重復節點時用的是while而并非if,如果采用if就會導致重復節點超過兩個沒辦法刪除。雖然這里采用兩個loop相互嵌套,實際上running time還是線性的,其實這道題不需要dummy node而且也不需要兩個循環嵌套,更改程序如下:

  ListNode *deleteDuplicate(ListNode *head){
          if(head == nullptr)
              return head;

          for(ListNode *prev = head, *cur = prev->next; cur; cur = cur->next){
                 if(prev->val == cur->val){ 
                      prev->next = cur->next;
                      delete cur;
                  }else{
                      prev = cur;
                  }
             }
           return head;
      }

這里不需要用dummy node, 用一個循環來遍歷整個linked list,這里注意head其實并沒有改變因此可以直接返回。

下面增加一定難度,仍然是刪除重復節點, 但是只保留沒有重復的節點

 ListNode *deleteDuplicate(ListNode *head){
          if(head == nullptr)
             return head;
      
          ListNode dummy(INT_MIN);
          dummy.next = head;
          ListNode *prev = &dummy, *cur = head;
          while(cur){
             bool duplicated = false;
             while(cur->next && cur->next->val == cur->val){
                     duplicated = true;
                     ListNode *tmp = cur;
                     cur = cur->next;
                     delete tmp;
                   }
             if(duplicated){
                  ListNode *tmp = cur;
                  cur = cur->next;
                  delete tmp;
                  continue;
               }
               prev->next = cur;
               prev = prev->next;
               cur = cur->next;
           }
            prev->next = cur;
            return dummy.next;
     }

如果只保留沒有重復的節點,復雜度就提高了。首先需要一個boolean值來記錄該節點是否是重復的,然后需要dummy node因為head也有可能被刪除。然后就是雙重循環在內循環中刪除當前節點,然后指向下一個節點。如果是重復的節點還需要把最后一個節點刪除,然后continue。整個循環中prev節點是不動的,他永遠指向下一個刪除過后的節點,然后再更新自己。最后不要忘記鏈接prev和cur兩個節點。

刪除的一個簡單變化,刪除alternative node采用雙指針iterative方法來處理,特別注意cur指針更新時候需要判斷prev是否是空指針:

 void deleteAlt(Node *head){
        if(head->next == nullptr) return;

        Node *prev = head, *cur = head->next;
        while(prev && cur){
            prev->next = cur->next;
            delete cur;
            prev = prev->next;
            if(prev)
                cur = prev->next;
       }
   }

Recurive方法是典型的tail-recursive的實例:

  void deleteAlt(Node *head){
        if(head == nullptr) return;
        Node *next = head->next;
        if(next){
             head->next = next->next;
             delete next;
             deleteAlt(head->next);
          }
    }

關于刪除, Delete N nodes after M nodes of linked list特別注意遍歷鏈表的時候檢查是否走到鏈表的盡頭了。

void skipMdeleteN(Node *head, int M, int N){
     Node *cur = head;
     while(cur){
          for(int i = 1; i < M && cur; ++i)
                cur = cur->next;
          if(cur == nullptr) return;
          Node *t = cur->next;
          for(int i = 1; i < N && t; ++i){
                Node *tmp = t;
                t = t->next;
                delete t;
            }
             cur->next = t;
             cur = cur->next;
            }
          }

另一個簡單的例子就是找到linked list第N個節點,如果沒有找到就返回一個極小值,此時區別于數組,這里需要遍歷linked list。

 int getNth(Node *head, int N){
    Node *cur = head;
    int count = 0;
    while(cur){
       if(count == n)
         return cur->val;
       count++;
       cur = cur->next;
     }
   return INT_MIN;
 }

利用雙指針的方法,可以用來找到linked list的中間節點,這里不用區分linked list是偶數個還是奇數個node(奇數和偶數的唯一區別在于while循環的終止條件:奇數為fast->next ==nullptr;偶數為fast ==nullptr):

  void printMid(Node *head){
         Node *slow = head, *fast = head;
         if(head){
              while(fast && fast->next){
                     fast = fast->next->next;
                     slow = slow->next;
                    }
             cout << slow->val << endl;
           }
    }

類似的方法可以用于尋找距離尾部N個位置的節點:

 Node * findNEnd(Node *head, int n){
          int count  = 0;
          Node *slow = head, *fast = head;
          while(count < n){
                if(fast == nullptr)
                   return nullptr;
                fast = fast->next;
                count++;
          }
         
          while(fast){
              slow = slow->next;
              fast = fast->next;
           }
         
          return slow;
    }

這里需要注意的是在第一個while循環里面,需要判斷一下fast是否已經到達鏈表的尾部,即n的值大于linked list的長度時候需要返回nullptr.

改變結構

類似于array,linked list也可以進行rotate,但是相對于array,鏈表需要進行遍歷找到改變的節點位置。Rotate a linked list

Node *rotate(Node *head, int k){
      if(head == nullptr) return head;
      Node *cur = head, *new, *last;
      for(int i = 1; i < k && cur; ++i)
              cur = cur->next;

      new = cur->next; last = new;
      cur->next = nullptr;
      while(last->next)
             last = last->next;
      last->next = head;

      return new;
  }

這里有個地方要注意,需要檢查k是否超過了linked list的總長度,也就是在循環中加一個條件cur != nullptr.
對于linked list有很多問題圍繞在改變原來linked list的node之間的順序,比如下面這道題:將原來的linked list中在偶數位置的節點按照倒序方式接在linked list的末尾。

 void modify(Node *head){
      if(head == nullptr || head->next == nullptr || head->next->next == nullptr)
          return;

      Node *first = head, *second = first->next;
      first->next = first->next->next;
      first = first->next;
      second->next = nullptr;

      while(first && first->next){
            Node *tmp = first->next;
            first->next = first->next->next;
            tmp->next = second;
            second = tmp;
            first = first->next;
      }
      first->next = second; 
}

這里需要注意幾個問題,首先如果linked list不超過三個節點那么沒有任何節點的順序需要改變,然后就是需要雙指針一個用來記錄當前奇數位的節點另一個用來記錄偶數位的節點,注意偶數位的節點需要用倒序的方式添加新的節點,整個過程像構建了兩個linked list最后在頭尾相連完成整個函數的構建。如果是對于原來的linked list倆倆互換的情況呢?
Swap Nodes in Pairs
第一種偷懶的方法就是直接互換節點的數值:

Node *pairswap(Node *head){
         Node *p = head;

         while(p && p->next){
              swap(p-val, p->next->val);
              p = p->next->next;
             }
      }

下面看如何倆倆互換節點的指針來解決問題,首先討論特殊情形,即一共有少于兩個節點,那么只要返回原linked list就可以了。接著創建一個dummy node,接著需要三個node, prev, cur, next,每次需要互換cur和next,之后在update三個節點。在更新next節點時候需要判斷cur是否存在,如果不存在則設為nullptr,因此整個循環的判斷也是next是否為nullptr。

  Node *pairswap(Node *head){
           if(head == nullptr || head->next == nullptr) return head;
           Node dummy(-1);
           dummy.next = head;
           for(Node *prev = &dummy, *cur = prev->next, *next = cur->next; next; prev = cur,  
                 cur=cur->next, next = cur? cur->next: nullptr){
                 prev->next = next;
                 cur->next = next->next;
                 next->next = cur;
            }
          return dummy.next;
   }

在上面的問題基礎上,我們看一下如何對一個linked list反轉,對于array反轉是很容易的,因為array可以直接對每一對頭尾的元素倆倆交換從兩頭向中間靠攏,array查找是常數時間復雜度。但是對于singly linked list只有單向的遍歷因此如果需要向前遍歷則需要從頭開始遍歷。鑒于以上情形,linked list的反轉需要三個節點, prev指向的最前面的那個節點, cur指向當前節點或者中間的節點,next指向的是下一個節點。首先我們先看一下迭代的方法,總體來說就是在循環里面讓cur->next指向prev, 這樣prev用cur來代替,然后cur用next來替換。整個循環的作用就是把prev node和current node之間的前后關系顛倒過來,并且current node和next node之間的連接關系斷開。注意這里不可以使用dummy node的技術,因為reverse之后dummy node會變為最后一個node這樣比原來的linked list要多出一個節點。

     Node* reverse(Node * head){
          Node *prev = nullptr, *cur = head, *next;
          while(cur){
               next = cur->next;
               cur->next = prev;
               prev = cur;
               cur = next;       
            }
        return prev;
      } 

在看一下遞歸方法:

 void reverse(Node **head){
     Node *first, *rest;
     if(*head == nullptr || (*head)->next == nullptr)
         return;

     first = *head;
     rest = first->next;
        
     recursive(&rest);
     first->next->next = first;
     first->next = nullptr;

     *head = first;
   }

遞歸方法中把linked list分為兩段,head和其他部分,其他部分作為新的head傳入到recursive function里面,之后在顛倒head及其后面節點的順序,最后再update head node。 在以上的基礎上,還可以進一步的簡化遞歸過程如下:

  Node *reverse(Node *cur, Node *prev){
         if(cur == nullptr) return prev;
         Node *last = reverse(cur->next, cur);
          cur->next =prev;
          return last;
      }

接著通過擴展前面倆倆交換的例子現在變為以k個node為一個單位reverse linked list.

 Node *reverseKGroup(Node *head, int k){
        if(head == nullptr|| head->next == nullptr || k < 2) return head;
         Node dummy(-1);
         dummy.next = head;
         for(Node * prev = &dummy, end = head; end; end = prev->next){
                for(int i = 1; i < k && end; ++i)
                         end = end->next;

                 if(end == nullptr)
                       break;

                 prev = reverse(prev, prev->next, end);
            }
        return dummy.next;
     }

Node *reverse(Node *prev, Node *begin, Node *end){
       Node *end_next = end->next;
       for(Node *p = prev, *cur = prev->next, *next = cur->next; cur != end_next;
             p = cur, cur = next, next = next?next->next:nullptr){
             cur->next = p;
          }
      begin->next = end->next;
      prev->next = end;
      return begin;
 }

這里有幾個需要注意的地方: 如果k<2或者linked list的節點數不超過兩個, 原linked list保持不變;在循環中始終需要查看end節點是否存在,如果end為空表明指針走到了linked list的尾端整個循環應該停止;這里的reverse函數與之前的reverse linked list本質是一樣的,只是在循環中加了關于結尾指針的判斷cur != end_next(之前只需要判斷cur != nullptr,因為cur在整個linked list的最后一個節點必然是nullptr). 下面把問題稍微增加一點復雜度: Reverse alternative K nodes in a Singly linked list.

  Node *kAltReverse(Node *head, int k){
            if(head == nullptr || head->next == nullptr || k < 2) return head;
            Node dummy(-1);
            dummy.next = head;
            for(Node *prev = &dummy, *end = head, int i = 0; end; end = prev->next){
                        for(int j = 1; j < k && end ; ++j){
                                   end = end->next;
                                   ++i;
                             }
                      
                        if(end == nullptr) break;
                        
                        if(i %(2*k) > 0)
                             prev = reverse(prev, prev->next, end);
                        else
                             prev = end;
                  }
          
            return dummy.next;
        }

   Node *reverse(Node *prev, Node *begin, Node *end){
           Node *end_next = end->next;
           for(Node *p = prev, *cur = prev->next, *next = cur->next; cur != end_next;
                p = cur, cur = p->next; next= next? next->next; nullptr){
                    cur->next = p;
                 }
               
                begin->next = end_next;
                prev->next = end;
                return begin;
         }

這道題增加了一個判斷條件i%(2k)*來判斷是否需要reverse,另外需要注意的是當不需要reverse時候prev指針的更新。接著看這道題: 給定一個linked list對于偶數位的node reverse并且接在奇數位的node之后。

  void modify(Node *head){
            if(head == nullptr || head->next == nullptr || head->next->next == nullptr)
                 return;
      
            Node *first = head, *second = head->next;
            first->next = first->next->next;
            first  = first->next;
            second ->next = nullptr;

            while(first && first-next){
                Node *tmp = first->next;
                first->next = first->next->next;
                tmp->next = second;
                second = tmp;
              }
          first->next = second;
     }

這里用兩個pointer分別記錄偶數位的node和奇數位的node,對于偶數位的reverse沒有按照之前的方法用三個指針來做,而是把遍歷到新的偶數位node添加在目前的偶數pointer(second)的前面,這樣可以做到reverse on the fly一次遍歷就可以把整個問題解決。接下來看一個綜合以上的幾個題目的題目,判斷linked list是否是palindrome.最直觀的方法就是用一個stack,然后遍歷整個linked list,push every node到stack里面,然后做第二遍遍歷時候pop stack然后與當前linked list里面的node進行比較,如果遍歷下來所有node都一致那么就是palindrome反之就不是。下面再看一個解法:

 bool isPalindrome(Node *head){
        Node *slow = head, *fast = head;
        Node *second , *prev_slow = head;
        Node *mid_node = nullptr;
   
        bool res = true;
        if(head == nullptr || head->next == nullptr) return res;
        
        while(fast && fast->next){
             prev_slow = slow;
             slow = slow->next;
             fast = fast->next->next;
           }

         if(fast){
               mid_node = slow;
               slow = slow->next;
          }

         second = slow;
         prev_slow ->next = nullptr;
         reverse(&second);
         res = compare(head, second);
       
         reverse(&second);
         if(mid_node){
              prev_slow->next = mid_node;
              mid_node->next = second;
          }
             prev_slow->next = second;
         return res;
      }

   void reverse(Node **head){
         Node *prev = nullptr, *cur = *head, *next;
         while(cur){
             next = cur->next;
             cur->next = prev;
             prev = cur;
             cur= next;
         }
        *head = prev;
    }

  bool compare(Node *first, Node *second){
         Node *p1 = first, *p2 = second;
         while(p1 && p2){
              if(p1->val == p2->val){
                    p1 = p1->next;
                    p2 = p2->next;
               }else
                     return false;
           }
            
           if(p1 == nullptr && p2 == nullptr)
                return true;
   
        return false;
    }

這個解法結合了上面的linked list reverse和雙指針方法尋找mid point,但是由于在reverse linked list過程中破壞了原來linked list的結構,因此需要再一次reverse進行復原,在時間消耗上是比較費事的。另外在compare這個函數中,最后采用判斷p1和p2是否同時為空比較巧妙, compare函數本身可以作為一個經典的兩個linked list比較問題。 為了避免改變linked list的原來結構,可以采用雙指針遞歸的方法:

bool isPalindromUnit(Node *&left, Node *right){
         if(right == nullptr)
              return true;

         bool isp = isPalindromUnit(left, right->next);
         if(!isp) return false;

         bool ispl = (right->val == left->val);
         left = left->next;

         return ispl;
  }

 bool isPalindrom(Node *head){
        isPalindrom(head, head);
 }

這個解法有幾個注意的地方,第一個就是base case的討論,如果right pointer已經到達linked list的尾部(==nullptr)那么返回true,另外left pointer需要pass by reference這里用的是*&也可以用pointer to pointer來表示。最后一點這里不是tail-recursive call,算上遞歸棧空間是O(n).
Linked List問題有時候可以增加一些復雜性,但是本質上還是與傳統的問題是一致的,比如說這道題Given a linked list of line segments, remove middle points, 具體的要求可以查看鏈接,這道題的核心是需要三個節點來判斷中間節點是否需要刪除。

 Node *removeMid(Node *head){
           Node *cur = head;
           while(cur && cur->next && cur->next->next){
                   Node *mid = cur->next;
                   Node *last = mid->next;
                   if(mid->x == last->x && cur->x == mid->x || cur->y == mid->y && mid->y == last->y){
                    cur->next = last;
                    delete mid;
                }else if(cur->x == mid->x && mid->x != last->x||(cur->y == mid->y && mid->y != last->y){
                    cur->last;
                }else{
                    cout << "Invalid" << endl;
                    eixt(1);
                }        
             }
          return head;
       }

接下來看一道題對于結構變化的linked list,怎樣判斷一個linked list是否有loop?

   bool hasCycle(ListNode *head){
           ListNode *slow = head, *fast = head;
           while(fast && fast->next){
                 fast = fast->next->next;
                 slow = slow->next;
                 if(slow == fast)
                     return true;
             }
          return false;
     }

Follow up, 如果需要返回cycle開始的節點呢?

 ListNode * detectCycle(ListNode *head){
            ListNode *slow = head, *fast = head;
            while(fast && fast->next){
                  fast = fast->next->next;
                  slow = slow->next;
                  if(slow == fast){
                      slow = head;
                      while(slow != fast){
                          slow = slow->next;
                          fast = fast->next;
                      }
                      return slow;
                  }
              return nullptr;
       }

還有一道綜合題,結合了reverse linked list和節點刪除的技巧
delete nodes which have a greater value on right side:當然永遠可以使用暴力的解法采用雙重循環檢查每一個節點看是否有右邊的節點擁有更大的數值,但是復雜度將達到O(n^2),下面的解法可以達到線性計算時間。

 void delLesserNodes(Node **head_ref){
          reverseList(head_ref);
          _delLesserNode(*head_ref);
          reverseList(head_ref);
  }

  void reverseList(Node **head){
      Node *cur = head, *prev, *next;
       while(cur){
           next = cur->next;
           cur->next = prev;
           prev = cur;
           cur = next;
        }
        *head = prev;
     }

   void _delLesserNode(Node *head){
          Node *current = head, maxNode = head;
          Node *tmp;
          while(current && current->next){
               if(current->next->val < maxNode->val){
                      tmp = current->next;
                      current->next = tmp->next;
                      delete tmp;
                 }else{
                     current = current->next; 
                     maxnode = current; 
                 }
              }
       }

首先需要reverse linked list,然后一個個比較最大節點與當前節點的數值,如果當前節點的數值小則刪除當前節點,最后還原linked list的結構。
當linked list中的value是特殊的數值時,排序linked list也可以采用特殊的方法。比如linked list只含有0, 1 ,2數值,排序linked list.

void sortList(Node *head){
        vector<int> tmp(3,0);
        Node *cur = head;
        while(cur){
           if(cur->val == 0){
                 tmp[0]++;
             }else if(cur->val == 1){
                 tmp[1]++;
             }else{
                 tmp[2]++;
             }
              cur = cur->next;
          }

          cur = head;
          while(cur){
              if(tmp[i] == 0)
                ++i;
              else{
                cur->data = i;
                --tmp[i];
                cur = cur->next;
               }
             }
         }

這里采用vector相當于一個hash table記錄0,1, 2的個數,code還可以寫的更簡潔一些:

  void sortList(Node *head){
        vector<int> count(3, 0);
        Node *ptr = head;
        while(ptr){
             count[ptr->val]++;
             ptr = ptr->next;
         }
         int i = 0;
         ptr = head;

         while(ptr){
            if(count[i] == 0)
               ++i;
            else{
               ptr->val = i;
               --count[i];
               ptr = ptr->next;
              }
            }
          }

兩個或者多個linked list的問題

首先看一下如何將一個linked list分成兩個:

 void AltSplit(ListNode *source, ListNode **a, ListNode **b){
          ListNode dummyA(-1);
          ListNode dummyB(-1);
          ListNode *cur = source, *ta = &dummyA, *tb = &dummyB;
          while(cur && cur->next){
              ta->next = cur;
              tb->next = cur->next;
              ta = ta->next;
              tb = tb->next;
           }
          if(cur) ta->next = cur;
          *a = dummyA.next;
          *b = dummyB.next;
    }

Merge 兩個 sorted linked list, 由于linked list本身已經排好序,可以通過比較兩個list node的值大小來merge,若節點為空則設置為無窮大。

   ListNode *mergeList(ListNode *a, ListNode*b){
                 ListNode dummy(-1);
                 for(ListNode *cur = &dummy; a||b; cur=cur->next){
                      int valA = (a == nullptr)? INT_MAX: a->val;
                      int valB = (b == nullptr)? INT_MAX: b->val;
                      if(valA < valB){
                         cur->next = a;
                         a = a->next;
                      }else{
                         cur->next = b;
                         b = b->next;
                      }
                }
           return dummy.next;
   }

當然也可以通過一個個確認節點是否為空來merge:

    ListNode *mergeList(ListNode *a, ListNode *b){
              if(a == nullptr) return b;
              if(b == nullptr) return a;
              ListNode dummy(-1);
              ListNode * result = &dummy;
              for( ; a && b; result = result->next){
                   if( a->val < b->val){
                         result->next = a;
                         a = a->next;
                     }else{
                         result->next = b;
                         b = b->next;
                     }
                 }
               result->next = (a? a : b);
           return dummy.next;
      }

對于兩個list,經典題型有尋找兩個linked list的intersection

  ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    if(headA == nullptr || headB == nullptr) return nullptr;
    ListNode *curA = headA, *curB = headB;
    ListNode *tailA = nullptr, *tailB = nullptr;

    while(true){
       if(curA == nullptr)
          curA = headB;
        
       if(curB == nullptr)
          curB = headA;
           
       if(curA->next == nullptr)
           tailA = curA;
    
       if(curB->next == nullptr)
           tailB = curB;
           
       if(tailA && tailB && tailA->val!= tailB->val) return nullptr;
       
       if(curA == curB) return curA;
       
       curA = curA->next;
       curB = curB->next;
    }

這道題的關鍵linked list A的尾部鏈接到linked list B的頭部,同樣linked list B的尾部鏈接到linked list A的頭部。提前可以判斷的是如果兩個linked list的尾部不等那么肯定沒有intersection,只有當一個時刻兩個pointer指向同一個節點那么它就是intersection.

如果linked list本身是有序的,尋找intersection假設這樣的intersection是存在的:

 Node *intersect(Node *a, Node *b){
     Node dummy(-1);
     Node *cur = &dummy;
     while(a && b){
        if(a->val == b->val){
           cur->next = a;
           cur = cur->next;
           a = a->next;
           b = b->next;
        }else if(a->val < b->val){
                 a = a->next;
        }else{
              b = b->next;
         }
         cur->next = nullptr;
         return dummy.next;
   }

這里采用了dummy node的方法,類似于之前的merge方法,只是選擇數值相同的節點。另外需要注意的是兩個linked list的節點更新。下面是遞歸的方法:

 Node *intersect(Node *a, Node *b){
        if(a == nullptr || b == nullptr)
              return nullptr;

        if(a->data < b->data)
               return intersect(a->next, b);

        if(a->data > b->data)
                return intersect(a, b->next);

        Node *tmp = new Node(a->data);
        tmp->next = Intersect(a->next, b->next);
        return tmp;
   }

同樣是two sorted linked list, construct a maximum sum linked list out of them:

 void findMaxSumlist(Node *a, Node *b){
         Node *result =  nullptr;
         Node *pre1 = a, *cur1 = a; 
         Node *pre2 = b, *cur2 = b;

         while(cur1 || cur2){
             int sum1 = 0, sum2 = 0;
             while(cur1 && cur2 && cur1->data != cur2->data){
                   if(cur1->data < cur2->data){
                        sum1+= cur1->data;
                        cur1 = cur1->next;
                     }else{
                        sum2 += cur2->data;
                        cur2 = cur->next;
                     }
            }

            if(cur1 == nullptr){
                 while(cur2){
                    sum2 += cur->data;
                    cur2 = cur2->next;
                   }
               }

           if(cur2 == nullptr){
                while(cur1){
                    sum1 += cur->data;
                    cur1 = cur1->next;
                  }
             }
    
           if(pre1 == a && pre2 ==b)
                  result = (sum1 > sum2)? pre1 : pre2;
           else{
                  if(sum1 > sum2)
                      pre2->next = pre1->next;
                  else
                      pre1->next = pre2->next;
              }

                pre1 = cur1, pre2 = cur2;

             if(cur1)
                  cur1 = cur1->next;

            if(cur2)
                  cur2 = cur2->next;
         }
          while(result){
               cout << result->data << " ";
               result = result->next;
           }
        }

這道題的思路是在使用merge的同時,一直記錄到達到共同的節點之前各自鏈表所有節點之和,比較兩者之后選擇是否更新prev節點使得最后的結果得到最大的和。
當linked list代表一個數字時候,兩個數的加法可以用linked list操作來進行。

 Node *addTwoList(Node *first, Node *second){
    Node *result, *temp, *prev;
    int carry = 0, sum;

    while(first || second){ 
         sum = carry + (first? first->data: 0) + (second? second->data: 0);
     
         carry = sum/10;
         sum = sum%10;
          
        temp = new Node(sum);
        
        if(result == nullptr)
              result = temp;
        else
              prev->next = temp;
     
        prev = temp;

        if(first) first = first->next;
        if(second) second = second->next;
       }
  
    if(curry) 
       temp->next = new Node(carry);

    return result;
} 

如果linked list結構發生一些變化,比如說node本身不僅指向下一個Node,還有一個額外的random Node,如何clone

  RandomListNode *copyRandomList(RandomListNode *head){
           for(RandomListNode *cur = head; cur; ){
                RandomListNode *newNode = new RandomListNode(cur->label);
                newNode->next = cur->next;
                cur->next = newNode;
                cur = cur->next->next;
             }
            
           for(RandomListNode *cur = head;cur; ){
              if(cur->random){
                 cur->next->random = cur->random->next;

              cur = cur->next->next;
            }

           RandomListNode dummy(-1);
           dummy.next = head;

          for(RandomListNode *prev = &dummy, *cur = head;cur;){
                  prev->next = cur->next;
                  prev = prev->next;
                  cur->next = cur->next->next;
                  cur = cur->next;
            }

          return dummy.next;
       }

這道題的傳統做法需要復制原來鏈表所有的節點和random節點,對于空間的要求是O(n),或者用hashtable記錄所有的label來記錄random節點和next節點位置。這里巧妙第一次遍歷在原linked list中每一個節點后復制其自身,然后第二次遍歷將cur節點random pointer信息復制出去通過cur->next。最后就是將一個linked list分開成兩個linked list.

類似于在array里面的3sum問題,分別從三個linked list里面找到一個節點使得之和為一個給定數。通過之前3sum的問題我們可以發現array需要排序來縮小搜索范圍,所以我們對其中一個linked list b生序排序,另一個linked list c降序排序,然后再進行類似3sum的操作,以下程序假設排序已經完成以后的操作:

  bool isSumSorted(Node *aHead, Node *bHead, Node *cHead, int x){
          Node *a = aHead;
          while(a){
               Node *b = bHead;
               Node *c = cHead;
               while(b && c){
                   int sum = a->val  + b->val + c->val;
                   if(sum == x){
                       cout << a->val << " " << b->val << " " << c->val << endl;
                       return true;
                    }else if( sum < x){
                       b = b->next;
                    }else{
                       c = c->next;
                    }
                a = a->next;
              }
         return false;
      }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容

  • 背景 一年多以前我在知乎上答了有關LeetCode的問題, 分享了一些自己做題目的經驗。 張土汪:刷leetcod...
    土汪閱讀 12,761評論 0 33
  • //leetcode中還有花樣鏈表題,這里幾個例子,冰山一角 求單鏈表中結點的個數----時間復雜度O(n)這是最...
    暗黑破壞球嘿哈閱讀 1,524評論 0 6
  • 花2天把Leetcode上Linked lists的題目刷完了,下面是一些我覺得比較有傾訴欲望的題。 237. D...
    __小赤佬__閱讀 615評論 0 1
  • 2. Add Two Numbers 先初始化兩個結點,一個用來做head,一個作為指引node不斷向下延續的指針...
    Morphiaaa閱讀 927評論 0 0
  • 這兩天在讀書會聽到了一本畢淑敏老師的書,書名叫做世界如織心如梭。 這是一本游記在讀書會里很少會推薦游記,畢竟...
    悠爺閱讀 248評論 1 0