链表 part 01
统一删除
1. 203 移出链表元素 E
给你一个链表的头节点
head
和一个整数val
,请你删除链表中所有满足Node.val == val
的节点,并返回 新的头节点 。
1.1 思路
1.2 不使用虚拟头节点
class Solution {
public ListNode removeElements(ListNode head, int val) {
//不使用虚拟头节点
//头节点的删除
//不是用if
/* if (head!=null&&head.val==val) {
head=head.next;
}*/
//删除值相同的头结点后,可能新的头结点也值相等,用循环解决
while(head!=null&&head.val==val){
head=head.next;
}
if (head == null) {
return head;
}
//非头节点删除
//创建一个临时节点指向头节点 之后开始依次遍历
ListNode cur=head;
//若节点不为空就一直循环下去,所以用while
while (cur.next != null) {
//若是移出的元素,则让cur指向节点的下下个 元素
if (cur.next.val==val) {
cur.next=cur.next.next;
}else {
//若不是,啧继续向下移动一位;
cur=cur.next;
}
}
return head;
}
}
- 时间复杂度:O(n) 空间复杂度:O(1)
1.3 使用虚拟头节点
class Solution {
public ListNode removeElements(ListNode head, int val) {
//添加虚拟头节点 dummynode
if (head == null) {
return head;
}
//创建一个虚拟头节点,对所有节点进行统一操作
ListNode dummyNode = new ListNode(-1, head);//虚拟头节点
//创建两个指针 指向虚拟节点 头节点
ListNode pre = dummyNode;
ListNode cur = head;
//cur若不为null一直循环
while (cur != null) {
if (cur.val == val) {
pre.next = cur.next;
} else {
pre = cur;
}
cur = cur.next;
}
return dummyNode.next;
}
}
- 时间复杂度:O(n) 空间复杂度:O(1)
1.4 总结及注意
-
在不使用虚拟头节点进行删除头节点 要用while 不能用if
- 删除值相同的头结点后,可能新的头结点也值相等,用循环解决
-
虚拟头节点删除最后返回的是
dummyNode.next
-
为什么要使用临时指针?
答:操作完链表之后要返回头结点,直接操作头结点会改变其值,故需要定义一个临时指针指向头结点
2. 707 设计链表 M
2.1思路
本题思路看代码随想录吧!:链接
2.2 代码
class MyLinkedList {
//size指链表存储个数
int size;
//虚拟头节点
ListNode head;
//初始化链表
public MyLinkedList() {
size = 0;
head = new ListNode(0);
}
//获取第index个节点 index从0开始,第0个就是头节点
public int get(int index) {
//如果index非法,返回-1
if (index < 0 || index >= size) {
return -1;
}
ListNode cur = head;
//包含了一个虚拟头节点,所以查找index+1个节点
for (int i = 0; i < index + 1; i++) {
cur = cur.next;
}
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;
}
size++;
//
ListNode pre = head;
//找到插入节点的前驱
for (int i = 0; i < index; i++) {
pre = pre.next;
}
//定义要添加的节点值
ListNode toAdd = new ListNode(val);
toAdd.next = pre.next;
pre.next = toAdd;
}
public void deleteAtIndex(int 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;
}
}
2.3 总结及注意
-
获取第index节点值
-
注意index非法
-
因为index是从0开始的,最后一个位置是size-1;所以最大值index>=size就已经非法了
if (index < 0 || index >= size) { return -1; }
-
-
-
addAtIndex(index,val添加节点)第index前
- size++
- 要注意先找到第index的前驱节点才能添加——>for循环遍历取得
- 设置一个pre节点指向head虚拟节点,开始遍历
-
删除节点
- 同意要注意index非法
- size–
- 若index为0
- 若index是其他
- 设置一个pre指向head (pre=head)
- for循环遍历节点,找到前驱
3. 206 反转链表 E
给你单链表的头节点
head
,请你反转链表,并返回反转后的链表。
3.1 思路分析
-
**注意:**修改当前节点的next值指向 前驱节点 时,修改前,需要先保存当前节点的 后继,防止后续节点丢失
-
如何让第一个节点的操作和后续其他节点保持一致?
答:让pre初始值设为null
3.2 双指针解法
class Solution {
public ListNode reverseList(ListNode head) {
//双指针
ListNode pre=null;
ListNode cur=head;
//因为后面要存cur的下一个节点,所以发现需要设置一个临时指针
ListNode temp=null;
//注意不是cur.next!=null
while (cur != null) {
//反转指针
temp=cur.next;//临时节点保存cur的下一个节点
cur.next=pre;
//移动指针
pre=cur;
cur=temp;
}
return pre;
}
}
- 时间复杂度: O(n)
- 空间复杂度: O(1)
3.3 递归解法
class Solution {
public ListNode reverseList(ListNode head) {
return reverse(null,head);
}
private ListNode reverse(ListNode pre,ListNode cur){
if (cur==null) {
return pre;
}
ListNode temp=null;
temp=cur.next;//先保存下一个节点
cur.next=pre;//反转一个节点
//依次递归
return reverse(cur,temp);
}
- 时间复杂度: O(n), 要递归处理链表的每个节点
- 空间复杂度: O(n), 递归调用了 n 层栈空间
3.4 总结及注意
-
双指针解法,while循环中是
cur != null
,并非cur.next!= null
对于while( cur. next!= null)
举例1->2->3->null,若cur指向3,此时cur. next=null,我们代码中while循环里是让cur. next=pre,及在此处让3指向2,但由于不满足循环体要求,无法执行操作