【边刷Leetcode边学Rust】56. 合并区间——元组型结构体 + 切片

【边刷Leetcode边学Rust】56. 合并区间

今天,我们通过56. 合并区间这道题来学习Rust中"元组型结构体(tuple-like structs) "和"向量(vector)的切片(slice)"这两个概念(语法项)。

首先来看题目给出的函数的定义(签名),

rust 复制代码
pub fn merge(intervals: Vec<Vec<i32>>) -> Vec<Vec<i32>>

这里是用Vec<i32>来表示区间的。这种表示方法显然不够直观,因为Vec<i32>可以包含任意个元素,而一个区间必须由[start, end]两个元素构成。

元组型结构体(tuple-like structs)

我们可以定义如下的元组型结构体 明确表示merge函数处理的对象是一系列区间Vec<Interval>)。

rust 复制代码
struct Interval(i32, i32);

// pub fn merge(intervals: Vec<Interval>) -> Vec<Interval>

构造一个区间非常简单,还可以通过形如.0.1的语法来访问区间的起点和终点。

rust 复制代码
struct Interval(i32, i32);

fn main() {
    let a = Interval(1, 2);
    println!("[{}, {}]", a.0, a.1);
}

元组型结构体与类型别名

表达式Interval(1, 2)看起来像函数调用,实际上也确实隐式定义了一个函数:

rust 复制代码
fn Interval(elem0: i32, elem1: i32) -> Interval { ... }

于是这就带来了一个小问题:

rust 复制代码
struct Foo(i32);
type Bar = Foo;

fn main() { 
    Bar(1);
}

//   |
// 5 |     Bar(1);
//   |     ^^^
//   |
//   = note: can't use a type alias as a constructor

无法直接使用元组型结构体的类型别名(type alias)构造值,除非再定义一个Bar()函数

rust 复制代码
fn Bar(a: i32) -> i32 { a }

合并算法(递归)

了解了元组型结构体的使用方法后,我们先将参数Vec<Vec<i32>>中的区间按起点排序(因为在已排序的区间列表中,可以合并的区间一定是连续的),再转换成Vec<Interval>

rust 复制代码
struct Interval(i32, i32);

pub fn merge(intervals: Vec<Vec<i32>>) -> Vec<Vec<i32>> {
    // sort intervals by start
    let mut intervals = intervals;
    intervals.sort_by(|a, b| a[0].cmp(&b[0]));

    // convert `Vec<Vec<i32>>` to `Vec<Interval>`
    let mut sorted_intervals = Vec::<Interval>::with_capacity(intervals.len());
    for interval in intervals {
        sorted_intervals.push(Interval(interval[0], interval[1]));
    }
  
    // ...
}

如图所示,经过排序后,标记为蓝色、黄色和绿色的区间分别可以合并成一个大区间。

如果用递归的思路,合并过程可以分为几步:

  1. 若整个待合并的区间列表为空,直接返回空列表;否则

  2. 将整个待合并的区间列表分割为两部分:可合并的部分 + 剩余部分

  3. 合并前者,将合并成的大区间放入结果中

  4. 对后者的剩余部分(视作新的整个待合并的区间列表)执行步骤0~2

用伪代码表示就是

rust 复制代码
fn merge(sorted_intervals: Vec<Intervals>) -> Vec<Intervals> {
    if sorted_intervals.len() == 0 {
      return vec![];
    }
  
    let mut ans = vec![];
    mergeable, remaining = split(sorted_intervals)
    
    result.push(mergeable);
    // merge the rest intervals recursively
    result.append(merge(remaining));
    return result;
}

向量(vector)的切片(slice)

在分割区间列表sorted_intervals: Vec<Intervals>时,显然无须生成区间列表的副本,只要记录分割位置即可。

在Rust中,类型&[T]称为类型T共享切片,是对一系列元素的引用,这些元素来自于数组或向量。或者说,切片是数组或向量中的一个区域。例如:

rust 复制代码
#[derive(Debug)]
struct Interval(i32, i32);

fn main() {
    let interval_vec = vec![
        Interval(1, 2),
        Interval(4, 9),
        Interval(8, 10),
        Interval(0, 0),
    ];
    
    print_interval_slice(&interval_vec); // [Interval(1, 2), Interval(4, 9), Interval(8, 10), Interval(0, 0)]

    print_interval_slice(&interval_vec[0..2]);    // [Interval(1, 2), Interval(4, 9)]

    print_interval_slice(&interval_vec[2..]);    // [Interval(8, 10), Interval(0, 0)]
}

fn print_interval_slice(interval_slice: &[Interval]) { // 以切片为参数
    println!("{:?}", interval_slice);
}

对于本题,合并操作只需要处理Vec<Interval>的切片。所以我们定义了_merge()函数:

