1.删除链表中等于给定值val的所有节点。
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;
}else {
prev.next=cur;
prev=cur;
}
cur=cur.next;
}
if(head.val==val) {
head=head.next;
}
return head;
}
}
总结:首先我们在列表的题目时,先判断链表是否为空。这道题运用方法是双指针也称为快慢指针。当我们拿到这道题的时候,我们先想遇到给定值怎么办,删除这个节点还是采取其他的办法,
其次这是一个链表,直接跳过这个节点就相当于删除了。遇到不同的怎么办,不同的我们是不是得让这些不同的连在一起啊,所以就有了prev.next=cur.next。最后一单也是最重要的,当首节点也是给定值的时候,所以需要判断一下。因为在循环中只有cur的val进行判断了,而prev的val没有判断。
2.反转一个单链表
class Solution {
public ListNode reverseList(ListNode head) {
if(head==null) {
return null;
}
ListNode cur=head.next;
head.next=null;
while(cur!=null) {
ListNode curNext=cur.next;
cur.next=head;
head=cur;
cur=curNext;
}
return head;
}
}
总结:一上来还是先判断这个链表是否为空。 我们在模拟实现一个链表的时候,头插法导致链表的插入顺序和显示出来的顺序是反过来的。所以这里可以运用头插法,先遍历链表,让每个节点都
都进行头插,在进行头插的时候,每个链表的下一个指向不是原来的指向,所以在进行头插之前,先保存下来这个节点的下一个指向也就是ListNode curNext=cur.next,头插完成时,再赋值给cur。
3.链表的中间节点
class Solution {
public ListNode middleNode(ListNode head) {
ListNode fast=head;
ListNode slow=head;
while(fast!=null&&fast.next!=null) {
fast=fast.next.next;
slow=slow.next;
}
return slow;
}
}
总结:这个链表不可能额为空,因为要返回中间的节点。运用的还是快慢指针,由此可见快慢指针多么重要,让fast一次走两步,slow一次走一步,退出循环的时候slow正好是中间节点。这时就有人问了,这是为什么?举个例子,有一段路程一个人的速度是另一个人的速度的两倍,当快的走完全程的时候,慢的正好走到中间,这里也是一样的。
这里还分情况讨论:当节点的数量为奇数时,如图
当节点的数量为偶数的时候,如图
4.合并两个有序链表
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
if(list1==null&&list2!=null) {
return list2;
}
if(list1!=null&&list2==null) {
return list1;
}
if(list1==null&&list2==null) {
return null;
}
ListNode head=null;
ListNode cur=null;
while(list1!=null&&list2!=null) {
if(list1.val<list2.val) {
if(head==null&&cur==null) {
head=list1;
cur=list1;
} else {
cur.next=list1;
cur=cur.next;
}
list1=list1.next;
} else {
if(cur==null&&head==null) {
head=list2;
cur=list2;
} else {
cur.next=list2;
cur=cur.next;
}
list2=list2.next;
}
}
if(list1==null) {
cur.next=list2;
}
if(list2==null) {
cur.next=list1;
}
return head;
}
}
总结:当其中一个为空,另一个就是已经合并好的,直接返回这个链表的头引用就行了。当两个链表都为空的时候,直接返回空。这道题采用的是小的进行尾插。我们在逻辑上开辟了一个新的链表,而物理上并没有。让head来指向这个新链表的头部,cur来遍历新链表。而list1和list2分别遍历自己的链表。head的指向无非就是这两个链表其中的一个,在第一次比较的时候就出现了,head等于list1或等于list2,而cur也需要这样赋值,之后就是来比较list1和list2的val值,小的链接在cur的后面。最后可能出现的情况是,一个链表遍历完了,另一个链表没有遍历完,这里所说的两个链表是list1和list2,所以需要进行判断, 不为空的插入到cur后面。
5.链表的回文结构
链表的回文结构_牛客题霸_牛客网 (nowcoder.com)
public class PalindromeList {
public boolean chkPalindrome(ListNode A) {
// write code here
ListNode head=A;
ListNode fast=A;
ListNode slow=A;
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;
}
while(head!=slow) {
if(head.val!=slow.val) {
return false;
}
if(head.next==slow) {
return true;
}
head=head.next;
slow=slow.next;
}
return true;
}
}
总结:这道题看着很难,但是经过分析过后一点也不难。在进行回文结构的判断的时候,我们先想到的是让一个引用在前面一个引用在后面,分别进行遍历。但是这是一个单链表如何进行反着遍历呢?
1.先找到这个链表的中间节点。
这个方法在上面的题目说过
2.利用中间节点再该表中间节点的指向
所谓的改变指向就是将一个节点不指向后面而是指向前面节点,其实就是反转链表,上面题目也说过。
3.然后分别从头和从尾部进行判断这个链表是否为回文结构。
当我们完成后半段链表的反转的时候slow是指向在最后一个节点的。
奇数个节点的情况
偶数个节点的数量
偶数个节点情况比较特殊一点,当我们判断完val值是否相等的时候,还要判断head.next==slow
如果相等返回直接返回true。因为当前面都没有返回的时候,此时正好head.next==slow而且他们两个所指向的val值还想等。
6.相交链表
public class Solution {
public ListNode getIntersectionNode(ListNode head1, ListNode head2) {
//相交的链表是下一个next域相同,并不是val值相等
//先让长的走差值步,之后再一起走。
int len1=0;
int len2=0;
ListNode pl=head1;
ListNode ps=head2;
while(pl!=null) {
len1++;
pl=pl.next;
}
while(ps!=null) {
len2++;
ps=ps.next;
}
int len=len1-len2;
pl=head1;
ps=head2;
if(len1-len2<0) {
pl=head2;
ps=head1;
len=len2-len1;
}
while(len!=0) {
pl=pl.next;
len--;
}
while(pl!=ps) {
pl=pl.next;
ps=ps.next;
}
//如果他们两个走到空的时候说明没有相遇,返回空,此时pl和ps正好为空。
return pl;
}
}
总结:先补充一个小知识,相交链表一定是Y字型的,而不是X字型的,因为相交链表是两个链表的其中一个节点的next域相同。如图:
整体思路就是:分别遍历自己的链表,之后让长的链表走差值步,之后在一起走。如果,指向相同即返回这个指向,如果没有相交的节点,此时pl==null,返回pl正好代表null。
还有一点:随机认定一个长的,当len结果出来后再修改数据。
7.环形链表
141. 环形链表 - 力扣(LeetCode)判断一个链表是否存在有环。
public class Solution {
public boolean hasCycle(ListNode head) {
//有没有环,看快慢指针是否能够相遇
//这里最好一个走一步,一个走两步,如果一个走的步数较多,会进行跳过另一个
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;
}
}
总结:还是快慢指针。让一个走两步,一个走一步。为什么不让一个多走几步呢?因为判断一个链表是否有环是通过fast和slow是否相遇来判断的。链表上的相遇跟我们平时的相遇不一样,fast一次走的步数多的话,会跳过这个slow,导致相遇的机会变少,也有可能导致无法相遇,所以一次走两步是最好的(fast)。 还要判断空指针的异常问题
8.环形链表Ⅱ
结论:让一个指针从链表的起始位置开始遍历链表,同时让一个指针从相遇点的位置开始绕环,两个指针每次均走一步,最终肯定会在入口点的位置相遇。
public class Solution {
public ListNode detectCycle(ListNode head) {
//结论就是,快的在相遇点到入环的第一个节点与慢的在进入环的路程相等
//也就是L等于x
//L=(N-1)C+Y说明环很小,fast走了很多圈
//慢的不可能进行套圈的,因为他们两个最大的距离为一个圈的长度,慢的不可能在环里走的一圈的,如果走一圈,
//快的走两圈,满足这种情况只有快的和慢的在入环口开始一起走,这种情况下,快的和慢的已经相遇了。
//先让他们两个相遇
//之后再让慢的从头开始走,相遇便是入环口
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;
}
}
if(fast==null||fast.next==null) {
return null;
}
slow=head;
while(fast!=slow) {
fast=fast.next;
slow=slow.next;
}
return fast;
}
}
结论推导: