链表经典题目

Java链表

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

一、移除链表元素        LeetCode链接

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == 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
输出:[]

思路

方便代码书写,设置虚拟节点。运用双指针,pre指向虚拟节点,cur指向头结点。判断cur指向的节点数据是否等于val,若等于则直接让pre断开与cur的链接,指向cur的下一个节点,接着cur移动到该节点,pre不需要动;若不等于则让pre移动到cur处,cur后移一位。

public ListNode removeElements(ListNode head, int val) {
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        ListNode cur = dummyHead.next;
        ListNode pre = dummyHead;
        while (cur != null){
            if (cur.val == val){
                pre.next = cur.next;
            }else {
                pre = cur;
            }
            cur = cur.next;
        }
        return dummyHead.next;
    }

 二、反转链表        LeetCode链接

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

注意是反转节点,而不是节点里的数据。

示例 1:


输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]


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

输入:head = []
输出:[]

思路

设置cur = head,pre = null。需要将head.next改为null,即cur.next = pre。此时两指针再往后移动时失去目标地址,于是设置tmp用于保存cur的下一节点。当cur反转后,cur可以通过tmp找到下一节点,先把pre移动到此时的cur处,接着cur向后移动。

 public ListNode reverseList(ListNode head){
        ListNode tmp = null,pre = null,cur = head;
        while(cur != null){
            tmp = cur.next;//保存cur的下一节点
            cur.next = pre;//反转链表
            pre = cur;//指针后移
            cur = tmp;

        }
        return pre;//此时cur为null,pre指向旧链表的最后一个节点,成为反转链表后的头结点
    }

三、 两两交换链表中的节点        LeetCode链接

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例 1:


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

输入:head = []
输出:[]
示例 3:

输入:head = [1]
输出:[1]

思路

交换节点有三个步骤

                                                                                         (图片来自代码随想录,用于学习记录)

 交换两个节点需在这前一个节点前放置一个指针cur用于连接节点。两个节点分别用pre1,pre2指向。节点交换,第二个节点移到前边,即cur.next = pre2;第一个节点移到后边,即pre2.next = pre1;接着把链表连接起来,但节点三因为和节点二断开连接无法通过next找到,所以用tmp保存节点三的地址,然后连接链表即 pre1.next = tmp。最后把cur移动到下两个需交换的节点前,即cur = pre1。

public ListNode swapPairs(ListNode head) {
        ListNode dummyHead = new ListNode(0,head);
        ListNode cur = dummyHead;
        ListNode pre1,pre2,tmp;
        while(cur.next != null && cur.next.next != null){//保证后续有两个节点操作
            pre1 = cur.next;//保存节点1
            pre2 = cur.next.next;//保存节点2
            tmp = pre2.next;//保存节点3
            cur.next = pre2;//节点2移到前边
            pre2.next = pre1;//节点1移到节点2后边
            pre1.next = tmp;//节点3连接到节点2后边 
            cur = pre1;
        }
        return dummyHead.next;
    }

其实代码可以简化,因为在开始时可以通过next找到节点二,所以可以不保存第二个节点,直接让cur.next = cur.next.next。之后思路和上边一样,让节点一移到节点二的后边,即cur.next.next = pre1,再后把节点三连接到节点二后边,即pre1.next = pre2(此时pre2定义为节点三)。

public ListNode swapPairs(ListNode head) {
        ListNode dummyHead = new ListNode(0,head);
        ListNode cur = dummyHead;
        ListNode pre1,pre2,tmp;
        while(cur.next != null && cur.next.next != null){
            pre1 = cur.next;//保存节点1
            pre2 = cur.next.next.next;//保存节点3
            cur.next = cur.next.next;//直接连接节点2
            cur.next.next = pre1;//节点1移到节点2后边
            pre1.next = pre2;//节点3连接到节点1后边
            cur = pre1;
          
        }
        return dummyHead.next;
    }

四、删除链表的倒数第N个节点         LeetCode链接

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

示例 1:


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

输入:head = [1], n = 1
输出:[]
示例 3:

输入:head = [1,2], n = 1
输出:[1]

思路

