day 3 链表1
链表的基础理论
1.链表的定义|Java(代码随想录补充笔记)
在Java中,链表是一种常见的数据结构,用于存储和操作一系列的元素。链表由节点组成,每个节点包含一个数据元素和一个指向下一个节点的引用。在Java中,可以使用自定义类来定义链表。以下是一个简单的链表节点类的示例:
class ListNode {
int val;
ListNode next;
public ListNode(int val) {
this.val = val;
this.next = null;
}
}
在上面的示例中,ListNode类表示链表的一个节点。它包含一个整型的数据元素val和一个指向下一个节点的引用next。构造函数用于初始化节点的值和next引用。
要创建一个链表,可以通过创建节点对象并将它们连接起来来实现。例如,以下是一个创建链表的示例:
ListNode head = new ListNode(1);
ListNode second = new ListNode(2);
ListNode third = new ListNode(3);
head.next = second;
second.next = third;
在上面的示例中,我们创建了一个包含三个节点的链表,节点的值分别为1、2和3。通过将节点的next引用连接起来,我们形成了一个链表的结构。
public class ListNode {
// 结点的值
int val;
// 下一个结点
ListNode next;
// 节点的构造函数(无参)
public ListNode() {
}
// 节点的构造函数(有一个参数)
public ListNode(int val) {
this.val = val;
}
// 节点的构造函数(有两个参数)
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
【1】203移除链表元素
class Solution {
public ListNode removeElements(ListNode head, int val) {
if (head == null) {
return head;
}
// 创建一个dummy节点,将其指向头节点
ListNode dummy = new ListNode(-1, head);
// pre指针用于跟踪上一个节点
ListNode pre = dummy;
// cur指针用于遍历当前节点
ListNode cur = head;
// 遍历链表
while (cur != null) {
// 如果当前节点的值等于给定值,删除当前节点
if (cur.val == val) {
pre.next = cur.next;
cur = cur.next;
}
// 如果当前节点的值不等于给定值,更新pre指针为当前节点
else {
pre = cur;
cur = cur.next;
}
}
// 返回dummy节点的下一个节点作为新的头节点
return dummy.next;
}
}
【2】707.设计链表
建议: 这是一道考察 链表综合操作的题目,不算容易,可以练一练 使用虚拟头结点
注意:
1.首先记得合法性判断;
2.第n个节点一定时cur.next;
3.插入节点时,注意指针变动的现后顺序。
class MyLinkedList {
ListNode head; // 虚拟头节点
int size;
//0.MyLinkedList() 初始化 MyLinkedList 对象
public MyLinkedList() {
head = new ListNode(0); // 创建虚拟头节点
size = 0;
}
//1. 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 。
public int get(int index) {
//判断index的合法性
if (index < 0 || index >= size) {
return -1; // 索引越界,返回-1
}
ListNode curr = head.next; // 从第一个真实节点开始遍历
for (int i = 0; i < index; i++) {
curr = curr.next;
}
return curr.val;
}
//2.在链表头部添加节点
public void addAtHead(int val) {
ListNode newNode = new ListNode(val);
newNode.next = head.next; // 将新节点的下一个节点指向原来的第一个节点
head.next = newNode; // 将头节点的下一个节点指向新节点
size++;
}
//3.在链表尾部添加节点
public void addAtTail(int val) {
ListNode newNode = new ListNode(val);
ListNode curr = head;
while (curr.next != null) {
curr = curr.next;
}
curr.next = newNode; // 将最后一个节点的下一个节点指向新节点
size++;
}
//4.在指定索引处添加节点
public void addAtIndex(int index, int val) {
if (index < 0 || index > size) {
return; // 索引越界,不进行操作
}
ListNode newNode = new ListNode(val);
ListNode curr = head;
for (int i = 0; i < index; i++) {
curr = curr.next;
}
newNode.next = curr.next; // 将新节点的下一个节点指向当前节点的下一个节点
curr.next = newNode; // 将当前节点的下一个节点指向新节点
size++;
}
// 5.删除指定索引处的节点
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) {
return; // 索引越界,不进行操作
}
ListNode curr = head;
for (int i = 0; i < index; i++) {
curr = curr.next;
}
curr.next = curr.next.next; // 将当前节点的下一个节点指向下下个节点,跳过要删除的节点
size--;
}
}
【3】206.反转链表
class Solution {
public ListNode reverseList(ListNode head) {
// 初始化三个指针变量
ListNode cur = head; // 当前节点,用于遍历链表
ListNode temp = null; // 临时节点,用于保存当前节点的下一个节点
ListNode pre = null; // 反转后链表的头节点
// 遍历链表,反转节点指针
while (cur != null) {
// 保存当前节点的下一个节点
temp = cur.next;
// 将当前节点的next指针指向反转后链表的头节点
cur.next = pre;
// 更新pre为当前节点,cur为下一个节点,继续遍历
pre = cur;
cur = temp;
}
// 返回反转后的链表头节点
return pre;
}
}
day4 链表2
【4】24. 两两交换链表中的节点
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummy=new ListNode(-1); //定义一个新的虚拟头节点
dummy.next=head;//虚拟头节点的next是head
//定义4个临时节点,cur,fist,second,temp;
ListNode cur=dummy;//初始化cur指向虚拟头指针,要交换的两个节点的前一个节点
ListNode first;//临时节点,交换的两个节点的第一个节点
ListNode second;//临时节点,交换的两个节点的第二个节点
ListNode temp;//临时节点。交换的两个节点的后一个节点
while(cur.next!=null&&cur.next.next!=null){//考虑奇数个和偶数个节点的情况
first=cur.next;
second=cur.next.next;
temp=cur.next.next.next;
cur.next=second;//步骤一
second.next=first;//步骤二
first.next=temp;//步骤二
cur=first;//易错点,因为first和second交换了,所以第二轮cur指向first
}
return dummy.next;//易错点 不能返回head有可能会出错,因为head有可能是空的。
}
}
【5】19.删除链表的倒数第N个节点
//关键:找到第n个节点
//虚拟头节点
//操作指针要在删除指针节点的前一步
//快慢指针
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy=new ListNode(-1);
dummy.next=head;
ListNode fast=dummy;
ListNode slow=dummy;
//快指针先走n步,保证快慢指针相差n个节点
for(int i=0;i<n;i++){//注意这里!!!
fast=fast.next;
}
//当fast的next为空时,slow在要删除的节点的前一个节点
while(fast.next!=null){//注意这里。是fast.next!=null,而不是fast!=null
fast=fast.next;
slow=slow.next;
}
slow.next=slow.next.next;
return dummy.next;
}
}
【6】面试题 02.07. 链表相交
//简单题
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA = headA;
ListNode curB = headB;
int lenA = 0, lenB = 0;
while (curA != null) { // 求链表A的长度
lenA++;
curA = curA.next;
}
while (curB != null) { // 求链表B的长度
lenB++;
curB = curB.next;
}
curA = headA;
curB = headB;
// 让curA为最长链表的头,lenA为其长度
if (lenB > lenA) {
//1. swap (lenA, lenB);
int tmpLen = lenA;
lenA = lenB;
lenB = tmpLen;
//2. swap (curA, curB);
ListNode tmpNode = curA;
curA = curB;
curB = tmpNode;
}
// 求长度差
int gap = lenA - lenB;
// 让curA和curB在同一起点上(末尾位置对齐)
while (gap-- > 0) {
curA = curA.next;
}
// 遍历curA 和 curB,遇到相同则直接返回
while (curA != null) {
if (curA == curB) {
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
}
}
【7】142.环形链表II(难题)
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow = head; // 慢指针,每次移动一步
ListNode fast = head; // 快指针,每次移动两步
while (fast != null && fast.next != null) {
slow = slow.next; // 慢指针每次移动一步
fast = fast.next.next; // 快指针每次移动两步
if (slow == fast) { // 如果快慢指针相遇,说明有环
ListNode index1 = fast; // 创建一个指针index1,指向相遇节点
ListNode index2 = head; // 创建一个指针index2,指向链表头结点
// 两个指针从不同的起点开始,每次移动一步,直到相遇,相遇点即为环的入口节点
while (index1 != index2) {
index1 = index1.next;
index2 = index2.next;
}
return index1; // 返回环的入口节点
}
}
return null; // 如果没有环,返回null
}
}
这段代码使用快慢指针的思想来判断链表是否有环,并找到环的入口节点。首先,我们初始化两个指针slow
和fast
,它们都指向链表的头结点。然后,我们使用一个循环来移动指针,每次慢指针移动一步,快指针移动两步。如果链表有环,那么快慢指针一定会相遇。
当快慢指针相遇时,我们创建两个新的指针index1
和index2
,分别指向相遇节点和链表的头结点。然后,我们使用两个指针以相同的速度移动,每次移动一步,直到它们相遇。相遇点即为环的入口节点。
最后,我们返回环的入口节点。如果链表没有环,那么快指针会先到达链表的末尾,此时我们返回null。