1.知识点
1.1链表知识
链表节点包含:值阈,指针阈
单向链表:节点指向一个方向,节点由值阈,next指针组成
双向链表:节点指向两个方向,节点由值阈,next指针,prev指针组成
循环链表:尾节点指向头节点,形成环
1.2链表性质
1.数组的存储在内存中是连续的,而链表是分散的,通过节点的指针阈将这些内存区域"连接"起来
2.链表的组成决定了,链表写方便,读不方便,所以写操作多,读操作少的场景可以考虑链表,而数组适合写操作少,读操作多的场景
1.3链表题目处理技巧
1.链表删除元素时,如果头节点是要删除的元素,需要单独考虑,为了避免这种情况,可以新增加一个虚拟头节点,并指向链表头节点,通过虚拟头节点遍历,就可以将头节点和其他节点作为一样的节点了.详见题目203代码.
2.链表增加元素时候,通过虚拟头节点得到要增加位置index的前置节点pre,注意节点处理的顺序
要先处理新增节点new.next=cur.next,最后再处理pre.next=new,详见题目707
2.刷题
203.移除链表元素
LeetCode链接 203. 移除链表元素 - 力扣(LeetCode)
题目描述
方法1:新增虚拟头节点
package daimasuixiangshuati.day03_lianbiao;
/**
* @Author LeiGe
* @Date 2023/9/23
* @Description todo
*/
public class YiChuLianBiaoYuanSu203_2 {
/**
* 方法1:设置一个虚拟头节点,dummyHead
* 方便处理头节点,如果头节点的值是要删除的值,
* 增加虚拟头节点后,头节点和其他节点一样对待了
*
* @param head
* @param val
* @return
*/
public ListNode removeElements(ListNode head, int val) {
// 设置虚拟头节点,并指向head
ListNode dummyHead = new ListNode(-2, null);
dummyHead.next = head;
//记录前一个节点:用于处理节点
ListNode pre = dummyHead;
//当前遍历到的节点:用于遍历
ListNode cur = head;
while (cur != null) {
// 如果当前遍历的节点是要删除的元素
if (cur.val == val) {
pre.next = cur.next;
} else {
pre = cur;
}
// 继续往后节点遍历
cur = cur.next;
}
// 注意这里,如果删除了头节点,头节点就变化了,需要重新赋值头节点
head = dummyHead.next;
return head;
}
/**
* 节点类
*/
public static class ListNode {
private final int val;
private ListNode next;
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
}
时间复杂度O(N)
额外空间复杂度O(1)
方法2:直接用原来的头节点
package daimasuixiangshuati.day03_lianbiao;
/**
* @Author LeiGe
* @Date 2023/9/23
* @Description todo
*/
public class YiChuLianBiaoYuanSu203_3 {
/**
* 方法2:不设置虚拟头节点
*
* @param head
* @param val
* @return
*/
public ListNode removeElements(ListNode head, int val) {
// 如果头节点是要删除的节点
while (head != null && head.val == val) {
head = head.next;
}
if (head == null) {
return null;
}
// 此时的head已经不会被删除了
//记录前一个节点:用于处理节点
ListNode pre = head;
//当前遍历到的节点:用于遍历
ListNode cur = head.next;
while (cur != null) {
if (cur.val == val) {
pre.next = cur.next;
} else {
pre = cur;
}
cur = cur.next;
}
return head;
}
/**
* 节点类
*/
public static class ListNode {
private final int val;
private ListNode next;
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
}
时间复杂度O(N)
额外空间复杂度O(1)
707.设计链表
LeetCode链接 707. 设计链表 - 力扣(LeetCode)
题目描述
方法1:单链表实现
package daimasuixiangshuati.day03_lianbiao;
/**
* @Author LeiGe
* @Date 2023/9/23
* @Description todo
*/
//单链表 节点类
class ListNode {
int val;
ListNodeDouble next;
public ListNode(int val, ListNodeDouble next) {
this.val = val;
this.next = next;
}
public ListNode(int val) {
this.val = val;
}
}
public class SheJiLianBiao707_2 {
// 记录元素个数
private int N;
// 虚拟头节点
ListNodeDouble dummyHead;
/**
* 构造函数
*/
public SheJiLianBiao707_2() {
dummyHead = new ListNodeDouble(0);
N = 0;
}
/**
* get(index):获取链表中index 节点的值。如果索引无效,则返回-1。
*
* @param index
* @return
*/
public int get(int index) {
if (index >= N || index < 0) {
return -1;
}
ListNodeDouble cur = this.dummyHead;
for (int i = 0; i <= index; i++) {
cur = cur.next;
}
return cur.val;
}
/**
* addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点
*
* @param val
*/
public void addAtHead(int val) {
addAtIndex(0, val);
}
/**
* addAtTail(val):将值为 val 的节点追加到链表的最后一个元素
*
* @param val
*/
public void addAtTail(int val) {
addAtIndex(N, val);
}
/**
* addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。
* 如果 index 等于链表的长度,则该节点将附加到链表的末尾。
* 如果 index 大于链表长度,则不会插入节点。
*
* @param index
* @param val
*/
public void addAtIndex(int index, int val) {
if (index > N) {
return;
}
if (index < 0) {
index = 0;
}
ListNodeDouble newNode = new ListNodeDouble(val);
ListNodeDouble pre = this.dummyHead;
// 找到要插入节点的前驱
for (int i = 0; i < index; i++) {
pre = pre.next;
}
// 注意:最后处理pre.next
newNode.next = pre.next;
pre.next = newNode;
N++;
}
/**
* deleteAtIndex(index):如果索引 index 有效,则删除链表中下标为 index 的节点。
*
* @param index
*/
public void deleteAtIndex(int index) {
if (index >= N || index < 0) {
return;
}
N--;
if (index == 0) {
dummyHead = dummyHead.next;
return;
}
ListNodeDouble pre = this.dummyHead;
for (int i = 0; i < index; i++) {
pre = pre.next;
}
pre.next = pre.next.next;
}
}
方法2:双链表实现
package daimasuixiangshuati.day03_lianbiao;
/**
* @Author LeiGe
* @Date 2023/9/24
* @Description todo
*/
//双链表 节点类
class ListNodeDouble {
int val;
ListNodeDouble next;
ListNodeDouble prev;
public ListNodeDouble(int val) {
this.val = val;
}
}
public class SheJiLianBiao707_3 {
int N;
// 记录链表虚拟头节点和尾节点
ListNodeDouble head;
ListNodeDouble tail;
/**
* 构造函数
*/
public SheJiLianBiao707_3() {
this.N = 0;
this.head = new ListNodeDouble(0);
this.tail = new ListNodeDouble(0);
//这一步非常关键,否则在加入头节点的操作中会出现null.next错误
head.next = tail;
tail.prev = head;
}
/**
* get(index):获取链表中index 节点的值。如果索引无效,则返回-1。
*
* @param index
* @return
*/
public int get(int index) {
if (index >= N || index < 0) {
return -1;
}
ListNodeDouble cur = this.head;
// 判断是哪一边遍历时间更短.
if (index >= N / 2) {
cur = tail;
for (int i = 0; i < N - index; i++) {
cur = cur.prev;
}
} else {
for (int i = 0; i <= index; i++) {
cur = cur.next;
}
}
return cur.val;
}
/**
* addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点
*
* @param val
*/
public void addAtHead(int val) {
addAtIndex(0, val);
}
/**
* addAtTail(val):将值为 val 的节点追加到链表的最后一个元素
*
* @param val
*/
public void addAtTail(int val) {
addAtIndex(N, val);
}
/**
* addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。
* 如果 index 等于链表的长度,则该节点将附加到链表的末尾。
* 如果 index 大于链表长度,则不会插入节点。
*
* @param index
* @param val
*/
public void addAtIndex(int index, int val) {
if (index > N) {
return;
}
if (index < 0) {
index = 0;
}
N++;
ListNodeDouble newNode = new ListNodeDouble(val);
// 找到要插入节点的前驱
ListNodeDouble pre = this.head;
for (int i = 0; i < index; i++) {
pre = pre.next;
}
// 记录下一个节点
ListNodeDouble nextNode = pre.next;
// 注意:最后修改 pre.next的值
newNode.next = nextNode;
nextNode.prev = newNode;
pre.next = newNode;
newNode.prev = pre;
}
/**
* deleteAtIndex(index):如果索引 index 有效,则删除链表中下标为 index 的节点。
*
* @param index
*/
public void deleteAtIndex(int index) {
if (index >= N || index < 0) {
return;
}
N--;
if (index == 0) {
head = head.next;
return;
}
// 找到index位置的前驱
ListNodeDouble pre = this.head;
for (int i = 0; i < index; i++) {
pre = pre.next;
}
// 注意:最后修改 pre.next的值
pre.next.next.prev = pre;
pre.next = pre.next.next;
}
}
206.反转链表
LeetCode链接 206. 反转链表 - 力扣(LeetCode)
题目描述
方法1:双指针法
package daimasuixiangshuati.day03_lianbiao;
/**
* @Author LeiGe
* @Date 2023/9/24
* @Description todo
*/
public class FanZhuanLianBiao206_2 {
/**
* 方法1-双指针法
* 1.保存下一个节点
* 2.反转(将当前节点的下一个节点变为上一个节点)
* 3.前一个节点往后走
* 4.当前节点往后走
*
* @param head
* @return
*/
public ListNode1 reverseList(ListNode1 head) {
//上一个节点
ListNode1 prev = null;
//当前节点
ListNode1 cur = head;
//下一个节点
ListNode1 temp;
while (cur != null) {
//1.保存下一个节点
temp = cur.next;
//2.反转(将当前节点的下一个节点变为上一个节点)
cur.next = prev;
//3.前一个节点往后走
prev = cur;
//4.当前节点往后走
cur = temp;
}
return prev;
}
static class ListNode1 {
int val;
ListNode1 next;
ListNode1() {
}
ListNode1(int val) {
this.val = val;
}
ListNode1(int val, ListNode1 next) {
this.val = val;
this.next = next;
}
}
}
方法2:递归法
package daimasuixiangshuati.day03_lianbiao;
/**
* @Author LeiGe
* @Date 2023/9/24
* @Description todo
*/
public class FanZhuanLianBiao206_3 {
/**
* 方法2-递归法
* 逻辑同双指针法:
* 1.记录顺序链表的前一个节点,当前节点,下一个节点
* 2.将当前链表的下一个节点设置为上一个节点
* 3.更新上一个节点,当前节点
*
* @param head
* @return
*/
public ListNode reverseList(ListNode head) {
return reverse(null, head);
}
private ListNode reverse(ListNode prev, ListNode cur) {
if (cur == null) {
return prev;
}
ListNode temp;
//保存下一个节点
temp = cur.next;
//反转
cur.next = prev;
//递归
return reverse(cur, temp);
}
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;
}
}
}
3.小结
链表基础知识,链表删除元素,链表增加元素,链表反转