当前位置:   article > 正文

算法小白学习日记-2:链表

算法小白学习日记-2:链表

学习日记第二期,整理了几道链表有关题目。复杂的链表操作确实容易乱,欢迎批评指正~


题目1:删除链表的倒数第N个节点(leetcode-19)

题目描述

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

样例输入

head = [1,2,3,4,5], n = 2

样例输出

[1,2,3,5]

题目分析

本题目要用到链表操作中的两个技巧:虚拟头节点法快慢指针法

1、利用虚拟头节点法,可以避免讨论所要删除的第一个节点是否是头节点。否则,如果要删除的不是头节点,则可以重新连接要删除节点的前后两个节点,如果要产出的是头节点,则修改头节点位置为第二个节点,需要分别讨论这两种情况。

2、利用快慢指针法,我们可以方便地定位到要删除节点的前一个节点。否则需要先通过遍历求出链表的节点个数,进而定位到要删除节点的位置,而遍历操作会增加程序运行的时间。

最终代码

  1. class Solution {
  2. public:
  3. ListNode* removeNthFromEnd(ListNode* head, int n) {
  4. ListNode newhead;
  5. newhead.next = head;//定义虚拟头节点
  6. ListNode *p = &newhead, *q = p;//定义快慢指针,并让快指针提前开始移动
  7. for(int i = 0; i < n; i++){
  8. q = q->next;
  9. }
  10. while(q->next){//快慢指针隔开一点距离后,开始同时移动
  11. p = p->next;
  12. q = q->next;
  13. }
  14. p->next = p->next->next;//跳过要删除的节点
  15. return newhead.next;//注意此处要返回虚拟头节点的下一个节点
  16. }
  17. };

题目2:环形链表(leetcode-141)

题目描述

给你一个链表的头节点 head ,判断链表中是否有环。如果链表中存在环 ,则返回 true 。 否则,返回 false 。

样例输入

样例输出

true

题目分析

本题目可以利用快慢指针法进行求解,与上一题不同的是,我们令快慢指针同时出发,但是快指针一次前进两个节点,满指针一次前进一个节点。如果链表有环,则会出现套圈,即快指针追上满指针并相遇;如果链表没有环,则快指针会首先遇到空节点。

最终代码

  1. class Solution {
  2. public:
  3. bool hasCycle(ListNode *head) {
  4. ListNode *p = head, *q = head;
  5. while(p != NULL && p->next != NULL){
  6. p = p->next->next;//快指针一次前进两个节点,满指针一次只前进一个阶段
  7. q = q->next;
  8. if(p == q) return true;//两指针相遇则证明有环
  9. }
  10. return false;
  11. }
  12. };

题目3:快乐数(leetcode-202)

题目描述

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。

如果 n 是 快乐数 就返回 true ;不是,则返回 false 。(1 <= n <= 2^31 - 1

样例输入

n = 19

样例输出

true
计算过程:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

题目分析

这道题目不是一道链表的题目,但是可以用题目2的快慢指针法的思路来解决。

1、n的所有位数的平方和是有限大的。首先n是在int范围内的,最大是一个十位数。而对于一个十位数来说,想要其所有位数的平方和最大,可以另每一位都是9,即n取9999999999。此时n的所有位数的平方和为810。

2、如果n不是一个快乐数,则经过有限次迭代后,会出现迭代结果与之前某次的结果相同。经过前面的分析可以得到,每次迭代的结果最大是810,根据抽屉原理,经过有限次迭代,会出现迭代结果与之前某次的结果相同。若不好理解,可以类比:若某个学校有367个学生,则至少有两个学生的生日相同。

3、因此,本题目可以采取与快慢指针法类似的思路。即设置两个数字,他们迭代的初始值都是n,运行中一个数字每次迭代两次,一个数字每次迭代一次。如果是快乐数,则迭代快的数会首先成为1;如果不是快乐数,则在有限次迭代后,两个数的迭代结果相同。

最终代码

  1. class Solution {
  2. public:
  3. int Happy(int n){//迭代函数
  4. int a = 0;
  5. do{
  6. a += (n % 10) * (n % 10);
  7. n = n / 10;
  8. } while(n);
  9. return a;
  10. }
  11. bool isHappy(int n) {
  12. int a = n, b = n;
  13. while(1){
  14. a = Happy(a);
  15. b = Happy(Happy(b));
  16. if(b == 1) return true;//如果迭代结果为1,则是快乐数
  17. if(a == b) return false;//如果两个数字迭代结果相同,则不是快乐数
  18. }
  19. }
  20. };


总结

在做链表有关题目时,一定要理清楚每一步的逻辑,可以用笔在纸上写出每一步的流程,否则容易出现数据的丢失,或者逻辑混乱。此外,链表中也有许多经典的方法,比如虚拟头节点法,快慢指针法,可以提高结题的效率。

对于题目3,虽然这并不是一道标准的链表题目,但是本题目的迭代关系与链表类似,本质都是一通过一系列的映射,把许多值串联起来。因此,在解其他题目的时候,也可以借鉴链表的思想,把快慢指针、虚拟头节点等方法活学活用。

最后分享一个我在写程序时犯的一个小错误。在利用虚拟头节点法解题目1时,我在定义虚拟头节点时,将虚拟头节点定义为一个指针,而不是一个实际的节点,代码如下:

  1. class Solution {
  2. public:
  3. ListNode* removeNthFromEnd(ListNode* head, int n) {
  4. ListNode *newhead;//注意这里定义了指针,但没有定义指针指向哪里
  5. newhead->next = head;
  6. ListNode *p = newhead, *q = p;
  7. for(int i = 0; i < n; i++){
  8. q = q->next;
  9. }
  10. while(q->next){
  11. p = p->next;
  12. q = q->next;
  13. }
  14. p->next = p->next->next;
  15. return newhead->next;
  16. }
  17. };

运行后程序在“newhead->next = head;”了报错,后来经过思考明白了其中的逻辑:在上面的代码中,我定义了一个新的指针,想将其作为虚拟头节点,并让其指向链表的第一个节点。“newhead->next = head;”语句的意思是让newhead所指向节点的next指针等于head指针。但由于我并没有定义newhead指针指向的节点,因此程序会发生报错。

因此,为解决这个bug,需要首先定义newhead指针所指向的节点,修改程序如下:

  1. class Solution {
  2. public:
  3. ListNode* removeNthFromEnd(ListNode* head, int n) {
  4. ListNode *newhead = new ListNode;//注意这里定义了实际的节点
  5. newhead->next = head;
  6. ListNode *p = newhead, *q = p;
  7. for(int i = 0; i < n; i++){
  8. q = q->next;
  9. }
  10. while(q->next){
  11. p = p->next;
  12. q = q->next;
  13. }
  14. p->next = p->next->next;
  15. return newhead->next;
  16. }
  17. };

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

闽ICP备14008679号