ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

leetcode链表专题

2020-12-31 18:01:08  阅读:144  来源: 互联网

标签:head 专题 ListNode next 链表 节点 null leetcode


文章目录

数据结构复习(链表)

最近在复习算法,为明年的春招做准备,欢迎互关呀,共同学习,进步!

前言

链表是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针

在这里插入图片描述

由于不必须按顺序存储,链表在插入的时候可以达到 O(1) 的复杂度,比另一种线性表 —— 顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要 O(N)的时间,而顺序表相应的时间复杂度分别是 O(logN) 和 O(1)

使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。

基本链表分类:

  • 单链表
  • 双向链表
  • 循环链表
  • 跳跃链表

jdk集合中的链表

redis中的链表:

1.反转链表

链表反转I

在这里插入图片描述

递归解法

基本思路

其实这里的递归就是相当于遍历到链表最后一个节点,然后从链表尾节点开始反转

使用递归解法,时间复杂度:O(N);空间复杂度:O(N)

基本流程分析

在这里插入图片描述

实现:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        //空链表或者只有一个节点,则反转结果和原链表一样
        if(head == null || head.next == null){
            return head;
        }
        //递归得到的节点是反转后的头结点,所以递归到最后得到的头结点就是反转链表的头结点(原链表最后的节点)
        ListNode newHead = reverseList(head.next);
        //反转逻辑
        head.next.next = head;
        //不要忘了尾节点要指向null
        head.next = null;
        return newHead;
    }
}

迭代解法

使用迭代解法的话需要三个指针,前驱指针pre,当前节点指针cur,过渡指针tmp,其实就是从头结点开始,两个节点两个节点的反转,最后讲反转后的尾节点head指向null即可

时间复杂度:O(N);空间复杂度:O(1)

    public ListNode reverse2(ListNode head){
        if(head == null){
            //空链表
            return null;
        }else if(head.next == null){
            //单节点链表
            return head;
        }else {
            // pre -> cur
            ListNode pre = head;
            ListNode cur = head.next;
            ListNode tmp;
            //反转逻辑
            while (cur != null) {
                // pre -> cur -> cur.next
                // tmp = cur.next
                tmp = cur.next;
                // pre <- cur -> tmp
                cur.next = pre;
                // pre <- cur -> tmp
                //        pre -> cur(之后再反转这一部分)
                pre = cur;
                cur = tmp;
            }
            //别忘了反转后的head要指向null
            head.setNext(null);
            return pre;
        }
    }

反转前N个节点

反转前N个节点和反转链表思路差不多,注意这个

        //原头结点和反转后的头结点连接起来
        head.next = tail;

在这里插入图片描述

比如这里的可以大概解释下设置tail的目的,递归反转如下:

reverseTopNNode(1,3)//头结点是1,反转前3个节点
	-> reverseTopNNode(2,2)
		-> reverseTopNNode(3,1)
			-> 此时到达递归终止条件,tail是方法外的变量,tail = 4,反转后的头结点是3
           	 	-> 上面那一步其实已经知道了反转后的头结点了,然后从3开始往前遍历,一步步将递归栈清空
            		-> 原头结点和tail连接起来

实现

    /**
     * 递归反转前N个节点
     * 反转以 head 为起点的 n 个节点,返回反转后新的头结点
     * @param head
     * @return
     */
    //后继节点
    ListNode tail = null;
    public ListNode reverseTopNNode(ListNode head,int n){
        if(n == 1){
            tail = head.next;
            return head;
        }
        //反转前n - 1个节点
        ListNode newHead = reverseTopNNode(head.next,n - 1);
        head.next.next = head;
        //原头结点和tail连接起来
        head.next = tail;
        return newHead;
    }

链表反转II

在这里插入图片描述

迭代解法

在迭代解法中,我用到了哑结点的方法

何为哑结点?

