第三天 | 203.移除链表元素、707.设计链表、206.反转链表

先学习一下前置知识:关于链表,你该了解这些!

分享一位群内大佬的idea刷题指南:分享一个优雅的力扣刷题姿势

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

分类:单链表、双链表、循环链表(顾名思义)。单链表只能单向查询,双链表可以双向查询,循环链表可以解决约瑟夫环问题。

与数组的区别:数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。数组查询复杂度为O(1),增删复杂度为O(n);链表相反。

构建函数:参考了刷题指南中的构建方法。

//leetcode.editor.util.ListNode
package leetcode.editor.util;
import java.util.Arrays;
public class ListNode {
    public int val;
    public ListNode next;
    public ListNode() {
    }
    public ListNode(int val) {
        this.val = val;
    }
    public ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("[");
        ListNode p = this;
        while (p != null) {
            sb.append(p.val).append(',');
            if (p.next == null) {
                sb.deleteCharAt(sb.length() - 1);
            }
            p = p.next;
        }
        return sb.append(']').toString();
    }
    /**
     * @param s 形如"1,2,3,4,5"的字符串
     * @return [1, 2, 3, 4, 5] 的头节点:1
     */
    public static ListNode of(String s) {
        if ("".equals(s)) return null;
        int[] array = Arrays.stream(s.split(",")).mapToInt(Integer::valueOf).toArray();
        return of(array);
    }
    public static ListNode of(int... array){
        ListNode _head = new ListNode(), p = _head;
        for (int i : array) {
            p.next = new ListNode(i);
            p = p.next;
        }
        return _head.next;
    }
}

重写了一些方法,添加了一些方法,使得对于链表的各种操作都变得方便。简单的构造形式如下:

//leetcode.editor.util.ListNode
package leetcode.editor.util;
import java.util.Arrays;
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;
    }
}

删除节点:将上一个节点的next指针指向下一个节点。由于Java自带的回收机制,不需要考虑内存释放。

增加节点:上一个节点的next指向新节点,新节点的next指向本节点。

表述方式:删除某节点(不要查询)与删除第几个节点(要查询)。

203.移除链表元素

文章讲解:代码随想录-移除链表元素

视频讲解: 手把手带你学会操作链表

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

难点:对于链表的基础理解。主要是对于头节点的处理。

思路:查找val,删除对应节点。一种方法是删除头节点时head向后移动。另一种方法是创建一个虚拟的头节点。

public ListNode removeElements(ListNode head, int val) {
    if (head == null) {
        return head;
    }
    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;
}

易错点:空链表时,直接返回原头节点。

复杂度分析:

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

707.设计链表

文章讲解:代码随想录-设计链表

视频讲解: 帮你把链表操作学个通透!

题设:你可以选择使用单链表或者双链表,设计并实现自己的链表。

单链表中的节点应该具备两个属性:valnextval 是当前节点的值,next 是指向下一个节点的指针/引用。

如果是双向链表,则还需要属性 prev 以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。

实现 MyLinkedList 类:

  • MyLinkedList() 初始化 MyLinkedList 对象。
  • int get(int index) 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1
  • void addAtHead(int val) 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
  • void addAtTail(int val) 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
  • void addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
  • void deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。

难点:一道题包含所有链表的基本操作。

//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 currentNode = head;
    //包含一个虚拟头节点,所以查找第 index+1 个节点
    for (int i = 0; i <= index; i++) {
        currentNode = currentNode.next;
    }
    return currentNode.val;
}

//在链表最前面插入一个节点,等价于在第0个元素前添加
public void addAtHead(int val) {
    addAtIndex(0, val);
}

//在链表的最后插入一个节点,等价于在(末尾+1)个元素前添加
public void addAtTail(int val) {
    addAtIndex(size, val);
}

// 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果 index 大于链表的长度,则返回空
public void addAtIndex(int index, int val) {
    if (index > size) {
        return;
    }
    if (index < 0) {
        index = 0;
    }
    size++;
    //找到要插入节点的前驱
    ListNode pred = head;
    for (int i = 0; i < index; i++) {
        pred = pred.next;
    }
    ListNode toAdd = new ListNode(val);
    toAdd.next = pred.next;
    pred.next = toAdd;
}

//删除第index个节点
public void deleteAtIndex(int index) {
    if (index < 0 || index >= size) {
        return;
    }
    size--;
    if (index == 0) {
        head = head.next;
        return;
    }
    ListNode pred = head;
    for (int i = 0; i < index ; i++) {
        pred = pred.next;
    }
    pred.next = pred.next.next;
}

易错点:初始需要头节点+链表长度,注意定义细节;头部插入节点时的设置顺序;插入后size要增加。

总结:这道题相当综合,一刷时折磨了自己很久也没debug出来。先把标准答案看会,双向链表有空再说。

206.反转链表

文章讲解:代码随想录-反转链表

视频讲解: 帮你拿下反转链表

题设:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

难点:递归算法的实现。

思路:先用双指针实现,再对照着双指针用递归。画图非常有助于理解,强烈建议先在草稿纸上画一下。

1.双指针法:

public ListNode reverseList(ListNode head) {
    ListNode cur = head;
    ListNode pre = null;
    while (cur != null) {
        ListNode temp = cur.next;
        cur.next = pre;
        pre = cur;
        cur = temp;
    }
    return pre;
}

复杂度分析:

  • 时间复杂度:O(n),其中 n 是链表的长度。需要遍历链表一次。
  • 空间复杂度:O(1)

2.递归法:

public ListNode reverseList(ListNode head) {
    return reverse(null, head);
}

public ListNode reverse(ListNode pre, ListNode cur) {
    if (cur == null) return pre;
    ListNode temp = cur.next;
    cur.next = pre;
    pre = cur;
    cur = temp;
    return reverse(pre, cur);
}

复杂度分析:

  • 时间复杂度:O(n),其中 n 是链表的长度。需要对链表的每个节点进行反转操作。

  • 空间复杂度:O(n),其中 n 是链表的长度。空间复杂度主要取决于递归调用的栈空间,最多为 n 层。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,064评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,606评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,011评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,550评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,465评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,919评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,428评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,075评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,208评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,185评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,191评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,914评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,482评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,585评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,825评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,194评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,703评论 2 339

推荐阅读更多精彩内容