30 KiB
Rust Vector 练习题
建议先手动写出每道题的答案,再运行代码验证。
目录
一、概念辨析题
判断以下说法是否正确,并简要说明原因。
题目 1-1
Vec<T>只能存储相同类型的数据。
题目 1-2
从空 Vec 中
pop()会引发 panic。
题目 1-3
以下两种创建 Vec 的方式完全等价,没有性能差异:
let v1 = Vec::new(); let v2 = vec![];
题目 1-4
使用索引
v[3]访问 Vec 元素和使用v.get(3)的行为完全一致。
题目 1-5
对 Vec 进行
for循环遍历会消耗 Vec 的所有权,遍历后 Vec 不再可用。
题目 1-6
当 Vec 的容量满了再
push新元素时,Rust 会自动重新分配一块更大的内存并将原数据拷贝过去。
题目 1-7
Vec::with_capacity(n)创建的 Vec 的长度也为n。
题目 1-8
可以在遍历 Vec 的同时向其中添加元素。
二、填空题:补充代码
补全下列代码使其能通过编译并达到期望输出。
题目 2-1:基本创建和操作
fn main() {
// 填空:创建一个包含元素 1, 2, 3, 4, 5 的 Vec
let mut v = ________;
// 填空:向末尾添加元素 6
v.________(6);
// 填空:移除并返回最后一个元素
let last = v.________();
println!("移除的元素是: {}", last);
// 填空:获取当前长度
println!("当前长度: {}", ________);
// 填空:获取指定位置元素的值(安全方式)
match v.________(2) {
Some(value) => println!("索引 2 的值是: {}", value),
None => println!("没有该元素"),
}
}
// 期望输出:
// 移除的元素是: 6
// 当前长度: 5
// 索引 2 的值是: 3
题目 2-2:遍历 Vec
fn main() {
let v = vec![10, 20, 30, 40, 50];
// 填空:不可变遍历,打印每个元素
for ________ in ________ {
print!("{} ", i);
}
println!();
// 填空:可变遍历,每个元素翻倍
let mut v2 = vec![1, 2, 3, 4, 5];
for ________ in ________ {
*i ________ 2;
}
println!("翻倍后: {:?}", v2);
}
// 期望输出:
// 10 20 30 40 50
// 翻倍后: [2, 4, 6, 8, 10]
题目 2-3:插入和删除
fn main() {
let mut v = vec![1, 2, 4, 5];
// 填空:在索引 2 处插入元素 3
v.________(2, 3);
// 填空:删除索引 4 处的元素
v.________(4);
// 填空:删除索引 0 处的元素
v.________(0);
println!("结果: {:?}", v);
}
// 期望输出:结果: [2, 3, 4]
题目 2-4:枚举实现不同类型存储
// 填空:定义一个枚举,包含整数、浮点数、文本三种变体
________ Value {
Integer(i32),
Float(f64),
________(String),
}
fn main() {
// 填空:使用宏创建一个包含不同变体的 Vec
let row = ________;
Value::Integer(3),
Value::Float(3.14),
Value::Text(String::from("Hello")),
];
for v in &row {
match v {
Value::Integer(n) => print!("Int:{} ", n),
Value::Float(f) => print!("Float:{} ", f),
Value::Text(s) => print!("Text:{} ", s),
}
}
}
// 期望输出:Int:3 Float:3.14 Text:Hello
题目 2-5:切片操作
fn main() {
let v = vec![1, 2, 3, 4, 5, 6, 7, 8];
// 填空:获取 v[1..4] 的切片
let slice1 = ________;
println!("slice1: {:?}", slice1);
// 填空:获取前 3 个元素的切片
let slice2 = ________;
println!("slice2: {:?}", slice2);
// 填空:获取从索引 5 到末尾的切片
let slice3 = ________;
println!("slice3: {:?}", slice3);
}
// 期望输出:
// slice1: [2, 3, 4]
// slice2: [1, 2, 3]
// slice3: [6, 7, 8]
题目 2-6:容量管理
fn main() {
// 填空:预分配容量为 100 的空 Vec
let mut v: Vec<i32> = Vec::________(100);
println!("创建后 - 长度: {},容量: {}", v.len(), v.capacity());
for i in 0..50 {
v.push(i);
}
println!("添加 50 个元素后 - 长度: {},容量: {}", v.len(), v.capacity());
// 填空:收缩容量以匹配长度(使用合适的方法)
v.________();
println!("收缩后 - 长度: {},容量: {}", v.len(), v.capacity());
}
题目 2-7:常用方法组合
fn main() {
let mut v = vec![5, 2, 8, 1, 9, 3];
// 填空:排序(升序)
v.________();
// 填空:检查是否包含某个值
let has_five = v.________(&5);
// 填空:反转顺序
v.________();
println!("排序并反转后: {:?}", v);
println!("是否包含 5: {}", has_five);
}
// 期望输出:
// 排序并反转后: [9, 8, 5, 3, 2, 1]
// 是否包含 5: true
题目 2-8:extend 和 drain
fn main() {
let mut v1 = vec![1, 2, 3];
let v2 = vec![4, 5, 6];
// 填空:将 v2 的所有元素追加到 v1
v1.________(&v2);
println!("追加后: {:?}", v1);
let mut v = vec![10, 20, 30, 40, 50];
// 填空:移除索引 1..4 的元素,返回被移除元素的迭代器
let drained: Vec<_> = v.________(1..4).collect();
println!("被移除的: {:?}", drained);
println!("剩余的: {:?}", v);
}
// 期望输出:
// 追加后: [1, 2, 3, 4, 5, 6]
// 被移除的: [20, 30, 40]
// 剩余的: [10, 50]
题目 2-9:迭代器适配器
fn main() {
let v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 填空:过滤出偶数,映射为平方,收集到新 Vec
let result: Vec<i32> = v.________()
.________(|x| x % 2 == 0)
.________(|x| x * x)
.collect();
println!("偶数的平方: {:?}", result);
// 填空:求和
let sum: i32 = v.iter().________();
println!("总和: {}", sum);
}
// 期望输出:
// 偶数的平方: [4, 16, 36, 64, 100]
// 总和: 55
三、找出并修复错误
以下每段代码都有编译错误,请指出错误并写出修正后的代码。
题目 3-1
fn main() {
let mut v = vec![1, 2, 3];
let first = &v[0]; // 不可变借用
v.push(4); // 可变借用
println!("{}", first); // 使用不可变借用
}
题目 3-2
fn main() {
let v = vec![1, 2, 3, 4, 5];
let third = v[10];
println!("{}", third);
}
题目 3-3
fn main() {
let mut v = Vec::new();
v.push("hello");
v.push(42);
println!("{:?}", v);
}
题目 3-4
fn main() {
let mut v = vec![1, 2, 3];
for i in v.iter_mut() {
v.push(4); // 同时修改
}
println!("{:?}", v);
}
题目 3-5
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
for i in 0..v.len() {
if v[i] % 2 == 0 {
v.remove(i);
}
}
println!("{:?}", v);
}
题目 3-6
fn main() {
let v = vec![String::from("hello")];
let s = &v[0]; // 借用了 v 中的元素
drop(v); // 显式释放 v
println!("{}", s); // 使用了 s
}
题目 3-7
fn main() {
let v = vec![1, 2, 3];
// 想要获得 [1, 4, 9]
let squared: Vec<i32> = v.iter().map(|x| x * x);
println!("{:?}", squared);
}
题目 3-8
fn main() {
let mut v: Vec<u32> = Vec::new();
v.push(1);
let popped = v.pop();
println!("{:?}", popped); // 期望打印 1
let popped2 = v.pop(); // Vec 已空
println!("{}", popped2); // 这行会怎样?
}
题目 3-9
fn main() {
let v = vec![1, 2, 3, 4, 5];
for item in v {
println!("{}", item);
}
println!("v 还有 {} 个元素", v.len()); // 再次使用 v
}
题目 3-10
fn main() {
let mut v = vec![1, 2, 3];
let slice = &mut v[0..2]; // 可变借用切片
v.push(4); // 尝试修改原 Vec
slice[0] = 10;
println!("{:?}", v);
}
四、编程题
请在 main.rs 中编写以下程序,并实际运行验证。
题目 4-1:成绩统计分析器
编写一个程序,完成以下功能:
- 创建一个包含 10 个学生成绩的
Vec<i32>(成绩范围 0~100) - 计算并打印:最高分、最低分、平均分
- 找出所有不及格(< 60)的成绩,存入新 Vec 并打印
- 判断是否有满分(100 分),打印结果
- 将所有成绩从高到低排序后打印
期望输出示例(成绩不同则输出不同):
成绩列表: [85, 42, 96, 78, 55, 88, 91, 63, 100, 74]
最高分: 100
最低分: 42
平均分: 77.2
不及格成绩: [42, 55]
有满分: 是
排序后(高到低): [100, 96, 91, 88, 85, 78, 74, 63, 55, 42]
题目 4-2:斐波那契数列生成器
编写一个函数 fibonacci(n: usize) -> Vec<u64>,接收项数 n,返回一个包含前 n 项斐波那契数列的 Vec。
- n = 0 时返回空 Vec
- n = 1 时返回
[1] - n = 2 时返回
[1, 1] - n >= 3 时正常生成
在 main 中调用并打印前 20 项。
期望输出(前 20 项):
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
题目 4-3:购物车模拟
编写一个模拟购物车的程序:
- 定义一个
Item结构体,包含name: String和price: f64 - 创建一个购物车
Vec<Item>,提供以下功能:add_item(cart: &mut Vec<Item>, name: &str, price: f64)— 添加商品remove_item(cart: &mut Vec<Item>, name: &str)— 按名称移除商品(只移除第一个匹配项)total_price(cart: &Vec<Item>) -> f64— 计算总价list_items(cart: &Vec<Item>)— 列出所有商品
- 在
main中演示完整流程:添加 5 个商品,列出清单,计算总价,移除一个商品,再次列出清单和计算总价
期望输出示例:
购物车清单:
- 苹果: ¥3.50
- 香蕉: ¥2.00
- 牛奶: ¥6.80
- 面包: ¥5.00
- 鸡蛋: ¥12.00
当前总价: ¥29.30
移除"香蕉"后:
购物车清单:
- 苹果: ¥3.50
- 牛奶: ¥6.80
- 面包: ¥5.00
- 鸡蛋: ¥12.00
当前总价: ¥27.30
题目 4-4:单词统计器
编写一个程序,统计一段文本中每个单词出现的次数。
- 定义输入文本:
"the quick brown fox jumps over the lazy dog the fox was quick" - 将文本按空格分割成单词,存入
Vec<&str> - 使用一个辅助的 Vec(或使用
HashMap,但核心流程用 Vec 实现)统计每个不重复单词及其出现次数 - 打印所有不重复单词及其出现次数(按单词字母顺序排序)
提示:
- 可以用
Vec<(String, i32)>存储"单词-次数"对 - 遍历单词 Vec,如果在统计 Vec 中找到该单词则计数+1,否则追加新纪录
期望输出:
单词统计结果:
brown: 1
dog: 1
fox: 2
jumps: 1
lazy: 1
over: 1
quick: 2
the: 3
was: 1
题目 4-5:矩阵转置
使用 Vec<Vec<i32>> 表示一个二维矩阵,编写一个函数 transpose(matrix: &Vec<Vec<i32>>) -> Vec<Vec<i32>> 实现矩阵转置。
- 输入:一个 m×n 的矩阵
- 输出:一个 n×m 的转置矩阵
- 使用迭代器方法实现,避免使用显式的 C 风格循环
在 main 中测试:
原始矩阵:
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
转置后:
[1, 4, 7]
[2, 5, 8]
[3, 6, 9]
题目 4-6:简易栈(Stack)实现
只使用 Vec 实现一个 Stack<T> 结构体,封装栈操作:
fn new() -> Selffn push(&mut self, item: T)fn pop(&mut self) -> Option<T>fn peek(&self) -> Option<&T>fn is_empty(&self) -> boolfn len(&self) -> usize
在 main 中测试括号匹配功能:编写函数 is_balanced(s: &str) -> bool,利用你实现的 Stack 判断字符串中的 ()、[]、{} 是否正确匹配。
测试用例:
测试用例:
"()" -> true
"()[]{}" -> true
"(]" -> false
"([)]" -> false
"{[]}" -> true
"(((" -> false
"" -> true
五、综合思考题
题目 5-1
阅读以下代码:
fn main() {
let mut v = vec![String::from("Rust"), String::from("is"), String::from("awesome")];
let first = &v[0]; // ①
v.push(String::from("!")); // ②
println!("{}", first); // ③
}
- 这段代码能编译吗?为什么?
- 如果改成
let first = &v[0].clone();,会怎样?为什么? v[0]和v.get(0)的区别在哪里?分别适用于什么场景?- 如果
v的类型是Vec<i32>,问题 1 的代码是否能编译?为什么?
题目 5-2
分析以下两种删除 Vec 中所有偶数元素的方式:
方式 A:
fn remove_evens_a(v: &mut Vec<i32>) {
let mut i = 0;
while i < v.len() {
if v[i] % 2 == 0 {
v.remove(i);
} else {
i += 1;
}
}
}
方式 B:
fn remove_evens_b(v: &mut Vec<i32>) {
v.retain(|x| x % 2 != 0);
}
- 两种方式的时间复杂度分别是多少?为什么?
- 方式 A 中的
i += 1放在else分支中,如果去掉这个条件(每次循环都i += 1)会发生什么? retain方法的底层原理是什么?它比手动remove高效在哪里?
题目 5-3
fn first_n_even(n: usize) -> Vec<i32> {
let mut result = Vec::new();
let mut num = 2;
while result.len() < n {
if num % 2 == 0 {
result.push(num);
}
num += 1;
}
result
}
- 上述代码有没有更好的 Rust 风格写法?请使用迭代器重写。
- 如果
n非常大(比如 10000000),从性能角度应该如何优化?(提示:Vec::with_capacity和迭代器)
题目 5-4
阅读以下代码,回答:
fn append_vector(v: &mut Vec<i32>, other: &mut Vec<i32>) {
v.append(other);
}
fn main() {
let mut v1 = vec![1, 2, 3];
let mut v2 = vec![4, 5, 6];
append_vector(&mut v1, &mut v2);
println!("v1: {:?}", v1);
println!("v2: {:?}", v2);
}
v2在append调用后会变成什么?为什么?append和extend有什么区别?各适用于什么场景?- 如果希望
v2在追加后保持不变(即"复制追加"),应该使用什么方法?
题目 5-5
假设你正在实现一个游戏中的物品栏系统:
#[derive(Debug)]
struct Inventory {
items: Vec<Item>,
capacity: usize,
}
#[derive(Debug, Clone)]
struct Item {
name: String,
count: u32,
}
impl Inventory {
fn new(capacity: usize) -> Self {
Inventory {
items: Vec::new(),
capacity,
}
}
fn add_item(&mut self, item: Item) -> Result<(), String> {
// 待实现
todo!()
}
fn remove_item(&mut self, name: &str, count: u32) -> Result<(), String> {
// 待实现
todo!()
}
}
- 实现
add_item:如果物品已存在则叠加数量;如果背包已满则返回错误信息。注意借用检查器的约束。 - 实现
remove_item:如果数量足够则减少,如果降至 0 则移除整个条目;如果物品不存在或数量不足则返回错误信息。 - 这个设计中有一个潜在的问题:当背包容量已满但要添加的是已有物品时,叠加数量并不需要新增槽位。如何修改
add_item的逻辑来正确处理这种情况?
参考答案
请独立完成再查看答案。
点击展开答案
一、概念辨析题
1-1:✅ 正确。Vec<T> 是泛型容器,所有元素必须是相同类型 T。如果要存储不同类型,需要使用枚举(如 Vec<MyEnum>)或 trait 对象(Vec<Box<dyn MyTrait>>)。
1-2:❌ 错误。空 Vec 调用 pop() 不会 panic,而是返回 None。pop 的返回类型是 Option<T>,在空 Vec 上返回 None。
1-3:❌ 错误。Vec::new() 创建一个容量为 0 的 Vec(不分配堆内存),vec![] 同样不分配。两者在这种情况下等价。但如果使用 vec![1, 2, 3] 和手动 new 后 push 三次,vec![] 可能预先分配合适的容量,性能略好。
1-4:❌ 错误。v[3] 索引越界时会 panic(程序崩溃),而 v.get(3) 返回 Option<&T>,越界时返回 None,不会崩溃。get 是安全的访问方式。
1-5:❌ 错误。for item in v 会消耗 Vec 的所有权,但 for item in &v 仅不可变借用,for item in &mut v 仅可变借用。只有转移所有权的遍历才会使 Vec 之后不可用。
1-6:✅ 正确。当 len == capacity 时再 push,Vec 会自动分配一块更大的内存(通常是当前容量的两倍),将旧元素移动/拷贝到新内存,然后释放旧内存。
1-7:❌ 错误。Vec::with_capacity(n) 创建的 Vec 长度为 0,容量为 n。需要通过 push 等方法添加元素后长度才会增加。
1-8:❌ 错误。在遍历 Vec 时不能同时修改 Vec 本身(添加或删除元素),这违反了借用规则。遍历时持有的是对 Vec 或其元素的借用,而修改需要可变借用,两者不能共存。
二、填空题
2-1:
let mut v = vec![1, 2, 3, 4, 5];
v.push(6);
let last = v.pop().unwrap();
println!("当前长度: {}", v.len());
match v.get(2) {
2-2:
for i in &v {
print!("{} ", i);
}
// 可变遍历
for i in &mut v2 {
*i *= 2;
}
2-3:
v.insert(2, 3);
v.remove(4);
v.remove(0);
2-4:
enum Value {
Integer(i32),
Float(f64),
Text(String),
}
let row = vec![
Value::Integer(3),
Value::Float(3.14),
Value::Text(String::from("Hello")),
];
2-5:
let slice1 = &v[1..4];
let slice2 = &v[..3];
let slice3 = &v[5..];
2-6:
let mut v: Vec<i32> = Vec::with_capacity(100);
// ... 添加元素后
v.shrink_to_fit();
2-7:
v.sort();
let has_five = v.contains(&5);
v.reverse();
2-8:
v1.extend(&v2);
let drained: Vec<_> = v.drain(1..4).collect();
2-9:
let result: Vec<i32> = v.iter()
.filter(|x| *x % 2 == 0)
.map(|x| x * x)
.collect();
let sum: i32 = v.iter().sum();
三、修复错误
3-1:不可变借用和可变借用冲突。first 是 v 的不可变引用,而 push 需要 v 的可变引用。修复:调整顺序,先使用 first 再 push,或在 push 前不保留引用。
let mut v = vec![1, 2, 3];
let first = v[0]; // 直接拷贝值(i32 实现了 Copy)
v.push(4);
println!("{}", first);
对于非 Copy 类型,可以:
let first = v[0].clone(); // 克隆值
v.push(4);
println!("{}", first);
3-2:索引越界,Vec 只有 5 个元素(索引 0~4),访问 v[10] 会 panic。修复:
let third = v.get(10); // 返回 None,不会 panic
match third {
Some(val) => println!("{}", val),
None => println!("索引越界"),
}
3-3:Vec::new() 无法推断类型,需要显式标注。且同一个 Vec 不能混装 &str 和 i32。修复:使用枚举:
enum Value<'a> {
Text(&'a str),
Number(i32),
}
let mut v: Vec<Value> = Vec::new();
v.push(Value::Text("hello"));
v.push(Value::Number(42));
3-4:在遍历 Vec 的同时修改 Vec 的大小,违反借用规则。修复:先收集要添加的元素,再 extend:
let mut v = vec![1, 2, 3];
v.extend(vec![4, 4, 4]);
println!("{:?}", v);
3-5:一边按索引遍历一边删除元素会导致跳过元素(因为删除后后续元素前移)。修复:使用 retain 或从后往前遍历:
let mut v = vec![1, 2, 3, 4, 5];
v.retain(|x| x % 2 != 0);
println!("{:?}", v); // [1, 3, 5]
3-6:s 借用了 v 中的元素,drop(v) 释放 v 后 s 变成悬垂引用。修复:先 clone 再释放:
let v = vec![String::from("hello")];
let s = v[0].clone(); // 获取独立的所有权
drop(v);
println!("{}", s);
3-7:map 返回迭代器,需要 .collect() 消费才能得到 Vec。修复:
let squared: Vec<i32> = v.iter().map(|x| x * x).collect();
3-8:pop 返回 Option<u32>,不能直接用 println!("{}", popped2) 打印(类型不匹配,且值为 None)。修复:
println!("{:?}", popped); // Some(1)
println!("{:?}", popped2); // None
3-9:for item in v 消耗了 v 的所有权,之后不能再用 v。修复:使用引用遍历:
for item in &v {
println!("{}", item);
}
println!("v 还有 {} 个元素", v.len());
3-10:slice 是 v 的可变借用,push 需要另一个可变借用,冲突。修复:先操作切片,释放后再 push:
let mut v = vec![1, 2, 3];
{
let slice = &mut v[0..2];
slice[0] = 10;
} // slice 的借用在此结束
v.push(4);
println!("{:?}", v); // [10, 2, 3, 4]
四、编程题
4-1 成绩统计分析器:
fn main() {
let scores = vec![85, 42, 96, 78, 55, 88, 91, 63, 100, 74];
println!("成绩列表: {:?}", scores);
let max = scores.iter().max().unwrap();
let min = scores.iter().min().unwrap();
let avg = scores.iter().sum::<i32>() as f64 / scores.len() as f64;
println!("最高分: {}", max);
println!("最低分: {}", min);
println!("平均分: {:.1}", avg);
let failing: Vec<i32> = scores.iter().filter(|&&s| s < 60).copied().collect();
println!("不及格成绩: {:?}", failing);
let has_perfect = scores.contains(&100);
println!("有满分: {}", if has_perfect { "是" } else { "否" });
let mut sorted = scores.clone();
sorted.sort_by(|a, b| b.cmp(a));
println!("排序后(高到低): {:?}", sorted);
}
4-2 斐波那契数列生成器:
fn fibonacci(n: usize) -> Vec<u64> {
match n {
0 => vec![],
1 => vec![1],
_ => {
let mut fib = vec![1, 1];
for _ in 2..n {
let next = fib[fib.len() - 1] + fib[fib.len() - 2];
fib.push(next);
}
fib
}
}
}
fn main() {
let fib = fibonacci(20);
println!("{:?}", fib);
}
4-3 购物车模拟:
struct Item {
name: String,
price: f64,
}
fn add_item(cart: &mut Vec<Item>, name: &str, price: f64) {
cart.push(Item {
name: name.to_string(),
price,
});
}
fn remove_item(cart: &mut Vec<Item>, name: &str) {
if let Some(pos) = cart.iter().position(|item| item.name == name) {
cart.remove(pos);
}
}
fn total_price(cart: &Vec<Item>) -> f64 {
cart.iter().map(|item| item.price).sum()
}
fn list_items(cart: &Vec<Item>) {
for item in cart {
println!(" - {}: ¥{:.2}", item.name, item.price);
}
}
fn main() {
let mut cart = Vec::new();
add_item(&mut cart, "苹果", 3.50);
add_item(&mut cart, "香蕉", 2.00);
add_item(&mut cart, "牛奶", 6.80);
add_item(&mut cart, "面包", 5.00);
add_item(&mut cart, "鸡蛋", 12.00);
println!("购物车清单:");
list_items(&cart);
println!("当前总价: ¥{:.2}", total_price(&cart));
remove_item(&mut cart, "香蕉");
println!("\n移除\"香蕉\"后:");
println!("购物车清单:");
list_items(&cart);
println!("当前总价: ¥{:.2}", total_price(&cart));
}
4-4 单词统计器:
fn main() {
let text = "the quick brown fox jumps over the lazy dog the fox was quick";
let words: Vec<&str> = text.split_whitespace().collect();
let mut stats: Vec<(String, i32)> = Vec::new();
for word in &words {
if let Some(pos) = stats.iter().position(|(w, _)| w == word) {
stats[pos].1 += 1;
} else {
stats.push((word.to_string(), 1));
}
}
stats.sort_by(|a, b| a.0.cmp(&b.0));
println!("单词统计结果:");
for (word, count) in &stats {
println!(" {}: {}", word, count);
}
}
4-5 矩阵转置:
fn transpose(matrix: &Vec<Vec<i32>>) -> Vec<Vec<i32>> {
if matrix.is_empty() {
return vec![];
}
let rows = matrix.len();
let cols = matrix[0].len();
(0..cols)
.map(|col| {
(0..rows)
.map(|row| matrix[row][col])
.collect()
})
.collect()
}
fn main() {
let matrix = vec![
vec![1, 2, 3],
vec![4, 5, 6],
vec![7, 8, 9],
];
println!("原始矩阵:");
for row in &matrix {
println!("{:?}", row);
}
let transposed = transpose(&matrix);
println!("\n转置后:");
for row in &transposed {
println!("{:?}", row);
}
}
4-6 简易栈实现:
struct Stack<T> {
data: Vec<T>,
}
impl<T> Stack<T> {
fn new() -> Self {
Stack { data: Vec::new() }
}
fn push(&mut self, item: T) {
self.data.push(item);
}
fn pop(&mut self) -> Option<T> {
self.data.pop()
}
fn peek(&self) -> Option<&T> {
self.data.last()
}
fn is_empty(&self) -> bool {
self.data.is_empty()
}
fn len(&self) -> usize {
self.data.len()
}
}
fn is_balanced(s: &str) -> bool {
let mut stack = Stack::new();
for ch in s.chars() {
match ch {
'(' | '[' | '{' => stack.push(ch),
')' => {
if stack.pop() != Some('(') {
return false;
}
}
']' => {
if stack.pop() != Some('[') {
return false;
}
}
'}' => {
if stack.pop() != Some('{') {
return false;
}
}
_ => {}
}
}
stack.is_empty()
}
fn main() {
let tests = [
("()", true),
("()[]{}", true),
("(]", false),
("([)]", false),
("{[]}", true),
("(((", false),
("", true),
];
println!("括号匹配测试:");
for (input, expected) in tests {
let result = is_balanced(input);
let status = if result == expected { "✓" } else { "✗" };
println!(" {} \"{}\" -> {} (期望: {})", status, input, result, expected);
}
}
五、综合思考题
5-1:
-
不能编译。
first是v[0]的不可变引用,而v.push()需要可变引用。即便push触发的内存重分配不影响索引 0 的元素(逻辑上 String 的数据在堆上),但 Rust 的借用规则在编译期不能精确判断这一点,因此直接拒绝在持有不可变引用时进行可变操作。 -
v[0].clone()会创建一个新的String,first拥有这个新 String 的所有权,不再依赖v。因此v.push()不受影响,可以编译。 -
v[0]返回&T,越界时 panic;v.get(0)返回Option<&T>,越界时返回None。v[0]适用于确定索引有效的场景,v.get(0)适用于索引可能越界的场景。 -
可以编译。因为
i32实现了Copytrait,let first = &v[0]在后续使用中,由于first是&i32,println!打印时需要解引用为i32值。但关键是将let first = &v[0]改成let first = v[0]后,first是i32值(Copy),与v无关,更安全。使用&v[0]仍然会有借用冲突。
5-2:
-
方式 A 的时间复杂度是 O(n²),因为每次
remove(i)都需要将后续元素向前移动一位。方式 B 的retain是 O(n),内部使用一次遍历 + 一次批量移动实现。 -
如果每次都
i += 1,删除偶数元素后索引不会重新检查当前位置(因为后续元素前移了),会跳过紧随其后的元素。例如[2, 2, 1],删除索引 0 的元素 2 后,数组变为[2, 1],i 变成 1,第二个 2 被跳过。 -
retain的底层原理类似于"双指针":慢指针j指向下一个要保留的位置,快指针i遍历所有元素。当元素满足保留条件时,移动到j位置并使j++;否则跳过。遍历完成后截断到j。避免了每次删除都移动后续元素的 O(n²) 开销。
5-3:
- 迭代器重写:
fn first_n_even(n: usize) -> Vec<i32> {
(1..).filter(|x| x % 2 == 0).take(n).collect()
}
- 优化:
fn first_n_even_optimized(n: usize) -> Vec<i32> {
let mut result = Vec::with_capacity(n); // 预分配
(1..).filter(|x| x % 2 == 0).take(n).for_each(|x| result.push(x));
result
}
// 或者更简单的:从 2 开始每次加 2
fn first_n_even_optimized(n: usize) -> Vec<i32> {
(2..).step_by(2).take(n).collect()
}
使用 Vec::with_capacity(n) 预分配可以避免多次重分配的开销。
5-4:
-
v2变成空 Vec[]。因为append方法将所有元素移动到目标 Vec,并清空源 Vec。这是 O(1) 时间,只交换了指针。 -
区别:
append:移动所有元素,源 Vec 被清空。适用于需要转移所有权的场景。extend:从迭代器克隆/复制元素到目标 Vec,源数据保持不变。适用于需要保留源的场景。- 性能上
append只需交换内部指针,是 O(1);extend需要逐个复制,是 O(n)。
-
使用
extend或clone:
v1.extend_from_slice(&v2); // v2 保持不变
// 或
v1.extend(v2.iter().cloned()); // 迭代器方式
5-5:
fn add_item(&mut self, item: Item) -> Result<(), String> {
// 先检查是否有同名物品
if let Some(existing) = self.items.iter_mut().find(|i| i.name == item.name) {
existing.count += item.count;
return Ok(());
}
// 新增物品,检查容量
if self.items.len() >= self.capacity {
return Err(format!("背包已满({}/{})", self.items.len(), self.capacity));
}
self.items.push(item);
Ok(())
}
fn remove_item(&mut self, name: &str, count: u32) -> Result<(), String> {
let pos = self
.items
.iter()
.position(|i| i.name == name)
.ok_or(format!("物品「{}」不存在", name))?;
if self.items[pos].count < count {
return Err(format!(
"物品「{}」数量不足(需要 {},当前 {})",
name, count, self.items[pos].count
));
}
self.items[pos].count -= count;
if self.items[pos].count == 0 {
self.items.remove(pos);
}
Ok(())
}
关键点是先检查是否可以叠加已有物品(不需要新槽位),再检查容量限制。这样当物品已存在时,即使背包"已满"也能正确叠加数量。