(数据结构)链表OJ——刷题练习
移除链表元素
https://leetcode.cn/problems/remove-linked-list-elements/description/
思路1:查找值为val的结点并返回结点位置,删除pos位置的结点
循环遍历查找并在内部搜索位置删除——两层嵌套循环
时间复杂度:O(n^2)
思路2:创建新链表,将原链表中值不为val的结点拿下来尾插
时间复杂度:O(n)
其实并没有真的创建新链表,只是创建了新指针指向旧结点
/*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/
struct ListNode* removeElements(struct ListNode* head, int val) {typedef struct ListNode ListNode;ListNode* newHead,*newTail;newHead=newTail=NULL;ListNode* pcur=head;while(pcur!=NULL){if(pcur->val!=val){if(newHead==NULL)newHead=newTail=pcur;else{newTail->next=pcur;newTail=newTail->next;}}pcur=pcur->next;}if (newTail){newTail->next=NULL;}return newHead;
}
注意出错点:
- 定义多个指针时,每个指针前都要加*
ListNode* newHead,*newTail
- 脑子要清楚旧链表与新指针的关系,且注意对于newTail可能需要手动清空next,因为newTail指向的旧节点可能并不是旧链表的尾结点!
反转链表
https://leetcode.cn/problems/reverse-linked-list/description/
对比反转数组的操作,显著显现出数组与链表不同的物理结构——单链表不能反向遍历也不能两两交换。
思路一:创建新链表,正向遍历原链表节点,往新链表多次头插
思路二(新思路):创建三个指针n1、n2、n3,通过一系列可复制的操作在原链表上逐步改变指针的指向。
初始链表
目标链表
- 初步形成解题逻辑
看图,第一步:n1=NULL ,n3存为n2->next, 把 n2->next指向n1 实现逆置
第二步:n1=n2,n2=n3,n1=n2->next
……
操作直到完全逆置(画图可知当n2为NULL时,逆置完成)
struct ListNode* reverseList(struct ListNode* head) {struct ListNode* n1,*n2,*n3;n1=NULL,n2=head,n3=n2->next;while(n2){n2->next=n1;n1=n2;n2=n3;if(n3)n3=n3->next;}return n1;
}
- 根据测试与报错信息,补充漏掉的特殊情况处理
当head=NULL时,代码会出现错误。所以进行特殊处理
struct ListNode* reverseList(struct ListNode* head) {struct ListNode* n1,*n2,*n3;if (head==NULL)return head;n1=NULL,n2=head,n3=n2->next;while(n2){n2->next=n1;n1=n2;n2=n3;if(n3)n3=n3->next;}return n1;
}
链表的中间节点
https://leetcode.cn/problems/middle-of-the-linked-list/description/
思路1:求链表的结点总数,除2求中间位置,返回中间位置对应的结点
时间复杂度为O(n)
思路2:快慢指针
设计fast、slow两个指针,fast每次走两步,slow每次走一步,当fast走到末尾时,slow正好就走到了中间结点
数学角度:
代码示例
struct ListNode* middleNode(struct ListNode* head) {struct ListNode* fast,*slow;fast=slow=head;while(fast&&fast->next)//这里条件不能交换顺序!因为fast有可能为空,fast->就是对空指针解引用,是不合法的{slow=slow->next;fast=fast->next->next;}return slow;
}
合并两个有序链表
https://leetcode.cn/problems/merge-two-sorted-lists/description/
类似双指针法的思路
+
简化代码的思路——哨兵结点(解决了因处理空指针解引用情况而比较冗余的问题)
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {//处理其中一个链表为空的情况if(!list1) return list2;if(!list2) return list1;struct ListNode* l1,*l2;l1=list1,l2=list2;struct ListNode* newHead,*newTail;newHead=newTail=(struct ListNode*)malloc(sizeof(struct ListNode));//指向非空结点,这样newHead不需要进行二次的移动了//类似数组的双指针处理方法while(l1&&l2){if(l1->val<=l2->val){newTail->next=l1;l1=l1->next;}else if(l1->val>l2->val){newTail->next=l2;l2=l2->next;}newTail=newTail->next;}//任意一个链表读到结尾就结束if(l1){newTail->next=l1;}if(l2){newTail->next=l2;}return newHead->next;
}
链表的回文结构
https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa
思路一:创建新链表保存原链表结点,反转新链表,遍历两个链表比较结点是否相同
思路二:把链表转化成数组,利用数组的方法判定回文结构(有点投机取巧,是基于链表大小限制为900的条件实现的,如果没有这个条件本方法就做不了)
思路三(通用方法 三合一练习):找链表的中间结点,将中间结点作为新的链表的头结点,反转新链表,再比较两链表的每个结点是否相同
ListNode* findMiddleNode(ListNode* head){ListNode* fast,*slow;fast=slow=head;while(fast&&fast->next){slow=slow->next;fast=fast->next->next;}return slow;
}ListNode* reverseList(ListNode* head){ListNode* n1,*n2,*n3;if(!head) return nullptr;n1=nullptr,n2=head,n3=head->next;while(n2){n2->next=n1;n1=n2;n2=n3;if(n3){n3=n3->next;}}return n1;
}
class PalindromeList {
public:bool chkPalindrome(ListNode* A) {// write code here//1.找到中间结点作为新链表头结点ListNode* mid=findMiddleNode(A);//2.反转链表ListNode* newHead=reverseList(mid);ListNode* pcur1=A,*pcur2=newHead;//3.将两个链表进行一一比较while(pcur2){if(pcur1->val==pcur2->val){pcur1=pcur1->next;pcur2=pcur2->next;}else{return false;}}return true;}
};
相交链表
https://leetcode.cn/problems/intersection-of-two-linked-lists/description/
-
可能的相交情况
不可能存在交叉(×型)的相交,因为指针不可能同时指向两个地址! -
如何判断链表相交?
判断尾结点是否相同 -
如何找到相交的结点?
关键在于解决两个链表长度差的问题,再依次遍历即可。
求两个链表各自的长度——相减获得长度差gap——长链表先走gap步——长短链表逐个结点进行比较直至找到相等的结点
typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {ListNode* p1=headA,*p2=headB;int sizeA=0,sizeB=0;while(p1){p1=p1->next;sizeA++;}while(p2){p2=p2->next;sizeB++;}int gap=abs(sizeA-sizeB);ListNode* longList=headA,*shortList=headB;if(sizeA<sizeB){longList=headB;shortList=headA;}while(gap){longList=longList->next;gap--;}while(shortList){if(shortList==longList) return shortList;shortList=shortList->next;longList=longList->next;}return NULL;
}
环形链表
判断环形链表——快慢指针
https://leetcode.cn/problems/linked-list-cycle/description/
环形链表的尾结点next指针不为空,而是指向链表中的任意一个结点
此时原来遍历链表的方法while(pcur)会使pcur一直在环状区域内循环
如何判断环形链表?
——设置快慢指针,在链表内追逐,若链表带环,快慢指针一定会相遇。
typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head) {ListNode* slowlist=head,*fastlist=head;while(fastlist&&fastlist->next){slowlist=slowlist->next;fastlist=fastlist->next->next;if(slowlist==fastlist)return true;}return false;
}
思考:快慢指针一定能在环形链表中相遇吗?
情况1:慢指针走一步,快指针走两步
slow⼀次走⼀步,fast⼀次走2步,fast先进环,假设slow也走完入环前的距离,准备进环,此时fast和slow之间的距离为N,接下来的追逐过程中,每追击一次,他们之间的距离缩小1步,所以这种情况下快慢指针一定可以相遇。
情况2-n:快指针一次走3步,走4步,…n步可以相遇吗?
假设:
环的周长为C,头结点到slow结点的长度为L,slow走⼀步,fast走n步,当slow指针⼊环后,slow和fast指针在环中开始进行追逐,假设此时fast指针已经绕环x周。
在追逐过程中,快慢指针相遇时所走的路径长度:
fast: L+xC+C-N
slow:L
由于慢指针走⼀步,快指针要走n步,因此得出: n * 慢指针路程 = 快指针路程 ,即:
nL=L+xC+C-N
(n-1)L=(x+1)C-N
解得:x = [(n - 1)L + N]/C - 1
数学角度:对于任意给定的正整数 (L)、(C) (代表链表结构特征),以及满足 (0 < N < C) 的正整数 (N),因为 (n>= 2) 且 n 为整数,总能找到一个非负整数 x使得等式 ((n - 1)L = (x + 1)C - N) 成立。这是因为 (n - 1)、(L)、(C)、(N) 都是确定的正整数,等式左边 (n - 1)L是一个确定的值,而随着 (x) 的取值变化,等式右边 ((x + 1)C - N) 可以取到一系列值,必然存在一个合适的非负整数 x 让等式成立。
- 虽然已经证明了快指针不论走多少步都可以满足在带环链表中相遇,但是在编写代码的时候会有额外的步骤引入,涉及到快慢指针的算法题中通常习惯使用慢指针走⼀步快指针走两步的方式。
寻找环形链表的入环结点
https://leetcode.cn/problems/linked-list-cycle-ii/
头结点到入环点的距离=相遇点到入环点的距离
让⼀个指针从链表起始位置开始遍历链表,同时让⼀个指针从判环时相遇点的位置开始绕环运行,两个指针都是每次均走⼀步,最终肯定会在入口点的位置相遇。
typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) {ListNode* slow,*fast;slow=fast=head;while(fast&&fast->next){fast=fast->next->next;slow=slow->next;if(slow==fast){ListNode* pcur=head;while(pcur!=slow){pcur=pcur->next;slow=slow->next;}return pcur;}}return NULL;
}
说明:
H为链表的起始点,E为环入口点,M与判环时候相遇点
假设:
环的长度为R,H到E的距离为L,E到M的距离为 X ,则:M到E的距离为 R-X
在判环时,快慢指针相遇时所走的路径长度:
fast: L+X + nR
slow:L+X
易知快慢指针路程满足:2*slow=fast
则2 * (L+X)=L+X+nR
L+X=nR
L=nR-X
L = (n-1)R+(R-X)
极端情况下,假设n=1,此时: L=R-X
即:⼀个指针从链表起始位置运行,⼀个指针从相遇点位置绕环,每次都走⼀步,两个指针最终会在入口点的位置相遇(即本题使用的情况)