哑结点就是一个虚伪的头结点,在原来链表的头结点的前驱增加一个节点指向头结点,方便处理头结点

    public ListNode reverseBetween(ListNode head, int m, int n) {
        if(m == n){
            //不用反转
            return head;
        }
        //设置一个哑结点,处理 m=1 的情况
        ListNode node = new ListNode(0);
        // 0 -> 1 -> 2 -> 3 -> 4 -> 5 -> null
        node.next = head;
        ListNode pre = node;
        ListNode cur = node.next;
        //让cur指向当前 m - 1节点
        for(int i = 1;i < m;i++){
            pre = pre.next;
            cur = cur.next;
        }
        //反转m到n之间的节点,转化为反转前k个节点问题,k = n - m
        for(int i = 0;i < n - m;i++){
            //采用头插法,不断地把要反转的节点插到反转区间头节点的前面。重点就是记录第m个结点的前驱结点和第n个结点的后续结点
            //现在就是和单链表头插法相似的内容,只是这个插入的节点是从链表中取出,而不是new出来
            //头结点:pre
            //要插入的节点,当前指针的后继节点(tmp)
            ListNode tmp = cur.next;
            //将tmp取出(cur指向tmp的后继),插入到pre和cur之间
            cur.next = tmp.next;
            //插入到pre和cur之间
            tmp.next = pre.next;
            pre.next = tmp;
        }
        //真正的头结点是哑结点的next节点
        return node.next;
    }

解释下上面头插法和尾插法

在单链表中,节点的插入有两种方法:

  • 头插法:每次插入新节点都是在在链表的头结点后插入

  • 尾插法:每次插入新节点都是在链表的尾节点后插入

所以,利用插入法实现链表反转(链表逆序)也是一个很好的途径:

原链表:
head -> 1 -> 2 -> 3 -> 4 -> 5 -> null
pre    cur  tmp
 
head -> 2 -> 1 -> 3 -> 4 -> 5 -> null
pre         cur   tmp

head -> 3 -> 2 -> 1 -> 4 -> 5 -> null
pre				cur  tmp

head -> 4 -> 3 -> 2 -> 1 -> 5 -> null
pre					 cur  tmp

反转完成
head -> 5 -> 4 -> 3 -> 2 -> 1 -> null

递归解法

如果使用递归解法的话,可以将其转化为反转链表前n个节点的问题

    /**
     * 反转指定区间的节点【m,n】
     */
    /**
     * 递归解法
     * @param head
     * @param m
     * @param n
     * @return
     */
    public ListNode reverseBetween1(ListNode head, int m, int n) {
        if(m == 1){
            //相当于从头结点开始的反转,转化为反转前n个节点的问题
            return reverseN(head, n);
        }
    	//其实递归就相当于遍历
        head.next = reverseBetween1(head.next,m-1,n-1);
        return head;
    }

    ListNode tail = null; // 后驱节点

    // 反转以 head 为起点的 n 个节点,返回新的头结点
    ListNode reverseN(ListNode head, int n) {
        if (n == 1) {
            // 记录第 n + 1 个节点
            tail = head.next;
            return head;
        }
        // 以 head.next 为起点,需要反转前 n - 1 个节点
        ListNode last = reverseN(head.next, n - 1);
        head.next.next = head;
        // 让反转之后的 head 节点和后面的节点连起来
        head.next = tail;
        return last;
    }

基于上面的头插法实现的链表反转:

    public ListNode reverseList(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }
        //头插法反转链表
        ListNode pre = new ListNode(0);
        pre.next = head;
        ListNode cur = pre.next;
        while(cur.next != null){
            //要取出来的节点
            ListNode tmp = cur.next;
            cur.next = tmp.next;
            tmp.next = pre.next;
            pre.next = tmp;
        }
        return pre.next;
    }

2.环形链表

环形链表

在这里插入图片描述

快慢指针法

快慢双指针,就是快指针的速度是慢指针的两倍

            slow = slow.next;
            fast = fast.next.next;

利用这个特点,还可以利用快慢指针解决寻找链表中间节点的问题(快指针走到链表尾节点,由于快指针速度是慢指针的2倍,所以,慢指针会处于链表的中间节点)

而在这里判断有无环,则可以使用快慢指针速度不同的特点,只要快指针和慢指针相遇,此链表必定有环,但是,快慢指针相遇点,不一定是链表环的入口。

时间复杂度:O(N),空间复杂度O(1)

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

利用哈希表

也可以基于哈希表实现,在遍历链表的过程中,将节点值放入哈希表,只要遇到哈希冲突(相同值),链表就是有环链表

