LeetCode之链表笔试全覆盖(一)

一.什么是链表

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

链表的入口节点称为链表的头结点也就是head。

如图所示:

链表类型

单链表

刚刚说的就是单链表。

双链表

单链表中的指针域只能指向节点的下一个节点。

双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。

双链表 既可以向前查询也可以向后查询。

如图所示:

循环链表

循环链表,顾名思义,就是链表首尾相连。

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

链表的定义

接下来说一说链表的定义。

链表节点的定义,很多同学在面试的时候都写不好。

这是因为平时在刷leetcode的时候,链表的节点都默认定义好了,直接用就行了,所以同学们都没有注意到链表的节点是如何定义的。

而在面试的时候,一旦要自己手写链表,就写的错漏百出。

    // 单链表链表节点
    private static class Node<E> {
        E val;
        Node<E> next;

        Node(E val) {
            this.val = val;
        }
    }

二.链表的增删改查的操作

对于链表增删改查操作不熟练的可以看下面的文章;

单链表的基本操作icon-default.png?t=N7T8https://blog.csdn.net/weixin_56522854/article/details/133909560?spm=1001.2014.3001.5502

三.沙场秋点兵:上LeetCode

LeetCode21:合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

示例 1:

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例 2:

输入:l1 = [], l2 = []
输出:[]

示例 3:

输入:l1 = [], l2 = [0]
输出:[0]
分析:本道题给两个升序的链表 进行合并成一条升序的大链表 ,解法还是比较简单的,既然是要把两条链表合并成一条链表的操作的话,我们就可以很明显的知道需要使用到辅助链表 那么所谓的辅助链表就是我们所说的 虚拟头结点 因为只要是链表的操作就是用头节点进行操作的.那么我们直接来看代码

其实下面的代码我们可以简单的理解  我们在穿衣服拉拉链的时候的操作 把两个链表看做拉链的两边进行合并  就是我们的while循环里面 我们谁小谁先放到辅助链表里面  但是要注意的是 我们要不断地 让辅助链表不断的前进. 当遇到P1或者P2越界的话 我就让其中一个没有越界剩下的元素 全部接到辅助链表里面去.

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        //第一步:创建一个虚拟头节点进行作为 辅助链表来进行操作
        ListNode dummy = new ListNode(-1);
        ListNode p = dummy;
        ListNode p1 = list1;
        ListNode p2 = list2;

        //第二步:进行对两个升序链表进行像拉拉链一样的操作
        while(p1!=null && p2!=null){
            if(p1.val>p2.val){
                //将小的放入到虚拟链表里面去
                p.next = p2;
                p2 = p2.next;
            }else {
                //这个情况就是p1<p2
                p.next = p1;
                p1 = p1.next;
            }
            //当跳出while循环的时候 p要继续往下走一步
            p = p.next;
        }
        if(p1!=null){
            p.next = p1;
        }
        if(p2!=null){
            p.next = p2;
        }
        //需要注意的是 要返回虚拟节点的下一个节点才是我们的头结点
        return dummy.next;
    }
}

LeetCode23:合并K个有序链表

分析其实这道题和上面的题目是有所谓的异曲同工之处的.就是上一题是进行对两个有序链表进行合并成一个大的有序链表 那这边进行对K个链表进行合并成一个有序的链表的话 我们依旧可以进行.但是既然是算法 我们就要考虑到所谓的时间复杂度和空间复杂度

下面的代码就是我们对整个升序链表进行两两 进行比较然后再进行合并成一个大的升序链表

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        ListNode res = null;
        //传入的是一个链表的集合
        for(ListNode node :lists){
            res = mergeTwoLists(res ,node)
        }
        return res;

    }
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        //第一步:创建一个虚拟头节点进行作为 辅助链表来进行操作
        ListNode dummy = new ListNode(-1);
        ListNode p = dummy;
        ListNode p1 = list1;
        ListNode p2 = list2;

        //第二步:进行对两个升序链表进行像拉拉链一样的操作
        while(p1!=null && p2!=null){
            if(p1.val>p2.val){
                //将小的放入到虚拟链表里面去
                p.next = p2;
                p2 = p2.next;
            }else {
                //这个情况就是p1<p2
                p.next = p1;
                p1 = p1.next;
            }
            //当跳出while循环的时候 p要继续往下走一步
            p = p.next;
        }
        if(p1!=null){
            p.next = p1;
        }
        if(p2!=null){
            p.next = p2;
        }
        //需要注意的是 要返回虚拟节点的下一个节点才是我们的头结点
        return dummy.next;
    }
}
解法二:最优解,整个在合并成K个有序链表的话 其实难点也是难道我们该如何找到K个链表里面的最小的那一个节点 最为大链表的头节点操作.  那么就要使用到一种叫优先级队列 优先级队列简单来说 你把K个元素放进去 他会将进行自动排序 并且在你每次去取元素的时候 都是取到的是K个元素里面最小的元素. 

