203.移除链表元素
题目链接:https://leetcode.cn/problems/remove-linked-list-elements/discussion/
文章地址:https://programmercarl.com/0203.%E7%A7%BB%E9%99%A4%E9%93%BE%E8%A1%A8%E5%85%83%E7%B4%A0.html#%E7%AE%97%E6%B3%95%E5%85%AC%E5%BC%80%E8%AF%BE
视频地址:
https://www.bilibili.com/video/BV18B4y1s7R9/?spm_id_from=333.788&vd_source=721f65ae0501389782be0dcb48a2c421
public class RemoveElements {
/**
* 添加虚节点方式
* 时间复杂度 O(n)
* 空间复杂度 O(1)
* @param head
* @param val
* @return
*/
public ListNode removeElements(ListNode head, int val) {
if (head == null) {
return head;
}
// 创建虚拟节点(dummy),将其 next 指针指向头节点
ListNode dummy = new ListNode(-1, head);
// 初始化前一个节点指针 pre 和当前节点指针 cur
ListNode pre = dummy;
ListNode cur = head;
while (cur != null) {
if (cur.val == val) {
// 如果当前节点的值等于目标值,则将前一个节点的 next 指针跳过当前节点,指向当前节点的下一个节点,
// 实现删除操作
pre.next = cur.next;
} else {
// 如果当前节点的值不等于目标值,则更新前一个节点指针 pre 为当前节点
pre = cur;
}
// 将当前节点指针 cur 指向下一个节点,继续遍历链表
cur = cur.next;
}
// 返回虚拟节点 dummy 的下一个节点,即删除操作后的链表头节点
return dummy.next;
}
}
小结
从内存地址的角度来分析 ListNode pre = dummy; 和 ListNode cur = head; 代码:
ListNode pre = dummy;:这行代码创建了一个新的引用变量 pre,它被赋值为 dummy 的内存地址。dummy 是一个 ListNode 对象,它在内存中有自己的地址。通过将 dummy 的地址赋值给 pre,pre 指向了和 dummy 相同的内存位置。因此,pre 和 dummy 引用的是同一个 ListNode 对象。
ListNode cur = head;:这行代码创建了另一个引用变量 cur,它被赋值为 head 的内存地址。head 是一个 ListNode 对象,它在内存中也有自己的地址。通过将 head 的地址赋值给 cur,cur 指向了和 head 相同的内存位置。因此,cur 和 head 引用的是同一个 ListNode 对象。
总结来说,ListNode pre = dummy; 和 ListNode cur = head; 这两句代码都是创建了新的引用变量,并将其分别指向了原有对象的内存地址。这样做是为了能够在后续的操作中对链表进行遍历并删除元素。
在本地的测试用例需要自己创建单链表,进行创建链接添加元素后才能做这道移除元素的题。头结点的作用用来标识单链表
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添加链接描述
视频链接:https://www.bilibili.com/video/BV1FU4y1X7WD/?vd_source=721f65ae0501389782be0dcb48a2c421
1、单链表节点
//创建(单链表)节点
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;
}
}
2、设计单链表
//单链表
public class MyLinkedList {
//size:存储链表元素的个数
int size;
//虚拟头结点
ListNode head;
//初始化链表,虚拟头结点指向给链表
public MyLinkedList(){
this.size=0;
this.head=new ListNode(0);
}
//1、获取第index个节点的数值,注意index是从0开始的,第0个节点就是头结点
public int get(int index){
//如果index非法,返回-1
if (index < 0 || index >= size){
return -1;
}
ListNode currentNode = head;
//包含虚拟节点,所以查找第index+1个节点
for (int i = 0; i <= index; i++) {
currentNode = currentNode.next;
}
return currentNode.val;
}
// 2、在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果 index 大于链表的长度,则返回空
public void addAtIndex(int index,int val){
//检验index的合法性
if(index>size){
return;
}
//如果小于0的索引默认添加到头部位置
if (index<0){
index = 0;
}
//链表长度加1
size++;
ListNode pre = head;
//找到要插入index位置的前节点
for (int i = 0; i < index; i++) {
pre = pre.next; //最后的pre存储的是要插入index位置前节点的内存地址
}
//创建要添加的节点
ListNode toAdd = new ListNode(val);
toAdd.next = pre.next;
pre.next = toAdd;
}
//3、在链表最前面插入一个节点,等价于在第0个元素前添加
public void addAtHead(int val){
this.addAtIndex(0,val);
}
//4、在链表的最后插入一个节点,等价于在(末尾+1)个元素前添加
public void addAtTail(int val){
addAtIndex(size,val);
}
//删除第index个节点
public void deleteAtIndex(int index){
//验证index合法性
if (index < 0 || index >= size) {
return;
}
//减少链表的长度
size--;
if (index == 0){
head = head.next;
return;
}
ListNode pre = head;
for (int i = 0; i < index; i++) {
pre = pre.next;
}
pre.next = pre.next.next;
}
}
3、双链表节点
//创建双链表节点
public class DoubleListNode {
//data域
int val;
//前后两个指针,其实也就是两个node节点的变量,存储的是链接node的内存地址
//pre:指向上一个节点
//next:指向下一个节点
DoubleListNode next,pre;
public DoubleListNode() {
}
public DoubleListNode(int val) {
this.val = val;
}
}
4、设计双链表
//双链表
public class MyDoubleLinkedList {
//记录链表中元素的数量
int size;
//记录链表的虚拟头结点和尾结点
DoubleListNode head, tail;
//1、初始化一个双链表
public MyDoubleLinkedList() {
size = 0;
head = new DoubleListNode(0);
tail = new DoubleListNode(0);
head.next = tail;
tail.pre = head;
}
//2、在index位置上添加元素
public void addAtIndex(int index, int val) {
//验证Index的合法性
if (index > size) {
return;
}
//如果小于0统一默认在第0个 位置插入
if (index < 0) {
index = 0;
}
//链表长度加1
size++;
DoubleListNode pre = head;
//如果index = 0 的情况,不需要再寻找前驱节点,跳过for循环
for (int i = 0; i < index; i++) {
pre = pre.next;
}
//新建节点
DoubleListNode newNode = new DoubleListNode(val);
//链接总共需要操作4条线
pre.next = newNode;
newNode.pre = pre;
newNode.next = pre.next.next;
pre.next.pre = newNode;
}
//3、在第0个元素前添加
public void addAtHead(int val) {
this.addAtIndex(0, val);
}
//4、在最后一个元素(Null)前添加
public void addAtTail(int val){
this.addAtIndex(size,val);
}
//5、获取index位置上的值
public int get(int index){
//验证index的合法性,注意这是在查找的索引
if (index<0 || index >= size){
return -1;
}
DoubleListNode cur = head;
//判断是哪一边遍历时间更短
if (index >=size/2){
//从尾指针开始
cur = tail;
for (int i = 0; i < size - index; i++) {
cur = tail.pre;
}
}else {
for (int i = 0; i <= index; i++) {
cur = head.next;
}
}
for (int i = 0; i < index+1; i++) {
cur = head.next;
}
return cur.val;
}
//6、删除index位置的元素
public void deleteAtIndex(int index){
//验证index的合法性
if (index <0 || index >=size){
return;
}
//链表长度减1
size--;
DoubleListNode pre = head;
for (int i = 0; i <index; i++) {
pre = pre.next;
}
pre.next = pre.next.next;
pre.next.next.pre = pre;
}
}
206.反转链表
题目链接:https://leetcode.cn/problems/reverse-linked-list/
文字链接:https://programmercarl.com/0206.%E7%BF%BB%E8%BD%AC%E9%93%BE%E8%A1%A8.html#%E7%AE%97%E6%B3%95%E5%85%AC%E5%BC%80%E8%AF%BE
视频地址:https://www.bilibili.com/video/BV1nB4y1i7eL/?vd_source=721f65ae0501389782be0dcb48a2c421
双指针写法
/**
* 题意:反转一个单链表。
*
* 示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL
*/
public class ReverseList {
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; // 更新前一个节点为当前节点
cur = temp; // 更新当前节点为下一个节点
}
return pre; // 返回反转后的头节点
}
}
递归写法
public class ReverseList {
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);
}
}