时间复杂度:O(N),空间复杂度:O(N)

    public boolean hasCycle(ListNode head) {
        Set<ListNode> set = new HashSet<>();
        while(head != null){
            if(set.contains(head)){
                return true;
            }else{
                set.add(head);
            }
            head = head.next;
        }
        return false;
    }

返回链表中间节点

在这里插入图片描述

刚刚有提到的使用快慢指针返回链表的中间节点

    public ListNode middleNode(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode slow = head;
        ListNode fast = head;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }

环形链表II

在这里插入图片描述

基于哈希表

这个题目要找的是环的入口,那么,基于哈希表的话,只要将发生哈希冲突的节点返回即是答案

时间复杂度:O(N),空间复杂度:O(N)

    /**
     * 返回链表开始入环的节点
     * 【基于哈希表】
     * @param head
     * @return
     */
    public ListNode detectCycle(ListNode head) {
        Set<ListNode> set = new HashSet<>();
        while(head != null){
            if(set.contains(head)){
                return head;
            }else{
                set.add(head);
            }
            head = head.next;
        }
        return null;
    }

基于快慢指针

刚刚我也提到过,使用快慢指针虽然能判断一个链表是否有环,但是无法判断快慢指针相遇节点是否是环的入口。

其实在这道题目中,用的是Floyd 算法

何为Floyd 算法?

当然一个跑得快的人和一个跑得慢的人在一个圆形的赛道上赛跑,会发生什么?在某一个时刻,跑得快的人一定会从后面赶上跑得慢的人。

Floyd 的算法被划分成两个不同的 阶段 。在第一阶段,找出列表中是否有环,如果没有环,可以直接返回 null 并退出。否则,用 相遇节点 来找到环的入口。

证明:第二阶段中,从头结点开始遍历,遍历到head = slow时找到环入口

在这里插入图片描述

实现:

    public ListNode detectCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        boolean hasCycle = false;
        //先判断是否有环
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
            if(slow == fast){
                hasCycle = true;
                break;
            }
        }
        //如果有环则寻找入环节点
        if(hasCycle){
            ListNode node = head;
            //从头结点开始寻找,到slow/fast为止,证明过程在上边
            while(node != slow){
                slow = slow.next;
                node = node.next;
            }
            return node;
        }
        return null;
    }

环形链表中插入节点,保持环形链表有序

这是左神的BAT算法精讲中提到的一道题目,大概意思就是有一个有序的给定头结点环形链表,现在要插入一个指定value的节点到这个环链中,插入完成后保持链表有序

    /**
     * 环形链表插入节点并且保持链表节点有序
     * @param head
     * @return
     */
    public ListNode insertionSortList(ListNode head,int val) {
        //如果链表为空,则生成的node就是头结点,next指针指向自己
        if(head == null){
            ListNode node = new ListNode(val);
            node.next = node;
        }
        //链表不止一个节点
        ListNode node = new ListNode(val);
        ListNode pre = head;
        while(pre.next != head){
            ListNode cur = pre.next;
            if(cur.val >= node.val && pre.val <= node.val){
                pre.next = node;
                node.next = cur;
            }else{
                pre = pre.next;
            }
        }
        //此时,可能出现两种情况,node.val比环形链表中的节点值都小或者都大,此时应该讲node插入到head节点前
        pre.next = node;
        node.next = head;
        return head;
    }

3.回文链表

回文链表

在这里插入图片描述

基于快慢指针和链表反转

