链表理论基础
1.分类:链表有单链表、双链表、循环链表
2.存储方式:相对于数组在内存中的连续存储,链表不是连续存储。
链表通过节点中的指针域,将内存中的各个节点连接起来,所以链表中的节点是散乱分布在内存中,分配机制取决于操作系统的内存管理。
如下图所示
3.链表操作
删除节点:将节点c的next指针指向e节点就行了。(如下图所示)
增加节点:将c节点的next指针指向新的节点就可。(如下图所示)
4、链表的定义
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;
}
}
203.移除链表元素
题目链接/文章讲解/视频讲解:点击跳转
方法:1、使用原来的链表;2、使用虚拟头结点。
整体流程如下所示:
1、删除开头所有等于 val 的节点:
head -> [val] -> [val] -> [1] -> [2] -> [val] -> [3]
↓变为
head -> [1] -> [2] -> [val] -> [3]
2、遍历链表并删除中间等于 val 的节点:(使用 curr 作为遍历指针 curr == head)
curr -> [1] -> [2] -> [val] -> [3]
↓变为
curr -> [1] -> [2] -> [3]
代码实现:
//方法1:使用原链表操作1
public ListNode removeElements(ListNode head, int val) {
//移除头结点为空或者头结点值与目标值相等的情况
while (head != null && head.val == val) {
head = head.next;
}
ListNode curr = head;
while (curr != null && curr.next != null) {
if (curr.next.val == val) {
curr.next = curr.next.next;
} else {
curr = curr.next;
}
}
return head;
}
}
curr.next = curr.next.next; 这行代码有点绕,可以换一种方式实现。
//方法1:使用原链表操作2
public ListNode removeElements(ListNode head, int val) {
//移除头结点为空或者头结点值与目标值相等的情况 head != null && head.val == val
while (head != null && head.val == val) {
head = head.next;
}
//头结点为空,直接退出操作 head == null
if (head == null) {
return null;
}
//head != null && head.val != val
ListNode pre = head; //头节点
ListNode curr = head.next; //需要判断的节点
while (curr != null) {
if (curr.val == val) {
pre.next = curr.next; //移除链表中的数据
} else {
pre = curr; //头节点后移一位
}
curr = curr.next; // 遍历的节点位置后移一位
}
return head;
}
//方法二: 使用虚拟有节点的方法,不用再考虑头节点的值与目标值相等的情况
public ListNode removeElements(ListNode head, int val) {
ListNode dummy = new ListNode();
dummy.next = head;
ListNode curr = dummy;
while (curr.next != null) {
if (curr.next.val == val) {
curr.next = curr.next.next;
} else {
curr = curr.next;
}
}
return dummy.next;
707.设计链表
题目链接/文章讲解/视频讲解:点击此处跳转
思路:画一个链表图,模拟一下操作的过程。
class MyLinkedList {
int size; //size存储链表元素的个数
ListNode head; //虚拟头结点
//初始化链表
public MyLinkedList() {
size = 0;
head = new ListNode(0);
}
//获取第index个节点的数值,注意index是从0开始的,第0个节点就是头结点
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) {
ListNode newNode = new ListNode(val);
newNode.next = head.next;
head.next = newNode;
size++;
// 在链表最前面插入一个节点,等价于在第0个元素前添加
// addAtIndex(0, val);
}
public void addAtTail(int val) {
ListNode newNode = new ListNode(val);
ListNode cur = head;
while (cur.next != null) {
cur = cur.next;
}
cur.next = newNode;
size++;
// 在链表的最后插入一个节点,等价于在(末尾+1)个元素前添加
// addAtIndex(size, val);
}
public void addAtIndex(int index, int val) {
//插入位置大于链表长度,该数据不会插入
if (index > size) {
return;
}
//插入位置小于链表的长度, 插入的位置为0
if (index < 0) {
index = 0;
}
size++;
//找到要插入位置的前一个节点
ListNode pre = head;
for (int i = 0; i < index; i++) {
pre = pre.next;
}
//插入节点
ListNode addNode = new ListNode(val);
addNode.next = pre.next;
pre.next = addNode;
}
public void deleteAtIndex(int index) {
//下标无效的情况
if (index < 0 || index >= size) {
return;
}
//下标有效,删除改下标的元素
//因为有虚拟头节点,所以不用对Index=0的情况进行特殊处理
ListNode pre = head;
size--;
for (int i = 0; i < index; i++) {
pre = pre.next;
}
pre.next = pre.next.next;
}
}
206.反转链表
题目链接/文章讲解/视频讲解:点击此处跳转
思路:使用双指针,只需要改变链表的next指针的指向,直接将链表反转 ,而不用重新定义一个新的链表。
代码如下
public ListNode reverseList(ListNode head) {
ListNode cur = head;
ListNode pre = null;
ListNode temp = null;
while(cur != null){
temp = cur.next; //保存节点
cur.next = pre; //反转指针
pre =cur; //pre指针向后移
cur = temp; //cur指针向后移动
}
return pre;
}