203.移除链表元素
文档讲解:代码随想录
视频讲解:代码随想录
状态:已完成
对于Java而言,移除链表元素的好处之一就是不需要去人为删除那些已经释放的节点。
思路
因为是单链表,在这种情况下的移除操作,就是让节点next指针直接指向下下一个节点就可以了,
那么因为单链表的特殊性,只能指向下一个节点,刚刚删除的是链表的中第二个,和第四个节点,那么如果删除的是头结点又该怎么办呢?
这里就涉及如下链表操作的两种方式:
- 直接使用原来的链表来进行删除操作。
- 设置一个虚拟头结点在进行删除操作。
而我则使用的是设置一个虚拟头节点进行删除操作,当你设置虚拟头节点之后,之后的所有移除节点操作都是统一的。
具体代码如下:
/**
* 添加虚节点方式
* 时间复杂度 O(n)
* 空间复杂度 O(1)
* @param head
* @param val
* @return
*/
/**
* Definition for singly-linked list.
* public 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 Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode dum = new ListNode();//虚拟头节点
if(head != null){
dum.val = -1;
dum.next = head;//设当前虚拟节点为头节点
}
//设置一个可移动的指针,该指针用于删除元素
ListNode cur = new ListNode();
cur = dum;
while(cur.next != null){
if(cur.next.val == val){
cur.next = cur.next.next;
}else{
cur = cur.next;
}
}
return dum.next;
}
}
707.设计链表
文档讲解:代码随想录
视频讲解:代码随想录
状态:已完成
思路
虚拟头节点!!!
删除链表节点:
添加链表节点:
链表操作的两种方式:
- 直接使用原来的链表来进行操作。
- 设置一个虚拟头结点在进行操作。
我采用的是设置一个虚拟头结点。
class MyLinkedList {
/*
单链表实现
*/
public class ListNode{
int val;
ListNode next;
ListNode(){}
ListNode(int val){this.val = val;}
}
//虚拟头节点
ListNode dum;
//链表的大小
int size;
//链表的初始化
public MyLinkedList() {
// 这里定义的头结点 是一个虚拟头结点,而不是真正的链表头结点
//值设置为0
dum = new ListNode(0);
size = 0;
}
public int get(int index) {
if(index >= size || index < 0){
return -1;
}
ListNode cur = dum;
for(int i = 0;i < index;i++){
cur = cur.next;
}
return cur.next.val;
}
public void addAtHead(int val) {
addAtIndex(0,val);
}
public void addAtTail(int val) {
addAtIndex(size ,val);
}
// 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果 index 大于链表的长度,则返回空
public void addAtIndex(int index, int val) {
if(index < 0){
index = 0;
}
if(index <= size){
ListNode newNode = new ListNode(val);
ListNode cur = new ListNode();
cur = dum;
for(int i = 0;i <index;i++){
cur = cur.next;
}
newNode.next = cur.next;
cur.next = newNode;
size ++;
}
}
public void deleteAtIndex(int index) {
//首先对index 进行范围判断
if(index < 0 || index >= size){
return;
}
//定义一个指针,指向虚拟头节点
size--;
ListNode cur = dum;
for(int i = 0; i < index;i++){
cur = cur.next;
}
cur.next = cur.next.next;
}
}
在写这道题目的时候,因为一些小细节没注意到,导致卡了近两个小时。
- 首先,在设计链表时需要充分考虑到当前结点的前驱,在写代码时像删除与新增都是需要考虑到当前结点的前驱的;
- 其次,对于链表的增删,要十分注意对链表长度 size 的操作,千万别遗漏了;
- 最后,回顾复习的时候要仔细的查看代码梳理思路!!!
206.反转链表
思路
首先最不考虑的其实就是再定义一个新的链表来实现,这其实就是对内存的一个浪费。
!!!其实最主要的就是要考虑当前结点的 next 指针的指向
那么接下来看一看是如何反转的呢?
我们拿有示例中的链表来举例,如动画所示:(纠正:动画应该是先移动pre,在移动cur)
具体代码如下:一些对代码的理解都写在注释中,复习的时候一定要仔细看,但整体逻辑同上
当然,目前使用的是双指针加上一个临时指针的写法,并没有用到递归的方法
/**
* Definition for singly-linked list.
* public 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 Solution {
public ListNode reverseList(ListNode head) {
//当前要翻转的结点
ListNode current = head;
//要翻转的结点的前一个结点
ListNode pre = null;
//临时保存当前结点的下一结点的信息
ListNode temp;
while(current != null){
//保存下一翻转的结点信息
temp = current.next;
//当前结点的next指向前一结点
current.next = pre;
//将前一结点指向当前结点
pre = current;
//将当前结点指向下一结点
current = temp;
}
return pre;
}
}
对于递归而言,暂时不太理解
但是看了卡哥的视频,其实就是和双指针一样的逻辑,同样当current 为 null 的时候结束循环,不断将 current 指向 pre 的过程。
双指针法写出来之后,理解如下递归写法就不难了,代码逻辑都是一样的。【希望如此】
// 递归
class Solution {
public ListNode reverseList(ListNode head) {
return reverse(null, head);
}
private ListNode reverse(ListNode prev, ListNode cur) {
if (cur == null) {
return prev;
}
ListNode temp = null;
temp = cur.next;// 先保存下一个节点
cur.next = prev;// 反转
// 更新prev、cur位置
// prev = cur;
// cur = temp;
return reverse(cur, temp);
}
}
2023年9月27日22点36分
今天上课和朋友一起仔细的研究了两节课翻转链表的双指针和递归法,总结如下:
- 对于传入的 cur 是下次递归的 pre 结点;
- 对于传入的 temp 是下次递归的 cur 结点;
- 每次递归的 temp 要看成重新定义的一个变量,用于储存下次递归所调用的 cur 结点。
———之后还是需要多回顾
双指针法写出来之后,理解如下递归写法就不难了,代码逻辑都是一样的。