利用快慢指针找到链表中点,然后反转后半部分链表,再比较前半部分和后半部分是否相同

    /**
     * 回文链表
     *
     * @param head
     * @return
     */
    public boolean isPalindrome(ListNode head) {

        if (head == null || head.next == null) {
            return true;
        }
        ListNode slow = head;
        ListNode fast = head;
        //快慢指针找到链表中点
        while (fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        //反转后半部分
        slow = reverse(slow.next);
        //比较前后部分
        while (slow != null) {
            if (slow.val != head.val) {
                return false;
            } else {
                slow = slow.next;
                head = head.next;
            }
        }
        return true;
    }

    /**
     * 反转链表
     *
     * @param head
     * @return
     */
    public ListNode reverse(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode newNode = reverse(head.next);
        head.next.next = head;
        head.next = null;
        return newNode;
    }

基于栈

利用回文链表的顺序和逆序都是相同的特点,遍历链表和遍历出栈比较

    public boolean isPalindrome(ListNode head) {
        if (head == null || head.next == null) {
            return true;
        }
        Stack<ListNode> stack = new Stack();
        //将链表所有元素放入栈中,此时出栈节点顺序是链表的逆序
        ListNode p = head;
        while(p != null){
            stack.push(p);
            p = p.next;
        }
        //利用回文链表的顺序和逆序都是相同的特点,遍历链表和遍历出栈比较
        ListNode cur = head;
        while(cur != null){
            if(cur.val != stack.pop().val){
                return false;
            }
            cur = cur.next;
        }
        return true;
    }

4.相交链表

找到两个链表相交的起始节点

在这里插入图片描述

基于长度计算

计算出两链表长度后,根据长度,从相同起点出发,遍历链表,判断是否相等

    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA == null || headB == null) {
            return null;
        }
        ListNode a = headA;
        ListNode b = headB;
        int lenA = 0;
        int lenB = 0;
        //分别计算长度
        while(a != null){
            lenA++;
            a = a.next;
        }
        while(b != null){
            lenB++;
            b = b.next;
        }
        //从同一起点出发
        while(lenA != lenB){
            if(lenA > lenB){
                headA = headA.next;
                lenA--;
            }else{
                headB = headB.next;
                lenB--;
            }
        }
        //遍历找到相交点
        while(headA != headB){
            headA = headA.next;
            headB = headB.next;
        }
        return headA;
    }

基于哈希表实现

    /**
     * 基于哈希表
     * @param headA
     * @param headB
     * @return
     */
    public ListNode getIntersectionNode1(ListNode headA, ListNode headB) {
        if(headA == null || headB == null) {
            return null;
        }
        Set<ListNode> set = new HashSet<>();
        while(headA != null){
            set.add(headA);
            headA = headA.next;
        }
        while(headB != null){
            if(set.contains(headB)){
                return headB;
            }else{
                headB = headB.next;
            }
        }
        return null;
    }

返回两个有环链表的相交节点

对于两个有环链表的相交有一下两种情况:

  • 第一种情况就是只有一个唯一确定的相交节点
  • 第二种情况就是有两个相交节点,返回第一个

在这里插入图片描述

5.删除节点

删除链表中的节点

在这里插入图片描述

其实这道题目实际上并不是删除了指定节点,而是进行拷贝删除后继节点的操作,意思就是:

  • 先拷贝node节点的后继节点值,改变node的val为后继节点val
  • 改变node后继指针指向为node后继节点的后继指针指向

这种操作其实是由缺陷的,当要删除的node节点是尾节点,这个操作是行不通的,删除不了尾节点

    public void deleteNode(ListNode node) {
        node.val = node.next.val;
        node.next = node.next.next;
    }

删除链表中的指定节点

在这里插入图片描述

双指针法,在单链表问题中,由于没有像双向链表那样,一个节点具有前驱和后继指针,所以,双指针法在单链表中是一种很好的解决问题的方法,比如下面的删除链表的指定节点,我自定义了两个指针

        //前驱指针
        ListNode pre = head;
        //当前指针
        ListNode cur = head.next;

那么指定了这两个指针后,我们用当前指针遍历链表,只要找到这个指定值

cur.val == val

那么双指针操作的简易性就出来了,要删除这个cur指向的节点,只需要前驱指针指向cur指向节点的后继节点,最后更新cur,防止后序还有重复需要删除节点

pre.next = cur.next;
cur = cur.next;

实现

    public ListNode removeElements(ListNode head, int val) {
        if(head == null){
            return null;
        }
        while (head.val == val) {
            head = head.next;
            if (head == null) {
                return null;
            }
        }
        //前驱指针
        ListNode pre = head;
        //当前指针
        ListNode cur = head.next;
        while(cur != null){
            if(cur.val == val){
                pre.next = cur.next;
                cur = cur.next;
            }else{
                pre = pre.next;
                cur = cur.next;
            }
        }
        return head;
    }

