赞
踩
学习日记第二期,整理了几道链表有关题目。复杂的链表操作确实容易乱,欢迎批评指正~
给你一个链表,删除链表的倒数第 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;//注意此处要返回虚拟头节点的下一个节点
- }
- };
给你一个链表的头节点 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;
- }
- };
编写一个算法来判断一个数 n
是不是快乐数。
「快乐数」 定义为:
如果 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;
- }
- };
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。