【链表】328. 奇偶链表 & 61.旋转链表 & 86. 分隔链表 & 725 分隔链表

LeetCode 专栏收录该内容
69 篇文章 2 订阅

328.奇偶链表

题目

328. 奇偶链表
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

示例 1:

输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL
示例 2:

输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL
说明:

应当保持奇数节点和偶数节点的相对顺序。
链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。

解法

如果链表为空,则直接返回链表。

对于原始链表,每个节点都是奇数节点或偶数节点。头节点是奇数节点,头节点的后一个节点是偶数节点,相邻节点的奇偶性不同。因此可以将奇数节点和偶数节点分离成奇数链表和偶数链表,然后将偶数链表连接在奇数链表之后,合并后的链表即为结果链表。

原始链表的头节点 head 也是奇数链表的头节点以及结果链表的头节点,head 的后一个节点是偶数链表的头节点。令 evenHead = head.next,则 evenHead 是偶数链表的头节点。

维护两个指针 odd 和 even 分别指向奇数节点和偶数节点,初始时 odd = head,even = evenHead。通过迭代的方式将奇数节点和偶数节点分离成两个链表,每一步首先更新奇数节点,然后更新偶数节点。

更新奇数节点时,奇数节点的后一个节点需要指向偶数节点的后一个节点,因此令 odd.next = even.next,然后令 odd = odd.next,此时 odd 变成 even 的后一个节点。

更新偶数节点时,偶数节点的后一个节点需要指向奇数节点的后一个节点,因此令 even.next = odd.next,然后令 even = even.next,此时 even 变成 odd 的后一个节点。

在上述操作之后,即完成了对一个奇数节点和一个偶数节点的分离。重复上述操作,直到全部节点分离完毕。全部节点分离完毕的条件是 even 为空节点或者 even.next 为空节点,此时 odd 指向最后一个奇数节点(即奇数链表的最后一个节点)。

最后令 odd.next = evenHead,将偶数链表连接在奇数链表之后,即完成了奇数链表和偶数链表的合并,结果链表的头节点仍然是 head。
在这里插入图片描述

public ListNode oddEvenList(ListNode head) {
        if (head == null) {
            return head;
        }
        ListNode evenHead = head.next;
        ListNode odd = head;
        ListNode even = head.next;
        while (even != null && even.next != null) {
            odd.next = even.next;
            odd = odd.next;
            even.next = odd.next;
            even = even.next;
        }
        odd.next = evenHead;
        return head;
    }

61.旋转链表

61. 旋转链表
给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。

示例 1:

输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL
示例 2:

输入: 0->1->2->NULL, k = 4
输出: 2->0->1->NULL
解释:
向右旋转 1 步: 2->0->1->NULL
向右旋转 2 步: 1->2->0->NULL
向右旋转 3 步: 0->1->2->NULL
向右旋转 4 步: 2->0->1->NULL

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/rotate-list
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解法

【双指针】首尾相连形成环,在中间断开

这题k是非负数,但k有可能比链表的长度还要大,所以先要计算链表的长度len,需要旋转的步数就是(k%len)。一种比较简单的方式就是先把链表连接成一个环,然后再把链表在某个合适的位置断开。

我们可以使用两个指针,一个快指针fast从头开始遍历直到走到链表的末尾,然后再把链表串成一个环形。还一个指针slow也是从头开始,走(len-k%len)步就是我们要返回的链表头,这里可能有点疑问,为什么不是走(k%len)步,这是因为我们需要把链表后面的(k%len)个移到前面,因为单向链表我们没法从后往前遍历,所以我们只能从前往后移动(len-k%len)步。但实际上操作的时候会少走一步,具体来举个例子看一下,这里就以示例1为例画个图来看一下
在这里插入图片描述

public ListNode rotateRight(ListNode head, int k) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode fast = head;
        ListNode slow = head;
        int len = 1;
        // 统计链表的长度,顺便找到链表的尾结点
        while (fast.next != null) {
            fast = fast.next;
            len++;
        }
        // 首尾相连,先构成环
        fast.next = head;
        // 计算慢指针移动的步数 并使slow指向旋转之后的尾节点
        int step = len - k % len;
        while (step-- > 1) {
            slow = slow.next;
        }
        // temp就是需要返回的结点,断开slow指针
        ListNode temp = slow.next;
        slow.next = null;
        return temp;
    }

86. 分隔链表

题目

86. 分隔链表
面试题 02.04. 分割链表
给你一个链表和一个特定值 x ,请你对链表进行分隔,使得所有小于 x 的节点都出现在大于或等于 x 的节点之前。

你应当保留两个分区中每个节点的初始相对位置。

示例:

输入:head = 1->4->3->2->5->2, x = 3
输出:1->2->2->4->3->5

解法

直观来说我们只需维护两个链表small 和large 即可,small 链表按顺序存储所有小于 x 的节点,large 链表按顺序存储所有大于等于 x 的节点。遍历完原链表后,我们只要将small 链表尾节点指向 large 链表的头节点即能完成对链表的分隔。

为了实现上述思路,我们设 smallHead 和 largeHead 分别为两个链表的哑节点,即它们的next 指针指向链表的头节点,这样做的目的是为了更方便地处理头节点为空的边界条件。同时设 small 和 large 节点指向当前链表的末尾节点。开始时 smallHead=small,largeHead=large。随后,从前往后遍历链表,判断当前链表的节点值是否小于 x,如果小于就将small 的 next 指针指向该节点,否则将 large 的 next 指针指向该节点。

遍历结束后,我们将 large 的next 指针置空,这是因为当前节点复用的是原链表的节点,而其 next 指针可能指向一个小于 x 的节点,我们需要切断这个引用。同时将small 的next 指针指向largeHead 的next 指针指向的节点,即真正意义上的large 链表的头节点。最后返回 smallHead 的next 指针即为我们要求的答案。

public ListNode partition(ListNode head, int x) {
        // 指向较小链表的哑结点
        ListNode dummySmall = new ListNode(-1);
        ListNode small = dummySmall;
        // 指向较大链表的哑结点
        ListNode dummyLarge = new ListNode(-1);
        ListNode large = dummyLarge;
        // 如果当前值小于x,则将小链表指向它。否则将大链表指向它。
        while (head != null) {
            if (head.val < x) {
                small.next = head;
                small = small.next;
            } else {
                large.next = head;
                large = large.next;
            }
            head = head.next;
        }
        // 将两个链表拼接起来,大链表尾部置空
        large.next = null;
        small.next = dummyLarge.next;
        return dummySmall.next;
    }

725.分隔链表

题目

725. 分隔链表

给定一个头结点为 root 的链表, 编写一个函数以将链表分隔为 k 个连续的部分。

每部分的长度应该尽可能的相等: 任意两部分的长度差距不能超过 1,也就是说可能有些部分为 null。

这k个部分应该按照在链表中出现的顺序进行输出,并且排在前面的部分的长度应该大于或等于后面的长度。

返回一个符合上述规则的链表的列表。

举例: 1->2->3->4, k = 5 // 5 结果 [ [1], [2], [3], [4], null ]

示例 1:

输入: root = [1, 2, 3], k = 5 输出: [[1],[2],[3],[],[]] 解释:
输入输出各部分都应该是链表,而不是数组。 例如, 输入的结点 root 的 val= 1, root.next.val = 2,
\root.next.next.val = 3, 且 root.next.next.next = null。 第一个输出 output[0]
是 output[0].val = 1, output[0].next = null。 最后一个元素 output[4] 为 null,
它代表了最后一个部分为空链表。

示例 2:

输入: root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3 输出: [[1, 2, 3, 4],
[5, 6, 7], [8, 9, 10]] 解释:
输入被分成了几个连续的部分,并且每部分的长度相差不超过1.前面部分的长度大于等于后面部分的长度。

解法

如果链表有 N 个结点,则分隔的链表中每个部分中都有 n/k 个结点,且前 N%k 部分有一个额外的结点。我们可以用一个简单的循环来计算 N。
现在对于每个部分,我们已经计算出该部分有多少个节点:width + (i < remainder ? 1 : 0)。
我们将不创建新列表,而是直接拆分原链表,并根据需要返回指向原始链表中节点的指针列表。
在这里插入图片描述


 public ListNode[] splitListToParts(ListNode root, int k) {
        // 计算链表长度
        int len = 0;
        ListNode cur = root;
        while (cur != null) {
            len++;
            cur = cur.next;
        }
        // 每个子链表中应该有的节点个数
        int width = len / k;
        // 有几个子链表应该多存一个节点
        int rem = len % k;
        // pre节点为哑结点,方便删除
        ListNode pre = null;
        cur = root;
        ListNode[] ans = new ListNode[k];
        // 遍历k组子节点 每次rem-1,表面要多存1个的子链表在减少
        for (int i = 0; i < k; i++, rem--) {
            ans[i] = cur;
            // 计算当前子链表的宽度 并向后移动该宽度 记录当前节点的前一个节点
            int part_len = width + (rem > 0 ? 1 : 0);
            for (int j = 0; j < part_len; j++) {
                pre = cur;
                cur = cur.next;
            }
            // pre有可能为空
            if (pre != null) {
                pre.next = null;
            }
        }
        return ans;
    }
  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:技术黑板 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值