程序员程序猿葵花宝典JAVA学习之路

Linked List的复习总结

2015-04-17  本文已影响5150人  dol_re_mi

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;
      }
上一篇下一篇

猜你喜欢

热点阅读