LeetCode算法题 (最小栈)Day10!!!C/C++

https://leetcode.cn/problems/min-stack/description/

一、题目描述

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

  • MinStack() 初始化堆栈对象。
  • void push(int val) 将元素val推入堆栈。
  • void pop() 删除堆栈顶部的元素。
  • int top() 获取堆栈顶部的元素。
  • int getMin() 获取堆栈中的最小元素。

二、相关知识点了解

栈的基本概念及结构:

        栈:一种特殊的线性表,其只允许在固定一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last in First Out)的原则。

压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。

出栈:栈的删除操作叫出栈。出数据也在栈顶。

三、示例分析

        

输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]

输出:
[null,null,null,null,-3,null,0,-2]

解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2.

        通过示例可知,每次往栈中压入一个数据,之后得出此时栈内的最小值。当栈内元素pop时,栈内元素的最小值也会有相应的变化,简单来说就是需要用getMin()函数获取到当前栈内元素的最小值。

四、解题思路&代码实现

        今天的题目其实难度不大,只不过要完成的模块相对较多。首先来分析一下各个模块内都需要填写哪些内容。

// 定义一个名为 MinStack 的结构体,用于表示最小栈
// 这里的结构体成员用于实现最小栈的各种功能,目前结构体内容为空,后续会填充具体成员
typedef struct { 

} MinStack;

// 创建一个最小栈的函数,返回一个指向 MinStack 结构体的指针
// 该函数负责初始化最小栈所需的资源
MinStack* minStackCreate() { 

}

// 将一个整数 val 压入最小栈 obj 中的函数
// 该函数实现了最小栈的入栈操作,并且可能需要处理与最小值相关的逻辑
void minStackPush(MinStack* obj, int val) { 

}

// 从最小栈 obj 中弹出栈顶元素的函数
// 该函数实现了最小栈的出栈操作,并且可能需要更新与最小值相关的信息
void minStackPop(MinStack* obj) { 

}

// 获取最小栈 obj 栈顶元素的函数,返回栈顶元素的值
// 该函数用于获取当前位于最小栈顶部的元素
int minStackTop(MinStack* obj) { 

}

// 获取最小栈 obj 中最小元素的函数,返回最小元素的值
// 该函数实现了快速获取最小栈中当前最小元素的功能
int minStackGetMin(MinStack* obj) { 

}

// 释放最小栈 obj 所占用资源的函数
// 该函数负责释放创建最小栈时分配的内存等资源,避免内存泄漏
void minStackFree(MinStack* obj) { 

}

        大概框架有了了解,那么我们就可以开始填充内容了,这里我们可以联想一下喝水的步骤,首先喝水最重要的是什么?要有容器吧,没有容器怎么喝。本题也是一样,首先我们需要先创建一个堆栈出来,也就是在这里定义上我们所需要的变量(容器)。

// 定义一个新的类型别名 MinStack,它是一个结构体类型
// 该结构体用于表示一个栈,其中包含了存储栈数据的指针和栈的容量信息
typedef struct {
    // 定义一个整型指针 data,用于指向存储栈中元素的内存区域
    // 这个指针将用来动态分配内存以存储栈中的数据
    int* data;
    // 定义一个整型变量 capacity,用于表示这个栈的最大容量
    // 它决定了这个栈最多能容纳多少个元素
    int capacity;
} MinStack;

        有了“容器”,那么下一步我们就要给“容器”进行初始化。

// 创建一个最小栈的函数,返回一个指向 MinStack 结构体的指针
MinStack* minStackCreate() {
    // 使用 malloc 函数为 MinStack 结构体分配内存空间
    // 并将分配的内存地址强制转换为 MinStack* 类型,然后赋值给指针 s
    MinStack* s = (MinStack*)malloc(sizeof(MinStack));
    // 将 s 所指向的 MinStack 结构体中的 data 指针初始化为 NULL
    // 表示此时栈中还没有存储任何数据
    s->data = NULL;
    // 将 s 所指向的 MinStack 结构体中的 capacity 初始化为 0
    // 表示此时栈的初始容量为 0
    s->capacity = 0;
    // 返回指向已初始化的 MinStack 结构体的指针 s
    return s;
}

“容器”也有了,也进行了初始化。那么下一步就该“倒水”了吧,这里也就是把数据压入栈的操作。

// 向最小栈中压入一个元素的函数
// obj 是指向 MinStack 结构体的指针,代表要操作的最小栈
// val 是要压入栈中的整数值
void minStackPush(MinStack* obj, int val) {
    // 使用 realloc 函数重新分配 obj->data 所指向的内存空间
    // 新的内存大小为当前容量(obj->capacity)加 1 个 int 类型的大小
    // 这是为了在栈中添加一个新的元素
    obj->data = realloc(obj->data, sizeof(int) * (obj->capacity + 1));
    // 将传入的 val 值存储到新分配内存的最后一个位置
    // 这个位置就是当前栈的栈顶位置
    obj->data[obj->capacity] = val;
    // 将栈的容量增加 1,以反映栈中已经增加了一个元素
    obj->capacity++;
}

 那么现在就差最后一步,栈的基本功能就快实现完了,就是“喝水”(喝水就是取数据,这里pop和top类似,就一起说了)。

// 从最小栈中弹出栈顶元素的函数
// obj 是指向 MinStack 结构体的指针,代表要操作的最小栈
void minStackPop(MinStack* obj) {
    // 将栈的容量减 1
    // 这相当于模拟了弹出栈顶元素的操作,虽然并没有真正释放栈顶元素的内存
    // 后续如果有新元素压入,会覆盖原来栈顶元素的位置
    obj->capacity--;
}

// 获取最小栈栈顶元素的函数
// obj 是指向 MinStack 结构体的指针,代表要操作的最小栈
int minStackTop(MinStack* obj) {
    // 返回栈顶元素的值
    // 由于栈顶元素位于数组 data 的第 capacity - 1 个位置
    // 所以通过 obj->data[obj->capacity - 1] 来获取栈顶元素
    return obj->data[obj->capacity - 1];
}

然后就是最重要的一步,在上期和大家说了堆区开辟的空间需要手动释放,在这里有个专门的函数进行空间销毁。

// 释放最小栈所占用资源的函数
// obj 是指向 MinStack 结构体的指针,代表要释放资源的最小栈
void minStackFree(MinStack* obj) {
    // 释放存储栈中数据的内存空间
    // obj->data 是指向存储栈数据的指针,通过 free 函数释放该内存
    free(obj->data);
    // 这一步完成后,整个最小栈所占用的内存都被释放,避免内存泄漏
    free(obj);
}

 

至此,整个栈的基本功能就已经实现完毕了,现在我们需要的是求出栈内的最小元素。这里首先咱们用第一种思路,暴力法。直接循环遍历,找到栈中最小的元素,return即可。

// 获取最小栈中最小元素的函数
// obj 是指向 MinStack 结构体的指针,代表要操作的最小栈
int minStackGetMin(MinStack* obj) {
    // 检查栈是否为空
    if (obj->capacity == 0) {
        // 如果栈为空,返回一个特定的错误值,这里返回 -1 表示错误情况
        return -1;
    }
    // 初始化最小值为栈中的第一个元素
    int min = obj->data[0];
    // 遍历栈中从第二个元素开始到最后一个元素
    for (int i = 1; i < obj->capacity; i++) {
        // 如果当前遍历到的元素小于当前记录的最小值
        if (min > obj->data[i]) {
            // 更新最小值为当前遍历到的元素
            min = obj->data[i];
        }
    }
    // 返回找到的最小元素
    return min;
}

 这种方法行倒是行,但是堆栈本身是并不提供遍历的,这样遍历的话会丧失堆栈本身后进先出的数据结构。而且在时间复杂度上这种方法为O(N),并不符合题目所给出的要求在常数时间内检索到最小元素。

        所以这里,我们可以转变一下思路,我们可以利用空间换时间啊。

        只需要在结构体内部再多定义一个变量,专门用来存放每次压入栈之后的数据中的最小值就OK了。整体代码如下:


// 1. int* data:用于存储栈中的数据
// 2. int* min:用于存储每个位置对应的最小元素
// 3. int capacity:表示栈的当前容量
typedef struct {
    int* data;
    int* min;
    int capacity;
} MinStack;

// 创建一个最小栈的函数,返回一个指向 MinStack 结构体的指针
// 该函数负责初始化最小栈所需的资源
MinStack* minStackCreate() {
    // 使用 malloc 函数为 MinStack 结构体分配内存空间
    // 并将分配的内存地址强制转换为 MinStack* 类型,然后赋值给指针 s
    MinStack* s = (MinStack*)malloc(sizeof(MinStack));
    // 将 s 所指向的 MinStack 结构体中的 data 指针初始化为 NULL
    // 表示此时栈中还没有存储任何数据
    s->data = NULL;
    // 将 s 所指向的 MinStack 结构体中的 min 指针初始化为 NULL
    // 表示此时还没有记录任何最小元素
    s->min = NULL;
    // 将 s 所指向的 MinStack 结构体中的 capacity 初始化为 0
    // 表示此时栈的初始容量为 0
    s->capacity = 0;
    // 返回指向已初始化的 MinStack 结构体的指针 s
    return s;
}

// 将一个整数 val 压入最小栈 obj 中的函数
// 该函数实现了最小栈的入栈操作,并且在入栈过程中更新最小元素记录
void minStackPush(MinStack* obj, int val) {
    // 使用 realloc 函数重新分配 obj->data 所指向的内存空间
    // 新的内存大小为当前容量(obj->capacity)加 1 个 int 类型的大小
    // 这是为了在栈中添加一个新的元素
    obj->data = realloc(obj->data, sizeof(int) * (obj->capacity + 1));
    // 使用 realloc 函数重新分配 obj->min 所指向的内存空间
    // 新的内存大小同样为当前容量(obj->capacity)加 1 个 int 类型的大小
    // 这是为了记录新元素加入后的最小元素信息
    obj->min = realloc(obj->min, sizeof(int) * (obj->capacity + 1));
    // 将传入的 val 值存储到新分配内存的最后一个位置
    // 这个位置就是当前栈的栈顶位置
    obj->data[obj->capacity] = val;
    // 如果当前栈为空(obj->capacity == 0)或者新加入的元素 val 小于当前记录的最小元素
    if (obj->capacity == 0 || obj->min[obj->capacity - 1] > val) {
        // 则将新元素 val 记录为当前最小元素
        obj->min[obj->capacity] = val;
    }
    // 否则,当前最小元素保持不变
    else {
        obj->min[obj->capacity] = obj->min[obj->capacity - 1];
    }
    // 将栈的容量增加 1,以反映栈中已经增加了一个元素
    obj->capacity++;
}

// 从最小栈 obj 中弹出栈顶元素的函数
// 该函数实现了最小栈的出栈操作
void minStackPop(MinStack* obj) {
    // 将栈的容量减 1
    // 这相当于模拟了弹出栈顶元素的操作,虽然并没有真正释放栈顶元素的内存
    // 后续如果有新元素压入,会覆盖原来栈顶元素的位置
    obj->capacity--;
}

// 获取最小栈 obj 栈顶元素的函数,返回栈顶元素的值
int minStackTop(MinStack* obj) {
    // 返回栈顶元素的值
    // 由于栈顶元素位于数组 data 的第 capacity - 1 个位置
    // 所以通过 obj->data[obj->capacity - 1] 来获取栈顶元素
    return obj->data[obj->capacity - 1];
}

// 获取最小栈 obj 中最小元素的函数,返回最小元素的值
int minStackGetMin(MinStack* obj) {
    // 返回当前记录的最小元素的值
    // 由于最小元素记录在数组 min 的第 capacity - 1 个位置
    // 所以通过 obj->min[obj->capacity - 1] 来获取最小元素
    return obj->min[obj->capacity - 1];
}

// 释放最小栈 obj 所占用资源的函数
// 该函数负责释放创建最小栈时分配的内存等资源,避免内存泄漏
void minStackFree(MinStack* obj) {
    // 释放存储栈中数据的内存空间
    free(obj->data);
    // 释放存储最小元素记录的内存空间
    free(obj->min);
    // 释放 MinStack 结构体本身所占用的内存空间
    free(obj);
}

千万千万不要忘记了,这里咱们的min也是在堆区,一定要记得手动释放,避免造成内存泄露!!!

五、题目总结

        本题旨在设计 MinStack,除常规入栈、出栈、查看栈顶元素操作外,重点要在常数时间检索最小元素。先回顾栈知识,依示例理解需求,暴力法遍历找最小元素虽可行却破坏栈结构且不满足时间要求,后用空间换时间策略,结构体新增指针记录最小元素,各操作函数按规则实现对应功能,最终达成设计要求,还让我们掌握算法优化与复杂度权衡。

        本期就到这里,谢谢大家!!!荆轲刺秦!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值