今天开始链表这一章节,链表的理论基础包括链表的结构,内存存储结构,链表的常用操作。
具体在707设计链表这个题中都有完整的体现,先记录下各个题目了。
203.移除链表元素
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
这个题目在leetcode做过几遍了,之前有点死记硬背,今天仔细学了一遍之后,首先有个认识上的进步是为什么有的解答里需要建个虚拟头结点,有的不需要,主要是因为针对链表元素删除的操作,头结点和非头结点所需要的操作是不一样的:
1.头结点,因为没有pre节点,所以没办法将pre.next指向pre.next.next,而是需要将head节点移动到head.next;
2.非头结点,正常按照pre.next = pre.next.next操作就好了。
所以对应有两种写法,见代码:
public ListNode removeElements(ListNode head, int target) {
//这里有两种方法,为什么有两种,因为头结点和后续的节点,对应的删除操作是不一样的
//如果不借助一个虚拟头结点,则需要分开处理当前链表头结点和非头结点的删除
//这里为什么是while循环而不是if,是因为有可能新的head节点的值还会是target
while(head != null && head.val == target) {
head = head.next;
}
//处理完head之后,处理其他节点,这里设定一个临时的指针,用这个指针来移动
ListNode curr = head;
//因为后续要取curr.next和curr.next.val所以需要两个非null的判断
while(curr != null && curr.next != null) {
if(curr.next.val == target) {
curr.next = curr.next.next;
}else {
curr = curr.next;
}
}
return head;
//借助一个虚拟头结点,可以将原来节点的头结点和非头结点使用同样的操作来删除
// ListNode dummy = new ListNode();
// dummy.next = head;
//
// ListNode curr = dummy;
// while(curr != null &&curr.next != null) {
// if(curr.next.val == target) {
// curr.next = curr.next.next;
// }else {
// curr = curr.next;
// }
// }
// return dummy.next;
}
public class ListNode {
private int val;
private ListNode next;
public ListNode() {
}
ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode head) {
this.val = val;
this.next = head;
}
}
707.设计链表
这里其实我的基础比较差,对于设计一个链表一开始是懵圈的,主要是MyLinkedList类下需要有个默认的ListNode内部类,再有一个长度的定义,最后才是我们需要实现的各种方法,理清这个其实对应每个方法并不难,就是需要动手。
package linkedList;
public class MyLinkedList {
int size;
ListNode head;
//init method
public MyLinkedList() {
this.size = 0;
this.head = new ListNode(0);
}
public int get(int index) {
if(index >= size || index < 0) {
return -1;
}
//找到index所在的节点
ListNode curr = head;
for(int i = 0; i <= index; i++) {
curr = curr.next;
}
return curr.val;
}
public void addAtHead(int val) {
ListNode newNode = new ListNode(val);
newNode.next = head.next;
head.next = newNode;
this.size++;
}
public void addAtTail(int val) {
ListNode newNode = new ListNode(val);
ListNode curr = head;
//先找到链表末尾
while(curr.next != null) {
curr = curr.next;
}
curr.next = newNode;
size++;
}
public void addAtIndex(int index, int val) {
if(index > size) {
return;
}
if(index < 0) {
index = 0;
}
//找到index位置的前驱节点
ListNode pre = head;
for(int i = 0; i < index; i++) {
pre = pre.next;
}
ListNode newNode = new ListNode(val);
newNode.next = pre.next;
pre.next = newNode;
size++;
}
public void deleteAtIndex(int index) {
if(index > size) {
return;
}
if(index < 0) {
index = 0;
}
size--;
//找到index所在的节点前驱节点
ListNode curr = head;
for(int i = 0; i < index;i++) {
curr = curr.next;
}
curr.next = curr.next.next;
}
class ListNode {
int val;
ListNode next;
ListNode(){}
ListNode(int val) {
this.val = val;
}
}
}
206. 反转链表
反转链表我个人认为是比较简单的,因为逻辑上我认为其实就是遍历原链表时用头插法将元素指针重新调整下,但是代码真写起来总是容易错,这里记录下各种指针next来回改的情况,好记性不如烂笔头。。。
package linkedList;
public class ReverseLinkedListDemo {
public static void main(String[] args) {
}
class ListNode {
int val;
ListNode next;
}
public ListNode reverseList(ListNode head) {
ListNode curr = head;
ListNode newNode = null;
ListNode temp = null;
//这里我理解等于是将原链表遍历的时候,每一个节点采用头插法放入newNode这个链表中
//但是这里while循环里的赋值总写不好,写下来加深下印象
//1.将当前节点的next节点暂存,因为后面还要接着遍历
//2.将当前节点的next指针指向我们创建的空节点,也就是curr.next = newNode;
//3.新空节点往头移,也就是移到curr节点上,newNode=curr
//4.curr返回去,等于我们之前暂存的节点,继续往下逐个遍历重复头插
while(curr != null) {
temp = curr.next;
curr.next = newNode;
newNode = curr;
curr = temp;
}
return newNode;
}
}