ps:括号里的数字代表力扣上的题号
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链表的类型:单链表、双联表、循环链表
链表的存储方式:数组在内存中是连续分布的,而链表在内存中不是连续分布的,每个节点通过之后通过连接。
(1)直接使用原来的链表来进行删除:分为头节点等于目标值和不等于目标值两种情况,进行讨论并删除节点即可。
public static ListNode removeElements(ListNode head, int val) {
ListNode temp; //节点指针
while (head!= null && head.val == val)
head = head.next;
if (head == null)
return head;
temp = head;
while (temp.next != null){
if (temp.next.val == val){
temp.next = temp.next.next;
}else
temp = temp.next;
}
return head;
}
(2)设置一个虚拟头结点在进行删除操作:不需要单独讨论头节点的特殊情况,原链表的所有节点就都可以按照统一的方式进行移除
public class MyLinkedList {
//size存储链表元素的个数
int size;
//虚拟头节点
ListNode head;
//初始化链表
public MyLinkedList() {
size = 0;
head = new ListNode(0);
}
//获得第index个节点的数值
public int get(int index) {
if (index<0 || index>=size)
return -1;
ListNode currentNode = head;
for(int i=0;i <= index;i++){
currentNode = currentNode.next;
}
return currentNode.val;
}
//在链表最前面插入一个节点
public void addAtHead(int val) {
addAtIndex(0,val);
}
//在链表的最后插入一个节点
public void addAtTail(int val) {
addAtIndex(size,val);
}
//在链表中的第 index 个节点之前添加值为 val 的节点
public void addAtIndex(int index, int val) {
if(index>size)
return;
if (index<0) {
index = 0;
}
size++;
ListNode pre = head;
for (int i=0;i<index;i++) {
pre = pre.next;
}
ListNode toAddNode = new ListNode(val);
toAddNode.next = pre.next;
pre.next = toAddNode;
}
//删除第index个节点
public void deleteAtIndex(int index) {
if (index<0 || index>size-1)
return;
size--;
ListNode pre = head;
for (int i=0;i<index;i++) {
pre = pre.next;
}
pre.next = pre.next.next;
}
}
第一想法:设置双指针,left指针指向head,right移动链表尾部,left遍历节点链表头插建立新链表。
参考题解:
(1)双指针法
直接将链表中的每一个节点的指向改变,即得到新的反转链表
public static ListNode reverseList(ListNode head) {
ListNode temp;
ListNode cur = head;
ListNode pre = null;
while (cur!=null) {
temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}
(2)递归法
递归的出口是cur指向null,当cur指向null的时候,返回pre,之前的每一层已经执行到最后一句 return reverse(cur,temp); ,故直接一层层地向上传递pre,直到退出递归。
//递归法 从后往前翻转
public static ListNode reverse(ListNode pre, ListNode cur) {
if (cur==null) return pre;
ListNode temp = cur.next;
cur.next = pre;
// 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
// pre = cur;
// cur = temp;
return reverse(cur,temp);
}
public static ListNode reverseList(ListNode head) {
return reverse(null,head);
}
第一想法:把节点拿下来重新组合节点排列顺序
参考题解:使用虚拟节点交换相邻两个节点的顺序
ps:此题代码随想录上有详细动画方便理解
public static ListNode swapPairs(ListNode head) {
ListNode dummyNode = new ListNode(); //设置一个虚拟头节点
dummyNode.next = head; //将虚拟头节点指向head节点,方便后面返回头节点
ListNode cur = dummyNode;
ListNode temp;
ListNode temp1;
while (cur.next!=null && cur.next.next!=null) {
temp = cur.next; //记录临时节点
temp1 = cur.next.next.next; //记录临时节点
cur.next = cur.next.next; //第一步
cur.next.next = temp; //第二步
cur.next.next.next = temp1; //第三步
cur = cur.next.next; //将cur向右移动两格
}
return dummyNode.next;
}
第一想法:利用快满指针,快指针先走n步,然后快慢指针同时向后移动,快指针到达链表尾部时,满指针到达链表倒数第n个节点
public static ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyNode = new ListNode(); //设置虚拟节点,避免讨论头节点删除问题
dummyNode.next = head;
ListNode left = dummyNode; //快指针
ListNode right = dummyNode; //慢指针
for (int i=0;i<n+1;i++) { //快指针 先走n+1步
right = right.next;
}
while (right != null) { //快慢指针同时移动,直到快指针移到表尾
left = left.next;
right = right.next;
}
left.next = left.next.next; //删除倒数第n个节点
return dummyNode.next;
}
第一想法:计算两个不同头节点链表的长度,但没想到怎么利用这个长度;将两个链表的节点放在两个数组中,但只能判断节点的值相等,无法判断节点指针是否相等。
参考题解:求出两个链表的长度,并求出两个链表长度的差值,然后让curA(长的那一个链表)移动到,和curB(短的那一个链表) 末尾对齐的位置,然后同时向后移动,每移动一次判断节点是否相等(包含节点值和指针)
public static ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA = headA; //A链表当前指针
ListNode curB = headB; //B链表当前指针
int lenA=0, lenB=0;
int chaAB;
while (curA != null) { //得到A链表的长度
++lenA;
curA = curA.next;
}
while (curB != null) { //得到B链表的长度
++lenB;
curB = curB.next;
}
curA = headA; //A,B链表当前指针回到各自头节点位置
curB = headB;
chaAB = lenA-lenB; //求出两链表长度差值
if (chaAB > 0) { //A链表长
while (chaAB != 0) {
curA = curA.next;
--chaAB;
}
} else { //B链表长
chaAB = -chaAB;
while (chaAB != 0) {
curB = curB.next;
--chaAB;
}
}
while (curA != null) {
if (curA == curB) //判断A,B链表当前指针是否相等
return curA;
curA = curA.next; //A,B当前指针都向后移动一步
curB = curB.next;
}
return null;
}
第一想法:new一个跟链表一样长的数组,初始化为0,从头节点开始遍历链表,若是当前节点的对应下标的数组元素值为0并且当前节点的下一个节点对应下标的数组元素值为0,那么就把当前节点对应下标的数组元素值变为1,退出循环的点即为循环入口点。
运行结果:错误;
参考题解:先利用快慢指针判断该链表是否有环,快指针每次移动两步,慢指针每次移动一步,若两指针相遇则代表该链表有环。(因为若有环,则代表每次快指针在向慢指针靠近一步,只要有环,最终一定会相遇)
再利用index1和index2,让一个指针指向头节点,另一个指针指向相遇节点,两指针每次移动一步,则他们最终相遇的位置就是循环入口点。
特点:代码简洁,主要在于数学证明。
public ListNode detectCycle(ListNode head) {
ListNode fast = head, low = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
low = low.next;
if (fast == low) { //如果快慢指针相遇,让index1指向相遇节点,index2指向头节点
ListNode index1 = fast;
ListNode index2 = head;
while (index1 != index2) { //当index1==index2相等时,index1此时即为循环入口点
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;
}