1. 链表的定义和类型
- 定义:链表是通过指针串联在一起的线性结构,每一个节点包含两部分:数据+下一个节点的指针
注意:普通的单项列表不能回溯,移动前需要考虑是否需要用到目前节点的信息
- 类型:单链表、双链表、循环链表
- 双链表:每个节点有两个指针域,即可以镶嵌查询也可以向后查询
- 循环链表
- 链表在内存中的存储方式:在内存中非连续分布,通过指针域的指针连接在内存中各个节点。
2. 链表的代码实现
1)定义
在java中以class进行定义,代码实现如下:
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;
}
}
2)性能分析
删除和添加节点的复杂度都是O(1), 查找的复杂度是O(n),并且长度可以动态改变
适用场景:数据量不固定,频繁增删,较少查询
3. 203. 移除链表元素
leetcode URL: 203. 移除链表元素 - 力扣(LeetCode)
问题描述:删除所有元素为target的节点(可能需要删除不止一个)
- 思路1:分为要删除的target是头节点和不是头节点的两种情况讨论
class Solution {
public ListNode removeElements(ListNode head, int val) {
// delete head pointer
// 注意这里使用的是while循环,因为有可能是 1,1,2,3,4,此时需要删除不止一个头节点
while (head != null && head.val == val){
head = head.next;
}
// delete non-head pointer
ListNode current = head;
while(current != null && current.next != null){
//注意:这里也需要用while循环,避免target 2,需要删除[1,2,2,1]这种情况
while( current.next != null && current.next.val == val){
current.next = current.next.next;
}
current = current.next;
}
return head;
}
}
- 思路2:设置一个虚拟的头节点,以统一的方式删除所有节点,更加简洁
class Solution {
public ListNode removeElements(ListNode head, int val) {
// 新建一个虚拟的头节点,并且让他指向头节点
ListNode dummyHead = new ListNode();
dummyHead.next = head;
// 后面按照非头节点的方式删除
ListNode current = dummyHead;
while(current != null){
while(current.next!=null && current.next.val == val){
current.next = current.next.next;
}
current = current.next;
}
// 注意return的值
return dummyHead.next;
}
}
- 在这题实现过程中需要注意的问题:
1. 注意不能操作空字符串
2. 注意删除的元素可能不止一个,需要在一些地方使用while循环,判断是选择if还是while
4. 707 设计链表 (包含链表的基础操作-重要)
Leetcode link: 707. 设计链表 - 力扣(LeetCode)
问题描述:进行链表的基本操作:头/尾部插入,删除第n个,在第n个节点前插入
思路:这里运用了虚拟头的方式,先建了一个虚拟头节点
遇到的问题:由于加了虚拟头对于尾结点和size的边界不清楚,此时index=size
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;
}
}
class MyLinkedList {
//
ListNode dummyhead;
int size;
public MyLinkedList() {
size = 0;
dummyhead = new ListNode(0);
}
//获取index的节点的值
public int get(int index){
if (index < 0 || index >= size) {
return -1;
}
ListNode current = dummyhead;
while(index != 0){
current = current.next;
index--;
}
return current.next.val;
}
public void addAtHead(int val) {
addAtIndex(0,val);
}
public void addAtTail(int val) {
addAtIndex(size,val);
}
public void addAtIndex(int index, int val) {
ListNode current = dummyhead;
// 遇到的问题:面对插入最后的节点,由于虚拟头本身占一个位置,因此尾结点应该是index=size
if(index >= 0 && index <= size){
while(index != 0){
current = current.next;
index--;
}
//add
ListNode newNode = new ListNode(val);
newNode.next = current.next;
current.next = newNode;
size++;
}
}
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) {
return;
}
size--;
ListNode cur = dummyhead;
while(index != 0){
cur = cur.next;
index--;
}
cur.next = cur.next.next;
}
}
5. 206. 反转列表|双指针法|递归法
Leetcode link: 206. 反转链表 - 力扣(LeetCode)
两种写法:双指针 & 递归
- 双指针法:
两个指针:current和previous两个指针 (同时用temp存储current的下一个值)
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode current = head;
while(current!= null){
ListNode temp = current.next;
current.next = pre;
pre = current;
current = temp;
}
return pre;
}
}
2. 递归法:(有一点难理解,可以直接看206.反转链表
写的时候遇到的问题:没有理解return在递归中的作用
class Solution {
public ListNode reverseList(ListNode head) {
return reverse(null, head);
}
ListNode reverse(ListNode pre, ListNode current){
//终止方式
if(current == null) return pre;
ListNode temp = current.next;
current.next = pre;
pre = current;
// 一定要注意每个阶段都在return什么
return reverse(current,temp);
}
}
6. 小结
今天的链表部分总体来说比较简单,做的时候最好还是画图理解一下
需要注意:1. 空节点不能进行操作 2.虚拟头节点方法可以避开对head的分类讨论
3. 理解递归的思想