写出一段代码将链表中的两个节点位置互换位置_刷了LeetCode的链表专题,我发现了一个秘密!...

news/2024/7/24 10:22:04

刷了LeetCode的链表专题,我发现了一个秘密!

  • 引言

  • 1、链表的几个概念讲解

    • 1.1链表中的的指针是什么

    • 1.1指针指向哪儿

    • 1.3判断边界的条件

  • 2、必须掌握的几类题目

    • 2.1单链表反转(LeetCode206)

    • 2.2链表中环的检测(LeetCode141)

    • 2.3两个有序的链表合并(LeetCode21)

    • 2.4删除链表(LeetCode18)

    • 2.5删除链表倒数第 n 个结点(LeetCode19)

    • 2.6求链表的中间结点(LeetCode876)

  • 3、学习链表的体会

  • 总结

  • 刷题组合拳推荐

引言

链表是以节点(node)存储的链式存储结构,一个node包含一个data域(存放数据)和一个next域(存放下一个node的指针),链表的各个节点不一定是连续的,它可以分为带头结点不带头结点。头结点仅包含next域。

994d50509c1046ebfdc7e765820ff2e0.png
image-20201103222213252

如果不熟悉链表的同学,建议先看看我的一篇文章。在这篇文章中,主要讲解使用链表的小技巧,如何使用这些技巧来解题,深入解析了LeetCode中具有代表性的链表题目,相信我,看了这篇文章,你再也不用担心关于链表的题目了。

1、链表的几个概念讲解

事实上,链表的结构比较简单,阻碍我们理解链表的常常是因为链表的指针边界问题等,这有时会让我们很烦躁,不要慌,我们下面一一对这下概念解析,相信你看了会有收获。

1.1链表中的的指针是什么

我们学习C语言时,学过指针,它描述的是指向一个内存地址,在Java语言中,是不存在指针的,但是我们可以把它理解为引用

当我们将某个变量(对象)赋值给指针(引用),实际上就是将这个变量(对象)的地址赋值给指针(引用)。

p—>next = q; //表示p节点的后继指针存储了q节点的内存地址。
p—>next = p—>next—>next; //表示p节点的后继指针存储了p节点的下下个节点的内存地址。

1.1指针指向哪儿

我们写链表代码时,使用的指针的指来指去,很快就把我们搞糊涂了,在这种情况下很容易发生指针丢失内存泄漏。我们先普及下这两个概念:

指针丢失:自己定义的指针不知道指到哪里了,没有明确的指向。

内存泄漏:链表中的节点没有确切的指针判断,运行时会抛出空指针异常。

我们以插入节点删除结点来分析指针丢失和内存泄漏的具体情况

  • 插入节点

在节点a和节点b之间插入节点x,b是a的下一节点,p指针指向节点a,

p—>next = x;
x—>next = p—>next; 

这样的代码会造成指针丢失和内存泄漏,因为这会导致x节点的后继指针指向了自己本身。

正确代码应该为:

x—>next = p—>next;
p—>next = x;
f76c8ecf98cf6950178267348d860dd0.png
image-20201103224355467
  • 删除节点

同样的,在节点a和节点c之间删除节点b,b是a的下一节点,p指针指向节点a,正确的代码应该为:

p—>next = p—>next—>next;
1147a7544b197280b81f8013af4a53fc.png
image-20201103234222288

在删除节点,考虑到删除的节点可能是链表中的第一个节点,我们通常在链表头部加入哨兵(头结点),这样可以使得删除链表的代码是一致的,不用再额外考虑是否是第一个节点的情况。

在链表加入哨兵的代码为

 //定义一个哨兵作为传入链表的头结点
 ListNode pre =new ListNode(0);
 pre.next=head;
fb8caeadd67b643e4bb8756d670617a7.png
image-20201103233816980

1.3判断边界的条件

处理链表问题时,要充分考虑链表的边界判断条件,通常情况下,我们经常使用以下几种判断条件:

  • 如果链表为空时,代码是否能正常工作?

  • 如果链表只包含一个结点时,代码是否能正常工作?

  • 如果链表只包含两个结点时,代码是否能正常工作?

  • 代码逻辑在处理头结点和尾结点的时候,是否能正常工作?

这些判断条件需要结合自己的实际场景来使用

2、必须掌握的几类题目