这边文章就很好的解释了什么是优先级队列  既然我们拿到了K个元素的最小值之后 就可以进行对剩下的元素进行比较操作 然后在插入到我们需要使用的虚拟节点的 辅助链表中 那么就直接来看

    了解什么是优先级队列icon-default.png?t=N7T8https://blog.csdn.net/l_ppp/article/details/107549160

 

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        ListNode dummy = new ListNode(-1);
        ListNode p = dummy;
        //优先级队列
        PriorityQueue<ListNode> pq = new PriorityQueue<>(
                lists.length,(a,b)->(a.val-b.val));
        //将K个链表的头头结点加入到pq中去
        for(ListNode list:lists){
            if(list!=null){
                pq.add(list);
            }
        }

        //通过优先级队列获取到最小的 头结点
        while (!pq.isEmpty()){
            ListNode minPoll = pq.poll();
            //获取到最小的节点之后 就可以进行比较操作
            p.next = minPoll;
            if(minPoll.next!=null){
                pq.add(minPoll.next);
            }
            //注意的是要让我们的辅助链表不断像前走
            p = p.next;
        }
        return dummy.next;
    }
}

LeetCode160:两个链表是否相交

分析:对于本题我们采用的思路其实是, 我们将两个链表合成两个大的链表进行遍  第一个大链表是 先遍历A小链表在接着后面的B小链表 在去遍历第二个大链表 但是先遍历B小链表在接着遍历A小链表.
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode p1 = headA , p2 = headB;
        //这边定义两个节点 进行遍历两个条大链表
        while (p1!=p2){
            //这边定义p1进行定义先走A-B  p2进行定义先走B-A
            if(p1==null){
                //当p1==null就相当于 A小链表走完了 那么就走B小链表
                p1 = headB;
            }else {
                p1 = p1.next;
            }
            if(p2==null){
                p2 = headA;
            }else {
                p2 = p2.next;
            }
        }
        return p2;
    }
}

当然还以简单的做法 既然是要看两个链表是否存在相同的数据 那么就可以使用到哈希表来进行 判断 使用hashMap/hashSet来进行判断

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        //第二种做法 使用hash表
        HashSet set = new HashSet();
        while (headA!=null){
            set.add(headA);
            headA = headA.next;
        }

        //接下里进行判断是否有存在 就是进行遍历B链表
        while (headB!=null){
            if(set.contains(headB)){
                return headB;
            }
            headB = headB.next;
        }
        return null;
    }
}

LeetCode2:给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。 请你将两个数相加,并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外,这两个数都不会以 0 开头。 示例 1: 输入:l1 = [2,4,3], l2 = [5,6,4] 输出:[7,0,8] 解释:342 + 465 = 807.;

分析:本题的难点其实就是在于 关于进位的问题 我们来一起分析仪  : 比如两个链表A[6 7 2]  B[ 2 3 4]  这样子的链表 我们对链表进行遍历操作 先取到A的第一个元素 6 再去取到B的第二个元素2 相加等于8 很好这样子不用进行进位操作  那么在while循环里面我们进行继续 遍历链表操作 就到了A的7 加上 B的3 这样子等于10 那么我们怎么办? 用一个sum=10 我们要知道他的进位carry = 1 . 那么sum/10 = 1 那么就知道进位是carry==1  这个时候 对于两个链表的下面两个元素相加 的话 就要进行加一操作了 那么我们就只需要加carry值即可.  但是但是但是 重要的事情说三遍 
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(-1);
        ListNode cur = dummy;
        //用于记录进位值
        int carry = 0;
        while (l1!=null || l2!=null){
            int x = l1==null?0:l1.val;
            int y = l2==null?0:l2.val;
            int sum = x+y+carry;

            carry = sum/10;
            sum = sum%10;
            cur.next = new ListNode(sum);

            cur = cur.next;
            if(l1!=null){
                l1 = l1.next;
            }
            if(l2!=null){
                l2 = l2.next;
            }

        }
        //经过测试如果不写这行代码的话 当链表的最后一位相加大于10的话 就没有进行进位操作
        //意思就是如果遍历结束了,还是要进位,就创建一个新的节点,这个节点就是进位的值,也就是1
        if(carry==1){
            cur.next = new ListNode(carry);
        }
        return dummy.next;
    }
}

