刷题 徒步旅行 思考存在先消耗后购买情况下的实现 rust实现

45 阅读4分钟

最近在学习rust语言,用刷题来熟悉基础用法,遇见了这个徒步旅行的题; 题目如下: image.png

这道题在网上有很多解法,其中我个人感觉比较特别的一种思路,我用rust语言来实现,思路如下:

fn solution(n: i32, k: i32, data: Vec<i32>) -> i32 {
    let mut min_arr: Vec<[i32; 2]> = Vec::new();
    let mut i = 0;
    let mut sum = 0;
    while i < n {
        while !min_arr.is_empty() && min_arr.last().unwrap()[1] > data[i as usize] {
            min_arr.pop();
        }

        min_arr.push([i, data[i as usize]]);

        while min_arr[0][0] <= i - k {
            min_arr.remove(0);
        }

        println!("e-----{:?}", min_arr);

        sum += min_arr[0][1];

        i += 1;
    }

    println!("{}", sum);

    sum
}

结果:

e-----[[0, 1]]
e-----[[0, 1], [1, 2]]
e-----[[1, 2], [2, 3]]
e-----[[2, 3], [3, 3]]
e-----[[4, 2]]
9
true
e-----[[0, 4]]
e-----[[1, 1]]
e-----[[1, 1], [2, 5]]
e-----[[1, 1], [3, 2]]
e-----[[4, 1]]
e-----[[4, 1], [5, 3]]
9
true
e-----[[0, 3]]
e-----[[1, 2]]
e-----[[2, 4]]
e-----[[3, 1]]
10
true

这个思路是将数组中的元素与索引一起保存在min_arr中,然后每次取出最小的元素,并判断是否超出滑动窗口的范围。相当于每天的消耗都是从过去时间或当前取,一种逆向思维。

针对题中样例一的问题

题中样例一是

输入:n = 5 ,k = 2 ,data = [1, 2, 3, 3, 2]
输出:9

按照手动一天一天的排的结果总是8;思路如下:

第一天:先买2 吃1 剩1
第二天:先吃1 买2 剩2
第三天:吃1   买0 剩1
第四天:吃1   买0 剩0
第五天:先买1 吃1 剩0

总的加个是1+1+2+2+2=8,和题中答案不一致,和上面思路算出来也不一样。

思考:

按照题目给出的解法和上面那种思路,对于样例一是1+1+2+3+2=9,和上面手动推导的区别在于第二天,题目解法是先买了后吃,第二天只能购买1份;我手动推导是先吃后买,第二天可以购买2份。

个人感觉手动推导的更符合实际点,所以想将上面那种思路改造一下,试了好多种方法都改不了,问题点在那也没想通,希望有大佬帮忙解释一下,所以我按推导的思路重新实现了一下。

新思路

fn solution_new(n: i32, k: i32, data: Vec<i32>) -> i32 {
    let mut sum = 0;
    let mut food_num = 0;
    for i in 0..n as usize {
        let mut is_eat = false;
        // 有食物时 先消耗 并记录是否吃了
        if food_num > 0 {
            food_num -= 1;
            is_eat = true;
        }

        // 初始化每天购买量,先吃了就为0,没吃就为 1
        let mut buy_food_num = if is_eat { 0 } else { 1 };

        // 按照最大携带量k,计算最大能坚持到第几天,当天已经吃了就多加一天
        let end: i32 = if is_eat {
            i as i32 + k + 1
        } else {
            i as i32 + k
        };
        // 最大天数不能超过n天;
        let max = if end > n { n } else { end };
        let mut last_food = food_num;
        // 循环后面的天数,如果后面天的价格大于当天,就买一份;否则接终止循环,等到了这天再买
        for j in (i as i32 + 1) as usize..max as usize {
            if data[j] >= data[i] {
                // 后续天数先消耗之前的剩余食物,消耗完再购买
                if last_food > 0 {
                    last_food -= 1;
                } else {
                    buy_food_num += 1;
                }
            } else {
                break;
            }
        }

        println!("buy {}", buy_food_num);

        sum += data[i] * buy_food_num;
        food_num += buy_food_num;

        if !is_eat {
            food_num -= 1;
        }
    }

    println!("{}", sum);

    sum
}

结果:

buy 2
buy 2
buy 0
buy 0
buy 1
8
true
buy 1
buy 3
buy 0
buy 0
buy 2
buy 0
9
true
buy 1
buy 1
buy 1
buy 1
10
true

两种思路的测试如下:

pub fn test() {
    // 1+1+2+3+2=9
    println!("{}", solution(5, 2, vec![1, 2, 3, 3, 2]) == 9);
    // 4+1+1+1+1+1=9
    println!("{}", solution(6, 3, vec![4, 1, 5, 2, 1, 3]) == 9);
    // 3+2+4+1=10
    println!("{}", solution(4, 1, vec![3, 2, 4, 1]) == 10);

    print!("------------------------------------------\n");

    // 1+1+2+2+2=8  先消耗后购买 2 2 0 0 1
    println!("{}", solution_new(5, 2, vec![1, 2, 3, 3, 2]) == 8);
    // 4+1+1+1+1+1=9
    println!("{}", solution_new(6, 3, vec![4, 1, 5, 2, 1, 3]) == 9);
    // 3+2+4+1=10
    println!("{}", solution_new(4, 1, vec![3, 2, 4, 1]) == 10);
}

补充

查找规律发现,每天的价格符合[a,...([k-2]个比a,b大的数),b,...(k个比b大的数),c] 这样的都会出现上述先吃后买的问题

有问题,请指正哦