在上面的学习中,我们对链表的一些易错的概念进行了解析,下面,我们就真正的代码实践,我在LeetCode上刷题时发现,链表题目通常分为以下几类:

  • 单链表的反转(LeetCode206)
  • 链表中环的检测(LeetCode141)
  • 两个有序链表的合并(LeetCode21)
  • 删除链表(LeetCode18)
  • 删除链表倒数第n个结点(LeetCode19)
  • 求链表的中间结点(LeetCode876)

这几类链表题基本涵盖了大部分知识点,在下面的学习中,我们将一一攻克它,相信掌握它们之后,在以后笔试/面试中,更能随心所欲。

2.1单链表反转(LeetCode206)

思路:从前往后将每个节点的指针反向,即.next内的地址换成前一个节点的,但为了防止后面链表的丢失,在每次换之前需要先创建个指针指向下一个节点。

class Solution {
    public ListNode reverseList(ListNode head) {

if(head==null||head.next==null){
return head;
}
    ListNode p1=head;
      //用一个新的链表
    ListNode p2=null;
    while(p1!=null){
        //每次更换指向之前都需要保存下一个节点
        ListNode temp=p1.next;
        p1.next=p2;
        p2=p1;
        p1=temp;
    }
    return p2;
    }
}

2.2链表中环的检测(LeetCode141)

思路:定义两个指针,p1和p2,指针p1每次走一步,指针p2每次走两步,如果链表中存在环,则必然会在某个时刻满足p1==p2

public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head==null||head.next==null){
            return false;
        }
        ListNode slow=head;
        ListNode fast=head.next;
       while(fast!=null&&fast.next!=null){
            if(slow==fast){
                return true;
            }
            slow=slow.next;
            fast=fast.next.next;
        }
        return false;
    }
}

NOTE:对于快指针来说,因为一次跳两步,如果要使用快指针作为判断条件,fast和fast.next都需要判断是否为空。(不可跨级)

2.3两个有序的链表合并(LeetCode21)

思路:可以新创建一个链表用于合并后的结果,合并的条件如下

  • 两个链表都不为空

定义一个指针,查找合适的节点并放入新创建链表的下一位置

  • 有一个链表为空

将不为空的链表放入新创建链表的下一位置

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {

        if(l1==null){
            return l2;
        }
        if(l2==null){
            return l1;
        }
        ListNode result=new ListNode(0);
        ListNode temp=result;
        //两个链表都不为空
        while(l1!=null&&l2!=null){
            if(l1.val<=l2.val){
                temp.next=l1;
                temp=temp.next;
                l1=l1.next;
            }
            else{
                temp.next=l2;
                temp=temp.next;
                l2=l2.next;
            }
        }
 //有一个链表为空
       if(l1==null){
           temp.next=l2;
       }
       else{
           temp.next=l1;
       }

        return result.next;
    }
}

2.4删除链表(LeetCode18)

思路:可以在链表头加一个哨兵(头结点),删除链表时先找到删除链表的上一个位置,按照删除规则删除即可。

class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        if(head==null){
            return null;
        }
        //定义一个哨兵作为传入链表的头结点
        ListNode pre =new ListNode(0);
        pre.next=head;
        
        ListNode temp=pre;
        while(temp!=null){
            if(temp.next.val==val){
                temp.next=temp.next.next;
                break;
            }
            else{
                temp=temp.next;
            }
        }
        return pre.next;
    }
}

2.5删除链表倒数第 n 个结点(LeetCode19)

思路:删除节点时要利用好哨兵(带头结点的链表)

  • 遍历数组的长度count
  • 找到要删除节点的前一个位置count-n-1
  • 删除节点
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode pre=new ListNode(0);
        pre.next=head;
        ListNode temp1=pre;
        ListNode temp2=pre;
        int count=0;
        while(temp1!=null){
            temp1=temp1.next;
            count++;
        }

        while(count-n-1>0){
            temp2=temp2.next;
            count--;
        }
        temp2.next=temp2.next.next;
        
        return pre.next;
    }
}

2.6求链表的中间结点(LeetCode876)

思路:找出链表中结点的个数count,然后count/2找出中间结点,删除即可。

class Solution {
    public ListNode middleNode(ListNode head) {
        if(head==null) return null;
        ListNode temp=head;
       int count=0;
       while(temp!=null){
        temp=temp.next;
        count++;
       }
       int mid=count/2;
       ListNode result=head;
       while(mid>0){
          result=result.next;
          mid--;
       }
       return result;
    }
}

Note:实践是检验真理的唯一标准,要真正的学好链表这个知识点,仅仅学理论是不可靠的,我们需要多敲代码多思考多写多练,针对抽象的题目,可以举例画图,来辅助的自己的思考。