设置一个头结点方便操作,运用双指针法(刚开始想法为算出链表长度,长度减去n,让指针从虚拟指针开始移动n-1次到该位置,但由于还要算链表长度,效率并不高)。想到了双指针,fast和slow都从虚拟指针开始,让fast先走n + 1步,然后让fast和slow一起走,直到fast为null,此时slow刚好指向需要删除的位置的前一个节点。只需一次遍历即可完成操作。

public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        ListNode fast,slow;
        fast = slow  = dummyHead;
        while (n > 0){
            fast = fast.next;
            n--;
        }
        fast = fast.next;//fast走第n+1步
        while (fast != null){
            fast = fast.next;
            slow = slow.next;
        }
        slow.next = slow.next.next;

        return dummyHead.next;

五、链表相交        LeetCode链接 

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

图示两个链表在节点 c1 开始相交:

 

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

示例 1:

 

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
示例 2:

 

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:

 

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。

思路

根据快慢法则,走的快的一定会追上走得慢的。在这道题里,有的链表短,他走完了就去走另一条链表,我们可以理解为走的快的指针。那么,只要其中一个链表走完了,就去走另一条链表的路。如果有交点,他们最终一定会在同一个位置相遇。

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode l1 = headA;
        ListNode l2 = headB;
        while (l1 != l2){//当遇到相同的节点时退出
            l1 = l1 == null ? headB : l1.next;//链表A走完去链表B走,否则在A继续移动
            l2 = l2 == null ? headA : l2.next;
        }
        return l1;


    }

六、环形链表II        LeetCode链接

给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

示例 1:

 

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:

 

输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:

 

输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。

思路

首先是怎么判断环形

在此复制代码随想录的原话

“可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。

为什么fast 走两个节点,slow走一个节点,有环的话,一定会在环内相遇呢,而不是永远的错开呢

fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。因为fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。”

接着是怎么找到入口

假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。最后 x = (n - 1) (y + z) + z。当x = z时,找到入口

“从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点

更加详细的请到 代码随想录 (programmercarl.com)

public ListNode detectCycle(ListNode head) {
        if (head == null)
            return null;
        ListNode slow,fast;
        slow = fast = head;
        while (fast.next != null && fast.next.next != null){//保证fast可以移动两步
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast){//保证有环
                ListNode cur = slow;
                ListNode pre = head;
                while (cur != pre){//相等时节点即为入口
                    cur = cur.next;
                    pre = pre.next;
                }
                return cur;
            }
        }
        return null;
    }

                                                                                                        (文章大部分图片来自力扣) 

练习!

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
旅游社交小程序功能有管理员和用户。管理员有个人中心,用户管理,每日签到管理,景点推荐管理,景点分类管理,防疫查询管理,美食推荐管理,酒店推荐管理,周边推荐管理,分享圈管理,我的收藏管理,系统管理。用户可以在微信小程序上注册登录,进行每日签到,防疫查询,可以在分享圈里面进行分享自己想要分享的内容,查看和收藏景点以及美食的推荐等操作。因而具有一定的实用性。 本站后台采用Java的SSM框架进行后台管理开发,可以在浏览器上登录进行后台数据方面的管理,MySQL作为本地数据库,微信小程序用到了微信开发者工具,充分保证系统的稳定性。系统具有界面清晰、操作简单,功能齐全的特点,使得旅游社交小程序管理工作系统化、规范化。 管理员可以管理用户信息,可以对用户信息添加修改删除。管理员可以对景点推荐信息进行添加修改删除操作。管理员可以对分享圈信息进行添加,修改,删除操作。管理员可以对美食推荐信息进行添加,修改,删除操作。管理员可以对酒店推荐信息进行添加,修改,删除操作。管理员可以对周边推荐信息进行添加,修改,删除操作。 小程序用户是需要注册才可以进行登录的,登录后在首页可以查看相关信息,并且下面导航可以点击到其他功能模块。在小程序里点击我的,会出现关于我的界面,在这里可以修改个人信息,以及可以点击其他功能模块。用户想要把一些信息分享到分享圈的时候,可以点击新增,然后输入自己想要分享的信息就可以进行分享圈的操作。用户可以在景点推荐里面进行收藏和评论等操作。用户可以在美食推荐模块搜索和查看美食推荐的相关信息。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值