基础知识
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
由于Java中不存在指针,所以结点通常为一个类,而next则是一个结点类实例的引用。
每一结点都保存了下一个节点的地址,所以链表中的节点在内存中不是连续分布的 ,分配机制取决于操作系统的内存管理。
链表类型
- 单链表:如上述
- 双链表:
每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
既可以向前查询也可以向后查询。
- 循环链表:
链表首尾相连。
可以用来解决约瑟夫环问题。
创建结点
在Java中,我们用一个类来表示结点这个结构
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.移除链表元素
题目: 删除链表中等于给定值 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 输出:[]
思路及实现
- 直接使用原来的链表来进行删除操作。
- 设置一个虚拟头结点在进行删除操作。
1、 直接使用原来的链表来进行删除操作
移除其他节点是通过前一个节点来移除当前节点,而头结点没有前一个节点
所以需区分移除头结点和移除其他节点
移除头结点:将头结点向后移动一位即可,且 java 中会自动清理内存,不需手动从内存中删除移除的节点。
移除其他节点:也可分为两种方法
1、需要两个指针,一个指向需要移除的节点,另一个指向需要移除的节点的上一个节点,然后将上一个节点的 next 指向移除的节点的 next 即可。比如移除下图值为2的节点
注意:记得判断当前节点是否为null的情况。
代码如下:
public ListNode removeElements(ListNode head, int val) {
while (head != null && head.val == val) {
head = head.next;
}
// 已经为null,提前退出
if (head == null) {
return head;
}
// 已确定当前head.val != val
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;
}
2、需要一个指针,指向需要移除的节点的上一个节点,然后将上一个节点的 next 指向当前的节点的 next.next 即可。
注意:此时记得判断当前节点及下一个节点是否为null的情况。
代码如下:
public ListNode removeElements(ListNode head, int val) {
while(head!=null && head.val==val){
head = head.next;
}
ListNode curr = head;
while(curr!=null){
while(curr.next!=null && curr.next.val == val){
curr.next = curr.next.next;
}
curr = curr.next;
}
return head;
}
2、设置一个虚拟头结点在进行删除操作
在单链表中移除头结点 和 移除其他节点的操作方式是不一样,需要单独写一段逻辑来处理移除头结点的情况,那么可不可以 以一种统一的逻辑来移除 链表的节点呢?
可以设置一个虚拟头结点,将虚拟头结点指向 head,这样移除头结点就和移除其他节点一样了,就不需要单独写一段删除头结点的代码了。
注意:dummyNode->next 才是新的头结点,应该return dummyNode->next
代码如下:
public ListNode removeElements(ListNode head, int val) {
if (head == null) {
return head;
}
// 因为删除可能涉及到头节点,所以设置dummy节点,统一操作
ListNode dummy = new ListNode(-1, head);
ListNode pre = dummy;
ListNode cur = head;
while (cur != null) {
if (cur.val == val) {
pre.next = cur.next;
} else {
pre = cur;
}
cur = cur.next;
}
return dummy.next;
}
707.设计链表
题目:在链表类中实现这些功能:
- get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
- addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
- addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
- addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
- deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
思路及实现
首先没想到先定义ListNode类
class ListNode {
int val;
ListNode next;
ListNode(){}
ListNode(int val){
this.val = val;
}
ListNode(int val, ListNode next){
this.val = val;
this.next = next;
}
}
为了方便统一操作,均采用添加虚拟头结点的方式
获取链表中第 index 个节点的值
注意:链表下标是从0开始
- 先判断 index 是否合法,非法直接返回1
- 定义一个cur指针指向头结点,即 cur = dummyNode.next
- 采用while(index){ cur = cur.next; index--;} 的方式判断是否找到第 index 个节点。
注:确定边界条件时,可取极端情况,看是否满足情况。比如获取头结点的值,即第0个节点的值,当前 cur 就指向 head,所以不需要执行 cur = cur.next,所以 while(index) 合理。
代码如下:
public int get(int index) {
if(index < 0 || index >= size){
return -1;
}
ListNode cur = dummyNode.next;
while(index != 0){
cur = cur.next;
index--;
}
return cur.val;
}
插入节点
- 由于使用虚拟头结点,可以统一调用 addAtIndex(index,val) 这一个函数即可。
- 对于头插:相当于在第0个元素前添加,即addAtIndex(0,val)。
- 对于尾插:由于链表下标从0开始,就是添加第 size 个节点,即addAtIndex(size,val)。
在第 index 个节点前插入就是要找到第 index-1 个节点,先将新节点指向第 index 个节点,再将第 index-1 个节点指向新节点,并且size++。
代码如下:
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 > size){
return;
}
if(index < 0){
index = 0;
}
ListNode pre = dummyNode;
while(index != 0){
pre = pre.next;
index--;
}
ListNode add = new ListNode(val);
add.next = pre.next;
pre.next = add;
size++;
}
删除第 index 个节点
- 判断 index 是否合法
- 找到 第 index-1 个节点
public void deleteAtIndex(int index) {
if(index < 0 || index >=size){
return;
}
ListNode pre = dummyNode;
while(index != 0){
pre = pre.next;
index--;
}
pre.next = pre.next.next;
size--;
}
完整代码
class ListNode {
int val;
ListNode next;
ListNode(){}
ListNode(int val){
this.val = val;
}
ListNode(int val, ListNode next){
this.val = val;
this.next = next;
}
}
class MyLinkedList {
int size;
//虚拟头结点
ListNode dummyNode;
public MyLinkedList() {
size = 0;
dummyNode = new ListNode(0);
}
public int get(int index) {
if(index < 0 || index >= size){
return -1;
}
ListNode cur = dummyNode.next;
while(index != 0){
cur = cur.next;
index--;
}
return cur.val;
}
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 > size){
return;
}
if(index < 0){
index = 0;
}
ListNode pre = dummyNode;
while(index != 0){
pre = pre.next;
index--;
}
ListNode add = new ListNode(val);
add.next = pre.next;
pre.next = add;
size++;
}
public void deleteAtIndex(int index) {
if(index < 0 || index >=size){
return;
}
ListNode pre = dummyNode;
while(index != 0){
pre = pre.next;
index--;
}
pre.next = pre.next.next;
size--;
}
}
206.反转链表
题目:反转一个单链表。
示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL
思路及实现
只需要改变链表的next指针的指向,如果再定义一个新的链表,实现链表元素的反转,其实这是对内存空间的浪费。
定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null
把 cur->next 节点用tmp指针保存一下,将cur->next 指向pre
最后 cur 指针已经指向了null,循环结束,return pre指针即可
代码如下:
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode cur = head;
ListNode temp = null;
while (cur != null) {
temp = cur.next;// 保存下一个节点
cur.next = prev;
prev = cur;
cur = temp;
}
return prev;
}
}
总结
今天任务说难也不难,说简单也不简单,都是一些链表的基本操作,拖到第二天,可算完成了。