代码随想录算法训练营 | day3 203.移除链表元素,707.设计链表,206.反转链表

基础知识

链表理论基础参考文章博客

链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。

由于Java中不存在指针,所以结点通常为一个类,而next则是一个结点类实例的引用。

每一结点都保存了下一个节点的地址,所以链表中的节点在内存中不是连续分布的 ,分配机制取决于操作系统的内存管理。

链表类型

  • 单链表:如上述
  • 双链表:

    每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。

    既可以向前查询也可以向后查询。

  • 循环链表:

    链表首尾相连。

    可以用来解决约瑟夫环问题。       

创建结点

在Java中,我们用一个类来表示结点这个结构

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;
    }
}

刷题 

203.移除链表元素

题目链接 | 文章讲解 | 视频讲解

题目: 删除链表中等于给定值 val 的所有节点。

示例 1: 输入:head = [1,2,6,3,4,5,6], val = 6 输出:[1,2,3,4,5]

示例 2: 输入:head = [], val = 1 输出:[]

示例 3: 输入:head = [7,7,7,7], val = 7 输出:[]

思路及实现

  • 直接使用原来的链表来进行删除操作。
  • 设置一个虚拟头结点在进行删除操作。
1、 直接使用原来的链表来进行删除操作

移除其他节点是通过前一个节点来移除当前节点,而头结点没有前一个节点

所以需区分移除头结点和移除其他节点

移除头结点:将头结点向后移动一位即可,且 java 中会自动清理内存,不需手动从内存中删除移除的节点。

移除其他节点:也可分为两种方法

1、需要两个指针,一个指向需要移除的节点,另一个指向需要移除的节点的上一个节点,然后将上一个节点的 next 指向移除的节点的 next 即可。比如移除下图值为2的节点

注意:记得判断当前节点是否为null的情况。

代码如下: 

public ListNode removeElements(ListNode head, int val) {
    while (head != null && head.val == val) {
        head = head.next;
    }
    // 已经为null,提前退出
    if (head == null) {
        return head;
    }
    // 已确定当前head.val != val
    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、需要一个指针,指向需要移除的节点的上一个节点,然后将上一个节点的 next 指向当前的节点的 next.next 即可。

注意:此时记得判断当前节点及下一个节点是否为null的情况。

代码如下:

public ListNode removeElements(ListNode head, int val) {
    while(head!=null && head.val==val){
        head = head.next;
    }
    ListNode curr = head;
    while(curr!=null){
        while(curr.next!=null && curr.next.val == val){
            curr.next = curr.next.next;
        }
        curr = curr.next;
    }
    return head;
}
2、设置一个虚拟头结点在进行删除操作

在单链表中移除头结点 和 移除其他节点的操作方式是不一样,需要单独写一段逻辑来处理移除头结点的情况,那么可不可以 以一种统一的逻辑来移除 链表的节点呢?

可以设置一个虚拟头结点,将虚拟头结点指向 head,这样移除头结点就和移除其他节点一样了,就不需要单独写一段删除头结点的代码了。

 注意:dummyNode->next 才是新的头结点,应该return dummyNode->next

代码如下:

public ListNode removeElements(ListNode head, int val) {
    if (head == null) {
        return head;
    }
    // 因为删除可能涉及到头节点,所以设置dummy节点,统一操作
    ListNode dummy = new ListNode(-1, head);
    ListNode pre = dummy;
    ListNode cur = head;
    while (cur != null) {
        if (cur.val == val) {
            pre.next = cur.next;
        } else {
            pre = cur;
        }
        cur = cur.next;
    }
    return dummy.next;
}

707.设计链表 

题目:在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val  的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

思路及实现

首先没想到先定义ListNode类

class ListNode {
    int val;
    ListNode next;
    ListNode(){}
    ListNode(int val){
        this.val = val;
    }
    ListNode(int val, ListNode next){
        this.val = val;
        this.next = next;
    }
}

为了方便统一操作,均采用添加虚拟头结点的方式 

获取链表中第 index 个节点的值 

注意:链表下标是从0开始

  • 先判断 index 是否合法,非法直接返回1
  • 定义一个cur指针指向头结点,即 cur = dummyNode.next
  • 采用while(index){ cur = cur.next; index--;} 的方式判断是否找到第 index 个节点。

注:确定边界条件时,可取极端情况,看是否满足情况。比如获取头结点的值,即第0个节点的值,当前 cur 就指向 head,所以不需要执行 cur = cur.next,所以 while(index) 合理。

代码如下:

public int get(int index) {
        if(index < 0 || index >= size){
            return -1;
        }
        ListNode cur = dummyNode.next;
        while(index != 0){
            cur = cur.next;
            index--;
        }
        return cur.val;
    }
插入节点
  • 由于使用虚拟头结点,可以统一调用 addAtIndex(index,val) 这一个函数即可。
  • 对于头插:相当于在第0个元素前添加,即addAtIndex(0,val)。
  • 对于尾插:由于链表下标从0开始,就是添加第 size 个节点,即addAtIndex(size,val)。

在第 index 个节点前插入就是要找到第 index-1 个节点,先将新节点指向第 index 个节点,再将第 index-1 个节点指向新节点,并且size++。

代码如下:

    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;
        }
        ListNode pre = dummyNode;
        while(index != 0){
            pre = pre.next;
            index--;
        }
        ListNode add = new ListNode(val);
        add.next = pre.next;
        pre.next = add;
        size++;
    }
删除第 index 个节点
  • 判断 index 是否合法
  • 找到 第 index-1 个节点
    public void deleteAtIndex(int index) {
        if(index < 0 || index >=size){
            return;
        }
        ListNode pre = dummyNode;
        while(index != 0){
            pre = pre.next;
            index--;
        }
        pre.next = pre.next.next;
        size--;
    }

完整代码

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 MyLinkedList {
    int size;
    //虚拟头结点
    ListNode dummyNode;

    public MyLinkedList() {
        size = 0;
        dummyNode = new ListNode(0);
    }
    
    public int get(int index) {
        if(index < 0 || index >= size){
            return -1;
        }
        ListNode cur = dummyNode.next;
        while(index != 0){
            cur = cur.next;
            index--;
        }
        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;
        }
        ListNode pre = dummyNode;
        while(index != 0){
            pre = pre.next;
            index--;
        }
        ListNode add = new ListNode(val);
        add.next = pre.next;
        pre.next = add;
        size++;
    }
    
    public void deleteAtIndex(int index) {
        if(index < 0 || index >=size){
            return;
        }
        ListNode pre = dummyNode;
        while(index != 0){
            pre = pre.next;
            index--;
        }
        pre.next = pre.next.next;
        size--;
    }
}

206.反转链表

题目:反转一个单链表。

示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL

思路及实现

只需要改变链表的next指针的指向,如果再定义一个新的链表,实现链表元素的反转,其实这是对内存空间的浪费。

  • 定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null

  • 把 cur->next 节点用tmp指针保存一下,将cur->next 指向pre

  • 最后 cur 指针已经指向了null,循环结束,return pre指针即可

 代码如下:

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode cur = head;
        ListNode temp = null;
        while (cur != null) {
            temp = cur.next;// 保存下一个节点
            cur.next = prev;
            prev = cur;
            cur = temp;
        }
        return prev;
    }
}

总结

今天任务说难也不难,说简单也不简单,都是一些链表的基本操作,拖到第二天,可算完成了。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值