文章目录
232.用栈实现队列
识别
这段代码定义了一个队列结构体MyQueue
,包含两个栈stackIn
和stackOut
,以及相应的操作函数。队列使用两个栈来实现,一个用于入队操作,另一个用于出队操作。
核心/易错
核心是理解如何使用两个栈实现队列的FIFO(先进先出)行为。易错点在于正确管理两个栈的栈顶指针。
难点/亮点
难点在于实现myQueuePop
和myQueuePeek
函数,这些函数需要在两个栈之间转移元素以保持队列的FIFO特性。亮点是这种实现方式可以在不改变元素顺序的情况下进行出队和查看队首元素的操作。
算法设计思路
算法使用两个栈来模拟队列:
stackIn
用于入队操作,新元素总是被压入stackIn
的栈顶。stackOut
用于出队操作,如果为空,则将stackIn
中的所有元素逆序复制到stackOut
中,这样stackOut
的栈顶就是队列的队首元素。
代码实现
/*
1.两个type为int的数组(栈),大小为100
第一个栈stackIn用来存放数据,第二个栈stackOut作为辅助用来输出数据
2.两个指针stackInTop和stackOutTop,分别指向栈顶
*/
typedef struct {
int stackInTop, stackOutTop;
int stackIn[100], stackOut[100];
} MyQueue;
/*
1.开辟一个队列的大小空间
2.将指针stackInTop和stackOutTop初始化为0
3.返回开辟的队列
*/
MyQueue* myQueueCreate() {
MyQueue* queue = (MyQueue*)malloc(sizeof(MyQueue)); // 为队列结构体分配内存
queue->stackInTop = 0; // 初始化stackIn的栈顶指针为0
queue->stackOutTop = 0; // 初始化stackOut的栈顶指针为0
return queue; // 返回创建的队列
}
/*
将元素存入第一个栈中,存入后栈顶指针+1
*/
void myQueuePush(MyQueue* obj, int x) {
obj->stackIn[(obj->stackInTop)++] = x; // 在stackIn中压入元素x,并递增stackInTop
}
/*
1.若输出栈为空且当第一个栈中有元素(stackInTop>0时),将第一个栈中元素复制到第二个栈中
2.将栈顶元素保存
3.当stackTop2>0时,将第二个栈中元素复制到第一个栈中
*/
int myQueuePop(MyQueue* obj) {
int stackInTop = obj->stackInTop; // 备份stackInTop
int stackOutTop = obj->stackOutTop; // 备份stackOutTop
if(stackOutTop == 0) { // 如果stackOut为空
while(stackInTop > 0) { // 将stackIn中的元素复制到stackOut
obj->stackOut[stackOutTop++] = obj->stackIn[--stackInTop];
}
}
int top = obj->stackOut[--stackOutTop]; // 弹出stackOut的栈顶元素
while(stackOutTop > 0) { // 将stackOut中的元素放回stackIn
obj->stackIn[stackInTop++] = obj->stackOut[--stackOutTop];
}
obj->stackInTop = stackInTop; // 更新stackInTop
obj->stackOutTop = stackOutTop; // 更新stackOutTop
return top; // 返回弹出的元素
}
//返回输入栈中的栈底元素
int myQueuePeek(MyQueue* obj) {
return obj->stackIn[0]; // 返回stackIn的第一个元素,即队列的队首元素
}
//若栈顶指针均为0,则代表队列为空
bool myQueueEmpty(MyQueue* obj) {
return obj->stackInTop == 0 && obj->stackOutTop == 0; // 如果两个栈顶指针都为0,队列为空
}
//将栈顶指针置0
void myQueueFree(MyQueue* obj) {
obj->stackInTop = 0; // 重置stackInTop
obj->stackOutTop = 0; // 重置stackOutTop
}
225. 用队列实现栈
识别
这段代码定义了一个栈结构,并提供了基本的栈操作函数,包括创建栈、判断栈是否为空、入栈、出栈、获取栈顶元素和销毁栈。主函数中演示了这些操作的使用。
核心/易错
核心是栈的基本操作,易错点在于循环队列的索引处理和内存分配释放。
难点/亮点
难点在于正确处理循环队列的索引,确保不会出现数组越界。亮点是使用循环队列模拟栈操作,有效利用了数组空间。
算法设计思路
使用一个动态数组来模拟栈操作,利用数组的尾部作为栈底,头部作为栈顶。入栈操作在数组尾部添加元素,出栈和获取栈顶操作则需要将除栈顶外的元素移动到数组尾部。
代码实现
// 定义队列的结构体
typedef struct {
int *array; // 动态数组存储队列元素
int front; // 队头索引
int rear; // 队尾索引
int capacity; // 队列容量
int size; // 当前队列大小
} MyStack;
// 初始化栈
MyStack* createStack(int capacity) {
MyStack* stack = (MyStack*)malloc(sizeof(MyStack)); // 为栈结构体分配内存
stack->capacity = capacity; // 设置栈的容量
stack->size = 0; // 初始化栈的大小为0
stack->front = 0; // 初始化队头索引
stack->rear = -1; // 初始化队尾索引为-1,表示栈为空
stack->array = (int*)malloc(sizeof(int) * capacity); // 为栈元素分配内存
return stack; // 返回创建的栈
}
// 判断栈是否为空
int isEmpty(MyStack* obj) {
return obj->size == 0; // 如果栈的大小为0,返回1,表示栈为空
}
// 入栈操作
void push(MyStack* obj, int x) {
if (obj->size >= obj->capacity) return; // 如果栈已满,不进行操作
obj->rear = (obj->rear + 1) % obj->capacity; // 更新队尾索引,实现循环队列
obj->array[obj->rear] = x; // 在队尾添加元素
obj->size++; // 栈的大小增加
}
// 出栈操作
int pop(MyStack* obj) {
if (isEmpty(obj)) return -1; // 如果栈为空,返回-1
int size = obj->size; // 获取栈的大小
for (int i = 0; i < size - 1; i++) { // 将栈内元素(除最后一个)移动到数组尾部
int temp = obj->array[obj->front];
obj->front = (obj->front + 1) % obj->capacity;
push(obj, temp);
}
int result = obj->array[obj->front]; // 获取栈顶元素
obj->front = (obj->front + 1) % obj->capacity; // 更新队头索引
obj->size--; // 栈的大小减少
return result; // 返回弹出的元素
}
// 获取栈顶元素
int top(MyStack* obj) {
if (isEmpty(obj)) return -1; // 如果栈为空,返回-1
int size = obj->size; // 获取栈的大小
for (int i = 0; i < size - 1; i++) { // 将栈内元素(除最后一个)移动到数组尾部
int temp = obj->array[obj->front];
obj->front = (obj->front + 1) % obj->capacity;
push(obj, temp);
}
int result = obj->array[obj->front]; // 获取栈顶元素
obj->front = (obj->front + 1) % obj->capacity; // 更新队头索引
obj->size--; // 栈的大小减少
return result; // 返回栈顶元素
}
// 销毁栈
void destroyStack(MyStack* obj) {
free(obj->array); // 释放栈元素内存
free(obj); // 释放栈结构体内存
}
int main() {
MyStack* stack = createStack(10); // 创建一个容量为10的栈
push(stack, 1); // 入栈元素1
push(stack, 2); // 入栈元素2
printf("Top element is %d\n", top(stack)); // 打印栈顶元素
printf("Popped element is %d\n", pop(stack)); // 弹出一个元素并打印
if (isEmpty(stack)) { // 判断栈是否为空
printf("Stack is empty\n");
}
destroyStack(stack); // 销毁栈,释放内存
return 0;
}
20. 有效的括号
核心/易错
核心逻辑在于使用栈来匹配括号,易错点在于括号的匹配规则和栈操作。
难点/亮点
难点在于确保所有类型的括号都正确匹配,亮点是使用栈来简化匹配逻辑。
算法设计思路
算法使用一个栈来存储尚未匹配的左括号。遍历字符串中的每个字符:
- 如果是左括号,将其压入栈中。
- 如果是右括号,检查栈顶是否有对应的左括号。如果没有或不匹配,则字符串无效。
- 如果匹配,则弹出栈顶元素。
- 遍历结束后,如果栈为空,则所有括号有效匹配;否则,无效。
代码实现
// 辅助函数:判断栈顶元素与输入的括号是否为一对。若不是,则返回False
int notMatch(char par, char* stack, int stackTop) {
switch(par) {
case ']': // 如果当前字符是']',则检查栈顶元素是否为'['
return stack[stackTop - 1] != '[';
case ')': // 如果当前字符是')',则检查栈顶元素是否为'('
return stack[stackTop - 1] != '(';
case '}': // 如果当前字符是'}',则检查栈顶元素是否为'{'
return stack[stackTop - 1] != '{';
}
return 0; // 如果当前字符不是任何右括号,返回0
}
bool isValid(char * s){
int strLen = strlen(s); // 获取字符串长度
// 开辟栈空间
char stack[5000]; // 声明一个足够大的字符数组作为栈
int stackTop = 0; // 初始化栈顶指针为0
// 遍历字符串
int i;
for(i = 0; i < strLen; i++) {
// 取出当前下标所对应字符
char tempChar = s[i];
// 若当前字符为左括号,则入栈
if(tempChar == '(' || tempChar == '[' || tempChar == '{')
stack[stackTop++] = tempChar;
// 若当前字符为右括号,且栈中无元素或右括号与栈顶元素不符,返回False
else if(stackTop == 0 || notMatch(tempChar, stack, stackTop))
return 0; // 如果栈为空或不匹配,返回False
// 当前字符与栈顶元素为一对括号,将栈顶元素出栈
else
stackTop--; // 匹配成功,出栈
}
// 若栈中有元素,返回False。若没有元素(stackTop为0),返回True
return !stackTop; // 如果栈为空,返回True,否则返回False
}
1047. 删除字符串中的所有相邻重复项
方法一:使用栈
识别
- 这段代码的目的是移除一个字符串中的所有相邻重复字符。
核心/易错
- 核心:使用栈来存储字符,当遇到与栈顶字符相同的字符时,弹出栈顶字符。
- 易错:需要确保栈的大小足够大,并且正确处理字符串结束标志
'\0'
。
难点/亮点
- 难点:管理栈顶元素与当前字符的关系,以及正确地处理字符串结束。
- 亮点:使用栈来简化重复字符的检测和移除。
算法设计思路
- 初始化栈空间,长度为字符串长度加1。
- 遍历字符串,对于每个字符:
- 如果栈不为空且栈顶字符与当前字符相同,则弹出栈顶字符。
- 否则,将当前字符压入栈。
- 在栈的末尾添加字符串结束标志
'\0'
。 - 返回栈作为新的字符串。
代码实现
char * removeDuplicates(char * s){
//求出字符串长度
int strLength = strlen(s);
//开辟栈空间。栈空间长度应为字符串长度+1(为了存放字符串结束标志'\0')
char* stack = (char*)malloc(sizeof(char) * strLength + 1);
int stackTop = 0;
int index = 0;
//遍历整个字符串
while(index < strLength) {
//取出当前index对应字母,之后index+1
char letter = s[index++];
//若栈中有元素,且栈顶字母等于当前字母(两字母相邻)。将栈顶元素弹出
if(stackTop > 0 && letter == stack[stackTop - 1])
stackTop--;
//否则将字母入栈
else
stack[stackTop++] = letter;
}
//存放字符串结束标志'\0'
stack[stackTop] = '\0';
//返回栈本身作为字符串
return stack;
}
- 第1行:定义函数
removeDuplicates
,接受一个字符串s
作为参数。 - 第3行:计算输入字符串
s
的长度。 - 第5行:分配一个字符数组作为栈,大小为字符串长度加1,用于存储结果和字符串结束符。
- 第6行:初始化栈顶指针
stackTop
为0。 - 第8行:初始化索引
index
为0,用于遍历字符串。 - 第10-19行:遍历字符串:
- 第11行:取出当前索引对应的字符,并递增索引。
- 第13-14行:如果栈不为空且栈顶字符与当前字符相同,则减少栈顶指针。
- 第16行:否则,将当前字符压入栈。
- 第21行:在栈的末尾添加字符串结束标志
'\0'
。 - 第22行:返回栈,作为新的字符串。
方法二:双指针法
识别
- 移除一个字符串中的所有相邻重复字符。
核心/易错
- 核心:使用快慢指针,慢指针
slow
用于构建新字符串,快指针fast
用于遍历原字符串。 - 易错:需要确保在修改原字符串的同时正确处理字符串结束标志
'\0'
。
难点/亮点
- 难点:在原字符串上直接操作,需要小心处理指针和字符串结束标志。
- 亮点:避免了额外的内存分配,直接在原字符串上进行操作。
算法设计思路
- 初始化快慢指针,快指针
fast
用于遍历字符串,慢指针slow
用于构建新字符串。 - 遍历字符串,对于每个字符:
- 如果当前字符与慢指针前一个字符相同,则慢指针不移动。
- 否则,将快指针指向的字符赋值给慢指针指向的位置,然后移动慢指针。
- 在慢指针的下一个位置添加字符串结束标志
'\0'
。
代码实现
char * removeDuplicates(char * s){
//创建快慢指针
int fast = 0;
int slow = 0;
//求出字符串长度
int strLength = strlen(s);
//遍历字符串
while(fast < strLength) {
//将当前slow指向字符改为fast指向字符。fast指针+1
char letter = s[slow] = s[fast++];
//若慢指针大于0,且慢指针指向元素等于字符串中前一位元素,删除慢指针指向当前元素
if(slow > 0 && letter == s[slow - 1])
slow--;
else
slow++;
}
//在字符串结束加入字符串结束标志'\0'
s[slow] = 0;
return s;
}
- 第1行:定义函数
removeDuplicates
,接受一个字符串s
作为参数。 - 第3-4行:初始化快慢指针
fast
和slow
。 - 第6行:计算输入字符串
s
的长度。 - 第8-16行:遍历字符串:
- 第9行:将快指针指向的字符赋值给慢指针指向的位置,并递增快指针。
- 第11-12行:如果慢指针大于0且当前字符与慢指针前一个字符相同,则减少慢指针。
- 第14行:否则,增加慢指针。
- 第18行:在慢指针的下一个位置添加字符串结束标志
'\0'
。 - 第19行:返回修改后的字符串。
方法一使用额外的栈空间,但逻辑简单;
方法二在原字符串上操作,节省空间,但需要更小心地处理指针和字符串结束标志。