Java&C++题解与拓展——leetcode933.最近的请求次数【线段树】

每日一题做题记录,参考官方和三叶的题解

题目要求

在这里插入图片描述

思路一:队列模拟

  • 维护一个队列,依次放着请求时间,由于每次调用都使用更大的值,所以队首为最早请求;
  • 每次压入新的请求,检查头部请求,超时则弹出,然后返回队列大小。

Java

class RecentCounter {
    Queue<Integer> queue;

    public RecentCounter() {
        queue= new ArrayDeque<Integer>();
    }
    
    public int ping(int t) {
        queue.offer(t);
        while(queue.peek() < t - 3000)
            queue.poll();
        return queue.size();
    }
}
  • 时间复杂度:均摊下来是 O ( 1 ) O(1) O(1)
  • 空间复杂度: O ( n ) O(n) O(n) n n n指队列最长长度

C++

class RecentCounter {
    queue<int> queue;
public:
    RecentCounter() {}
    
    int ping(int t) {
        queue.push(t);
        while(queue.front() < t - 3000)
            queue.pop();
        return queue.size();
    }
};
  • 时间复杂度:均摊下来是 O ( 1 ) O(1) O(1)
  • 空间复杂度: O ( n ) O(n) O(n) n n n指队列最长长度

Rust

use std::collections::VecDeque;

#[derive(Default)]
struct RecentCounter {
    queue: VecDeque<i32>,
}

impl RecentCounter {

    fn new() -> Self {
        RecentCounter::default()
    }
    
    fn ping(&mut self, t: i32) -> i32 {
        self.queue.push_back(t);
        while self.queue.front().unwrap() < &(t - 3000) {
            self.queue.pop_front();
        }
        self.queue.len() as i32
    }
}

思路二:线段树

  • 单点修改+区间查询+强制在线 = 线段树(动态开点);【之前整理过,这次会用到懒标记】
  • 由于不保证插入和查询都是连续的,所以需额外存储左右节点下标 l s ls ls r s rs rs,所以 t r e e [ u ] . l s = 0 tree[u].ls=0 tree[u].ls=0 t r e e [ u ] . r s = 0 tree[u].rs=0 tree[u].rs=0表示子节点未被创建;
  • 线段树插入和查询复杂度均为 log ⁡ n \log n logn(懒标记确保),因此操作时最多创建数量级 log ⁡ n \log n logn的点,空间复杂度为 O ( m log ⁡ n ) O(m\log n) O(mlogn),而不是 O ( 4 × n ) O(4\times n) O(4×n)
  • 这段没看懂就直接引用三叶姐姐题解

    动态开点相比于原始的线段树实现,本质仍是使用「满二叉树」的形式进行存储,只不过是按需创建区间,如果我们是按照连续段进行查询或插入,最坏情况下仍然会占到 4 × n 4\times n 4×n的空间,因此盲猜 log ⁡ n \log n logn的常数在 4 4 4左右,保守一点可以直接估算到 6 6 6,因此我们可以估算点数为 6 × m × log ⁡ n 6\times m\times\log n 6×m×logn,其中 n = 1 e 9 n=1e9 n=1e9 m = 1 e 4 m=1e4 m=1e4分别代表值域大小和查询次数。
    当然一个比较实用的估点方式可以「尽可能的多开点数」,利用题目给定的空间上界和我们创建的自定义类(结构体)的大小,尽可能的多开( Java 的 128 M 128M 128M可以开到 5 × 1 0 6 5\times 10^6 5×106以上)。

Java

【注释少绝不是因为没有认真看懂,绝对不是】

class RecentCounter {
    class Node {
        int ls, rs; // 当前区间的左右子节点下标
        int val; // 当前区间包含请求个数
    }
    int N = (int)1e9, M = 800010, idx = 1;
    Node[] tree = new Node[M];
    void update(int u, int lc, int rc, int x, int v) {
        if(lc == x && rc == x) {
            tree[u].val += (rc - lc + 1) * v;
            return;
        }
        LazyCreate(u);
        int mid = lc + rc >> 1;
        if(x <= mid)
            update(tree[u].ls, lc, mid, x, v);
        else
            update(tree[u].rs, mid + 1, rc, x, v);
        pushup(u);
    }
    int query(int u, int lc, int rc, int l, int r) {
        if(l <= lc && r >= rc)
            return tree[u].val;
        LazyCreate(u);
        int mid = lc + rc >> 1, res = 0;
        if(l <= mid)
            res = query(tree[u].ls, lc, mid, l, r);
        if(r > mid)
            res += query(tree[u].rs, mid + 1, rc, l, r);
        return res;
    }
    void LazyCreate(int u) {
        if(tree[u] == null)
            tree[u] = new Node();
        if(tree[u].ls == 0) {
            tree[u].ls = ++idx;
            tree[tree[u].ls] = new Node();
        }
        if(tree[u].rs == 0) {
            tree[u].rs = ++idx;
            tree[tree[u].rs] = new Node();
        }
    }
    void pushup(int u) {
        tree[u].val = tree[tree[u].ls].val + tree[tree[u].rs].val;
    }

    public RecentCounter() {}
    
    public int ping(int t) {
        update(1, 1, N, t, 1);
        return query(1, 1, N, t - 3000, t);
    }
}
  • 时间复杂度:均摊是 O ( log ⁡ n ) O(\log n) O(logn)
  • 空间复杂度: O ( m log ⁡ n ) O(m\log n) O(mlogn) m m m为ping的调用次数

C++

【之前写的时候是靠定义一堆变量实现的,这次因为前几天写日志get到了一点定义类的tips,所以定义了类,虽然其实区别也不大啦】

static int N = 1e9, M = 800010;
class RecentCounter {
public:
    class Node {
    public:
        int ls, rs; // 当前区间的左右子节点下标
        int val; // 当前区间包含请求个数
    };
    int idx = 1;
    vector<Node*> tree = vector<Node*> (M);

    RecentCounter() {}

    void update(int u, int lc, int rc, int x, int v) {
        if(lc == x && rc == x) {
            tree[u]->val += (rc - lc + 1) * v;
            return;
        }
        LazyCreate(u);
        int mid = (lc + rc) >> 1;
        if(x <= mid)
            update(tree[u]->ls, lc, mid, x, v);
        else
            update(tree[u]->rs, mid + 1, rc, x, v);
        pushup(u);
    }   
    int query(int u, int lc, int rc, int l, int r) {
         if(l <= lc && r >= rc)
            return tree[u]->val;
        LazyCreate(u);
        int mid = (lc + rc) >> 1, res = 0;
        if(l <= mid)
            res = query(tree[u]->ls, lc, mid, l, r);
        if(r > mid)
            res += query(tree[u]->rs, mid + 1, rc, l, r);
        return res;
    } 
    void LazyCreate(int u) {
        if(tree[u] == nullptr)
            tree[u] = new Node();
        if(tree[u]->ls == 0) {
            tree[u]->ls = ++idx;
            tree[tree[u]->ls] = new Node();
        }
        if(tree[u]->rs == 0) {
            tree[u]->rs = ++idx;
            tree[tree[u]->rs] = new Node();
        }
    }
    void pushup(int u) {
        tree[u]->val = tree[tree[u]->ls]->val + tree[tree[u]->rs]->val;
    }

    int ping(int t) {
        update(1, 1, N, t, 1);
        return query(1, 1, N, t - 3000, t);
    }
};
  • 时间复杂度:均摊是 O ( log ⁡ n ) O(\log n) O(logn)
  • 空间复杂度: O ( m log ⁡ n ) O(m\log n) O(mlogn) m m m为ping的调用次数

总结

快乐模拟做完,回头发现三叶姐姐用了线段树……自从上次学完就没再看过,来用用看看忘得干不干净。

生学Rust的路子看起来有点小问题,还是要先了解点基础知识内容,调用各种和C++有点像的样子。


欢迎指正与讨论!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值