203.移除链表元素
建议:本题最关键是要理解 虚拟头结点的使用技巧,这个对链表题目很重要。
题目链接:https://leetcode.cn/problems/remove-linked-list-elements/
文章链接:https://programmercarl.com/0203.%E7%A7%BB%E9%99%A4%E9%93%BE%E8%A1%A8%E5%85%83%E7%B4%A0.html
思路分析
- 首先考虑如何删除节点,如图所示,即想要删除的当前节点的前置节点指向下下个节点
- 确定当前节点为判断是否要删除的目标,即定义cur
- 删除时需要前一个节点的帮助,需要定义变量pre
- 建立虚拟头节点dummy,指向head,使链表包括头节点在内的每一个节点都有前置节点
- 判断当前节点不为空,即cur!=null,开始遍历
- 最终返回虚拟头结点dummy的下一个节点,即dummy.next
在加一点细节,代码如下:
public ListNode removeElements(ListNode head, int val) {
if (head == null || (head.next == null && head.val != val)) {
return head;
}
if (head.next == null && head.val == val) {
return null;
}
//定义虚拟头节点,指定dummy的下一个节点是头节点,建立联系,将虚拟头节点加入链表
ListNode dummy = new ListNode(-1, head);
//定义临时节点pre,cur来遍历整个链表
//为什么不用dummy,head直接遍历,如果用dummy,head遍历,他们指向的值不断地再改变,最后无法返回最初链表的头节点
ListNode pre = dummy;
ListNode cur = head;
//判断当前节点不为空时,一直遍历链表
while (cur != null) {
//如果当前节点的值等于要删除的目标
if (cur.val == val) {
//将当前节点cur的前置节点pre的下一个节点指向当前节点的下一个节点cur.next
pre.next = cur.next;
} else {
//否则,当前节点cue的前置节点pre向下走一步遍历
pre = pre.next;
}
//一直判断的都是当前节点cur的值,所以不管是否等于目标,都要向下遍历
cur = cur.next;
}
//返回最初设置的虚拟节点的下一个节点
return dummy.next;
}
总结
使用虚拟头节点的话,删除头节点和非头节点的操作是比一样的,设置一个虚拟头结点,这样原链表的所有节点就都可以按照统一的方式进行移除了
707.设计链表
建议: 这是一道考察 链表综合操作的题目,不算容易,可以练一练 使用虚拟头结点
题目链接:https://leetcode.cn/problems/design-linked-list/
文章链接:https://programmercarl.com/0707.%E8%AE%BE%E8%AE%A1%E9%93%BE%E8%A1%A8.html#%E4%BB%A3%E7%A0%81
思路分析
简单的删除节点和添加节点操作,注意的是头节点的index为0,而我们创建的虚拟头节点的索引可以看为-1
public class MyLinkedList {
//记录链表中元素的数量
int size;
//记录链表的虚拟头结点
ListNode head;
public MyLinkedList() {
//初始化操作
this.size = 0;
this.head = new ListNode(0);
}
/**
* get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
* @param index
* @return
*/
public int get(int index) {
//获取第index个节点的数值,注意index是从0开始的,第0个节点就是头结点
if (index < 0 || index >= size) {
return -1;
}
ListNode cur = head;
//包含一个虚拟头节点,所以查找第 index+1 个节点
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(size, val);
}
/**
* addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val
* 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。
* 如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
* @param index
* @param val
*/
public void addAtIndex(int index, int val) {
ListNode addNode = new ListNode(val);
if (index <= 0) {
//直接添加在头部
addNode.next = head.next;
head.next = addNode;
size++;
} else if (index <= size) {
//添加在中间或尾部
//第1个节点索引为0,所以第index+1个节点,索引为index
ListNode cur = head;
/*在链表中的第 index 个节点之前添加值为 val 的节点,
所以只需要遍历到第index-1的节点,进行添加
*/
for (int i = 0; i < index; i++) {
cur = cur.next;
}
addNode.next = cur.next;
cur.next = addNode;
size++;
}
}
/**
* deleteAtIndex(index):如果索引 index 有效,
* 则删除链表中的第 index 个节点。
* @param index
*/
public void deleteAtIndex(int index) {
if (index >= 0 && index < size) {
ListNode cur = head;
for (int i = 0; i < index ; i++) {
cur = cur.next;
}
cur.next = cur.next.next;
size--;
}
}
}
206.反转链表
题目链接:https://leetcode.cn/problems/reverse-linked-list/
思路分析
- 将已反转和未反转的看成两各部分,一直在两个部分的头,进行调换指向方向翻转操作
- 首先初始化当前节点cur=head,初始化prev=null
- 开始遍历,当cur不为空时
- 设置临时节点temp,我们在从前往后遍历的时候,在改变当前节点的指向方向时,应该记录原方向的下一个节点。若向翻转方向,没用temp保存,那么就不知道接着要遍历到哪一个节点了。
- 翻转方向cur.next=pre
- 向下遍历pre=cur,cur=temp,我们应该先移动prev,此时cur没变,cur为prev位置要遍历的下一个节点,所以先赋值prev,在赋值cur,否则我们该找不到prev按照图中从左到右遍历顺序的下一个节点
代码如下
public ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
ListNode temp = null;
while (cur != null) {
temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}
总结
首先应拆分翻转的部分,清楚每一次指向方向的改变以及谁先谁后的顺序,在遍历过程中也应该注意prev,cur,temp的赋值的先后顺序