训练题:复杂链表拷贝

题目:http://t.cn/A6qbIzAu

方法一:递归+哈希

  • 时间复杂度:O(n)
  • 空间复杂度:O(n) ```cpp

Node copyRandomList(Node head) { if(!head) return head;

  1. unordered_map<Node*, Node*> info;
  2. return _copy(info, head);

}

Node _copy(unordered_map<Node, Node>& info, Node node){ if(!node) return nullptr;

  1. Node* copy_node = nullptr;
  2. if(info.count(node)) copy_node = info[node];
  3. else{
  4. copy_node = copyNode(node);
  5. info[node] = copy_node;
  6. }
  7. if(!node->random) copy_node->random = nullptr;
  8. else
  9. {
  10. if(!info.count(node->random)) info[node->random] = copyNode(node->random);
  11. copy_node->random = info[node->random];
  12. }
  13. copy_node->next = _copy(info, node->next);
  14. return copy_node;

}

Node copyNode(Node node) { if(!node) return nullptr; return new Node(node->val); }

  1. <a name="5lft7"></a>
  2. ## 方法二:迭代+哈希
  3. - 时间复杂度:O(n)
  4. - 空间复杂度:O(n)
  5. ```cpp
  6. Node* copyRandomList(Node* head) {
  7. if(!head) return head;
  8. unordered_map<Node*, Node*> info;
  9. Node copyHead(1), *copyHeadPtr = &copyHead;
  10. for(Node* node = head; node; node = node->next){
  11. if(!info.count(node)) info[node] = new Node(node->val);;
  12. copyHeadPtr->next = info[node];
  13. copyHeadPtr = copyHeadPtr->next;
  14. if(!node->random) copyHeadPtr->random = nullptr;
  15. else{
  16. if(!info.count(node->random)) info[node->random] = new Node(node->random->val);
  17. copyHeadPtr->random = info[node->random];
  18. }
  19. }
  20. return copyHead.next;
  21. }

方法三

  • 时间复杂度:O(n)
  • 空间复杂度:O(1) ```cpp

Node copyRandomList(Node head) { if(!head) return head;

  1. Node* node = nullptr, *tmpNode = nullptr;
  2. // 第一步,在原链表每个节点后插入拷贝的节点
  3. // 原链表:node1->node2->node3
  4. // 新链表:node1->copy1->node2->copy2->node3->copy3
  5. for(node = head, tmpNode = nullptr; node; node = tmpNode->next){
  6. tmpNode = new Node(node->val);
  7. tmpNode->next = node->next;
  8. node->next = tmpNode;
  9. }
  10. // 为每个copy node赋值random
  11. // 在新链表中,任意node的next就是它的copy node
  12. for(node = head; node; node = node->next->next){
  13. if(node->random) node->next->random = node->random->next;
  14. }
  15. // 链表:node1->copy1->node2->copy2->node3->copy3
  16. // 拆分成两个链表:
  17. // 链表1:node1->node2->node3
  18. // 链表2:copy1->copy2->copy3
  19. Node* ret = head->next;
  20. for(node = head, tmpNode = head->next; tmpNode->next;){
  21. node->next = tmpNode->next;
  22. node = node->next;
  23. tmpNode->next = node->next;
  24. tmpNode = tmpNode->next;
  25. }
  26. node->next = nullptr;
  27. return ret;

}

  1. <a name="1XsV0"></a>
  2. # 训练题:删除链表节点
  3. 题目:[http://t.cn/A6VqjZ1i](http://t.cn/A6VqjZ1i)
  4. ```cpp
  5. ListNode* deleteNode(ListNode* head, int val) {
  6. if( !head ) { return head; }
  7. if( head->val == val) { return head->next; }
  8. ListNode* tmpHead = head, *tmpPre = nullptr;
  9. for( ; tmpHead && tmpHead->val != val; tmpPre = tmpHead, tmpHead = tmpHead->next );
  10. if( tmpHead ) tmpPre->next = tmpHead->next;
  11. return head;
  12. }

训练题:逆序打印链表

题目:http://t.cn/A62bSlA8

方法一:递归

  1. vector<int> reversePrint( ListNode* head )
  2. {
  3. vector<int> shit;
  4. int count = 0;
  5. for( ListNode* node = head; node; node = node->next, ++count );
  6. shit.reserve( count );
  7. return _reverse( head, shit );
  8. }
  9. vector<int> &_reverse( ListNode* head, vector<int> &container )
  10. {
  11. if( !head ) { return container; }
  12. _reverse( head->next, container );
  13. container.push_back( head->val );
  14. return container;
  15. }

方法二:迭代(栈)

  1. vector<int> reversePrint(ListNode* head) {
  2. stack<int> shit;
  3. int count = 0;
  4. for( count = 0; head; head = head->next, ++count ) { shit.push( head->val ); }
  5. vector<int> ret;
  6. ret.reserve( count );
  7. for( ; !shit.empty(); shit.pop() ) { ret.push_back( shit.top() ); }
  8. return ret;
  9. }

