加油学习!!冲冲冲呀!
努力努力在努力!
删除链表元素
思路:
即removeAllKey()的代码:但是注意head是不可以加this的,因为该类实现的对象是通过后台进行传入的(注意部分进行修改)
代码:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeElements(ListNode head, int val) {
// 其实就相当于实现removeAllKey
// 加头结点or先处理后面的节点再处理头结点
/* // 进来首先进行头结点是否为空的判断!!
if(head == null) {
return null;
}
// 需要一个循环:把整个链表都遍历完
ListNode pre = head;
ListNode cur = head.next; //自定义类型变量存储的是地址
while (cur != null) {
if(cur.val == val) {
pre.next = cur.next;
cur = cur.next;
} else {
pre = cur;
cur = cur.next;
}
}
// 单独处理头节点的问题,刚刚没有处理头节点问题!!
if(head.val == val) { // 注意是if,不用while循环,因为上面已经处理了所有头结点之后的节点
head = head.next;
}
// 注意把头结点的处理放在最后!!:因为如果是从头结点开始连续存在要删除的key值,头结点将会变换至下一个key,也就是会一直保留一个key在头结点位置
*/
// 另一种方法是 创建一个新的头节点!!
ListNode head1 = new ListNode(-1); // 随便存一个值就行
head1.next = head;
head = head1; // 注意此处的修改!
// 然后进行遍历处理(不用单独考虑头结点)--上面方法
ListNode pre = head;
ListNode cur = head.next;
while (cur != null) {
if(cur.val == val) {
pre.next = cur.next;
cur = cur.next;
} else {
pre = cur;
cur = cur.next;
}
}
head = head.next; // 注意要将头结点的位置进行变换!!
return head;
}
}
反转链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
// 考虑只有一个结点和没有结点的情况
if(head == null) {
return null;
}
if(head.next == null) {
return head;
}
/* 以下是错误代码:
ListNode curNext = head.next; //指向第二个结点
// 因为头结点要变成尾结点,所以next进行置空
head.next = null;
// 其余结点进行头插:需要进行循环遍历链表实现
while(curNext != null) {
// 头插
ListNode cur = head;
head = curNext;
head.next = cur; // 此处也有问题:头结点的定义重要的不是变量head,而是指向性问题!!
curNext = cur.next; // 思路有问题!!
}
*/
// 更正代码:
ListNode cur = head.next; // 当前需要反转的节点,head是当前需要反转的节点的前驱!
head.next = null; // 要变成尾结点
while(cur != null) { // 进行遍历
ListNode curNext = cur.next;
cur.next = head; // 注意此处逻辑!!!
head = cur;
cur = curNext;
}
return head;
}
}
链表的中间结点
方法1:遍历一遍求长度,然后再遍历求中间结点(但是该方法至少要遍历1.5遍)
方法2:要求只遍历一遍:使用快慢指针–slow所需时间是fast的两倍,则当fast走到末尾时,slow刚好走到中间位置!(但是奇偶数走到最后是有差异的,只要满足其中一个条件就说明fast走到了最后,但是如果两个条件只要其中一个不满足就说明是还没有到达末结点,需要继续走,其实一定要注意这里的逻辑符是
逻辑与,只要一个不满足就false!!即:格外注意while循环的条件!!还有两个条件的顺序:fast在前!!:走了两步很可能先为空。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode middleNode(ListNode head) {
// 首先判断是否为空
if(head == null) {
return null;
}
ListNode slow = head;
// 注意此处错误!!快慢结点都应该从头结点开始!!
//ListNode fast = head.next;
ListNode fast = head;
// 注意此处while语句的循环条件:只要一个不满足就说明已经走到了末指针,即false的短路-逻辑与
// 注意两个表达式的顺序关系
while(fast!=null && fast.next!=null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
/*// 方法二:求长度+求中间值进行返回结点
int count = 0;
ListNode cur = head;
while((size()/2) != count) { // 这里的话要新写size()方法
cur = cur.next;
count++;
}
return cur;*/
}
}
链表中倒数第k个结点
k进行合法性判断、
方法1:遍历求长度、然后(长度-k)就是所要打印的节点,进行遍历就行
方法2:要求只遍历一遍:使用快慢指针–相差(k-1)步,fast先走(k-1)步,然后slow与fast同时走,直到fast.next
==null才停止走!
import java.util.*;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
// 首先进行合法性判断
if(k<0 || head == null) {
return null;
}
ListNode fast = head;
ListNode slow = head;
//fast 先走k-1步 然后再一起走:要求是只遍历一遍
while(k-1 != 0) { // 如果k超过长度,会空;但是k-1并不会==0而退出循环
fast = fast.next; // 如果head为空,这里就是异常语句,所以要进行空指针判断!
if(fast == null) {
return null; // k太大了就无输出!!
}
k--; // 注意k要进行变换
}
// 同时走
while(fast.next != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
合并两个有序链表
设置一个傀儡结点,然后两个链表的节点依次进行大小比较,串在傀儡结点之后(但是要注意判断两个头结点的情况)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
// 新建一个虚拟结点
ListNode newHead = new ListNode(-1); // 要让该结点保持不变,稍后好进行返回
ListNode tmp = newHead;
// 开始依次进行大小比较
while(list1!=null && list2!=null) {
if(list1.val > list2.val) {
tmp.next = list2;
list2 = list2.next;
tmp = tmp.next;
} else {
tmp.next = list1;
list1 = list1.next;
tmp = tmp.next;
}
}
// 出来则说明要么head1==null 要么head2==null (短路原则)
if(list1==null) {
tmp.next = list2;
}
if (list2==null) {
tmp.next = list1;
}
return newHead.next;
}
}
链表分割
进行一次遍历,然后把结点分别放在两个链表上(这两个链表就是以判断标识来进行区分的),然后结束后进行两个分链表的连接就行,然后把最后指向null(链表的话定义头尾结点就行)(插入方式是进行尾插)
还要考虑一个问题:第一个段是否有结点满足! 把最后指向null是在存在后半段的情况下
import java.util.*;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Partition {
public ListNode partition(ListNode pHead, int x) {
// write code here
// 分成两段:before 与 after (start & end)
ListNode bs = null;
ListNode be = null;
ListNode as = null;
ListNode ae = null;
// 再创建一个临时结点表示当前结点位置
ListNode cur = pHead;
// 进行遍历
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;
}
// 如果后半段存在
if(ae!=null) {
ae.next = null;
}
// 进行连接
be.next = as;
return bs;
}
}
链表的回文结构
回文结构:前后对称!
要求空间复杂度O(1),但是前后同时遍历的思路是不可行的,因为是单向的
解决:找中间位置、将中间之后的位置进行反转;然后就可以实现head往后走、slow往前走(快慢指针)(需要创建变量)
注意赋值方式!!
import java.util.*;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class PalindromeList {
public boolean chkPalindrome(ListNode A) {
// write code here
// 找中间结点 + 中间之后进行反转 + 前后遍历 (会使用快慢指针)
// 进行空判断
if(A == null) {
return true;
}
// 使用快慢指针找中间位置
ListNode slow = A;
ListNode fast = A;
//while(fast != null) { // 注意这里条件!!!
// 有奇偶之分,则最后判断条件也是不一样的,同时满足才进行,只要不满足一个就停止
// 注意两个表达式的顺序!!!
while (fast!=null && fast.next!=null) {
fast = fast.next.next;
slow = slow.next;
}
// 此时slow就是中间结点
// 开始对中间结点之后的节点进行反转 + 建立变量
ListNode cur = slow.next;
while (cur != null) {
ListNode curNext = cur.next;
cur.next = slow;
//slow = slow.next;// 注意此处是错误的 这样会造成一个循环
slow = cur; // 直接这样子进行赋值,类似后面的赋值
cur = curNext;
}
// 反转完毕 + 进行回文比较--head与slow分别进行前后遍历
// 注意循环条件:考虑奇偶:只是简单的head!=slow 只能解决奇数问题,
// 还有偶数问题就要另外考虑:head
// 而偶数问题:head.next == slow 就是需要终止的时候了
while(A!=slow ) {
if(A.val != slow.val) {
return false;
}
// 在这里进行判断!!! 重要!!!
if(A.next==slow) {
return true;
}
// 进行结点变换
A = A.next;
slow = slow.next;
}
// 来到这儿 说明正确
return true;
}
}
相交链表
注意:两个链表相交是地址一样,而不是值一样!!!相交是一个Y字形
思路:1.先求两个链表的长度
2.计算两个链表的差值(创建两个变量,使得一个永远指向长的,另一个是短的)
3.让最长的链表走差值步
4.两个链表一起走,相遇的点就是相交点或者是没有相交点为null
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
// 1. 求两个链表的长度
int lengthA = 0;
int lengthB = 0;
// 创建两个变量,使之一直指向最长和最短链表,然后原来头结点就会保持不变
ListNode nodeL = headA; // long
ListNode nodeS = headB; // short
while(nodeL!=null) {
lengthA++;
nodeL=nodeL.next;
}
while (nodeS!=null) {
lengthB++;
nodeS = nodeS.next;
}
// 注意要执行后面的各条语句 一定要先把nodeL/S处于头结点的位置上!!
//而此时并没有在头结点处 所以执行后面语句!!!
nodeL = headA;
nodeS = headB;
// 2. 进行长度比较 + 最长最短的变换
int len = lengthA-lengthB;
if(len<0) {
nodeL = headB;
nodeS = headA;
len = lengthB-lengthA;
}
// 3. 长的先走差值步
while(len!=0) {
nodeL = nodeL.next;
len--;
}
// 4. 两个链表同时走
// 这里写法有问题,就会缺少返回值
/*while(nodeL!=null) {
if(nodeL==nodeS) {
return nodeL;
}
nodeL = nodeL.next;
nodeS = nodeS.next;
}*/
// 注意这个还可以再进行修改:还可以更加简便!
/*while(nodeL!=nodeS && nodeL!=null) { //如果等于就是相交
nodeL = nodeL.next;
nodeS = nodeS.next;
}
if (nodeS==null) {
return null;
}*/
while(nodeL!=nodeS ) { //如果等于时要么是相交 要么是二者都已经走完为空
nodeL = nodeL.next;
nodeS = nodeS.next;
}
// 此时来到这里,要么找到交点 要么是遍历完为null,
// 不管哪种情况都是二者相等的时候,所以随便输出那个都行,况且也不用进行判断
return nodeL;
}
}
环形链表
环一定是最后一个又指向之前的某一个,所以有环链表中是不存在null的;不可能是中间的某一个又指向之前的某一个,因为一个结点只能存储一个地址
– 思路:使用快慢指针,快慢指针是有速度差的(那么重点来了:为什么slow走一步 fast走两步,而不是fast走其他步数呢? 这是一个追击问题,最坏的情况是快慢指针相差一圈,而一次两步即使是在最坏情况下也是最多走一圈就可以追上,不会存在套圈问题。(如果环是2个,走3步就会迎来擦肩而过的问题)
– 如果fast或者fast.next==null,就说明不存在环(考虑奇偶问题,所以null有两种情况)
– 每次走完都判断一下是否相等
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
// 快慢指针
ListNode fast = head;
ListNode slow = head;
// 循环判断是否有环 如果相遇就说明有环 如果其中一个(fast or fast.next)是null就说明不存在环
// 环是没有null的
/*while(fast.next!=null && fast!=null) { //注意这里的顺序是错误的!!!!!
fast = fast.next.next; //一次走两步
slow = slow.next;
// 放在后面而不是前面的原因,环至少都有两个结点!!
if(fast == slow) {
return true;
}
}
// 出来就说明遇到了null
return false;*/
// 可以修改如下;
while( fast!=null && fast.next!=null ) { // 注意顺序,只有fast补null才有next
fast = fast.next.next; //一次走两步
slow = slow.next;
if(fast == slow) {
break;
}
}
// 出来有两种情况:要么遇到null 要么相等 进行判断
// 然后一定要注意这里的判断使用声明进行判断,用是否为null是因为要考虑一个结点的情况:
// 一个结点时并不是环,环至少要两个结点,如果是用地址相等判断就会为真,
// 但是实际上是假的,因为while循环的条件中满足其中一个为假直接出来应该为假
/*if(fast == slow) {
return true;
}
return false;*/
// 所以进行修改如下:
if(fast==null || fast.next==null) { // 只要一个为真就是真 所以要注意逻辑
return false;
}
return true;
}
}
环形链表II
原理:设起始点到入口点的距离为x,设相遇点到入口点的长度为y,环的长度为C;
在相同时间内,fast速度是slow的2倍,则fast路程也是slow的两倍:x=y时就是时就可以找到入口点
so:当找到相遇点后 将其中一个指针移到头结点位置,两种=者再以相同的速度走,当二者相等时就是入口点位置。
(注:即使是fast转了好多圈也是一样的情况,slow只走一圈;然后转圈越多说明环越小)
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
// 先判断有无环:使用快慢指针 +是否相等or遇到null(循环条件)
ListNode fast = head;
ListNode slow = head;
while(fast!=null && fast.next!=null) {
fast = fast.next.next;
slow = slow.next;
// 进行判断
if(slow==fast) {
break;
}
}
// 出来就说明要么==null 要么相等
if(fast==null || fast.next==null) {
return null;
}
// 如果有环在进行前后同时走相遇的操作:走到这里就说明有环,且此时相遇
slow = head;
while(slow!=fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
力扣链表 + 牛客网
注意习题的练习!思路以及逻辑!
还有快慢指针的使用!!!