链表知识点leetcode刷题

链表

移除链表元素

题目

力扣题目链接

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

示例 1:

img

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

思路

  • 注意这里是不带头节点的链表,所以对应头结点的处理要尤其重视**(所以还是申请带头结点的链表好)**
    1. 头结点为空的情况需要处理
    2. 头结点的值刚好为val的时候需要处理
  • 一般是保留删除的节点的前一个节点信息,这样方便接链和断链

具体实现由三种思路

  1. 直接处理,对特殊情况(头结点值刚好为需要删除的那个值)进行讨论。
  2. 申请一个新的头结点,然后就可以不用讨论特殊情况。
  3. 递归实现,如果能够理解递归,算法很简洁

代码一:直接对特殊情况进行处理

//思路一:直接处理,对特殊情况(头结点值刚好为需要删除的那个值)进行讨论。
class Solution {
public ListNode removeElements(ListNode head, int val) {
        if(head == null) return head;
        //特殊情况处理,注意这里不是if,不断判断当前头结点是否满足情况
        while(head != null && head.val == val) head = head.next;
        
        ListNode p = head;
        //注意while循环条件,同时只需要申请一个节点遍历链表就可以
        while(p != null && p.next != null){
            if(p.next.val == val){
                p.next = p.next.next;
            }
            else{
                p = p.next;
            }
        }
        return head;
    }
};

代码二:申请一个新的头结点

//思路二:申请一个新的头结点,然后对新的头结点进行处理,可以免除特殊情况讨论
class Solution {
public ListNode removeElements(ListNode head, int val) {
        //申请一个新的头结点,省去了头结点的特殊情况处理
       ListNode dhead = new ListNode(0, head);
        ListNode p = dhead;
        while(p != null && p.next != null){
            if(p.next.val == val){
                p.next = p.next.next;
            }
            else{
                p = p.next;
            }
        }
        //注意返回的是不带头结点的链表,所以要返回next
        return dhead.next;
    }
};

代码三:递归

class Solution {
public   ListNode removeElements(ListNode head, int val) {
        //递归实现
        //首先是递归出口,如果是null,直接返回
        if(head == null) return null;
        //然后进行递归
        head.next = removeElements(head.next, val);
        //递归结束后,到最末尾出栈判断进行返回
        if(head.val == val) return head.next;
        else return head;
    }
}

反转链表

题目

力扣题目链接

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

示例 1:

img

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

示例 2:

img

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

示例 3:

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

思路

  • 申请头结点之后,采用头插法实现链表的逆序

    • 头插法,将一个新的节点插入到头结点之后,大致分为四个步骤

      1. 保存第二个节点
      2. 当前节点指针的指向置为头结点指针的指向
      3. 头结点指针指向当前头结点
      4. 更新当前节点为保存的第二个节点

代码:

class Solution {
    public ListNode reverseList(ListNode head) {
        //反转链表
        ListNode newhead = new ListNode(0,null);
        ListNode p = head;
        while(p!=null){
            ListNode q = p.next;
            p.next = newhead.next;
            newhead.next = p;
            p = q;
        }
        return newhead.next;
    }
}

两两交换链表中的节点

题目

力扣题目链接

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

示例 1:

img

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

示例 2:

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

示例 3:

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

思路

  • 直接进行模拟即可
    • 注意需要申请一个新的头结点,避免对于头结点的单独讨论
    • 接三个链
    • 重新更新指针
    • 注意循环结束条件

代码:

class Solution {
    public ListNode swapPairs(ListNode head) {
        //申请头结点
        ListNode newhead = new ListNode(0,head);
        //只有一个节点或者只是空节点,直接返回head
        if(head == null || (head != null && head.next == null)) return head;
        ListNode pre = newhead;
        ListNode node = pre.next;
        ListNode nextNode = node.next;
        while(node != null && node.next != null){
            //接链和断链
            node.next = nextNode.next;
            nextNode.next = node;
            pre.next = nextNode;
            //更新三个指针,重新设置pre为node,进行下一次的两两交换
            pre = node;
            if(pre.next == null) break;
            node = pre.next;
            if(node.next == null) break;
            nextNode = node.next;
        }
        return newhead.next;
    }
}

K个一组翻转链表

题目

力扣题目链接

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例 1:

img

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

示例 2:

img

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

提示:

  • 链表中的节点数目为 n
  • 1 <= k <= n <= 5000
  • 0 <= Node.val <= 1000

**进阶:**你可以设计一个只用 O(1) 额外内存空间的算法解决此问题吗?

思路

  • 对链表进行统计计数,首先申请一个节点用于遍历,每次遍历都进行计数
  • 当计数器与k节点相同时,进行链表的逆转。这里逆转采用头插法进行
  • 逆转完需要更新first指针和pre指针。

代码:

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        //k个一组翻转链表
        int count = 0;
        ListNode newhead = new ListNode(0, head);
        ListNode p = head;  //遍历指针
        ListNode pre = newhead;
        while(p != null){
            p = p.next;
            count++;
            //计数器达到k个顶点,逆转链表
            if(count == k){
                //头插法逆转链表
                ListNode q = pre.next;      //q为遍历指针
                ListNode first = pre.next;  //first保存第一个节点,后序指针修改需要用到
                while(q != p){              //头插法逆转链表
                    ListNode nextq = q.next;
                    q.next = pre.next;
                    pre.next = q;
                    q = nextq;
                }                           //更新指针pre,修改first指针指向
                first.next = p;
                pre = first;
                count = 0;
            }
        }
        return newhead.next;
    }
}

删除链表的倒数第N个节点

题目

力扣题目链接

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

示例 1:

img

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

示例 2:

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

示例 3:

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

思路

  • 双指针
  • 一个指针先走k步,然后另外一个指针指向链表的开头,然后两个指针一起走。
  • 当一个指针走到了链表的末尾的时候,另外一个指针就走到了链表的导数第k个节点
  • 注意由于是删除,所以需要保存倒数第k个节点的前一个节点,然后再进行删除。这里的操作是删除指针指向的新申请的头结点,这样就可以少走一步,最后指向了要删除节点的前一个节点。

代码:

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        //思路:先走k步,然后两个指针一起走,先走k步的到达终点说明另外一个节点

        //特殊情况考虑,空节点或者只有一个节点直接返回null
        if(head==null || (head !=null && head.next == null)) return null;

        //p为先走k步的节点,del为找到删除节点的前一个节点,newhead是为了删除头结点的时候不出错
        ListNode newhead = new ListNode(0, head);
        ListNode p = head;  
        ListNode del = newhead;
        
        //先走n步,保证后序del指向要删除的前一个节点
        for(int i = 0; i < n; i++) p = p.next;

        //一起往前走
        while(p != null){
            p = p.next;
            del = del.next;
        }
        //删除
        del.next = del.next.next;
        
        //返回
        return newhead.next;
    }
}

链表相交

题目

力扣题目链接

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

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

img

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

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

示例 1:

img

输入: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:

img

输入: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:

img

输入: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 class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        //两个指针一起走,走到终点之后再去走另外一个链表
        if(headA == null || headB == null) return null;
        ListNode p = headA;
        ListNode q = headB;
        while(p != q){
            if(p == null)   p = headB;
            else    p = p.next;

            if(q == null)   q = headA;
            else    q = q.next;
        }
        return p;
    }
}

环形链表II

题目

力扣题目链接

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

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

不允许修改 链表。

示例 1:

img

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

示例 2:

img

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

示例 3:

img

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

思路

  • 双指针(快慢指针)
  • 如果存在环,快指针一定可以追上慢指针。
  • 如果快慢指针相遇说明存在环,这时候快指针重新指向头结点,然后两个指针一起往前走,相遇的节点就是环的入口。
  • 如果快慢指针不相遇说明不存在环,直接返回null

代码:

public class Solution {
    public ListNode detectCycle(ListNode head) {
        //空节点或者没有节点
        if(head == null || (head != null && head.next == null)) return null;
        //快慢指针
        ListNode slow = head;
        ListNode fast = head;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
            if(slow == fast){		//相遇了,说明存在环
                ListNode p = slow;	//两个指针,一个指针指向慢指针或者快指针
                ListNode pre = head;	//一个指针指向头结点,然后一起走,直到相遇
                while(pre != p){
                    pre = pre.next;
                    p = p.next;
                }
                return p;			//返回相遇节点
            }
        }
        return null;
    }
}

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值