训练题:合并链表

场景一:合并两个有序链表

题目:http://t.cn/A6VzAxiN

方法一:迭代

  • 时间复杂度:O(n+m)
  • 空间复杂度:O(1) ```cpp

ListNode mergeTwoLists(ListNode l1, ListNode* l2) { if( !l1 ) { return l2; } if( !l2 ) { return l1; }

  1. // newHead是合并后的表头
  2. // tail是当前迭代过程中的表尾
  3. // tmpNode是l1和l2迭代过程中更小的表头。
  4. // 每一次迭代去取更小的表头,该链表表土也相应位移
  5. ListNode* newHead = nullptr, *tail = nullptr, *tmpNode = nullptr;
  6. while( l1 && l2 )
  7. {
  8. // 第一步取更小的节点
  9. if( l1->val < l2->val )
  10. {
  11. tmpNode = l1;
  12. l1 = l1->next;
  13. }
  14. else
  15. {
  16. tmpNode = l2;
  17. l2 = l2->next;
  18. }
  19. // 第二步,拼接到newHead~tail构成的链表的尾部。
  20. if( !newHead )
  21. {
  22. newHead = tail = tmpNode;
  23. }
  24. else
  25. {
  26. tail->next = tmpNode;
  27. tail = tmpNode;
  28. }
  29. }
  30. if( !l1 ) { tail->next = l2; } // l1比l2短,直接接上剩余的l2部分
  31. if( !l2 ) { tail->next = l1; } // 同理
  32. return newHead;

}

  1. <a name="rtATP"></a>
  2. ### 方法二:递归
  3. 时间复杂度:O(n+m)<br />空间复杂度:O(n+m),递归栈空间<br />既然可以迭代,就可以考虑递归
  4. ```cpp
  5. // 先去l1和l2更小的表头,比如是l1
  6. // 则接下来合并l1->next和l2即可,并让l1->next = 新合并的表头即可。
  7. ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
  8. if( !l1 ) { return l2; }
  9. if( !l2 ) { return l1; }
  10. if( l1->val < l2->val )
  11. {
  12. l1->next = mergeTwoLists( l1->next, l2 );
  13. return l1;
  14. }
  15. else
  16. {
  17. l2->next = mergeTwoLists( l1, l2->next );
  18. return l2;
  19. }
  20. }

场景二:合并K个链表

题目:http://t.cn/EAeXgtU

方法一:顺序合并

  • 时间复杂度:O(k2n)
  • 空间复杂度:O(1) ```cpp

ListNode mergeKLists( vector<ListNode> &lists ) { ListNode* head = nullptr; for( auto list : lists ) { head = mergeTwoLists( head, list ); } return head; }

  1. <a name="cQpI8"></a>
  2. ### 方法二:分治合并(递归)
  3. 归并排序的分治思想。
  4. - 时间复杂度:O(kn x logk)
  5. - 空间复杂度:O(log k)
  6. ```cpp
  7. ListNode* mergeKLists(vector<ListNode*>& lists) {
  8. if(lists.empty()) return nullptr;
  9. if(lists.size() == 1) return lists[0];
  10. return merge(lists, 0, lists.size() - 1);
  11. }
  12. ListNode* merge(vector<ListNode*>& lists, int left, int right){
  13. if(right <= left) return lists[left];
  14. int mid = left + ((right - left) >> 1);
  15. ListNode* node1 = merge(lists, left, mid);
  16. ListNode* node2 = merge(lists, mid+1, right);
  17. return mergeTwoLists(node1, node2);
  18. }
  19. ListNode* mergeTwoLists(ListNode* listNode1, ListNode* listNode2){
  20. if(!listNode1) return listNode2;
  21. if(!listNode2) return listNode1;
  22. if(listNode1->val < listNode2->val){
  23. listNode1->next = mergeTwoLists(listNode1->next, listNode2);
  24. return listNode1;
  25. }
  26. listNode2->next = mergeTwoLists(listNode1, listNode2->next);
  27. return listNode2;
  28. }

方法三:分治合并(迭代)

有递归,就可以再考虑迭代,这里我们可以借助队列queue实现。

  1. ListNode* mergeKLists( vector<ListNode*> &lists )
  2. {
  3. if( lists.empty() ) { return nullptr; }
  4. if( lists.size() == 1 ) { return lists[0]; }
  5. queue<ListNode*> q;
  6. for( auto shit : lists ) if( shit ) { q.push( shit ); }
  7. // 每次迭代去两个出来合并,在插入队列尾部。
  8. for( ListNode* node1 = nullptr, *node2 = nullptr; q.size() > 1 ; )
  9. {
  10. node1 = q.front();
  11. q.pop();
  12. node2 = q.front();
  13. q.pop();
  14. q.push( mergeTwoLists( node1, node2 ) );
  15. }
  16. if( q.empty() ) { return nullptr; }
  17. return q.front();
  18. }

