链 表 OJ
- 🏳️一. 移除链表元素
- 🏴二.反转链表
- 🏁三.链表的中间结点
- 🚩四.链表中倒数第k个结点
- 🏳️🌈五.合并两个有序链表
- 🏳️⚧️六.链表的回文结构
- 🏴☠️七.链表分割
- 🏴八.相交链表
- 🏳️🌈九.环形链表
- 🍹十.环形链表 II
🏳️一. 移除链表元素
简介:
示例1:
示例2:
示例3:
提示:
要求:
首先我们需要知道的是:
所以我们在这里定两个指针cur和prev,其中cur代表的是要删除的节点,prev代表的是cur的前驱
- 先考虑特殊情况:头节点不为空
if(head == null){
return null;
}
- 定义cur和prev指针位置
ListNode cur = head.next;
ListNode prev = head;
- 在删除节点之前,我们得知道cur走动的前提是不为空的
while(cur != null){
......
}
- 在cur 不为空的前提下,我们该如何删除元素呢?
if(cur.val == val){
prev.next = cur.next;
cur = cur.next;
}else{
prev = cur;
cur = cur.next;
}
- 最终代码就完成了,但是还有一个特殊情况,那就是cur是从第二个元素开始遍历,并没有考虑到头节点,所以我们在这里单独判断一下头节点
if(head.val == val){
head = head.next;
}
附上总的代码:
class Solution {
public ListNode removeElements(ListNode head, int val) {
if(head == null){
return null;
}
ListNode cur = head.next;
ListNode prev = head;
while(cur != null){
if(cur.val == val){
prev.next = cur.next;
cur = cur.next;
}else{
prev = cur;
cur = cur.next;
}
}
//单独处理了头节点
if(head.val == val){
head = head.next;
}
return head;
}
}
🏴二.反转链表
简介:
示例 1:
示例 2:
示例 3:
提示:
要求:
注意:
反转前的链表:
反转后的链表:
由上图可知,我们需要把头节点挪到最后面,让每个节点的指向发生反向改变,这时候我们就需要一个前驱信息来支撑我们的节点指向发生改变
- 先考虑特殊情况
①:当头节点为空的情况下直接返回空
if(head == null){
return null;
}
②:只有一个节点
if(head.next == null){
return head;
}
- 根据图表直接把头节点的next设为null
head.next = null;
- 定义两个指针,一个遍历转变节点指向的指针cur,一个成为前驱信息支撑第一个指针遍历的指针curNext,cur在头节点head后一位
ListNode cur = head.next;
反转循环:
while(cur != null){
ListNode curNext = cur.next;
cur.next = head;
head = cur;
cur = curNext;
}
附上总的代码:
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null){
return null;
}
//只有一个头节点
if(head.next == null){
return head;
}
ListNode cur = head.next;
head.next = null;
while(cur != null){
ListNode curNext = cur.next;
cur.next = head;
head = cur;
cur = curNext;
}
return head;
}
}
🏁三.链表的中间结点
简介:
示例 1:
示例 2:
提示:
要求:
思路:
定义快慢指针,判断终止条件及快慢指针的走动
public ListNode middleNode(){
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
🚩四.链表中倒数第k个结点
描述:
示例1:
思路:
- 先考虑特殊情况(k的合法性和头节点)
if(k <= 0 ){
return null;
}
if(head == null){
return null;
}
- 定义快慢指针,并且执行快指针比慢指针多的步数
ListNode fast = head;
ListNode slow = head;
while(k - 1 > 0){
fast = fast.next;
if(fast == null){
return null;
}
k--;
}
- 当快指针比慢指针快了 k - 1 步的时候,两个指针一起走,直至快指针达到临界条件
while (fast.next != null){
fast = fast.next;
slow = slow.next;
}
附上总的代码:
public ListNode findKthTOTail(int k){
if(k <= 0 ){
return null;
}
if(head == null){
return null;
}
ListNode fast = head;
ListNode slow = head;
while(k - 1 > 0){
fast = fast.next;
if(fast == null){
return null;
}
k--;
}
//fast已经走了k-1步了
while (fast.next != null){
fast = fast.next;
slow = slow.next;
}
return slow;
}
🏳️🌈五.合并两个有序链表
要求:
示例 1:
示例 2:
示例 3:
提示:
思路:
- 先考虑特殊情况,任何一链表为空
if (head1 == null) return head2;
if (head2 == null) return head1;
- 定义一个新的链表来接收俩链表的合成,并且定义一个新的指针完成比较大小的走动来遍历打印新的链表
ListNode newHead = new ListNode(-1);
ListNode tmp = newHead;
- tmp作为新的指针,在比较俩链表遍历大小时,每当俩节点谁最小,tmp便指向它,然后一直这样走,就可以把俩链表整体大小归纳到tmp当中,传输给newHead链表
while(head1 != null && head2 != null){
if(head1.val < head2.val){
tmp.next = head1;
tmp = tmp.next;
head1 = head1.next;
}else {
tmp.next = head2;
tmp = tmp.next;
head2 = head2.next;
}
}
- 在这里我们还有个特殊情况,当任意一个链表走完之后,我们另外的一个链表就不用比较了,直接把剩下的节点拼接上去就可以
if(head1 != null){
tmp.next = head1;
}
if(head2 != null){
tmp.next = head2;
}
附上总的代码:
public ListNode mergeTwoLists(ListNode head1, ListNode head2) {
if (head1 == null) return head2;
if (head2 == null) return head1;
ListNode newHead = new ListNode(-1);
ListNode tmp = newHead;
while(head1 != null && head2 != null){
if(head1.val < head2.val){
tmp.next = head1;
tmp = tmp.next;
head1 = head1.next;
}else {
tmp.next = head2;
tmp = tmp.next;
head2 = head2.next;
}
}
if(head1 != null){
tmp.next = head1;
}
if(head2 != null){
tmp.next = head2;
}
return newHead.next;
}
🏳️⚧️六.链表的回文结构
描述:
给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
测试样例:
思路:
- 考虑特殊情况,链表为空或者只有一个头节点
if(this.head == null) return false;
if (this.head.next == null) return true;//只有一个节点
- 找中间节点
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
}
- 反转后半部分链表
ListNode cur = slow.next;
while(cur != null){
ListNode curNext = cur.next;
cur.next = slow;
slow = cur;
cur = curNext;
}
- slow走到最后一个节点的时候,往中间遍历,此时头节点往中间遍历,看两边分别遍历是否相同,判断是否为回文串
while (head != slow){
if(head.val != slow.val){
return false;
}
//偶数的情况
if (head.next == slow){
return true;
}
head = head.next;
slow = slow.next;
}
附上总的代码:
public boolean chkPalindrome() {
if(this.head == null) return false;
if (this.head.next == null) return true;//只有一个节点
//1.找中间节点
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
}
//2.slow一定是中间节点,再翻转
ListNode cur = slow.next;
while(cur != null){
ListNode curNext = cur.next;
cur.next = slow;
slow = cur;
cur = curNext;
}
//3.slow走到了最后一个节点的地方
while (head != slow){
if(head.val != slow.val){
return false;
}
//偶数的情况
if (head.next == slow){
return true;
}
head = head.next;
slow = slow.next;
}
return true;
}
🏴☠️七.链表分割
描述:
注意:
思路:
- 考虑特殊情况
if(head == null) return null;
- 定义两条链表的首尾节点
ListNode bs = null;
ListNode be = null;
ListNode as = null;
ListNode ae = null;
- 遍历链表,然后进行分类,判断是分类到哪一条新链表中(尾插)
ListNode cur = head;
while(cur != null){
if(cur.val < x){
//判断是不是第一次插入
if(bs == null){
bs = cur;
be = cur;
}else {
be.next = cur;
be = be.next;
}
}else {
if(as == null){
as = cur;
ae = cur;
}else {
ae.next = cur;
ae = ae.next;
}
}
cur = cur.next;
}
- 拼接两个新链表
if(bs == null){
//说明第一个区间没有数据
return as;
}
be.next = as;
if(as != null){
ae.next = null;
}
return bs;
附上总的代码:
public ListNode partition(int x) {
if(head == null) return null;
ListNode bs = null;
ListNode be = null;
ListNode as = null;
ListNode ae = null;
ListNode cur = head;
while(cur != null){
if(cur.val < x){
//判断是不是第一次插入
if(bs == null){
bs = cur;
be = cur;
}else {
be.next = cur;
be = be.next;
}
}else {
if(as == null){
as = cur;
ae = cur;
}else {
ae.next = cur;
ae = ae.next;
}
}
cur = cur.next;
}
if(bs == null){
//说明第一个区间没有数据
return as;
}
be.next = as;
if(as != null){
ae.next = null;
}
return bs;
}
🏴八.相交链表
简介:
图示两个链表在节点 c1 开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
自定义评测:
评测系统 的输入如下(你设计的程序 不适用 此输入):
评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA 和 headB 传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。
示例 1:
示例 2:
示例 3:
提示:
要求:
思路:
- 特殊情况,有链表为空
if(headA == null || headB == null) return null;
- 遍历两个链表的长度
int lenA = 0;
int lenB = 0;
ListNode pl = headA;//pl:永远指向长的链表
ListNode ps = headB;//ps:永远指向短的链表
while(pl != null){
lenA++;
pl = pl.next;
}
while (ps != null){
lenB++;
ps = ps.next;
}
- 我们还需要用到pl,ps,所以这里还需要换回来
pl = headA;
ps = headB;
- 差值步的计算和判断
//差值步 计算
int len = lenA - lenB;
if(len < 0){
pl = headB;
ps = headA;
len = lenB - lenA;//更新差值步
}
//走到这里,pl一定指向的是那个最长的链表,ps一定指向的是最短的链表,且len一定是一个正数
while (len != 0){
pl = pl.next;
len--;
}
//说明pl走了差值步了,接下来一起走直到他们两个相遇
- 差值步走完,俩链表一起走,直到相遇
while(pl != ps){
pl = pl.next;
ps = ps.next;
}
- 还要考虑特殊情况,没有交点的时候
if(headA == headB && headA == null){
return null;
}
附上总的代码:
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA == null || headB == null) return null;
//1,求俩个链表的长度
int lenA = 0;
int lenB = 0;
ListNode pl = headA;//pl:永远指向长的链表
ListNode ps = headB;//ps:永远指向短的链表
while(pl != null){
lenA++;
pl = pl.next;
}
while (ps != null){
lenB++;
ps = ps.next;
}
pl = headA;
ps = headB;
//差值步 计算
int len = lenA - lenB;
if(len < 0){
pl = headB;
ps = headA;
len = lenB - lenA;//更新差值步
}
//走到这里,pl一定指向的是那个最长的链表,ps一定指向的是最短的链表,且len一定是一个正数
while (len != 0){
pl = pl.next;
len--;
}
//说明pl走了差值步了,接下来一起走直到他们两个相遇
while(pl != ps){
pl = pl.next;
ps = ps.next;
}
//说明没有交点
if(headA == headB && headA == null){
return null;
}
return pl;
}
🏳️🌈九.环形链表
简介:
示例 1:
示例 2:
示例 3:
提示:
思路:
扩展问题:
- 为什么快指针每次走两步,慢指针走一步可以?
- 快指针一次走3步,走4步,…n步行吗?
附上总的代码:
public boolean hasCycle(ListNode head) {
if(head == null) return false;
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
return true;
}
}
return false;
}
🍹十.环形链表 II
简介:
示例 1:
示例 2:
示例 3:
提示:
导图:
思路:
fast只走了一圈的情况下:
fast走了n圈:
总结:此处说明fast此时如果和slow速度一样,让slow从起点开始走,再走X步他们就会在相遇点相遇
- 考虑特殊情况
if(head == null) return null;
- 定义快慢指针,并且由总结可知,相遇的时候需要另外挪动指针
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
break;
}
}
- break之后有两种情况,一是不满足循环,二是遇到break;说明是环且相遇
if(fast == null || fast.next == null){
return null;
}
slow = head;
while(slow != fast){
fast = fast.next;
slow = slow.next;
}
附上总的代码:
public ListNode detectCycle(ListNode head) {
if(head == null) return null;
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
break;
}
}
//走到这里说明遇到俩种情况
//1.不满足循环,有一个为空
//2.遇到break;说明是环且相遇
if(fast == null || fast.next == null){
return null;
}
slow = head;
while(slow != fast){
fast = fast.next;
slow = slow.next;
}
return slow;
}