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 相关方法的熟练程度。