【算法】Java 中链表的基本操作

22 篇文章 2 订阅

  链表是一种重要的数据结构,在工程项目中广泛使用。对于链表,要搞清楚是否有头节点,即第一个节点不存任何数据,只是表示链表的头部,而首节点才是链表第一个真正存放数据的节点。通常情况下,对有头节点的链表进行操作比较方便。如果原来的链表没有头节点,可以 new 一个 dummyHead 作为头节点,这样往往能减少大量边界条件的判断。下面通过几个例题来学习链表。

1.力扣206.反转链表[1]

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

示例 1:

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

反转链表是链表中最常考到的问题。可以使用头插法反转链表。具体解法如下:

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode pre = null;	// 首节点
        while (head != null) {
            ListNode next = head.next;	// 临时存储下次要操作的节点
            head.next = pre;	// 头插法的要点是每次在首节点的前面插入
            pre = head;		// 插入后的节点变成新的首节点
            head = next;	// 取下次要操作的节点
        }
        return pre;
    }
}

或者定义一个 dummyHead,每次在 dummyHead 的后面插入:

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode dummyHead = new ListNode(0, null);
        ListNode p = head;
        while (p != null) {
            ListNode next = p.next;
            p.next = dummyHead.next;
            dummyHead.next = p;
            p = next;
        }
        return dummyHead.next;
    }
}
2.力扣92. 反转链表 II[2]

给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。

示例 1:

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

这道题和上一题基本一样,只是需要先将指针移动到要操作的区间。

class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        // 定义一个哑节点,这样即使 left = 0 也不需要特殊处理
        ListNode dummy = new ListNode(0, head);
        
        // pre 用来指向要翻转的第一个节点
        ListNode pre = dummy;
        // 将指针移动到要反转的第一个节点的上一个节点
        for (int i = 1; i < left; i++) pre = pre.next;
        
        // cur 用来指向要翻转的第一个节点,始终将 cur 的下一个节点进行头插
        ListNode cur = pre.next;
        
        // 头插法
        for (int i = 0; i < right - left; i++) {
            ListNode next = cur.next;	// 将 cur 的下一个节点取出来,准备头插
            cur.next = next.next;   // cur 连接到后面剩余的节点
            
            // 头插
            next.next = pre.next;
            pre.next = next;
        }
        return dummy.next;
    }
}

具体过程如下:

next = cur.next:

在这里插入图片描述

cur.next = next.next:

在这里插入图片描述

next.next = pre.next:

在这里插入图片描述

pre.next = next:

在这里插入图片描述

3.力扣25.K 个一组翻转链表[3]

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

进阶:

你可以设计一个只使用常数额外空间的算法来解决此问题吗?
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例 1:

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

示例 2:

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

若不考虑进阶的要求,可以用 Map 来记录下标:

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        if (head == null) return head;
        int idx = 0;
        Map<Integer, ListNode> map = new HashMap<>();
        ListNode p = head;
        while (p != null) {
            map.put(idx++, p);
            p = p.next;
        }
        int cnt = idx / k;      // idx是节点总数,cnt表示有几个段要翻转
        for (int i = 0; i < cnt; i++) {
            // 每段第一个节点连接到下一段第一个,防止最后一段不够k个导致不用翻转
            map.get(i * k).next = map.get((i+1) * k);
            // 从倒数第1个节点翻转到倒数第k-1个
            for (int j = 1; j < k; j++) {
                map.get((i+1) * k - j).next = map.get((i+1) * k - j - 1);
            }
            if (i > 0) {    // 把前面一段的第一个节点的next连接到当前一段的最后一个节点
                map.get((i-1) * k).next = map.get((i+1) * k - 1);
            }
        }
        return map.get(k - 1);
    }
}

若考虑进阶的要求,则不能借助 Map 记录下标:

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        if (head == null || head.next == null){
            return head;
        }
        // dummy->1->2->3->4->5
        ListNode dummy=new ListNode(0, head);
        ListNode pre=dummy; // pre指每次要翻转的链表的头结点的上一个节点
        ListNode end=dummy; // end指每次要翻转的链表的尾节点

        while(end.next!=null){
            //循环k次,找到需要翻转的链表的结尾
            for(int i=0;i<k&&end != null;i++){
                end=end.next;
            }
            //如果end==null,即需要翻转的链表的节点数小于k,不执行翻转。
            if(end==null) break;

            ListNode next=end.next; // 下一次要翻转的链表
            end.next=null;  //然后断开链表

            ListNode start=pre.next;    //记录下要翻转链表的头节点
            pre.next=reverse(start);

            //翻转后头节点变到最后。通过.next把断开的链表重新链接。
            start.next=next;
            //将pre和end初始化,换成下次要翻转的链表的头结点的上一个节点。即start
            pre=start;
            end=start;
        }
        return dummy.next;
    }

    private ListNode reverse(ListNode head) {
        ListNode pre = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode next = curr.next;
            curr.next = pre;
            pre = curr;
            curr = next;
        }
        return pre;
    }
}

欢迎关注公众号。
在这里插入图片描述

Reference

[1]力扣206. 反转链表:https://leetcode-cn.com/problems/reverse-linked-list/

[2]力扣92. 反转链表 II:https://leetcode-cn.com/problems/reverse-linked-list-ii/

[3]力扣25.K 个一组翻转链表:https://leetcode-cn.com/problems/reverse-nodes-in-k-group/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值