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

本文讲述了如何使用链表操作技巧如虚拟头节点法和快慢指针解决LeetCode中的链表删除倒数第N个节点、检测环形链表以及判断快乐数问题,强调逻辑清晰和迭代理解的重要性。
摘要由CSDN通过智能技术生成

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


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

题目描述

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

样例输入

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

样例输出

[1,2,3,5]

题目分析

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

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

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

最终代码

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        
        ListNode newhead;
        newhead.next = head;//定义虚拟头节点

        ListNode *p = &newhead, *q = p;//定义快慢指针,并让快指针提前开始移动
        for(int i = 0; i < n; i++){
            q = q->next;
        }

        while(q->next){//快慢指针隔开一点距离后,开始同时移动
            p = p->next;
            q = q->next;
        }

        p->next = p->next->next;//跳过要删除的节点

        return newhead.next;//注意此处要返回虚拟头节点的下一个节点
    }
};

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

题目描述

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

样例输入

样例输出

true

题目分析

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

最终代码

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

题目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;如果不是快乐数,则在有限次迭代后,两个数的迭代结果相同。

最终代码

class Solution {
public:
    int Happy(int n){//迭代函数
        int a = 0;
        do{
            a += (n % 10) * (n % 10);
            n = n / 10;
        } while(n);
        return a;
    }

    bool isHappy(int n) {
        int a = n, b = n;
        while(1){
            a = Happy(a);
            b = Happy(Happy(b));
            if(b == 1) return true;//如果迭代结果为1,则是快乐数
            if(a == b) return false;//如果两个数字迭代结果相同,则不是快乐数
        }
    }
};


总结

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

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

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

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        
        ListNode *newhead;//注意这里定义了指针,但没有定义指针指向哪里
        newhead->next = head;

        ListNode *p = newhead, *q = p;
        for(int i = 0; i < n; i++){
            q = q->next;
        }

        while(q->next){
            p = p->next;
            q = q->next;
        }

        p->next = p->next->next;

        return newhead->next;
    }
};

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

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


class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        
        ListNode *newhead = new ListNode;//注意这里定义了实际的节点
        newhead->next = head;

        ListNode *p = newhead, *q = p;
        for(int i = 0; i < n; i++){
            q = q->next;
        }

        while(q->next){
            p = p->next;
            q = q->next;
        }

        p->next = p->next->next;

        return newhead->next;
    }
};

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值