方法四:优先队列

  1. struct SHIT
  2. {
  3. ListNode* node;
  4. SHIT() = default;
  5. SHIT( ListNode* n ) : node( n ) {}
  6. bool operator<( const SHIT &shit1 ) const
  7. {
  8. return node->val > shit1.node->val;
  9. }
  10. };
  11. ListNode* mergeKLists( vector<ListNode*> &lists )
  12. {
  13. priority_queue<SHIT> pq;
  14. ListNode head;
  15. for( auto shit : lists ) if( shit ) { pq.push( shit ); }
  16. for( ListNode* tail = &head; !pq.empty(); )
  17. {
  18. SHIT shit = pq.top();
  19. pq.pop();
  20. tail->next = shit.node;
  21. tail = tail->next;
  22. if( shit.node->next ) { pq.push( shit.node->next ); }
  23. }
  24. return head.next;
  25. }

方法五:普通迭代

时间复杂度:O(k2n)
空间复杂度:O(1)

  1. ListNode* mergeKLists( vector<ListNode*> &lists )
  2. {
  3. if( lists.empty() ) { return nullptr; }
  4. if( lists.size() == 1 ) { return lists[0]; }
  5. short tmpIdx = -1;
  6. ListNode head;
  7. ListNode* tail = &head;
  8. ListNode* tmpNode = nullptr;
  9. for( auto it = lists.begin(); it != lists.end(); )
  10. {
  11. if( !*it ) { it = lists.erase( it ); }
  12. else { ++it; }
  13. }
  14. while( !lists.empty() )
  15. {
  16. for( short idx = 0, size = lists.size(); idx < size; ++idx )
  17. {
  18. if( lists[idx] )
  19. {
  20. if( !tmpNode || ( tmpNode && lists[idx]->val < tmpNode->val ) )
  21. {
  22. tmpIdx = idx;
  23. tmpNode = lists[tmpIdx];
  24. }
  25. }
  26. }
  27. if( !lists[tmpIdx]->next ) { lists.erase( lists.begin() + tmpIdx ); }
  28. else { lists[tmpIdx] = lists[tmpIdx]->next; }
  29. tail->next = tmpNode;
  30. tail = tail->next;
  31. tmpNode = nullptr;
  32. }
  33. return head.next;
  34. }

训练题:翻转链表

情形一:整个翻转

题目:http://t.cn/A6cFDmo9

方法一:容器逆序

  1. // 方法一:C++ STL容器的逆序
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(n)
  4. ListNode* ReverseList( ListNode* pHead )
  5. {
  6. if( !pHead || !pHead->next ) { return pHead; }
  7. vector<ListNode*> vNodes;
  8. while( pHead )
  9. {
  10. vNodes.push_back( pHead );
  11. pHead = pHead->next;
  12. }
  13. // 将容器逆序
  14. reverse( vNodes.begin(), vNodes.end() );
  15. for( int i = 1; i < vNodes.size(); i++ )
  16. {
  17. vNodes[i - 1]->next = vNodes[i];
  18. }
  19. vNodes.back()->next = nullptr;
  20. return vNodes[0];
  21. }

方法二:栈

  1. // 方法二:栈的特性,先入栈,再弹栈。每次弹栈,插入链表尾部。
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(n)
  4. ListNode* ReverseList( ListNode* pHead )
  5. {
  6. if( !pHead || !pHead->next ) return pHead;
  7. stack<ListNode*> stackNodes;
  8. while( pHead )
  9. {
  10. stackNodes.push( pHead );
  11. pHead = pHead->next;
  12. }
  13. pHead = stackNodes.top();
  14. ListNode* cur = pHead;
  15. stackNodes.pop();
  16. while( !stackNodes.empty() )
  17. {
  18. cur->next = stackNodes.top();
  19. cur = cur->next;
  20. stackNodes.pop();
  21. }
  22. cur->next = nullptr;
  23. return pHead;
  24. }

方法三:递归一

  1. //
  2. // 递归方法一:
  3. // 设某个递归过程中,链表结构如下: * -> * -> cur -> next -> * -> ...
  4. // 递归子过程:
  5. // 1、翻转next起始的链表部分。
  6. // 2、翻转后,next为表尾,将cur接到next之后
  7. // 结果如下:* -> * -> cur <- next <- * <- ...
  8. //
  9. // 整个递归过程是从后往前进行逆序
  10. //
  11. // 时间复杂度:O(n)
  12. // 空间复杂度:O(n)
  13. ListNode* ReverseList( ListNode* pHead )
  14. {
  15. // 递归终止条件
  16. if( !pHead || !pHead->next ) { return pHead; } // 空链表或者只有1个节点。
  17. // 递归子过程:翻转head链表,就先翻转head->next链表,然后将head放到翻转后的结尾即可。
  18. ListNode* reverseNode = ReverseList_3( next ); // 翻转后:表头reverseNode,表尾next
  19. // 将head放到表尾next后面
  20. pHead->next->next = pHead;
  21. pHead->next = nullptr;
  22. return reverseNode;
  23. }