删除排序链表中的重复元素

在这里插入图片描述

这个题目和上一个题目的区别是,并没有指定要删除的节点,这个节点可以有多种,虽然上题中可以解决重复节点的问题,但是仅限于一种节点

    public ListNode deleteDuplicate(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }
        ListNode cur = head;
        while(cur.next != null){
            if(cur.val == cur.next.val){
                cur.next = cur.next.next;
            }else{
                cur = cur.next;
            }
        }
        return head;
    }

删除排序链表中的重复元素II

在这里插入图片描述

而在这道题目中,与上题不同的是,重复节点要全部删除

在这里插入图片描述

实现:

    public ListNode deleteDuplicates(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }
        //避免开头就出现重复数字,因此使用哑结点
        ListNode dump = new ListNode(-1);
        dump.next = head;
        ListNode p = dump;
        //遍历查找重复节点
        while(p.next != null && p.next.next != null){
            //找到一个重复节点,就用val记录
            if(p.next.val == p.next.next.val){
                int val = p.next.val;
                //循环向后继续再找相同的重复节点
                while(p.next != null && p.next.val == val){
                    //删除重复节点
                    p.next = p.next.next;
                }
            }else{
                p = p.next;
            }
        }
        return dump.next;
    }

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

在这里插入图片描述

方法一:

  • 反转顺序,倒数第n个就是顺数第sum - n个,然后遍历到遍历到(sum - n)- 1个节点,再删除节点
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dump = new ListNode(-1);
        dump.next = head;
        ListNode p = head;
        int sum = 0;
        while(p != null){
            sum++;
            p = p.next;
        }
        //反转顺序,倒数第n个就是顺数第sum - n个
        sum -= n;
        p = dump;  
        //遍历到(sum - n)- 1个节点
        while(sum > 0){
            sum--;
            p = p.next;
        }
        //删除节点
        p.next = p.next.next;
        return dump.next;
    }

方法二:

  • 一次遍历优化(双指针),第一个指针从列表的开头向前移动 n+1 步,而第二个指针将从列表的开头出发。现在,这两个指针被 n 个结点分开。我们通过同时移动两个指针向前来保持这个恒定的间隔,直到第一个指针到达最后一个结点。此时第二个指针将指向从最后一个结点数起的第 n 个结点。

这样解释吧,假设现在链表节点数是2n个

  • 第一个指针先走n+1个节点,还剩下 2n - (n + 1) = n - 1个节点
  • 然后第二个指针和第一个指针同时走,直到第一个指针走完链表
  • 此时第二个节点离走完链表还差n - 1个节点,即处于倒数第n-1个节点上,倒数第 n 个节点就是其后继
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dump = new ListNode(-1);
        dump.next = head;
        ListNode p = dump;
        ListNode q = dump;
        //p指针先走n + 1歩
        for(int i = 1;i <= n + 1;i++){
            p = p.next;
        }
        //p q同时开始走,知道p走到链表末尾为止
        while(p != null){
            p = p.next;
            q = q.next;
        }
        //此时p q之间有n个节点,q处于 倒数n - 1个节点
        q.next = q.next.next;
        return dump.next;
    }

从链表中删除总和为0的连续节点

在这里插入图片描述

    public ListNode removeZeroSumSublists(ListNode head) {
        ListNode dump = new ListNode(-1);
        dump.next = head;
        ListNode pre = dump;
        while(pre.next != null){
            ListNode p = pre.next;
            ListNode q = p;
            //sum是【p,q】区间的节点值和
            int sum = p.val;
            while(q.next != null || sum == 0){
                if(sum == 0){
                    //删除
                    pre.next = q.next;
                    break;
                }
                q = q.next;
                sum += q.val;
            }
            if(sum != 0) {
                pre = pre.next;
            }
        }
        return dump.next;
    }

6.链表其他题目

链表中的下一个更大的节点