rust 复制代码
fn _merge(sorted_intervals: &[Interval]) -> Vec<Interval> {

对整个sorted_intervals的引用&sorted_intervals是一个切片,也可以通过加上[..]来强调这一点,即&sorted_intervals[..]

当找到了分割位置i,以i为起点的部分区间列表&sorted_intervals[i..]又是切片。所以只需要递归调用_merge()函数即可合并所有能够合并的区间。

rust 复制代码
fn _merge(sorted_intervals: &[Interval]) -> Vec<Interval> {
    if sorted_intervals.len() == 0 {
        // 递归结束条件
        return vec![];
    }

    let mut i = 0;
    let mut max_end = sorted_intervals[0].1;
    let mut mergeable = Interval(sorted_intervals[0].0, max_end);
    let mut result = vec![];
  
    // 寻找分割位置`i`
    while i < sorted_intervals.len() {
        if sorted_intervals[i].0 > max_end {
            break;
        }

        // update `max_end`
        max_end = max_end.max(sorted_intervals[i].1);
        i += 1;
    }

    println!{"{:?}\t i = {}", mergeable, i};

    // found first mergeable interval
    mergeable.1 = max_end;
    result.push(mergeable);

    // merge the rest intervals recursively
    result.append(&mut Self::_merge(&sorted_intervals[i..]));
    return result;
}

完整的代码如下所示。

rust 复制代码
pub struct Solution;

#[derive(Debug, PartialEq, Eq)]
struct Interval(i32, i32);

impl Solution {
    ///
    /// ```
    /// use rust_leetcode::lc56::Solution;
    /// assert_eq!(vec![vec![1,6],vec![8,10],vec![15,18]], Solution::merge(vec![vec![1,3],vec![2,6],vec![8,10],vec![15,18]]));
    /// // assert_eq!(vec![vec![1,5]], Solution::merge(vec![vec![1,4],vec![4,5]]));
    /// // assert_eq!(vec![vec![0,5]], Solution::merge(vec![vec![4,5],vec![1,4], vec![0,1]]));
    /// ```
    pub fn merge(intervals: Vec<Vec<i32>>) -> Vec<Vec<i32>> {
        // sort intervals by start
        let mut intervals = intervals;
        intervals.sort_by(|a, b| a[0].cmp(&b[0]));

        let mut sorted_intervals = Vec::<Interval>::with_capacity(intervals.len());
        for interval in intervals {
            sorted_intervals.push(Interval(interval[0], interval[1]));
        }

        let result = Self::_merge(&sorted_intervals[..]);

        let mut ans = Vec::<Vec<i32>>::with_capacity(result.len());
        for interval in result {
            ans.push(vec![interval.0, interval.1]);
        }

        return ans;
    }

    fn _merge(sorted_intervals: &[Interval]) -> Vec<Interval> {
        if sorted_intervals.len() == 0 {
            return vec![];
        }

        let mut i = 0;
        let mut max_end = sorted_intervals[0].1;
        let mut mergeable = Interval(sorted_intervals[0].0, max_end);
        let mut result = vec![];
        while i < sorted_intervals.len() {
            if sorted_intervals[i].0 > max_end {
                break;
            }
            
            // update `max_end`
            max_end = max_end.max(sorted_intervals[i].1);
            i += 1;
        }

        println!{"{:?}\t i = {}", mergeable, i};

        // found first mergeable interval
        mergeable.1 = max_end;
        result.push(mergeable);

        // merge the rest intervals recursively
        result.append(&mut Self::_merge(&sorted_intervals[i..]));
        return result;
    }
}
相关推荐
程序猿小柒44 分钟前
leetcode hot100【LeetCode 79.单词搜索】java实现
java·算法·leetcode
新知图书2 小时前
Rust编程与项目实战-结构体
开发语言·后端·rust
yhy13152 小时前
Rust使用DX11进行截图并保存
开发语言·后端·rust
张晋涛-MoeLove2 小时前
我的 Rust 之旅,以及如何学习 Rust
开发语言·后端·学习·rust
Source.Liu2 小时前
【用Rust写CAD】第二章 第一个Rust程序 第三节 markdown语法
开发语言·rust
呜哈哈34245 小时前
二维数组的花式遍历技巧
数据结构·c++·算法·leetcode
九圣残炎6 小时前
【从零开始的LeetCode-算法】3242. 设计相邻元素求和服务
java·算法·leetcode
Curry_Math7 小时前
LeetCode热题100之贪心算法
算法·leetcode·贪心算法
小邓的技术笔记7 小时前
20241108,LeetCode 每日一题,用 Go 计算字符串中最长无重复字符
算法·leetcode·golang
陵易居士8 小时前
力扣每日一题 有序数组中的单一元素
数据结构·算法·leetcode