链表
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,存放数据的值和指向下一个节点的指针,链表的最后一个元素指向的是null
链表的三种类型,单链表和双链表还有循环链表。上述所说的就是单链表。双链表和单链表不同的地方在于多了一个指针,双链表多出来的指针会指向上一个元素,由此可知,双链表的头元素指向的也是null
循环链表为单链表的尾元素指针指向链表的头元素,形成一个循环
链表的定义
public class ListNode {
int value;
ListNode next;
public ListNode(int val) {
this.value = val;
}
public ListNode(int value, ListNode next) {
this.value = value;
this.next = next;
}
}
链表的操作
删除节点
删除D节点,只需要将C节点指向D节点的后一个节点即可,D节点会由java中的垃圾回收机制进行回收,但是注意不能频繁的进行删除节点的操作,容易造成频繁GC。那如果是删除头节点,则是将头节点设置为头节点的下一个节点。
添加节点
如果在中间添加节点,如图,在C和D中间添加一个节点F,只需要将C指向F同时将F指向D,这样就完成了添加节点的操作
由上图可见如果想删除或添加节点,可以直接添加到想要的位置,而数组则不行,数组在定义的时候已经确定了长度,如果要添加元素则需要重新定义一个新的数组。所以数组适合用于频繁查询,较少增删的场景,而链表则是适用于频繁增删,较少查询的场景。
移除链表元素
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
示例 1:
输入:head = [1,2,6,3,4,5,6], val = 6 输出:[1,2,3,4,5]
示例 2:
输入:head = [], val = 1 输出:[]
示例 3:
输入:head = [7,7,7,7], val = 7 输出:[]
提示:
- 列表中的节点数目在范围
[0, 104]
内 1 <= Node.val <= 50
0 <= val <= 50
本题目的主要思想还是按照链表的删除操作进行处理链表中的数据,因为根据上面的删除链表数据操作可知,如果是头节点删除的话,需要重新指定头节点,如果是其他部分的节点删除操作,那么只需要将被删除的节点前一个节点指向它的后一个节点就可以。那根据这个思想,我们是否可以在头节点的位置添加一个虚拟的头节点,这样链表中的任何一个数据的删除操作都是将前一个数据指向后一个数据
public ListNode removeElements(ListNode head, int val) {
//定义虚拟头节点
ListNode dummyHead = new ListNode(-1, head);
ListNode currentNode = dummyHead;
while (currentNode != null && currentNode.next != null) {
if (currentNode.next.val == val) {
currentNode.next = currentNode.next.next;
}else{
currentNode = currentNode.next;
}
}
return dummyHead.next;
}
设计链表
单链表中的节点应该具备两个属性:val
和 next
。val
是当前节点的值,next
是指向下一个节点的指针/引用。
如果是双向链表,则还需要属性 prev
以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。
实现 MyLinkedList
类:
MyLinkedList()
初始化MyLinkedList
对象。int get(int index)
获取链表中下标为index
的节点的值。如果下标无效,则返回-1
。void addAtHead(int val)
将一个值为val
的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。void addAtTail(int val)
将一个值为val
的节点追加到链表中作为链表的最后一个元素。void addAtIndex(int index, int val)
将一个值为val
的节点插入到链表中下标为index
的节点之前。如果index
等于链表的长度,那么该节点会被追加到链表的末尾。如果index
比长度更大,该节点将 不会插入 到链表中。void deleteAtIndex(int index)
如果下标有效,则删除链表中下标为index
的节点。
这题主要是考察链表的操作,修改链表数据和查询。最好也是定义一个虚拟头节点,这样添加节点的操作就可以直接使用
class ListNode {
int value;
ListNode next;
public ListNode(int val) {
this.value = val;
}
public ListNode(int value, ListNode next) {
this.value = value;
this.next = next;
}
}
class MyLinkedList {
/**
* 链表长度
*/
int size;
/**
* 虚拟头节点
*/
ListNode dummyHead;
public MyLinkedList() {
dummyHead = new ListNode(0);
size = 0;
}
public int get(int index) {
if (index < 0 || index >= size) {
return -1;
}
ListNode currentNode = dummyHead.next;
for (int i = 0; i < index; i++) {
currentNode = currentNode.next;
}
return currentNode.value;
}
public void addAtHead(int val) {
addAtIndex(0, val);
}
public void addAtTail(int val) {
addAtIndex(size, val);
}
public void addAtIndex(int index, int val) {
if (index < 0 || index > size) {
return;
}
ListNode currentNode = dummyHead;
ListNode addNode = new ListNode(val);
for (int i = 0; i < index; i++) {
currentNode = currentNode.next;
}
if (currentNode.next != null){
addNode.next = currentNode.next;
}
currentNode.next = addNode;
size++;
}
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) {
return;
}
ListNode currentNode = dummyHead;
for (int i = 0; i < index; i++) {
currentNode = currentNode.next;
}
currentNode.next = currentNode.next.next;
size--;
}
}
反转链表
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5] 输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2] 输出:[2,1]
示例 3:
输入:head = [] 输出:[]
提示:
- 链表中节点的数目范围是
[0, 5000]
-5000 <= Node.val <= 5000
这道题的主要思路是使用两个指针,一个指针指向链表的相邻位置,不停转换链表的指向。
currentNode=head,preNode=null.这时候将current.next交给临时保存,将currentNode指向preNode,再将preNode向前移动,再将currentNode向前移动。这样一直到currentNode为null时跳出循环。
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null){
return head;
}
ListNode currentNode = head;
ListNode preNode = null;
while(currentNode != null){
ListNode tempNode = currentNode.next;
currentNode.next = preNode;
preNode = currentNode;
currentNode = tempNode;
}
return preNode;
}