方法四:递归二

  1. // 递归方法二:当然就是从前往后进行逆序
  2. // 设某个递归过程中,链表结构如下: * <- * <- cur -> next -> * -> ...
  3. // 其中cur往左部分为已逆序部分,next之后部分为链表的原始部分
  4. // 递归一次之后:* <- * <- cur <- next -> * -> ...
  5. // 时间复杂度:O(n)
  6. // 空间复杂度:O(n)
  7. ListNode* ReverseList( ListNode* pHead )
  8. {
  9. if( !pHead || !pHead->next ) { return pHead; }
  10. ListNode* nextNode = pHead->next;
  11. pHead->next = nullptr;
  12. // 此时的pHead为首的链表是已逆序链表
  13. // 而nextNode则为pHead在原链表中的next节点
  14. return _ReverseList( pHead, nextNode ); // 尾递归,性能比普通递归要好一点
  15. }
  16. // 以curNode为首的链表是已逆序链表
  17. // nextNode为curNode在原链表中的next节点
  18. // 这里要做的就是把nextNode插入逆序链表中,使得其还是逆序,非常简单,
  19. // nextNode->next = curNode即可。则nextNode又是一个逆序链表
  20. ListNode* _ReverseList( ListNode* curNode, ListNode* nextNode )
  21. {
  22. if( !nextNode ) { return curNode; }
  23. ListNode* newNextNode = nextNode->next;
  24. nextNode->next = curNode;
  25. return _ReverseList( nextNode, newNextNode );
  26. }

方法五:迭代

  1. // 方法五:其实就是用迭代代替递归过程。
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(1)
  4. ListNode* ReverseList( ListNode* pHead )
  5. {
  6. if( !pHead || !pHead->next ) { return pHead; }
  7. // pReverseHead 逆序链表的表头
  8. // pHead 当前迭代的节点
  9. // tmpNext 下一节点
  10. // 把pHead插入到pReverseHead的表头,pHead又是一个逆序链表。
  11. // tmpNext保存了下一节点,然后继续下一个迭代。
  12. ListNode* pReverseHead = nullptr;
  13. ListNode* tmpNext = nullptr;
  14. while( pHead )
  15. {
  16. tmpNext = pHead->next;
  17. pHead->next = pReverseHead;
  18. pReverseHead = pHead;
  19. pHead = tmpNext;
  20. }
  21. return pReverseHead;
  22. }

情形二:k个一组翻转

题目:http://t.cn/A6VzVlse

方法一:栈

  1. ListNode* reverseKGroup( ListNode* head, int k )
  2. {
  3. // k = 0 / 1时,返回原数组
  4. // head为空或者只有1个元素时,返回原数组
  5. if( k < 2 || !head || !head->next ) { return head; }
  6. ListNode* newHead = nullptr; // 新链表的表头
  7. ListNode* tmpNodeStack = nullptr; // 栈遍历的tmpNode
  8. ListNode* tail = nullptr; // 新链表的表尾
  9. ListNode* oldHead = nullptr; // 在栈中原链表顺序的表头,即栈底元素
  10. stack<ListNode*> s; // 栈
  11. for( ListNode* tmpNode = head; tmpNode; )
  12. {
  13. oldHead = tmpNode;
  14. while( s.size() < k && tmpNode ) // 每次压栈k个元素
  15. {
  16. s.push( tmpNode );
  17. tmpNode = tmpNode->next;
  18. }
  19. if( s.size() == k ) // 压满,则全部弹栈,构成newHead为表头,tail为表尾的新链表
  20. {
  21. while( !s.empty() )
  22. {
  23. tmpNodeStack = s.top();
  24. s.pop();
  25. if( !newHead ) { newHead = tmpNodeStack; }
  26. if( !tail ) { tail = tmpNodeStack; }
  27. else
  28. {
  29. tail->next = tmpNodeStack;
  30. tail = tail->next;
  31. }
  32. }
  33. tail->next = nullptr;
  34. }
  35. else if(!s.empty()) // 栈中元素不够k个,按原链表顺序。
  36. {
  37. if( !newHead ) { newHead = head; } // 整个链表的数量都小于K个,链表原样输出
  38. else if( oldHead && tail ) { tail->next = oldHead; } // 将栈中的链表部分的头部oldHead接入到新链表表尾tail
  39. }
  40. }
  41. return newHead;
  42. }