3、学习链表的体会

1、 函数中需要移动链表时,最好新建一个指针来移动,以免更改原始指针位置。

2、 单链表有带头节点不带头结点的链表之分,一般做题默认头结点是有值的

3、 链表的内存时不连续的,一个节点占一块内存,每块内存中有一块位置(next)存放下一节点的地址。

3、 链表中找的思想:快慢指针,创建两个指针,一个快指针:一次走两步一个慢指针:一次走一步,若相遇则有环,若指向null则无环。

4、 链表找倒数第k个节点思想:创建两个指针,第一个指针查询链表中结点的个数count,然后count-k确定删除结点的位置,用第二个指针遍历链表到count-n-1个位置。

5、 反向链表思想:从前往后将每个节点的指针反向,即next内的地址换成前一个节点的,但为了防止后面链表的丢失,在每次换之前需要先创建个指针指向下一个节点

总结

无论学习任何一个知识点,我们都需要在掌握术(使用方法)的基础上,学习道(本源),学习数据结构与算法也是一样,我们不仅要掌握如何使用它,更要掌握为什么要是用它,相比其它的方法,它有什么优点,难道是时间复杂度低空间复杂度小,还是它的数据结构适合这个场景等等...

参考文献

[1]王争.数据结构与算法之美

[2]LeetCode中国网站

刷题组合拳推荐

笔者在过去的3个月时间里整理常用的数据结构与算法秒杀剑指offer,公众号分别回复数据结构与算法秒杀剑指offer,即可领取两套电子书,希望能够帮助大家。

94d23ec62a3d561cc46e7f380efdfcab.png
a5183f77092bc8c05d3ad49bf8e5880f.png

我是Simon郎,一个想要每天博学一点点的小青年,关注我,让我们一起进阶,一起博学。

34e61d259aa71d9c8e58fa436fa3aa1f.png

http://www.niftyadmin.cn/n/1463934.html

相关文章

__dirname

__dirname 是node的一个全局变量&#xff0c;获得当前文件所在目录的完整目录名

1月总结

记忆在12月末的时候&#xff0c;给自己定了一个目标&#xff0c;50天计划&#xff0c;现在已过大半时间&#xff0c;有收获也有失落。 失落&#xff1a; 1.没有坚定的坚持自己的计划&#xff0c;一遇到困难或者心情不好就放弃了。 2.没学会劳逸结合&#xff0c;永远一干活就忘…

python魔法属性_python

魔法函数的概念 python中以“__”&#xff08;双下划线&#xff09;开头和结尾的函数&#xff0c;如“__getitem__()”可以使类成为可迭代的类型&#xff0c;可以直接使用for循环对类对象&#xff08;含有可迭代属性&#xff09;进行遍历。一般不要自己命名魔法函数&#xff08…

python支持多种编程范式吗_Python:这遵循什么样的编程范式呢?

在过去的两天里&#xff0c;我一直在探索decorator以及如何使用它们来创建和修改类而不费吹灰之力。这个探索引出了这段代码。在 在工厂.py在#!/usr/bin/env python import sys import functools class Factory(object): def with_callback(self, func): postname func.func_n…

java jpanel添加图片_Java基础笔试练习(六)

Java基础笔试练习(六)1.在Java中&#xff0c;一个类可同时定义许多同名的方法&#xff0c;这些方法的形式参数个数、类型或顺序各不相同&#xff0c;传回的值也可以不相同。这种面向对象程序的特性称为?A.隐藏B.覆盖C.重载D.Java不支持此特性答案&#xff1a;C解析&#xff1a…

freemarker学习笔记(二)

1&#xff1a;判断和输出注释:<#-- ... -->格式部分,不会输出 FTL指令与HTML标签类似&#xff0c;前面加#&#xff0c;如果是用户自定义指定&#xff0c;则为FreeMarker的插值有如下两种类型:1,通用插值${expr};2,数字,日期,布尔值格式化插值:#{expr}或#{expr;format} 根…

python修改xml属性值_使用python操作XML增删改查

使用python操作XML增删改查 什么是XML&#xff1f; XML 指可扩展标记语言&#xff08;EXtensible Markup Language&#xff09; XML 是一种标记语言&#xff0c;很类似 HTML XML 的设计宗旨是传输数据&#xff0c;而非显示数据 XML 标签没有被预定义。您需要自行定义标签。 XML…