1.智能指针Rc<T>
再上一篇我们已经接触过一个智能指针Box<T>,并且完成了翻转链表,但是Box 是一个独占所有权的智能指针,意味着不能创建环形链表,因为环形链表势必有2个节点同时指向一个区域,因此就要采用可以共享所有权的Rc<T>智能指针
Rc<T> 智能指针
Rc<T>(引用计数指针)允许多个所有者共享数据,它使用引用计数来跟踪数据的所有者数量,并在所有者数量为零时释放数据。
Rc<T> 适用于单线程环境下的数据共享。
具体使用
use std::rc::Rc;
let data = Rc::new(42); // 引用计数 = 1
{
let clone1 = Rc::clone(&data); // 引用计数 = 2
let clone2 = Rc::clone(&data); // 引用计数 = 3
println!("{}", *clone1); // 输出 42
} // 引用计数减少到 1
println!("Final count: {}", Rc::strong_count(&data)); // 输出 1
RefCell<T> 智能指针
RefCell<T> 允许在运行时检查借用规则,它使用内部可变性来提供了一种安全的内部可变性模式,允许在不可变引用的情况下修改数据。
但是,RefCell<T> 只能用于单线程环境。
use std::cell::RefCell;
let cell = RefCell::new(42);
// 不可变作用域中修改值
{
let mut borrow = cell.borrow_mut();
*borrow += 10;
}
println!("{}", cell.borrow()); // 输出 52
Rc<RefCell<T>> 组合使用
优势
共享所有权 + 内部可变性:既允许多个所有者,又支持修改数据
动态借用检查:运行时管理借用规则
定义链表结构
use std::rc::Rc;
use std::cell::RefCell;
#[derive(Debug)]
struct ListNode {
val: i32,
next: Option<Rc<RefCell<ListNode>>>,
}
impl ListNode {
fn new(val: i32) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(ListNode { val, next: None }))
}
}
142. 环形链表 II
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0 输出:返回索引为 0 的链表节点 解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1 输出:返回 null 解释:链表中没有环。
提示:
- 链表中节点的数目范围在范围
[0, 104]
内 -105 <= Node.val <= 105
pos
的值为-1
或者链表中的一个有效索引
进阶:你是否可以使用 O(1)
空间解决此题?
(1)先解决为空问题
这次不仅要判断节点是否为空,还要处理返回值可能为空的情况,怎么办,判断节点为空还是之前的Option枚举类型,返回值可以自定义一个Result枚举类型
enum CycleResult {
NoCycle, // 表示链表中没有环
CycleStart(Rc<RefCell<ListNode>>), // 表示链表中有环,并返回环的起始节点
}
重要说明:本题目前力扣还不支持提交,因此只能本地运行测试。
(1)完整解题
use std::rc::Rc;
use std::cell::RefCell;
#[derive(Debug)]
struct ListNode {
val: i32,
next: Option<Rc<RefCell<ListNode>>>,
}
impl ListNode {
fn new(val: i32) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(ListNode { val, next: None }))
}
}
enum CycleResult {
NoCycle, // 表示链表中没有环
CycleStart(Rc<RefCell<ListNode>>), // 表示链表中有环,并返回环的起始节点
}
pub fn detect_cycle(head: Option<&Rc<RefCell<ListNode>>>) -> CycleResult {
let mut fast = head.cloned(); // 将 &Rc 转换为 Rc
let mut slow = head.cloned();
// 判断slow和fast是否为空
while let (Some(slow_node), Some(fast_node)) = (slow.as_ref(), fast.as_ref()) {
println!("慢指针: {}, 快指针: {}", slow_node.borrow().val, fast_node.borrow().val);
//慢指针走一步
slow = {
let next = slow_node.borrow().next.clone();
next
};
// 快指针走两步
fast = {
let next1 = fast_node.borrow().next.clone();
if let Some(f2) = next1 {
let next2 = f2.borrow().next.clone();
next2
} else {
None
}
};
if fast.is_none() {
return CycleResult::NoCycle; // 如果 fast 为 None,则没有环
}
// 判断是否快慢指针是否相遇,相遇代表有环,跳出循环
if let (Some(slow_node2), Some(fast_node2)) = (slow.as_ref(), fast.as_ref()) {
println!("慢指针: {}, 快指针: {}", slow_node2.borrow().val, fast_node2.borrow().val);
if Rc::ptr_eq(slow_node2, fast_node2) {
println!("有环");
break; // 找到相遇点
}
}
}
if slow.is_none() || fast.is_none() {
return CycleResult::NoCycle;
}
//寻找环的起始节点
let mut index1 = head.cloned();
let mut index2 = slow;
while let (Some(p1), Some(p2)) = (index1.as_ref(), index2.as_ref()) {
if Rc::ptr_eq(p1, p2) {
//println!("环的起始节点{}",p1.borrow().val);
return CycleResult::CycleStart(p1.clone()); // 找到环的起始节点
}
// 使用新的作用域释放借用
index1 = {
let next = p1.borrow().next.clone();
next
};
index2 = {
let next = p2.borrow().next.clone();
next
};
}
return CycleResult::NoCycle;
}
fn main() {
let mut node1 = ListNode::new(1);
let node2 = ListNode::new(2);
let node3 = ListNode::new(3);
let node4 = ListNode::new(4);
let node5 = ListNode::new(5);
// 构建链表:1 -> 2 -> 3 -> 4 -> 5
node1.borrow_mut().next = Some(node2.clone());
node2.borrow_mut().next = Some(node3.clone());
node3.borrow_mut().next = Some(node4.clone());
node4.borrow_mut().next = Some(node5.clone());
// 创建环:5 -> 3
node5.borrow_mut().next = Some(node3.clone());
// 调用 detect_cycle 函数
let result = detect_cycle(Some(&node1));
match result {
CycleResult::NoCycle => println!("链表中没有环"),
CycleResult::CycleStart(result) => println!("环的起始节点是: {}", result.borrow().val),
}
}
无环测试 注释掉//node5.borrow_mut().next = Some(node3.clone());
总结,本题有一定难度,一开始想放弃,原因有二,一个是智能指针还没完全掌握,在一个力扣还不能提交。但由于之前面试碰到过,还是咬牙坚持做出来。本题主要关键点,一个是智能指针Rc<RefCell<T>> 组合使用,在一个处理返回值,这次用了Result枚举类型,还要一个就力扣不能提交本地测试(这个建议用AI生成测试用例,不必自己去写)