方法二:递归

  1. ListNode* reverseKGroup( ListNode* head, int k )
  2. {
  3. // k = 0 / 1时,返回原数组
  4. // head为空或者只有1个元素时,返回原数组
  5. if( k < 2 || !head || !head->next ) { return head; }
  6. ListNode* tail = head;
  7. for( int i = 0; i < k ; i++ )
  8. {
  9. if( !tail ) { return head; }
  10. tail = tail->next;
  11. }
  12. // 链表初始状态
  13. // |---------k----------|
  14. // head, ..., tail1, tail, ...
  15. // 翻转[head, tail)左闭右开区间,返回的表头tail1
  16. // |---------k----------|
  17. // tail1, ..., head, tail, ...
  18. ListNode* newHead = reverse( head, tail );
  19. // 在翻转[tail, ...)区间,令head->next等于其表头
  20. head->next = reverseKGroup( tail, k );
  21. return newHead;
  22. }
  23. // 翻转链表:head, ..., tail1, tail
  24. // 得到结果:tail1, ..., head, tail
  25. // 返回tail1
  26. ListNode* reverse( ListNode* head, ListNode* tail )
  27. {
  28. if( head == tail ) { return head; }
  29. ListNode* pre = nullptr, *next = nullptr;
  30. while( head != tail )
  31. {
  32. next = head->next;
  33. head->next = pre;
  34. pre = head;
  35. head = next;
  36. }
  37. return pre;
  38. }

训练题:链表环

题目:http://t.cn/A6VZ6gl2

方法一:快慢指针

  • 时间复杂度:O(n)
  • 空间复杂度:O(1) ```cpp

bool hasCycle(ListNode *head) { if( !head || !head->next || !head->next->next ) { return false; }

  1. for(ListNode* slow = head, *fast = head; fast && fast->next;){
  2. if( slow ) slow = slow->next;
  3. fast = fast->next->next;
  4. if(slow == fast) return true;
  5. }
  6. return false;

}

  1. <a name="7yMPm"></a>
  2. ## 方法二:set集合
  3. - 时间复杂度:O(n)
  4. - 空间复杂度:O(n)
  5. ```cpp
  6. // 将所有节点记录到集合中,如果有重复,则包含环。
  7. bool hasCycle(ListNode *head) {
  8. set<ListNode*> s;
  9. while(head){
  10. if(s.count(head)) return true;
  11. s.insert(head);
  12. head = head->next;
  13. }
  14. return false;
  15. }

方法三:删除表头

  • 时间复杂度:O(n)
  • 空间复杂度:O(1) ```cpp

bool hasCycle(ListNode *head) {

  1. // 迭代删除表头head,且令head->next = head
  2. // 若没有环,则表会被全部删除
  3. // 若有环,则最终会出现head == head->next
  4. for(ListNode* tmpNode = nullptr; head;)
  5. {
  6. if( head && head == head->next ) { return head; }
  7. tmpNode = head->next;
  8. head->next = head;
  9. head = tmpNode;
  10. }
  11. return false;

}

  1. <a name="8ssuZ"></a>
  2. # 训练题:倒数第K节点
  3. 题目:[http://t.cn/A6VZCfdr](http://t.cn/A6VZCfdr)
  4. <a name="xv1Al"></a>
  5. ## 情形一:找出倒数第K节点
  6. <a name="SLaJi"></a>
  7. ### 方法一:统计链表长度
  8. - 时间复杂度:O(n) + O(n-k)
  9. - 空间复杂度:O(1)
  10. ```cpp
  11. ListNode* FindKthToTail(ListNode* pHead, int k) {
  12. if( k <= 0 ) { return nullptr; }
  13. int count = 0;
  14. for(ListNode* tmp = pHead; tmp; tmp = tmp->next, ++count);
  15. if(count < k) return nullptr;
  16. int n_k = count - k;
  17. for(ListNode* tmp = pHead;; n_k--, tmp = tmp->next){
  18. if(!n_k) return tmp;
  19. }
  20. }

方法二:双指针

  • 时间复杂度:O(n)
  • 空间复杂度:O(1) ```cpp

ListNode FindKthToTail(ListNode pHead, int k) { if( k— <= 0 ) { return nullptr; }

  1. // first和second之间距离相隔K个节点,当first到达链表尾部,second即是倒数第K个节点
  2. ListNode* first = pHead, *second = pHead;
  3. while( k-- && first ) { first = first->next; }
  4. if( !first ) { return nullptr; } // 链表没有K个节点,返回空。
  5. while( first->next )
  6. {
  7. first = first->next;
  8. second = second->next;
  9. }
  10. return second;

}

  1. <a name="1PAiW"></a>
  2. ## 情形二:删除倒数第K节点
  3. <a name="85eAN"></a>
  4. ### 方法一:统计链表长度
  5. 原理同上面方法一。
  6. <a name="Fpup3"></a>
  7. ### 方法二:双指针
  8. ```cpp
  9. ListNode* removeNthFromEnd(ListNode* head, int n) {
  10. // first和second之间距离相隔K个节点,当first到达链表尾部,second即是倒数第K个节点
  11. // beforeSecond是second之前的节点
  12. ListNode* first = head, *second = head, *beforeSecond = nullptr;
  13. while( n-- > 1 ) { first = first->next; }
  14. while( first->next )
  15. {
  16. first = first->next;
  17. beforeSecond = second;
  18. second = second->next;
  19. }
  20. if( !beforeSecond ) { head = head->next; } // 删除的是表头
  21. else { beforeSecond->next = second->next; } // 删除非表头
  22. return head;
  23. }