LeetCode19删除链表的倒数第K个节点

分析:对于倒数我们都知道不好进行处理 那么我们就进行用正数来进行操作 对于倒数第K个数 正数来其实就是N-K+1个数 (N为链表的长度) 既然我们知道了这一点那么就可以进行来做题了  对于删除节点的话 无非即使把要删除的这个节点的上一个节点指向要删除节点的下一个节点.并且这里还是同样使用了我们所谓的双指针的方式 来进行操作 让指针P1先走K步 那么P1就还剩下N-K步走到链表的尾部 这个时候我们再让 P2节点开始移动 直到P1走到末尾节点的时候 P2就是走到了N-K+1的节点了.
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode();
        dummy.next = head;
        //要找到倒数第N个节点 就要正数的第N-K+1个节点
        ListNode result = removNthFrom(dummy,n+1);
        result.next = result.next.next;
        return dummy.next;
    }
    public ListNode removNthFrom(ListNode head,int n){
        ListNode p1 = head;
        //先让P1走n步
        for (int i = 0; i < n; i++) {
            p1 = p1.next;
        }
        //接下来再让 P2 走n-k步
        ListNode p2 = head;
        while (p1!=null){
            p2 = p2.next;
            p1 = p1.next;
        }
        return p2;
    }
}

 

 

 

 

  • 23
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Rust 是一种现代的编程语言,特别适合处理内存安和线程安的代码。在 LeetCode 中,链表是经常出现的题目练习类型,Rust 语言也是一种非常适合处理链表的语言。接下来,本文将从 Rust 语言的特点、链表的定义和操作,以及 Rust 在 LeetCode链表题目的练习等几个方面进行介绍和讲解。 Rust 语言的特点: Rust 是一种现代化的高性能、系统级、功能强大的编程语言,旨在提高软件的可靠性和安性。Rust 语言具有如下几个特点: 1. 内存安性:Rust 语言支持内存安性和原语级的并发,可以有效地预防内存泄漏,空悬指针以及数据竞争等问题,保证程序的稳定性和可靠性。 2. 高性能:Rust 语言采用了“零成本抽象化”的设计思想,具有 C/C++ 等传统高性能语言的速度和效率。 3. 静态类型检查:Rust 语言支持静态类型检查,可以在编译时检查类型错误,避免一些运行时错误。 链表的定义和操作: 链表是一种数据结构,由一个个节点组成,每个节点保存着数据,并指向下一个节点。链表的定义和操作如下: 1. 定义:链表是由节点组成的数据结构,每个节点包含一个数据元素和一个指向下一个节点的指针。 2. 操作:链表的常用操作包括插入、删除、查找等,其中,插入操作主要包括在链表首尾插入节点和在指定位置插入节点等,删除操作主要包括删除链表首尾节点和删除指定位置节点等,查找操作主要包括根据数据元素查找节点和根据指针查找节点等。 Rust 在 LeetCode链表题目的练习: 在 LeetCode 中,链表是常见的题目类型,而 Rust 语言也是一个非常适合练习链表题目的语言。在 Rust 中,我们可以定义结构体表示链表的节点,使用指针表示节点的指向关系,然后实现各种操作函数来处理链表操作。 例如,针对 LeetCode 中的链表题目,我们可以用 Rust 语言来编写解法,例如,反转链表,合并两个有序链表,删除链表中的重复元素等等,这样可以更好地熟悉 Rust 语言的使用和链表的操作,提高算法和编程能力。 总之,在 Rust 中处理链表是非常方便和高效的,而 LeetCode 中的练习也是一个非常好的机会,让我们更好地掌握 Rust 语言和链表数据结构的知识。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值