LeetCode203.移除链表元素
一般我们处理链表有两种方式,一种是没有虚拟头结点,一种是有虚拟头结点。
那么这两者有什么区别呢?
没有虚拟头结点,需要注意的是需要对头结点也就是第一个结点做特殊的处理。
而使用了虚拟头结点,应该注意的是返回结果是应该是 dummyHead.next。
下面我们就对这两种方式的代码进行实现:
代码如下(不使用虚拟头结点):
public ListNode removeElements(ListNode head, int target) {
//如果头结点为空
//直接返回即可
if (head == null) {
return head;
}
//注意:如果不使用虚拟头结点,需要格外处理如下这种情况
//如果head.val == target
while (head != null && head.val == target) {
head = head.next;
}
//定义一个指针point
ListNode point = head;
//遍历链表
while (point != null) {
while (point.next != null && point.next.val == target) {
point.next = point.next.next;
}
point = point.next;
}
return head;
}
需要注意的是,我们要避免空指针的异常,所以我们要对指针进行非空的判断。
代码如下(使用虚拟头结点):
public ListNode removeElementsByDummyHead(ListNode head, int target) {
if (head == null) {
return head;
}
//创建虚拟头结点
ListNode dummyHead = new ListNode(head, -1);
ListNode point = dummyHead;
//遍历链表
while (point != null) {
while (point.next != null && point.next.val == target) {
point.next = point.next.next;
}
point = point.next;
}
//注意:使用虚拟头结点返回的时候要返回 dummyHead.next
return dummyHead.next;
}
LeetCode.707 设计链表
该题需要注意的点比较多,首先我们要知道我们在链表中如何删除或者添加一个元素到链表中。
添加:
我们需要找到他的前驱结点,并且我们这里还需要保存他前驱的节点的下一个节点。然后我们再将我们要添加的节点加入到前驱结点的后面,再将我们新添加的结点的next指向我们保存的结点即可。
//前驱结点
ListNode preNode;
//新结点
ListNode newNode;
//保存前驱节点的下一个节点
LisetNode temp = preNode.next;
//添加
preNode.next = newNode;
newNode.next = temp;
删除:
完成删除操作,也是要找到我们要删除的结点的前驱节点。也要保存我们要删除节点的下一个节点的下一个节点,然后我让前驱结点指向我们保存的结点,即完成了删除操作。
//前驱节点
ListNode preNode;
//保存前驱结点的下一个节点的下一个节点
ListNode temp = preNode.next.next;
//删除操作
preNode.next = temp;
明白了链表如何删除和添加,那么完成这个设计链表就比较简单了。
提醒:我们需要注意 索引(index) 是否可用。
代码如下:
class MyLinkedList {
//定义链表的数量
private int size;
//虚拟头结点
private ListNode dummyHead;
//初始化MyLinkedList
public MyLinkedList() {
size = 0;
dummyHead = new ListNode(null, 0);
}
public int get(int index) {
//判断索引是否有效
if (index >= size || index < 0) {
return -1;
}
ListNode point = dummyHead;
for (int i = 0; i <= index; i++) {
point = point.next;
}
//返回对应索引的值
return point.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++
size++;
//找到要加入节点的前驱结点
ListNode point = dummyHead;
for (int i = 0; i < index; i++) {
point = point.next;
}
//创建新的节点
ListNode newNode = new ListNode(val);
//添加
newNode.next = point.next;
point.next = newNode;
}
public void deleteAtIndex(int index) {
//判断索引是否正确
if (index >= size || index < 0) {
return;
}
//删除元素,size--
size--;
if (index == 0) {
dummyHead = dummyHead.next;
return;
}
ListNode point = dummyHead;
//找到要删除节点的前驱结点
for (int i = 0; i < index; i++) {
point = point.next;
}
point.next = point.next.next;
}
}
LeetCode.206 反转链表
本题我们使用双指针的思路去完成。
前言提到过,双指针的思路,我们必须明确定义的两个指针的作用。
point:用于指向旧链表的头结点
pre:用于指向新链表的头结点
开始对两个指针完成初始化。point 既然是旧链表的头结点,所以 point = head,pre 是指向新链表的头结点,而反转还没有开始,所以 pre = null。
注意这里还有一个细节问题,我们应该再定义一个指针,用于保存 point 的下一个节点 。
temp :用于保存 point 指针的下一个节点。
代码如下(双指针):
public ListNode reverseList(ListNode head) {
//定义两个指针
ListNode point = head;
//该指针用于记录 新的链表的头结点
ListNode pre = null;
//再定义一个指针,用于保存point.next的位置
ListNode temp = null;
while (point != null) {
//保存位置
temp = point.next;
//开始反转
point.next = pre;
//反转完成后,向后移动,继续遍历
pre = point;
point = temp;
}
return pre;
}
本题其实还可以使用递归的方式来实现。
递归的三要素:
- 返回类型和入参
- 终止条件
- 单层的逻辑
代码如下(递归):
public ListNode reverseListByDFS(ListNode head) {
//和双指针一样,我们初始化 point,pre
//传入 head,null
return reverse(head, null);
}
public ListNode reverse(ListNode point, ListNode pre) {
//终止条件
if (point == null) {
return pre;
}
//保存结点
ListNode temp = null;
temp = point.next;
//反转
point.next = pre;
//更新pre,point的位置
return reverse(temp, point);
}