训练题:链表环入口

题目:http://t.cn/A6VZN4eu

方法一:集合set

  • 时间复杂度:O(n)
  • 空间复杂度:O(n) ```cpp

ListNode detectCycle(ListNode head) { set s; while( head ) { if( s.count( head ) ) { return head; } s.insert( head ); head = head->next; } return head; }

  1. <a name="fGR4u"></a>
  2. ## 方法二:删除链表头
  3. - 时间复杂度:O(n)
  4. - 空间复杂度:O(1)
  5. ```cpp
  6. ListNode *detectCycle(ListNode *head) {
  7. // 迭代删除表头head,且令head->next = head
  8. // 若没有环,则表会被全部删除
  9. // 若有环,则最终会出现head == head->next
  10. for( ListNode* tmpNode = nullptr; head; )
  11. {
  12. if( head && head == head->next ) { return head; }
  13. tmpNode = head->next;
  14. head->next = head;
  15. head = tmpNode;
  16. }
  17. return nullptr;
  18. }

方法三:快慢指针

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

算法训练_链表 - 图1
算法训练_链表 - 图2

结论:slow指针从Y开始走一圈之内就会被fast指针追上
证明:设slow指针在Y位置时,fast指针在Z位置,从Z顺时针到Y的距离为c,我们想象fast指针“追赶”slow指针,slow每走一步,之间的距离就会减少1,也就是说slow从Y位置走c步,fast指针就追赶上了slow,显然c<=b+c,得证。

因此n1=0,有a=(b+c)*n2-b。这句话用现实意义表达就是:

设有有两个速度一样的指针(一次移动一个节点)p1、p2,p1从X出发,p2从Z顺时针出发,当p1到达Y时,走过的路程为a,此时p2也到达了Y。(先转n2圈,然后再(逆时针)回退距离b)。

综上,两个速度一样的指针,一个从链表头出发,一个从快慢指针的相遇点出发,最终会在环入口相遇。

  1. ListNode *detectCycle(ListNode *head) {
  2. if( !head || !head->next || !head->next->next ) { return nullptr; }
  3. // 快慢指针找到环
  4. for( ListNode* slow = head, *fast = head; fast && fast->next; )
  5. {
  6. if( slow ) { slow = slow->next; }
  7. fast = fast->next->next;
  8. if( slow == fast ) // 有环。
  9. {
  10. slow = head;
  11. for(; slow != fast; slow = slow->next, fast = fast->next);
  12. return slow;
  13. }
  14. }
  15. return nullptr;
  16. }

训练题:链表公共节点

题目:http://t.cn/A6VA4ArJ

方法一:set集合

  1. ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2 )
  2. {
  3. if( !pHead1 || !pHead2 ) { return nullptr; }
  4. set<ListNode*> dataSet;
  5. // 从两个表头同时开始遍历,保存到set中,一旦发现已存在,则表示找到第一公共节点
  6. while( pHead1 || pHead2 )
  7. {
  8. if( pHead1 )
  9. {
  10. if( dataSet.count( pHead1 ) ) { return pHead1; }
  11. dataSet.insert( pHead1 );
  12. pHead1 = pHead1->next;
  13. }
  14. if( pHead2 )
  15. {
  16. if( dataSet.count( pHead2 ) ) { return pHead2; }
  17. dataSet.insert( pHead2 );
  18. pHead2 = pHead2->next;
  19. }
  20. }
  21. return nullptr;
  22. }

方法二:移动到相同长度位置

  1. struct ListNode* FindFirstCommonNode( struct ListNode* pHead1, struct ListNode* pHead2 )
  2. {
  3. if( !pHead1 || !pHead2 ) { return nullptr; }
  4. // 链表长度
  5. const auto len1 = findListLength( pHead1 );
  6. const auto len2 = findListLength( pHead2 );
  7. // 将长的链表头移动到和短链表头长度一样的地方。
  8. if( len1 != len2 )
  9. {
  10. if( len1 > len2 ) { pHead1 = walkStep( pHead1, len1 - len2 ); }
  11. else { pHead2 = walkStep( pHead2, len2 - len1 ); }
  12. }
  13. // 然后同时移动,这两个表头必定会在公共节点相遇
  14. for( ; pHead1; pHead1 = pHead1->next, pHead2 = pHead2->next )
  15. {
  16. if( pHead1 == pHead2 ) { return pHead1; }
  17. }
  18. return nullptr;
  19. }
  20. int findListLength( struct ListNode* pHead1 )
  21. {
  22. if( !pHead1 ) { return 0; }
  23. int sum = 0;
  24. while( ( ++sum ) && ( pHead1 = pHead1->next ) );
  25. return sum;
  26. }
  27. struct ListNode* walkStep( struct ListNode* pHead1, int step )
  28. {
  29. while( step-- ) { pHead1 = pHead1->next; }
  30. return pHead1;
  31. }

