文章目录
150. 逆波兰表达式求值### 算法设计思路
1. 逆波兰表达式(后缀表达式)求值
算法思路:
- 定义栈:使用一个栈来存储临时的运算结果。
- 遍历表达式:从左到右遍历后缀表达式的每个元素。
- 处理数字:如果当前元素是数字,将其转换为整数并压入栈中。
- 处理运算符:如果当前元素是运算符,则从栈中弹出所需数量的元素(根据运算符的操作数要求),执行运算,并将结果压回栈中。
- 最终结果:遍历完成后,栈顶元素即为表达式的计算结果。
关键点:
- 后缀表达式中,运算符总是在对应的两个操作数之后出现。
- 利用栈的先进后出特性,可以方便地实现计算过程。
2. 二叉树的后序遍历
算法思路:
- 定义栈:使用一个栈来辅助遍历。
- 遍历过程:从根节点开始,先遍历左子树,再遍历右子树。
- 访问节点:在遍历到每个节点时,先将其压入栈中。
- 出栈操作:当一个节点的所有子节点都被访问后(即从栈中弹出时),访问该节点。
关键点:
- 后序遍历的顺序是左-右-根。
- 栈用于存储尚未访问的节点,确保左子树先于右子树访问。
3. 利用栈进行相邻字符的消除操作
算法思路:
- 定义栈:使用一个栈来存储临时字符。
- 遍历字符串:从左到右遍历字符串的每个字符。
- 处理匹配:如果当前字符与栈顶字符匹配(例如括号匹配或相同字符),则弹出栈顶字符,并继续匹配,直到不匹配为止。
- 处理运算:如果当前字符与栈顶字符可以进行某种运算(例如算术运算),则进行运算,并将结果压回栈中。
关键点:
- 栈用于存储待处理的字符,以及实现消除操作。
- 通过栈顶元素的匹配和运算,可以简化字符串的处理逻辑。
代码实现示例(逆波兰表达式求值)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
// 定义栈结构
typedef struct {
int *elements;
int size;
int capacity;
} IntStack;
// 初始化栈
void initStack(IntStack *s, int capacity) {
s->elements = (int *)malloc(sizeof(int) * capacity);
s->size = 0;
s->capacity = capacity;
}
// 释放栈
void freeStack(IntStack *s) {
free(s->elements);
s->elements = NULL;
s->size = 0;
s->capacity = 0;
}
// 检查栈是否为空
int isStackEmpty(IntStack *s) {
return s->size == 0;
}
// 压栈
void push(IntStack *s, int value) {
if (s->size >= s->capacity) {
s->capacity *= 2;
s->elements = (int *)realloc(s->elements, sizeof(int) * s->capacity);
}
s->elements[s->size++] = value;
}
// 弹栈
int pop(IntStack *s) {
if (isStackEmpty(s)) {
return 0; // 或者返回错误码
}
return s->elements[--s->size];
}
// 获取栈顶元素
int top(IntStack *s) {
if (isStackEmpty(s)) {
return 0; // 或者返回错误码
}
return s->elements[s->size - 1];
}
// 计算逆波兰表达式的值
int evalRPN(char **tokens, int tokensSize) {
IntStack s;
initStack(&s, 10);
for (int i = 0; i < tokensSize; i++) {
if (isdigit(tokens[i][0])) {
push(&s, atoi(tokens[i])); // 将数字压入栈
} else {
int num1 = pop(&s);
int num2 = pop(&s);
switch (tokens[i][0]) {
case '+': push(&s, num2 + num1); break;
case '-': push(&s, num2 - num1); break;
case '*': push(&s, num2 * num1); break;
case '/': push(&s, num2 / num1); break;
}
}
}
int result = pop(&s);
freeStack(&s);
return result;
}
int main() {
char *tokens[] = {"2", "1", "+", "3", "*"};
int tokensSize = sizeof(tokens) / sizeof(tokens[0]);
int result = evalRPN(tokens, tokensSize);
printf("Result: %d\n", result);
return 0;
}
这段代码实现了逆波兰表达式的求值算法,使用了栈来存储临时的运算结果,并在遇到运算符时进行计算。
逆波兰表达式后缀表达式,二叉树的后序遍历(遇见数字就加入到栈里,遇见操作符就从栈里取出元素做一个计算再把数字加入到栈里)
栈擅长于做相邻字符的消除操作,相邻字符可以是左括号和右括号匹配或相邻字符相同或符合某种操作(eg算术运算)后消除
定义栈,遍历字符串时遇见数字就加入到栈里,遇见操作符就从栈里取出元素做一个计算再把数字加入到栈里(注意遇见数字转int型)
识别
这段代码是一个C语言程序,实现了一个基于小顶堆(最小堆)的算法来找到数组中频率最高的前K个元素。
核心/易错
核心功能是使用一个小顶堆来维护频率最低的K个元素,从而快速得到频率最高的K个元素。易错点包括:
- 在
push
操作中正确维护堆的性质。 - 在
pop
操作中正确处理堆的调整。
难点/亮点
- 难点:正确实现堆的动态扩容和维护堆的性质。
- 亮点:使用小顶堆来优化查找高频元素的过程,避免对所有元素进行排序。
算法设计思路
- 统计频率:使用哈希表统计每个元素的出现频率。
- 构建堆:使用小顶堆来维护频率最低的K个元素。
- 堆操作:通过堆操作快速找到频率最高的K个元素。
代码实现
#include <stdio.h> // 包含标准输入输出库
#include <stdlib.h> // 包含标准库,用于动态内存分配和释放
#include <string.h> // 包含字符串处理库,用于calloc
// 定义一个整数比较函数,用于qsort
int compare(const void *a, const void *b) {
return (*(int*)a - *(int*)b);
}
// 定义一个结构体来存储元素和其频率
typedef struct {
int num;
int frequency;
} NumFreq;
// 定义一个结构体来存储小顶堆
typedef struct {
NumFreq *elements;
int capacity;
int size;
} MinHeap;
// 初始化堆
void initHeap(MinHeap *h, int capacity) {
h->elements = (NumFreq *)malloc(sizeof(NumFreq) * capacity);
h->capacity = capacity;
h->size = 0;
}
// 释放堆
void freeHeap(MinHeap *h) {
free(h->elements);
h->elements = NULL;
h->capacity = 0;
h->size = 0;
}
// 交换两个元素
void swap(NumFreq *a, NumFreq *b) {
NumFreq temp = *a;
*a = *b;
*b = temp;
}
// 维护堆的性质
void heapify(MinHeap *h, int index) {
int smallest = index;
int left = 2 * index + 1;
int right = 2 * index + 2;
if (left < h->size && h->elements[left].frequency < h->elements[smallest].frequency) {
smallest = left;
}
if (right < h->size && h->elements[right].frequency < h->elements[smallest].frequency) {
smallest = right;
}
if (smallest != index) {
swap(&h->elements[index], &h->elements[smallest]);
heapify(h, smallest);
}
}
// 插入元素到堆中
void push(MinHeap *h, NumFreq value) {
if (h->size >= h->capacity) {
h->capacity *= 2;
h->elements = (NumFreq *)realloc(h->elements, sizeof(NumFreq) * h->capacity);
}
h->elements[h->size] = value;
int i = h->size;
h->size++;
while (i != 0 && h->elements[(i - 1) / 2].frequency > h->elements[i].frequency) {
swap(&h->elements[(i - 1) / 2], &h->elements[i]);
i = (i - 1) / 2;
}
}
// 弹出堆中最小的元素
NumFreq pop(MinHeap *h) {
NumFreq root = h->elements[0];
h->elements[0] = h->elements[h->size - 1];
h->size--;
heapify(h, 0);
return root;
}
typedef struct {
int *result;
int resultSize;
int resultCapacity;
} Solution;
// 初始化Solution结构体
void initSolution(Solution *s, int capacity) {
s->result = (int *)malloc(sizeof(int) * capacity);
s->resultSize = 0;
s->resultCapacity = capacity;
}
}
# 239. 滑动窗口最大值
优先级队列这种数据结构就是大顶堆或者是小顶堆。
有可能成为最大值最大值放在这个队列的出回处左边遗奔的元素右边新加进来的元素。右边新加进来的元素push右边新加进来的元素返回来每个最大值↵。如果push 进来的元素比前面的都大pop的元素为最大值,前面的元素就要弹出。况2push新的最大值时把前面的元素全部卷走操作如下滑动窗口每向后移动一位前面这个元素就要被遗弃掉,前面的元素要采用 pop的操作而其他元素在我们push元素的时候已经把前面的元素给卷走了。
## 识别
实现了一个单调队列,即队列中的元素始终保持从大到小的顺序。通过循环数组实现双端队列的功能。
## 核心/易错
核心功能是维护一个单调递减的队列,易错点包括:
1. 在`push`操作中正确移除队列尾部元素以维护单调性。
2. 在`pop`操作中正确处理环形队列的索引。
## 难点/亮点
1. **难点**:在`push`操作中维护队列的单调性,需要从后向前比较并移除元素。
2. **亮点**:使用循环数组实现双端队列,节省了空间,同时在`push`操作中动态扩容。
## 算法设计思路
1. **队列定义**:定义一个结构体`MyQueue`来模拟双端队列的行为。
2. **队列操作**:实现`push`、`pop`、`front`等操作,以及一个检查队列是否为空的函数。
3. **动态扩容**:在`push`操作中,如果队列满,进行动态扩容。
## 代码实现
```c
#include <stdio.h>
#include <stdlib.h>
// 定义一个整数双端队列的结构体
typedef struct {
int* data; // 动态数组存储队列元素
int frontIndex; // 队列头部的索引
int rearIndex; // 队列尾部的索引
int capacity; // 队列的容量
int size; // 队列当前元素数量
} MyQueue;
// 初始化队列
void initQueue(MyQueue* q, int capacity) {
q->data = (int*)malloc(sizeof(int) * capacity);
q->frontIndex = 0;
q->rearIndex = 0;
q->capacity = capacity;
q->size = 0;
}
// 释放队列
void freeQueue(MyQueue* q) {
free(q->data);
q->data = NULL;
q->frontIndex = 0;
q->rearIndex = 0;
q->capacity = 0;
q->size = 0;
}
// 检查队列是否为空
int isQueueEmpty(MyQueue* q) {
return q->size == 0;
}
// 入队操作
void push(MyQueue* q, int value) {
while (!isQueueEmpty(q) && value > q->data[(q->rearIndex - 1 + q->capacity) % q->capacity]) {
q->rearIndex = (q->rearIndex - 1 + q->capacity) % q->capacity;
q->size--;
}
q->data[q->rearIndex] = value;
q->rearIndex = (q->rearIndex + 1) % q->capacity;
q->size++;
if (q->size > q->capacity) {
// 队列满,扩容
q->capacity *= 2;
int* newData = (int*)malloc(sizeof(int) * q->capacity);
for (int i = 0; i < q->size; i++) {
newData[i] = q->data[(q->frontIndex + i) % q->capacity];
}
free(q->data);
q->data = newData;
q->frontIndex = 0;
q->rearIndex = q->size;
}
}
// 出队操作
void pop(MyQueue* q, int value) {
if (!isQueueEmpty(q) && q->data[q->frontIndex] == value) {
q->frontIndex = (q->frontIndex + 1) % q->capacity;
q->size--;
}
}
// 获取队列前端元素
int front(MyQueue* q) {
if (isQueueEmpty(q)) {
return -1;
}
return q->data[q->frontIndex];
}
// 测试函数
int main() {
MyQueue q;
initQueue(&q, 10);
push(&q, 10);
push(&q, 20);
push(&q, 30);
printf("Front: %d\n", front(&q)); // 输出:Front: 10
pop(&q, 10);
push(&q, 40);
printf("Front: %d\n", front(&q)); // 输出:Front: 20
freeQueue(&q);
return 0;
}
347.前 K 个高频元素
首先是我们如何求这个数组里边的如何对这个频率进行排序并求这个前k个高频的元素### 识别
这段代码是一个C语言程序,实现了一个基于小顶堆(最小堆)的算法来找到数组中频率最高的前K个元素。
核心/易错
核心功能是使用一个小顶堆来维护频率最低的K个元素,从而快速得到频率最高的K个元素。易错点包括:
- 在
push
操作中正确维护堆的性质。 - 在
pop
操作中正确处理堆的调整。
难点/亮点
- 难点:正确实现堆的动态扩容和维护堆的性质。
- 亮点:使用小顶堆来优化查找高频元素的过程,避免对所有元素进行排序。
算法设计思路
- 统计频率:使用哈希表统计每个元素的出现频率。
- 构建堆:使用小顶堆来维护频率最低的K个元素。
- 堆操作:通过堆操作快速找到频率最高的K个元素。
代码实现
#include <stdio.h> // 包含标准输入输出库
#include <stdlib.h> // 包含标准库,用于动态内存分配和释放
#include <string.h> // 包含字符串处理库,用于calloc
// 定义一个整数比较函数,用于qsort
int compare(const void *a, const void *b) {
return (*(int*)a - *(int*)b);
}
// 定义一个结构体来存储元素和其频率
typedef struct {
int num;
int frequency;
} NumFreq;
// 定义一个结构体来存储小顶堆
typedef struct {
NumFreq *elements;
int capacity;
int size;
} MinHeap;
// 初始化堆
void initHeap(MinHeap *h, int capacity) {
h->elements = (NumFreq *)malloc(sizeof(NumFreq) * capacity);
h->capacity = capacity;
h->size = 0;
}
// 释放堆
void freeHeap(MinHeap *h) {
free(h->elements);
h->elements = NULL;
h->capacity = 0;
h->size = 0;
}
// 交换两个元素
void swap(NumFreq *a, NumFreq *b) {
NumFreq temp = *a;
*a = *b;
*b = temp;
}
// 维护堆的性质
void heapify(MinHeap *h, int index) {
int smallest = index;
int left = 2 * index + 1;
int right = 2 * index + 2;
if (left < h->size && h->elements[left].frequency < h->elements[smallest].frequency) {
smallest = left;
}
if (right < h->size && h->elements[right].frequency < h->elements[smallest].frequency) {
smallest = right;
}
if (smallest != index) {
swap(&h->elements[index], &h->elements[smallest]);
heapify(h, smallest);
}
}
// 插入元素到堆中
void push(MinHeap *h, NumFreq value) {
if (h->size >= h->capacity) {
h->capacity *= 2;
h->elements = (NumFreq *)realloc(h->elements, sizeof(NumFreq) * h->capacity);
}
h->elements[h->size] = value;
int i = h->size;
h->size++;
while (i != 0 && h->elements[(i - 1) / 2].frequency > h->elements[i].frequency) {
swap(&h->elements[(i - 1) / 2], &h->elements[i]);
i = (i - 1) / 2;
}
}
// 弹出堆中最小的元素
NumFreq pop(MinHeap *h) {
NumFreq root = h->elements[0];
h->elements[0] = h->elements[h->size - 1];
h->size--;
heapify(h, 0);
return root;
}
typedef struct {
int *result;
int resultSize;
int resultCapacity;
} Solution;
// 初始化Solution结构体
void initSolution(Solution *s, int capacity) {
s->result = (int *)malloc(sizeof(int) * capacity);
s->resultSize = 0;
s->resultCapacity = capacity;
}