当前位置:   article > 正文

【算法专题】链表算法题

【算法专题】链表算法题

1. 链表常用操作

        相信大家在学习数据结构的过程中已经接触过许多链表相关的题目了,在正式开始刷题之前,我想让大家先回顾一下过去处理链表相关问题时的一些常见操作。        

        首先肯定就是创建新节点了,如果使用C语言编写代码,我们需要自己用malloc函数分配内存,然后手动初始化节点,而使用C++就简单多了,直接new就行。然后是链表的尾插,这也非常简单,我们定义一个tail指针始终指向链表的尾部,当进行尾插时,令链表尾部的next指向尾插的节点,然后tail指针重新指向新的链表尾部即可。还有就是头插,如果我们直接进行头插操作,需要对链表为空的情况进行处理,但如果引入虚拟头节点newhead的话,就可以省去对边界情况的讨论,只需要令新节点指向newhead的next,然后newhead指向新节点,这样就非常方便的实现了头插。

        在这里我还想讲一些做链表题时的常用技巧,首先是画图,这有助于我们直观形象地理解题目,在处理比较复杂地题目时很有帮助;还有就是前面提到过的虚拟头节点,有了虚拟头节点,我们能够省去对许多边界情况的讨论,大大降低了代码编写的复杂程度;最后一点,我们大可不必吝啬定义一些辅助的变量,这能避免我们把自己都搞混乱了,举个例子:

相信大家如果经历过数据结构的卷面考试的话,对这种坑爹的题目肯定是不陌生的,令人看得眼花缭乱,出于考核的目的,这无可厚非,但我们自己写代码就没必要这样折磨自己了吧,直接定义一个next指针,指向prev的next,编写代码将会轻松许多。

2. 两数相加

2. 两数相加 - 力扣(LeetCode)

        本题要求我们将以链表形式逆序储存的两个数字进行相加,经常做加法的同学都知道,这实际上是降低了我们编写代码的难度,因为我们的加法运算本来就是从低位到高位进行的呀。所以我们只需要模拟加法这个过程,对两个链表进行遍历,当两个链表非空时进行相加,为了处理进位的情况,我们可以创建变量t来进行辅助运算,根据加法运算逢十进一的性质,把两个链表元素相加的结果加到t,对t取模10后添加到要返回的链表中,t除以10的结果就是进位数更新到t。只要两个链表有一个不为空或者t不为0,我们就继续运算,直到计算完毕为止。

        正如我们在常用操作那里提到的,要返回的链表我们也可以设置一个虚拟头节点,这有助于我们更轻松地编写代码,在最后,我们还需要把虚拟头节点new出来的空间销毁掉。

  1. class Solution {
  2. public:
  3. ListNode* addTwoNumbers(ListNode* l1, ListNode* l2)
  4. {
  5. ListNode *newhead = new ListNode(0);
  6. ListNode *cur = newhead, *cur1 = l1, *cur2 = l2;
  7. int t = 0;
  8. while(cur1 || cur2 || t)
  9. {
  10. if(cur1)
  11. {
  12. t += cur1->val;
  13. cur1 = cur1->next;
  14. }
  15. if(cur2)
  16. {
  17. t += cur2->val;
  18. cur2 = cur2->next;
  19. }
  20. cur->next = new ListNode(t % 10);
  21. t /= 10;
  22. cur = cur->next;
  23. }
  24. cur = newhead->next;
  25. delete newhead;
  26. return cur;
  27. }
  28. };

3. 两两交换链表节点

24. 两两交换链表中的节点 - 力扣(LeetCode)

        根据题意,我们需要把题目所给链表中的元素按两个一组的形式两两交换,并且不能只是修改链表元素的值,必须进行交换。在这里我们用迭代的方式来解决这道题,并且本题也是可以引入虚拟头节点来方便我们代码编写的。

       

如图所示,如果我们不引入虚拟头节点,最前面的两个节点的交换和后续节点的交换方式是不同的,这是因为此时首个节点并没有前驱节点,那我们就需要先设定好第二个节点作为新的头节点,而后续节点均有前驱节点,不需要进行这个步骤。如果这样的话,我们写代码就会麻烦些,但引入虚拟头节点后,首节点也有了前驱节点,这样我们就能用同一个逻辑处理所有节点了。

        

        如上图所示,画图对交换过程进行模拟,可以发现当next移动到链表尾端时,我们就能把所有符合的节点两两进行交换,接下来只要编写代码实现即可。

  1. class Solution {
  2. public:
  3. ListNode* swapPairs(ListNode* head)
  4. {
  5. if(!head || !head->next) return head;
  6. ListNode *newhead = new ListNode(0, head);
  7. ListNode *prev = newhead, *cur = head;
  8. while(cur && cur->next)
  9. {
  10. ListNode *next = cur->next, *nnext = next->next;
  11. prev->next = next;
  12. next->next = cur;
  13. cur->next = nnext;
  14. prev = cur;
  15. cur = nnext;
  16. }
  17. return newhead->next;
  18. }
  19. };

4. 重排链表

143. 重排链表 - 力扣(LeetCode)

        想必通过题目的描述,大家都能清晰地理解要求是什么,我们需要将链表的节点进行交换,最后链表被变换为首尾交替的状态。这个结果可以通过三个步骤得到:1. 找到链表的中间节点,并把链表从中间断开分为两个链表  2. 将后面的链表进行反转   3. 将前面的链表和反转后的链表合并

        这样看的话,其实每个步骤用到的知识点都不同,我们可以通过快慢指针找到链表的中间节点、头插法将后面的链表进行反转、最后合并链表。所以这是一道综合性比较强的题,能够锻炼到我们的代码能力,并且由于能一次性考好几个知识点,也是面试官喜欢出的类型。

        

  1. class Solution {
  2. public:
  3. void reorderList(ListNode* head)
  4. {
  5. // 当链表只有一个或两个时,不需要变换
  6. if(!head->next || !head->next->next) return;
  7. // 两个指针,一个一次走两步,一个一次走一步,则当快指针走完时,慢指针刚好走到中间节点
  8. ListNode *f = head, *s = head;
  9. while(f && f->next)
  10. {
  11. s = s->next;
  12. f = f->next->next;
  13. }
  14. ListNode *prev = s->next;
  15. s->next = nullptr; // 将链表分为两张
  16. ListNode *newhead = new ListNode(0);
  17. while(prev)
  18. {
  19. ListNode *next = prev->next; // 提前记录下一个节点,避免丢失
  20. prev->next = newhead->next;
  21. newhead->next = prev;
  22. prev = next;
  23. }
  24. ListNode *ret = new ListNode(0);
  25. ListNode *cur1 = head, *cur2 = newhead->next, *cur = ret;
  26. // 前面的链表肯定大于等于后面的链表
  27. while(cur1)
  28. {
  29. cur->next = cur1;
  30. cur1 = cur1->next;
  31. cur = cur->next;
  32. if(cur2)
  33. {
  34. cur->next = cur2;
  35. cur2 = cur2->next;
  36. cur = cur->next;
  37. }
  38. }
  39. // 释放空间
  40. delete ret;
  41. delete newhead;
  42. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/神奇cpp/article/detail/972927
推荐阅读
相关标签
  

闽ICP备14008679号