方法三:构造相同长度链表

算法训练_链表 - 图3

  1. ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
  2. if( !pHead1 || !pHead2 ) { return nullptr; }
  3. ListNode* node1 = pHead2, *node2 = pHead1;
  4. while( node1 != node2 )
  5. {
  6. node1 = node1 ? node1->next : pHead1;
  7. node2 = node2 ? node2->next : pHead2;
  8. }
  9. return node1;
  10. }

训练题:链表相加

题目:http://t.cn/A6VAJ1hk

方法一:栈

数字相加,最好从最低位开始加,因此最好从链表的表尾逆序。我们想到的第一个办法是借助栈来实现链表的逆序

  1. ListNode* addInList( ListNode* head1, ListNode* head2 )
  2. {
  3. if( !head1 ) { return head2; }
  4. if( !head2 ) { return head1; }
  5. // 两个栈分别保存,然后不断弹栈依次相加,做到从低位往高位依次相加的顺序。
  6. // 我们将结果保存到较长的链表中,如果最后还有一个进位,则用较短链表的一个节点接上。
  7. stack<ListNode*> set1, set2;
  8. stack<ListNode*> *bigSet = &set1, *smallSet = &set2; // bigSet指向更长链表的栈
  9. ListNode* headLong = head1; // 指向更长链表的表头
  10. ListNode* backupNode = nullptr; // 指向更短链表的表尾,用于最高相加之后还有1个进位时,需再加一个节点。
  11. for( ListNode* node1 = head1; node1; node1 = node1->next ) { set1.push( node1 ); }
  12. for( ListNode* node2 = head2; node2; node2 = node2->next ) { set2.push( node2 ); }
  13. if( set1.size() < set2.size() )
  14. {
  15. bigSet = &set2;
  16. smallSet = &set1;
  17. headLong = head2;
  18. backupNode = smallSet->top();
  19. }
  20. int carry = 0; //进位,1/0
  21. // 先根据更短链表进行遍历,然后将结果保存到更长的链表中
  22. for( int tmp = 0; !smallSet->empty(); smallSet->pop(), bigSet->pop() )
  23. {
  24. tmp = bigSet->top()->val + smallSet->top()->val + carry;
  25. bigSet->top()->val = tmp % 10;
  26. carry = tmp / 10;
  27. }
  28. // 再遍历更长的链表
  29. for( int tmp = 0; !bigSet->empty(); bigSet->pop() )
  30. {
  31. tmp = bigSet->top()->val + carry;
  32. bigSet->top()->val = tmp % 10;
  33. carry = tmp / 10;
  34. }
  35. // 最后还有一个进位,需额外接入一个节点到更长链表的表头。
  36. if( carry )
  37. {
  38. backupNode->val = 1;
  39. backupNode->next = headLong;
  40. headLong = backupNode;
  41. }
  42. return headLong;
  43. }

方法二:翻转链表

除了借助栈,我们还可以自己先翻转链表,然后再翻转回来。

  1. // 翻转链表
  2. ListNode* reverseList( ListNode* head, int &count )
  3. {
  4. count = 0;
  5. ListNode* pre = nullptr;
  6. ListNode* next = !head ? head : head->next;
  7. while( head )
  8. {
  9. ++count;
  10. head->next = pre;
  11. pre = head;
  12. head = next;
  13. if( next ) { next = next->next; }
  14. }
  15. return pre;
  16. }
  17. ListNode* addInList( ListNode* head1, ListNode* head2 )
  18. {
  19. if( !head1 ) { return head2; }
  20. if( !head2 ) { return head1; }
  21. if( head1 == head2 ) { return nullptr; }
  22. ListNode* backupNode = head2; // 同上,一个备份节点,用于最后还有1个进位的情况
  23. ListNode* rHeadLong1 = nullptr; // 翻转后的更长链表的表头
  24. int count1 = 0, count2 = 0; // 用于判断哪个链表更长
  25. int carry = 0; // 进位
  26. auto rHeadLong2 = rHeadLong1 = reverseList( head1, count1 ); // 翻转后更长的链表表头
  27. auto rHeadShort = reverseList( head2, count2 ); // 翻转后更短的链表表头
  28. if( count1 < count2 )
  29. {
  30. std::swap( rHeadLong2, rHeadShort );
  31. rHeadLong1 = rHeadLong2;
  32. backupNode = head1;
  33. }
  34. // 先遍历更短的链表
  35. for( int tmp = 0; rHeadShort; rHeadLong2 = rHeadLong2->next, rHeadShort = rHeadShort->next )
  36. {
  37. tmp = rHeadLong2->val + rHeadShort->val + carry;
  38. rHeadLong2->val = tmp % 10;
  39. carry = tmp / 10;
  40. }
  41. // 再遍历更长的链表
  42. for( int tmp = 0; rHeadLong2; rHeadLong2 = rHeadLong2->next )
  43. {
  44. tmp = rHeadLong2->val + carry;
  45. rHeadLong2->val = tmp % 10;
  46. carry = tmp / 10;
  47. }
  48. // 遍历完成将更长的链表再翻转
  49. rHeadLong1 = reverseList( rHeadLong1, count1 );
  50. // 最后还有一个进位的情况
  51. if( carry )
  52. {
  53. backupNode->val = 1;
  54. backupNode->next = rHeadLong1;
  55. rHeadLong1 = backupNode;
  56. }
  57. return rHeadLong1;
  58. }

