[H数据结构] lc295. 数据流的中位数(对顶堆+技巧+思维+代码实现)

本文解析了如何使用堆数据结构在数据流中动态维护中位数的问题,包括堆的设置、元素添加操作以及查找中位数的方法,强调了算法的巧妙性和空间时间复杂度分析。
摘要由CSDN通过智能技术生成

1. 题目来源

链接:295. 数据流的中位数

相关博文:

2. 题目解析

十分经典的问题了。算法常用,剑指 offer 中也会出现,这个数据结构设计的十分巧妙!

思路:

  • 中位数,实际上就是将数组分成有序的两段,L、R。当 L == R 时就是两端相邻点的平均值,当 L = R +1 时,就是 L 中多出来的那个数。
  • 如果需要动态维护的话,一般思路:
    • 将 L 设置成大顶堆,L 中实际上存的是数组中较小的一部分数,而堆顶是这较小数的最大值,方便向 R 去移动。
    • 将 R 设置成小顶堆,R 中实际上存的是数组中较大的一部分数,而堆顶是这较大数的最小值,方便向 L 去移动。
    • 设定:L.size() >= R.size() + 1,我们设置 L 的元素个数至多比 R 多一个。在这个设置下,中位数就可以从两个堆顶元素求得了。
  • 当 num 进入时,如果:
    • L.size() == R.size(),加入后,应当变成 L.size() = R.size() + 1,那么我们希望从将 R 中最小的一个加入到 L 中,所以 num 先进入 R,然后再将 R 堆顶元素加入 L,最后将 R 堆顶元素弹出。这样子做,不需要考虑 num 与 L、R 堆顶元素的大小关系。
    • L.size() != R.size(),说明 L.size() = R.size() +1,加入后,应当变成 L.size() == R.size() 才对,那么我们希望将 L 中最大的一个加入到 R 中去,所以 num 先进入 L 进行比较,然后再将 L 的堆顶元素加入 R,最后将 L 堆顶元素弹出,这样子做,也不需要考虑 num 与 L、R 堆顶元素的大小关系。
  • 获取中位数时,两个情况:
    • L.size() == R.size() 那么取 L、R 堆顶元素的平均值即可。
    • L.size() != R.size() 基于我们之前的设定,现在应该是 L.size() == R.size() + 1,那么直接返回 L 的堆顶元素即为中位数。

这个思路很是巧妙,可以自行画图理解下~

学习一下这个代码实现,避免情况判断。但是在效率上可能较低,如果明确了 num 和 堆顶的关系,可能我们直接进行 push 一次就行了,但实际在这里一直都是 push 两次 pop 一次。

eg:

  • 其实这里设定 L.size() >= R.size() +1 是很灵活的一个人为设定,同理我们设置 R.size() >= L.size()+1 也行,主要是为了维护左右两端的一个平衡而已。可以类比下 AVL 树。

  • 时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
  • 空间复杂度 O ( n ) O(n) O(n)

class MedianFinder {
public:
    priority_queue<int, vector<int>, less<int>> L;
    priority_queue<int, vector<int>, greater<int>> R;
    MedianFinder() {

    }
    
    void addNum(int num) {
        if (L.size() != R.size()) {
            L.push(num);
            R.push(L.top());
            L.pop();
        } else {
            R.push(num);
            L.push(R.top());
            R.pop();
        }
    }
    
    double findMedian() {
        return L.size() == R.size() ? (L.top() + R.top()) / 2.0 : L.top();
    }
};

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder* obj = new MedianFinder();
 * obj->addNum(num);
 * double param_2 = obj->findMedian();
 */
以下是FM33lc026n使用空闲中断+DMA实现数据接收的代码示例: ```c #include "fm33lc0xx_fl_dma.h" #include "fm33lc0xx_fl_uart.h" #define BUFFER_SIZE 256 uint8_t rx_buffer[BUFFER_SIZE]; uint16_t rx_counter = 0; void uart_idle_irq_handler(void) { if (FL_UART_IsRXFIFOEmpty(COM_UART)) { return; } // 读取 UART 接收数据 uint8_t data = FL_UART_ReceiveData8(COM_UART); rx_buffer[rx_counter++] = data; // 重置计数器 if (rx_counter == BUFFER_SIZE) { rx_counter = 0; } } void dma_irq_handler(void) { // 清除 DMA 中断标志位 FL_DMA_ClearFlag_TransferComplete(DMA_CH1); // 复位 DMA 传输计数器 FL_DMA_SetTransferCounter(DMA_CH1, BUFFER_SIZE); // 启动 DMA 传输 FL_DMA_EnableChannel(DMA_CH1); } int main(void) { // 初始化 UART FL_UART_InitTypeDef uart_init_struct; uart_init_struct.BaudRate = 115200; uart_init_struct.DataWidth = FL_UART_DATA_WIDTH_8B; uart_init_struct.StopBits = FL_UART_STOP_BIT_1; uart_init_struct.Parity = FL_UART_PARITY_NONE; uart_init_struct.Mode = FL_UART_MODE_RX; FL_UART_Init(COM_UART, &uart_init_struct); // 初始化 DMA FL_DMA_InitTypeDef dma_init_struct; dma_init_struct.Channel = DMA_CH1; dma_init_struct.Direction = FL_DMA_DIR_P2M; dma_init_struct.PeriphAddress = (uint32_t)&(COM_UART->DR); dma_init_struct.MemoryAddress = (uint32_t)rx_buffer; dma_init_struct.DataWidth = FL_DMA_DATA_WIDTH_BYTE; dma_init_struct.TransferCount = BUFFER_SIZE; dma_init_struct.IncPeriphAddr = FL_DMA_PERIPH_ADDR_NO_CHANGE; dma_init_struct.IncMemAddr = FL_DMA_MEM_ADDR_INCREMENT; dma_init_struct.Mode = FL_DMA_MODE_NORMAL; dma_init_struct.Priority = FL_DMA_PRIORITY_HIGH; FL_DMA_Init(&dma_init_struct); // 配置空闲中断 FL_UART_ClearFlag_IDLE(COM_UART); FL_UART_EnableIT_IDLE(COM_UART); NVIC_EnableIRQ(UART0_IRQn); // 修改为实际使用的 UART 中断号 // 配置 DMA 中断 FL_DMA_EnableIT_TransferComplete(DMA_CH1); NVIC_EnableIRQ(DMA1_Channel1_IRQn); // 修改为实际使用的 DMA 中断号 // 启动 DMA 传输 FL_DMA_EnableChannel(DMA_CH1); while (1) { // do something } return 0; } ``` 在上述代码中,通过使用空闲中断和 DMA,实现了对 UART 数据的接收。当 UART 接收到数据后,空闲中断被触发,然后将数据存储到缓冲区中。当 DMA 传输完成时,DMA 中断被触发,然后再次启动 DMA 传输。需要注意的是,此处仅为示例代码,实际使用时需要根据具体应用进行相应的修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值