春招打卡 | K 个一组翻转链表

220 阅读3分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

无意中看到掘金社区的春招打卡活动。抱着参与的心态,我再次拾起了之前没有做完的力扣题目。因为在学习 Rust,所以打算用 Rust 实现它。

题目描述

  • 给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
  • k 是一个正整数,它的值小于或等于链表的长度。
  • 如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。 进阶:
  • 你可以设计一个只使用常数额外空间的算法来解决此问题吗?
  • 你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。 例如
  • 输入:head = [1,2,3,4,5], k = 2
  • 输出: [2,1,4,3,5]

更加详细的题目描述可以去力扣网站看

思路分析

对于本题,常规思路即可解题。一个链表,先遍历一次,得到链表的节点个数 n,n 的作用是计算出有多少组 k 个节点,当最后一部分节点不足 k 个时,则无需反转。

从左往右遍历,当遍历到第 k 个节点 k1 时,就把 k1 及其之后的所有节点 take(拿走的意思,同时它也是 Rust 的一个常用方法),交给下一次循环处理,同时将剩下的 k 个元素的链表交给反转函数处理,得到反转后的链表头,将其拼接到新链表的结尾处。以此类推,直至 n/k 次循环结束后,将最后不足 k 个元素的剩余链表的头拼接到结果链表中。

AC 代码

struct Solution;

// Definition for singly-linked list.
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct ListNode {
    pub val: i32,
    pub next: Option<Box<ListNode>>,
}

impl ListNode {
    #[inline]
    fn new(val: i32) -> Self {
        ListNode { next: None, val }
    }
}

impl Solution {
    pub fn reverse_k_group(mut head: Option<Box<ListNode>>, k: i32) -> Option<Box<ListNode>> {
        if k <= 1 {
            return head;
        }
        if head.is_none() {
            return head;
        }
        if head.as_ref().is_some() && head.as_ref().as_ref().unwrap().next.is_none() {
            return head;
        }
        let total = Solution::get_node_num(&head);
        // 用一个新的节点放置反转后的链表 head
        let mut new_head = Some(Box::new(ListNode::new(0)));
        let mut new_tail = new_head.as_mut().unwrap();
        for _ in 0..(total / k) {
            let mut p = head.as_mut().unwrap();
            // 步进 k 次,然后 take
            for _ in 0..(k - 1) {
                p = p.next.as_mut().unwrap();
            }
            let tail = p.next.take();
            let reversed = Solution::reverse_one(head, k);
            new_tail = Solution::merge_one(new_tail, reversed);
            head = tail;
        }
        // 处理最后剩余不足 k 个的部分节点
        new_tail.next = head;

        new_head.unwrap().next
    }

    /// 将反转后的一组合并
    fn merge_one(
        mut tail: &mut Box<ListNode>,
        new_list: Option<Box<ListNode>>,
    ) -> &mut Box<ListNode> {
        tail.next = new_list;
        while tail.next.is_some() {
            tail = tail.next.as_mut().unwrap();
        }
        tail
    }

    /// 反转一组
    fn reverse_one(mut head: Option<Box<ListNode>>, k: i32) -> Option<Box<ListNode>> {
        let mut res: Option<Box<ListNode>> = None;
        for _ in 0..k {
            if let Some(mut tmp_node) = head {
                head = tmp_node.next.take();
                tmp_node.next = res;
                res = Some(tmp_node);
            }
        }
        res
    }

    /// 从头部开始,遍历计算出链表的长度
    pub fn get_node_num(head: &Option<Box<ListNode>>) -> i32 {
        let mut num = 0;
        if head.is_none() {
            return num;
        }
        let mut cur_node = head;
        while cur_node.is_some() {
            num += 1;
            cur_node = &cur_node.as_ref().unwrap().next;
        }
        num
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let mut node1 = Some(Box::new(ListNode::new(1)));
        let mut node2 = Some(Box::new(ListNode::new(2)));
        let mut node3 = Some(Box::new(ListNode::new(3)));
        let mut node4 = Some(Box::new(ListNode::new(4)));
        let mut node5 = Some(Box::new(ListNode::new(5)));
        node2.as_mut().unwrap().next = node1;
        node3.as_mut().unwrap().next = node2;
        node4.as_mut().unwrap().next = node3;
        node5.as_mut().unwrap().next = node4;
        let res = Solution::reverse_k_group(node5, 2);
        let expected_res = vec![4, 5, 2, 3, 1];
        let mut cur_node_op = res;
        for expected_one in expected_res {
            let mut cur_node = cur_node_op.as_mut().unwrap();
            assert!(cur_node.val == expected_one);
            cur_node_op = cur_node.next.take();
        }
    }
}

总结

本题虽然没有用到特别的算法,但是考验做题人的细心和耐心。将逻辑拆分为摘取 k 个节点,将节点反转的逻辑独立出来,最后再处理无需反转的剩余节点。此外,使用 Rust 实现可能和其他编程语言不太一样,因为需要注意各种参数的所有权以及对标准库中 Option 相关方法的熟练程度。