训练题:LRU机制

题目:https://leetcode-cn.com/problems/lru-cache/

  1. // 双向链表
  2. class DoubleLinkNode{
  3. public:
  4. DoubleLinkNode() = default;
  5. virtual ~DoubleLinkNode() = default;
  6. DoubleLinkNode(const int& key, const int& val, DoubleLinkNode* pre = nullptr, DoubleLinkNode* next = nullptr)
  7. : key(key)
  8. , val(val)
  9. , pre(pre)
  10. , next(next) {}
  11. public:
  12. int val;
  13. int key;
  14. DoubleLinkNode* pre;
  15. DoubleLinkNode* next;
  16. };
  17. class LRUCache {
  18. public:
  19. LRUCache(int capacity)
  20. : _capacity(max(capacity, 1))
  21. , _tail(nullptr){}
  22. int get(int key) {
  23. if(!dataContainer.count(key)) return -1; // 数据不存在
  24. dataUsed(key); // 使用过了,将其放在链表尾部
  25. return dataContainer[key]->val;
  26. }
  27. void put(int key, int value) {
  28. if(dataContainer.count(key)) {
  29. dataContainer[key]->val = value;
  30. dataUsed(key); // 使用过了,将其放在链表尾部
  31. }
  32. else {
  33. if(dataContainer.size() == _capacity) handleOverload(); // 超载,清除链表首元素,也就是最远未使用元素
  34. dataContainer[key] = new DoubleLinkNode(key, value); // 创建数据节点
  35. dataAdded(key); // 放在链表尾部,这是最近使用过的元素
  36. }
  37. }
  38. private:
  39. // 数据超载,清除最远未使用元素,也就是链表首部的元素
  40. void handleOverload(){
  41. if(!_head.next) return;
  42. DoubleLinkNode* nodeToDel = _head.next; // 要删除的元素
  43. DoubleLinkNode* next = nodeToDel->next; // 该元素的next元素
  44. // 衔接删除节点后的链表
  45. _head.next = next;
  46. if(next) next->pre = &_head;
  47. dataContainer.erase(nodeToDel->key); // 清除数据
  48. if(_tail == nodeToDel) _tail = nullptr; // 删除的刚好是尾部元素,即双向链表只有一个元素
  49. delete nodeToDel; // 别忘了释放内存
  50. }
  51. // 添加了数据,应在双向链表尾部添加该元素
  52. void dataAdded(const int& key){
  53. DoubleLinkNode* tmpNode = dataContainer[key];
  54. if(!_tail){
  55. _tail = tmpNode;
  56. _head.next = _tail;
  57. _tail->pre = &_head;
  58. _tail->next = nullptr;
  59. return;
  60. }
  61. _tail->next = tmpNode;
  62. tmpNode->pre = _tail;
  63. tmpNode->next = nullptr;
  64. _tail = tmpNode;
  65. }
  66. // 数据使用过,则将该数据对应的节点放到链表尾部
  67. void dataUsed(const int& key){
  68. DoubleLinkNode* tmpNode = dataContainer[key];
  69. if(tmpNode == _tail) return;
  70. DoubleLinkNode* pre = tmpNode->pre;
  71. DoubleLinkNode* next = tmpNode->next;
  72. pre->next = next;
  73. if(next) next->pre = pre;
  74. _tail->next = tmpNode;
  75. tmpNode->pre = _tail;
  76. tmpNode->next = nullptr;
  77. _tail = tmpNode;
  78. }
  79. private:
  80. int _capacity; // 数据容量,超过则需要删除最远未使用元素
  81. // 双向链表,越往后则表示数据是越近被访问,即首元素为最远未使用元素。
  82. DoubleLinkNode _head; // 链表虚拟首部,一直存在,_head->next才是首元素
  83. DoubleLinkNode* _tail; // 链表尾部,方便尾部添加
  84. // 数据容器
  85. std::unordered_map<int, DoubleLinkNode*> dataContainer;
  86. };