Leetcode 203. 移除链表元素
思路
由于链表的结构特殊性,删除头部结点和删除中间或尾部节点的操作有所不同。
当删除链表的中间或尾部节点时,我们直接将该结点的前一个结点的next指向该结点的下一个结点。而删除链表的头部结点时,则需要让头部结点指向该结点的下一个结点,从而作为新的头结点。
如此看来,删除结点时,还需要判断该点是否是头结点,下面介绍虚拟头结点方法,可以做到统一删除。
也就是在头部结点之前,再加入一个DummyHead。此时再删除头结点时,只需要将虚拟头结点直接指向头结点。
代码(Java)
写法1:分情况(是否为头结点)
时间复杂度 O(n)
空间复杂度 O(1)
/**
* 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) {
//链表的头结点的值为目标值
while (head!=null && head.val==val){
head=head.next;
}
//链表为null,直接返回头结点
if (head==null){
return head;
}
ListNode pre = head;
ListNode cur = head.next;
while (cur!=null){
if (cur.val==val){
pre.next=cur.next;
}else {
pre=cur;
}
cur=cur.next;
}
return head;
}
}
写法2:虚拟头结点
时间复杂度 O(n)
空间复杂度 O(1)
/**
* 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) {
//当头结点为空时,直接返回头结点
if (head==null)
return head;
ListNode dummyHead = new ListNode(-1,head);//添加虚拟头结点,next指向原本链表的头结点
ListNode pre = dummyHead;
ListNode cur = head;
while (cur!=null){
if(cur.val==val){
pre.next=cur.next;
}else {
pre=cur;
}
cur=cur.next;
}
return dummyHead.next;
}
}
代码随想录链接
下面是代码随想录的文章链接与视频链接,帮助大家更好地理解这道题。
文章
视频
Leetcode 707. 设计链表
思路
这道题帮助大家更好地巩固链表的学习,基于203的移除链表元素,可以更全面地学习链表的各种操作。这里采用虚拟头结点的方式解题,在链表题中常用。
代码(Java)
//单链表
class ListNode{
int val;
ListNode next;
ListNode(){}
ListNode(int val){
this.val=val;
}
}
class MyLinkedList {
int size;
ListNode head;//虚拟头结点
public MyLinkedList() {
size=0;
head=new ListNode(0);
}
public int get(int index) {
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;//如果index大于链表的长度,则返回空
}
if (index<0){
index=0;//若果index小于0,则插入到链表头结点之前,作为新的头结点
}
size++;
ListNode pre = head;
for (int i=0;i<index;i++){//拿到插入位置之前的结点
pre=pre.next;
}
ListNode temp= new ListNode(val);//定义一个新结点存放插入值
temp.next=pre.next;
pre.next=temp;
}
public void deleteAtIndex(int index) {
if (index<0 || index>size-1){
return;//如果index小于0或者大于等于链表的长度,则返回空
}
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;//让删除位置之前的结点指向删除位置的下一个结点
}
}
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList obj = new MyLinkedList();
* int param_1 = obj.get(index);
* obj.addAtHead(val);
* obj.addAtTail(val);
* obj.addAtIndex(index,val);
* obj.deleteAtIndex(index);
*/
Leetcode 206. 反转链表
思路
这道题需要将链表反转,即将原来每个结点的next指向该结点的前一个结点。
所以很容易想到双指针的解法,一个指针cur用来遍历所有的链表结点,另一个指针pre用来指向该结点的前一个结点。
递归写法的整体思想和双指针解法相同,建议结合双指针写法来理解。
难点
初始化两个指针时,cur指针定义为head,pre指针定位为null。
- 当遍历链表时,终止条件为cur==null,所以while()中应填入cur!=null。
- 当进入循环时,需要重新定义pre、cur指针,此时cur.next指向pre,如何再移动cur指针向后呢?所以还需要再添加一个临时指针temp,用来指向cur的下一个结点。
- 先移动cur还是先移动pre?
在让cur.next指向pre之前,将cur.next保存为temp指针,随后让cur.next指向pre。上述的过程完成了一个结点的反转,此时两个指针都需向后移动一位。如果先移动cur指针,也就是让cur=temp,那么pre指针无法移动到原来的cur指针。所以,应该先移动pre指针。
代码(Java)
写法1:双指针法
/**
* 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 cur = head;
ListNode pre = null;
while (cur!=null){
ListNode temp = cur.next;//临时指针存放cur.next,为了方便cur指针的移动
cur.next=pre;
pre=cur;
cur=temp;
}
return pre;
}
}
写法2:递归
/**
* 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 {
private ListNode reverse(ListNode cur,ListNode pre){
if (cur==null){
return pre;
}
ListNode temp = cur.next;
cur.next=pre;
return reverse(temp,cur);
}
public ListNode reverseList(ListNode head) {
return reverse(head,null);
}
}
代码随想录链接
下面是代码随想录的文章链接与视频链接,帮助大家更好地理解这道题。
文章
视频
总结
今天的三道题前两道的重点都在于巩固链表的基础操作,体会到了虚拟头结点dummy head的“香”,添加后对于删除链表结点可以统一处理。最后一题的反转链表的temp指针设计很巧妙,双指针解法在多种数据结构中都有应用,其重点还是在于理解两个指针各自的任务是什么,建议多画草图,一步步理解。另外虽然递归的写法看着很简洁,但也是基于双指针法的思想来的,直接看晦涩难懂,但是写出双指针解法后,递归写法也能轻松写出。