编程总结
每每刷完一道题后,其思想和精妙之处没有地方记录,本篇博客用以记录刷题过程中的遇到的算法和技巧
232. 用栈实现队列
要点就是,先要有栈的数据结构及其实现,然后用两个栈,一个作in stack,一个作out stack来实现队列的先入先出:
a. 进队列,都默认进到 in stack里;
b. 查队列首元素,要求先入先出,那就得先将 in stack 的数据都放到 out stack 里去,这时 out stack 里的top元素就是队列要出的元素;
c. 出队列时,先判断 out stack是否为空,为空则in2out搬运,否则,直接出out stack的top即可;
// 1. 栈数据结构的实现
typedef struct {
int *stk;
int stkSize;
int stkCapacity;
} Stack;
Stack *stackCreate(int capacity)
{
Stack *ret = (Stack *)malloc(sizeof(Stack));
ret->stk = (int *)malloc(sizeof(int) * capacity);
ret->stkSize = 0;
ret->stkCapacity = capacity;
return ret;
}
void stackPush(Stack *obj, int x)
{
obj->stk[obj->stkSize++] = x;
}
void stackPop(Stack *obj)
{
obj->stkSize--;
}
int stackTop(Stack *obj)
{
return obj->stk[obj->stkSize - 1];
}
bool stackEmpty(Stack *obj)
{
return obj->stkSize == 0; // size为0,则满足条件为空,返回1
}
void stackFree(Stack *obj)
{
free(obj->stk);
}
---------------------------------------------------------
// 2. 队列数据结构的实现
typedef struct {
Stack *inStack;
Stack *outStack;
} MyQueue;
// in stack -> out stack.
void in2out(MyQueue *obj) {
while (!stackEmpty(obj->inStack)) {
stackPush(obj->outStack, stackTop(obj->inStack));
stackPop(obj->inStack);
}
}
// 1. 建立两个栈
MyQueue *myQueueCreate() {
MyQueue *ret = (MyQueue *)malloc(sizeof(MyQueue));
ret->inStack = stackCreate(100);
ret->outStack = stackCreate(100);
return ret;
}
// 2. 进入到 in栈
void myQueuePush(MyQueue *obj, int x) {
stackPush(obj->inStack, x); // 直接入栈即可
}
// 3. 出队列的队首元素为 out栈的栈顶元素
int myQueuePop(MyQueue *obj) {
if (stackEmpty(obj->outStack)) { // 这三行是精髓的部分,判断out是否为空,若不为空,则直接出栈Top,若为空,则将in stack搬运过来
in2out(obj);
}
int x = stackTop(obj->outStack);
stackPop(obj->outStack);
return x;
}
// 4. 返回队列开头的元素,先判断如果out栈为空,就更新in的元素过去,最后返回out栈的栈顶元素
int myQueuePeek(MyQueue *obj) {
if (stackEmpty(obj->outStack)) {
in2out(obj);
}
return stackTop(obj->outStack);
}
bool myQueueEmpty(MyQueue *obj) {
return stackEmpty(obj->inStack) && stackEmpty(obj->outStack);
}
void myQueueFree(MyQueue *obj) {
stackFree(obj->inStack);
stackFree(obj->outStack);
}
020.有效括号
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串
思路:将字符串都判断如果是左括号(‘(’,‘[’,‘{’,‘(’)都进行入栈,遇到其他括号符号则:1)取出栈顶元素,2)同时将栈顶元素的match括号匹配上,3)然后Pop栈顶元素和match元素进行比对,如果一致继续循环判断,否则退出,直到遍历完所有的字符串长度。
a. 遇到左括号放入栈顶
b. 遇到右括号,取出栈顶的左括号并判断它们是否是相同类型的括号; 如果不是相同的类型,或者栈中并没有左括号,那么字符串无效,返回 False(先入栈也行,最后判断栈里是否都清空了)
C语言实现一个栈往往较为复杂,多数解题是通过一个数组来模拟栈
下面给出用数组模拟上述算法的代码:
bool isTrue(char a, char b)
{
if ((a == '(' && b == ')') || (a == '[' && b == ']') || (a == '{' && b == '}')) {
return true;
}
return false;
}
bool isValid(const char *s)
{
char str[10001] = {0}; // 定义一个数组当栈
int top = -1;
int i = 0;
int len = strlen(s);
if (len == 0) {
return false;
}
// 出现奇数个时,一定为false.
if (len % 2 == 1) {
return false;
}
str[++top] = s[i];
i++;
while (i < len) {
// 如果栈顶元素和字符匹配上了,则栈顶元素出栈
if (top >= 0 && (isTrue(str[top], s[i]) == true)) { // 注意判断top < 0 时的传值, 此时str[top]无效
top--;
} else {
str[++top] = s[i]; // 将字符入栈
}
i++;
}
if (top == -1) { // 栈内无元素了
return true;
} else {
return false;
}
}
顺带提一句在LeetCode平台的 False== 0; True == 1;
678. 有效的括号字符串
此题是上一题的变种,手法类似,但处理起来有个星号的出现有点变化
手法1: 准备两个栈,一个存括号,一个存星号
- 星号变成右括号的情况:当左括号栈空时,从星号栈取来匹配右括号;(其实这里按理说也要判断下星号的下标在右括号之前)
- 星号变成左括号的情况:为了将每个左括号匹配,需要将星号看成右括号,且每个左括号必须出现在其匹配的星号之前,这里需要对下标进行判断;
#define STACK_SIZE 101
bool checkValidString(char *s)
{
int stack1[STACK_SIZE] = {0}; // 存左括号
int stack2[STACK_SIZE] = {0}; // 存星号
int len = strlen(s);
int i = 0;
int top1 = 0;
int top2 = 0;
i = 0;
while (i < len) {
if (s[i] == '(') {
stack1[top1] = i; // 下标存储下来
top1++;
}
else if (s[i] == ')') {
// 手法1:如果左括号栈不为空,则从左括号栈弹出栈顶元素;
if (top1 > 0) {
top1--;
} else if (top1 == 0 && top2 > 0) { // 手法2:如果左括号栈为空且星号栈不为空,则从星号栈弹出栈顶元素;
top2--;
}
else { // 手法3:如果左括号栈和星号栈都为空,则没有字符可以和当前的右括号匹配,返回 false
return false;
}
}
else if (s[i] == '*') {
stack2[top2] = i;
top2++;
}
i++;
}
i = 0;
// 手法4:当两个栈都不为空时,每次从左括号栈和星号栈分别弹出栈顶元素
// 对应左括号下标和星号下标,判断是否可以匹配,匹配的条件是左括号下标小于星号下标,如果左括号下标大于星号下标则返回 false。
if (top2 != 0 && top1 != 0) {
while (top1 > 0 && top2 > 0) {
top1--;
top2--;
if (stack1[top1] > stack2[top2]) { // *要出现在(的右边,要求stack1[top1] < stack2[top2],否则为false.
return false;
}
}
}
if (top1 == 0) {
return true;
}
return false;
}
224.基本计算器
s有: 数字、‘+’、‘-’、‘(’、‘)’、和 ’ ’
实现一个基本的计算器来计算一个简单的字符串表达式的值。
字符串表达式可以包含左括号 ( ,右括号 ),加号 + ,减号 -,非负整数和空格
一个运算符栈opors,一个运算数栈opands。
括号内的计算优先级最高。
碰到’(‘,‘(‘入opors栈。
碰到’)’,opors栈中栈顶到’(‘的运算符都要计算出来。 这表示括号内的表达式结束了,然后, ‘(‘ 出栈。
碰到’+”-‘, opors栈中栈顶到’(‘的运算符都要计算出来。 这是左结合性导致的。然后 ‘+”-‘ 入栈。
碰到数字,提取出整数,然后入opands栈。
int calculate(char *s) {
int n = strlen(s);
int ops[n], top = 0;
int sign = 1;
ops[top++] = sign;
int ret = 0;
int i = 0;
while (i < n) {
if (s[i] == ' ') {
i++;
} else if (s[i] == '+') {
sign = ops[top - 1];
i++;
} else if (s[i] == '-') {
sign = -ops[top - 1];
i++;
} else if (s[i] == '(') {
ops[top++] = sign;
i++;
} else if (s[i] == ')') {
top--;
i++;
} else {
long num = 0;
while (i < n && s[i] >= '0' && s[i] <= '9') {
num = num * 10 + s[i] - '0';
i++;
}
ret += sign * num;
}
}
return ret;
}
227. 基本计算器 II
int calculate(char* s) {
int n = strlen(s);
int stk[n], top = 0;
char preSign = '+';
int num = 0;
for (int i = 0; i < n; ++i) {
if (isdigit(s[i])) {
num = num * 10 + (int)(s[i] - '0');
}
if (!isdigit(s[i]) && s[i] != ' ' || i == n - 1) {
switch (preSign) {
case '+':
stk[top++] = num;
break;
case '-':
stk[top++] = -num;
break;
case '*':
stk[top - 1] *= num;
break;
default:
stk[top - 1] /= num;
}
preSign = s[i];
num = 0;
}
}
int ret = 0;
for (int i = 0; i < top; i++) {
ret += stk[i];
}
return ret;
}
队列的典型应用:广度优先遍历
有两种通用的遍历树的策略:
1.深度优先搜索(DFS)
在这个策略中,我们采用深度作为优先级,以便从跟开始一直到达某个确定的叶子,然后再返回根到达另一个分支。
深度优先搜索策略又可以根据根节点、左孩子和右孩子的相对顺序被细分为先序遍历,中序遍历和后序遍历。
2.宽度优先搜索(BFS)
我们按照高度顺序一层一层的访问整棵树,高层次的节点将会比低层次的节点先被访问到。
102. 二叉树的层次遍历
给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。
利用二叉树的,先序遍历,根左右
这里需要注意的是最后是通过二维数组存放结果,对于指针的指针的处理需要注意,并且有树深度(returnSize)返回。
void helper(struct TreeNode* root, int **result, int *ColumnSizes, int i, int *maxh) {
if (root == NULL) { // 递归终止条件
return ;
}
result[i][ColumnSizes[i]] = root->val; // 二叉树的前序遍历
ColumnSizes[i]++; // ColumnSize[i] 存放当前二维数组的列元素
if (i + 1 > *maxh) { // 更新深度,由于 i+1 为深度,传值给 *maxh 为深度
*maxh = i + 1;
}
helper(root->left, result, ColumnSizes, i + 1, maxh); // i + 1 表示层次加1
helper(root->right, result, ColumnSizes, i + 1, maxh); // maxh 表示 树的深度
}
int **levelOrder(struct TreeNode *root, int *returnSize, int **returnColumnSizes) {
int **result = (int **)malloc(sizeof(int *) * MaxSize); // 二维数组,结果存放处, malloc行空间
int i;
for (i = 0; i < MaxSize; i++) { // malloc列空间
result[i] = (int *)malloc(sizeof(int) * MaxSize);
}
*returnColumnSizes = (int *)malloc(MaxSize * sizeof(int));
memset(*returnColumnSizes, 0, MaxSize * sizeof(int));
*returnSize = 0; // 二维数组里行元素个数,也就是树的深度
if (root == NULL) { // 异常判断
return NULL;
}
helper(root, result, *returnColumnSizes, 0, returnSize);
return result;
}
二叉树的层次遍历使用的更多的是利用队列的性质实现BFS,广度优先遍历:
下面还有队列的C语言实现。
#define MAX_QUEUE 10000
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
};
struct Queue
{
int front; // 队头删除
int rear; // 队尾新增
int size;
struct TreeNode *array[MAX_QUEUE]; // 指针数组
};
void QueueInit(struct Queue *queue)
{
memset(queue, 0, sizeof(*queue));
}
int QueueEmpty(struct Queue *queue)
{
return (queue->size == 0);
}
void QueuePush(struct Queue *queue, struct TreeNode *node)
{
queue->array[queue->rear] = node; // 往队尾增加一个元素
queue->rear++;
printf("Queue Push:queue->rear val %d\n", node->val);
queue->size++;
}
int QueuePop(struct Queue *queue, int *data)
{
if (QueueEmpty(queue)) {
return 0;
}
printf("QueuePop\n");
queue->front = (queue->front + 1);
queue->size--;
return 1;
}
void QueueDestroy(struct Queue* queue)
{
queue->front = 0;
queue->rear = 0;
queue->size = 0;
free(queue->array);
free(queue);
}
struct TreeNode *QueueFront(struct Queue *q)
{
if (q->size == 0)
return NULL;
printf("q->front val %d\n", q->array[q->front]->val);
return q->array[q->front];
}
int **levelOrder(struct TreeNode *root, int *returnSize, int **returnColumnSizes)
{
*returnSize = 0;
*returnColumnSizes = 0;
if (root == NULL) {
return NULL;
}
int layer = 0;
int data = 0;
int **res = (int **)malloc(MAX_QUEUE * sizeof(int *));
struct Queue *q = (struct Queue *)malloc(sizeof(struct Queue));
QueueInit(q);
*returnColumnSizes = (int *)malloc(MAX_QUEUE * sizeof(int));
QueuePush(q, root); // 插入root头结点
while (!QueueEmpty(q)) { // 依次入队头结点/左右节点,然后利用队列性质先进先出队列
int curNum = q->size;
int *array = (int *)malloc(sizeof(int) * curNum);
(*returnColumnSizes)[layer] = curNum; // returnColumnSizes 记录一个数组,数组元素是记录结果每一行的元素个数
printf("--current size curNum-- %d\n", curNum);
for (int i = 0; i < curNum; i++) {
struct TreeNode *node = QueueFront(q); // 获取队头节点
if (node) {
array[i] = node->val; // 获取每一行的结果
printf("-----node->val %d-----\n\n",node->val);
if (node->left) QueuePush(q, node->left); // 左节点入队
if (node->right) QueuePush(q, node->right); // 右节点入队
}
QueuePop(q, &data);// 队头节点出队列
}
res[layer++] = array; // res 是结果存放的二维数组,array是每一行结果存放的一维数组
printf("layer is %d\n", layer); // layer 是记录这个二维数组的行数,即深度
free(array);
}
*returnSize = layer;
free(q);
return res;
}
98. 验证二叉搜索树
给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
bool isValid(struct TreeNode *root, long min ,long max) {
if (root == NULL) {
return 1; // 递归终止条件
}
if (min >= root->val) { //根节点必须大于min
return false;
}
if (max <= root->val) { //根节点必须小于max
return false;
}
return isValid(root->left, min, root->val) && isValid(root->right, root->val, max);
}
bool isValidBST(struct TreeNode *root) {
long min = -2147483649; // 需要注意由于题目的 val 为int型,所以会有用例覆盖到 int的最值(-2147483649 - 2147483647),所以需要将min/max超过这个范围
long max = 2147483649; // 否则,如果出现一个根节点为min,将会直接判断false出去,但一个根节点的结果应该是true
return isValid(root, min, max);
}
225.使用队列实现栈的下列操作
push(x) – 元素 x 入栈
pop() – 移除栈顶元素
top() – 获取栈顶元素
empty() – 返回栈是否为空
考虑到队列是一种 FIFO 的数据结构,最后入队的元素应该在最后被出队。因此我们需要维护另外一个队列 q2,这个队列用作临时存储 q1 中出队的元素。q2 中最后入队的元素将作为新的栈顶元素。接着将 q1 中最后剩下的元素出队。我们通过把 q1 和 q2 互相交换的方式来避免把 q2 中的元素往 q1 中拷贝
typedef struct SqQueue {
int front;
int rear;
int dataLen;
int data[1024];
} SqQueue_t;
// 初始化队列
SqQueue_t *SqQueueCreate(int dataLen) {
SqQueue_t *queue = (SqQueue_t *)malloc(sizeof(SqQueue_t) + sizeof(int) * dataLen);
queue->front = 0;
queue->rear = 0;
queue->dataLen = dataLen;
return queue;
}
void SqQueueDestroy(SqQueue_t *queue) {
free(queue);
}
int SqQueueFull(SqQueue_t *queue) {
return (queue->front == (queue->rear + 1) % queue->dataLen);
}
int SqQueueEmpty(SqQueue_t *queue) {
return (queue->front == queue->rear);
}
int SqQueuePush(SqQueue_t *queue, int data) {
if (SqQueueFull(queue)){
return -1;
}
// 将数据放置到队尾
queue->data[queue->rear++] = data;
printf("Push:queue->rear is %d, data is %d\n", queue->rear, data);
return 0;
}
int SqQueuePop(SqQueue_t *queue, int *data) {
if (SqQueueEmpty(queue)) {
return -1;
}
// 先进先出,按队头顺序出队列
*data = queue->data[queue->front++];
printf("Pop:queue->front is %d, data is %d\n", queue->front, *data);
return 0;
}
/*---------------------两个队列实现栈---------------------*/
typedef struct {
SqQueue_t *qA;
SqQueue_t *qB;
} MyStack;
/** Initialize your data structure here. */
MyStack *myStackCreate() {
MyStack *stack = (MyStack *)malloc(sizeof(MyStack));
stack->qA = SqQueueCreate(20);
stack->qB = SqQueueCreate(20);
return stack;
}
/** Returns whether the stack is empty. */
bool myStackEmpty(MyStack *obj) {
return (SqQueueEmpty(obj->qA) && SqQueueEmpty(obj->qB));
}
/** Returns whether the stack is empty. */
bool myStackFull(MyStack *obj) {
return (SqQueueFull(obj->qA) || SqQueueFull(obj->qB));
}
/** Push element x onto stack. */
void myStackPush(MyStack *obj, int x) {
if (myStackFull(obj))
return;
SqQueue_t *enQueue = NULL;
if (!SqQueueEmpty(obj->qA)) {
enQueue = obj->qA;
} else {
enQueue = obj->qB;
}
SqQueuePush(enQueue, x);
}
/** Removes the element on top of the stack and returns that element. */
int myStackPop(MyStack *obj) {
if (myStackEmpty(obj)) {
return -1;
}
int data;
SqQueue_t *enQueue = NULL;
SqQueue_t *deQueue = NULL;
SqQueue_t *tmpQueue = NULL;
if (!SqQueueEmpty(obj->qA)) {
deQueue = obj->qA; // qA A队列出数据
enQueue = obj->qB; // qB B队列收数据
} else {
deQueue = obj->qB;
enQueue = obj->qA;
}
while (deQueue->rear != deQueue->front) {
printf("\n");
SqQueuePop(deQueue, &data); // 队列1出数据,队列2收数据
if (!SqQueueEmpty(deQueue)) { // 队列1出完最后一个元素,队列2不接收,直接作为最后一个输出,它就是栈的Top元素
SqQueuePush(enQueue, data);
}
}
printf("rear is %d, front is %d\n", deQueue->rear, deQueue->front);
return data;
}
/** Get the top element. */
int myStackTop(MyStack *obj) {
if (myStackEmpty(obj)) {
return -1;
}
SqQueue_t *enQueue = NULL;
SqQueue_t *deQueue = NULL;
int data;
// 交换deQueue 和 enQueue
if (!SqQueueEmpty(obj->qA)) {
deQueue = obj->qA;
enQueue = obj->qB;
} else {
deQueue = obj->qB;
enQueue = obj->qA;
}
while (deQueue->rear != deQueue->front) {
SqQueuePop(deQueue, &data);
SqQueuePush(enQueue, data);
}
return data;
}
void myStackFree(MyStack *obj) {
SqQueueDestroy(obj->qA);
SqQueueDestroy(obj->qB);
free(obj);
}
int main()
{
MyStack *stack = myStackCreate();
myStackPush(stack, 1);
myStackPush(stack, 2);
myStackPush(stack, 3);
myStackPush(stack, 4);
printf("Pop element is %d\n", myStackPop(stack));
return 0;
}
出栈时,队列A会先逐一拷贝元素至另一个队列B,直到最后一个元素Pop出去;
下次出栈时,就反过来,此队列A已经空了,作为存储队列,队列B Push进队列A,直到最后一个元素Pop出去。
typedef struct Stack {
int data[MAX_SIZE];
int top;
int min;
} MinStack; // 为Stack 起了一个别名,后续可以直接使用MinStack来定义变量
MinStack *minStackCreate() {
MinStack *obj = (MinStack *)malloc(sizeof(MinStack));
if (obj) {
obj->top = -1;
obj->min = INT_MAX;
return obj;
}
return NULL;
}
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) —— 将元素 x 推入栈中。
pop() —— 删除栈顶的元素。
top() —— 获取栈顶元素。
getMin() —— 检索栈中的最小元素。
输入:
[“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.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#define MAX_SIZE 100
typedef struct Stack {
int data[MAX_SIZE];
int top;
int min; // 保留一个最小值
} MinStack;
MinStack *minStackCreate() {
MinStack *obj = (MinStack *)malloc(sizeof(MinStack));
if (obj) {
obj->top = -1;
obj->min = INT_MAX;
return obj;
}
return NULL;
}
void minStackPush(MinStack *obj, int x) {
if (obj->top == MAX_SIZE - 1) { // 达到最大值
return;
}
obj->data[++obj->top] = x;
obj->min = fmin(obj->min, x); // 保存最小值
}
void minStackPop(MinStack *obj) {
int i = 0;
if (obj->top < 0) {
obj->min = INT_MAX;
return;
}
obj->top--;
obj->min = INT_MAX;
// top后这里为什么需要重新找 min 点
for (i = 0; i <= obj->top; i++) {
obj->min = fmin(obj->min, obj->data[i]);
}
}
int minStackTop(MinStack *obj) {
return obj->data[obj->top]; // 返回top值
}
int minStackGetMin(MinStack *obj) {
return obj->min; // 返回最小值
}
void minStackFree(MinStack *obj) {
free(obj);
}
682. 棒球比赛
int calPoints(char **ops, int opsSize)
{
int num[10001] = {0};
int top = -1;
int i = 0;
int result = 0;
int res = 0;
if (opsSize == 0) {
return 0;
}
while (i < opsSize) {
if (ops[i][0] == 'C') {
top--; // 出栈,前一次得分无效
}
else if (ops[i][0] == 'D') {
result = 2 * num[top];
num[++top] = result;
}
else if (ops[i][0] == '+') {
if (top > 0) {
result = num[top] + num[top - 1];
num[++top] = result;
}
}
else { // 由于分数可能出现超过2位的,所以我们在这统一使用 atoi 函数转
num[++top] = atoi(ops[i]);
}
i++;
}
for (i = 0; i <= top; i++) {
res = res + num[i];
}
return res;
}