在这里插入图片描述

    public int[] nextLargerNodes(ListNode head) {
        if(head == null || head.next == null){
            int[] res = new int[1];
            res[0] = 0;
            return res;
        }
        //链表节点数
        int size = 0;
        //stack1:逆序存储链表,栈顶是链表的最后一个节点
        Stack<Integer> stack1 = new Stack<>();
        while(head != null){
            stack1.push(head.val);
            head = head.next;
            size++;
        }
        //stack2:储存当前节点前的节点
        Stack<Integer> stack2 = new Stack<>();
        int[] res = new int[size];
        while(!stack1.isEmpty()){
            size--;
            while(!stack2.isEmpty() && stack1.peek() >= stack2.peek()){
                //比当前节点小的节点全都出栈
                stack2.pop();
            }
            //如果stack2没有节点,栈为空,则没有节点比当前节点大
            if(stack2.isEmpty()){
                res[size] = 0;
            }else{
                res[size] = stack2.peek();
            }
            stack2.push(stack1.pop());
        }
        return res;
    }

两数相加

在这里插入图片描述

    /**
     * 两数相加
     * @param l1
     * @param l2
     * @return
     */
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        //哑结点
        ListNode h = new ListNode(0);
        ListNode cur = h;
        //进位
        int c = 0;
        while(l1 != null || l2 != null || c != 0){
            int l1Val = (l1 != null ? l1.val : 0);
            int l2Val = (l2 != null ? l2.val : 0);
            int tmpSum = l1Val + l2Val + c;
            c = tmpSum / 10;
            ListNode tmp = new ListNode(tmpSum % 10);
            cur.next = tmp;
            cur = cur.next;
            if(l1 != null) {
                l1 = l1.next;
            }
            if(l2 != null) {
                l2 = l2.next;
            }
        }
        return h.next;
    }

两数相加II

在这里插入图片描述

    /**
     * 两数相加II
     * @param l1
     * @param l2
     * @return
     */
    public ListNode addTwoNumbers2(ListNode l1, ListNode l2) {
        //栈
        LinkedList<Integer> s1 = new LinkedList<>();
        LinkedList<Integer> s2 = new LinkedList<>();
        while(l1 != null) {
            s1.addFirst(l1.val);
            l1 = l1.next;
        }
        while(l2 != null) {
            s2.addFirst(l2.val);
            l2 = l2.next;
        }
        int carry = 0;
        ListNode lastNode = null;
        while(!s1.isEmpty() || !s2.isEmpty()) {
            int a1 = 0, a2 = 0;
            if(!s1.isEmpty()) {
                a1 = s1.removeFirst();
            }
            if(!s2.isEmpty()) {
                a2 = s2.removeFirst();
            }
            ListNode curNode = new ListNode((a1 + a2 + carry) % 10);
            carry = (a1 + a2 + carry) / 10;
            curNode.next = lastNode;
            lastNode = curNode;
        }
        if(carry > 0) {
            ListNode curNode = new ListNode(carry);
            curNode.next = lastNode;
            lastNode = curNode;
        }
        return lastNode;
    }

K个一组反转链表

在这里插入图片描述

基于栈实现

    /**
     * 给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
     * k 是一个正整数,它的值小于或等于链表的长度。
     * 如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
     *
     * 示例 :
     * 给定这个链表:1->2->3->4->5
     * 当 k = 2 时,应当返回: 2->1->4->3->5
     * 当 k = 3 时,应当返回: 3->2->1->4->5
     * @param head
     * @param k
     * @return
     */
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode dumy = new ListNode(0);
        ListNode p = dumy;
        Deque<ListNode> stack = new ArrayDeque<>();
        while(true){
            //记录是否到达k个
            int count = 0;
            //记录每k个的节点逆序前的头结点
            ListNode tmp = head;
            while(count != k && tmp != null){
                //进栈
                stack.add(tmp);
                tmp = tmp.next;
                count++;
            }
            //如果最后不足k个,则不需要逆序,直接接起来
            if(count != k){
                p.next = head;
                break;
            }
            while(!stack.isEmpty()){
                //完成逆序
                p.next = stack.pollLast();
                p = p.next;
            }
            //前k个完成逆序后指向下k个逆序的头结点
            p.next = tmp;
            head = tmp;
        }
        return dumy.next;
    }

标签:head,专题,ListNode,next,链表,节点,null,leetcode
来源: https://blog.csdn.net/weixin_41922289/article/details/102945549

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有