文章目录
- 前言
- 一、后缀表达式
- 题目分析
- 思路分析
- 代码
- 二、小行星碰撞
- 题目分析
- 思路分析
- 代码
- 三、每日温度
- 题目分析
- 思路分析
- 代码
- 四、直方图最大矩形面积
- 题目分析
- 思路分析
- 代码
- 五、矩阵中最大的矩形
- 题目分析
- 思路分析
- 代码
- 总结
前言
剑指offer专项突破版(力扣官网)——> 点击进入
本文所属专栏——>点击进入
- 多插几句
由于这是C语言,用到栈
的数据结构,需要手搓
,当然也很简单,但总归是费点时间
的,因此我们避免一道题造一次轮子的尴尬情况,请自觉备好一份栈的代码,以便于CV
。
进入今天的栈篇吧!
理论知识——栈和对列
栈的实现原理就是简单的顺序表
这里为了避免冗余代码,先把栈的代码给出。
typedef struct Stack
{
int* arr;
int size;
int capacity;
}Stack;
void Init(Stack* stack)
{
stack->arr = NULL;
stack->size = 0;
stack->capacity = 0;
}
void Push(Stack* stack,int val)
{
if(stack->capacity == stack->size)
{
int capacity = stack->capacity == 0 ? 4 : stack->capacity * 2;
stack->arr = (int*)realloc(stack->arr,sizeof(int)*capacity);
stack->capacity = capacity;
}
(stack->arr)[(stack->size)++] = val;
}
int Pop(Stack *stack)
{
if(stack->size > 0)
{
return (stack->arr)[--(stack->size)];
}
return 0;
}
bool is_empty(Stack* stack)
{
if(stack->size == 0)
{
return true;
}
return false;
}
void Stack_free(Stack* stack)
{
free(stack->arr);
}
int Top(Stack* stack)
{
return (stack->arr)[stack->size-1];
}
一、后缀表达式
题目分析
思路分析
为了弄懂这道题,首先我们先要清楚逆波兰表示法
,这个概念。
1+1等于2,这都知道,那换种写法,我说1 1 + 的结果也是 2。前一种,是我们
习惯的写法,后一种是计算机
的写法,我们一般把1+1
叫做中缀表示法,就是操作符在操作数之间。那 1 1 +
叫做后缀表示法/逆波兰表示法,操作数在前,操作符在后,如何从后缀表示法推到中缀表示法
,是我们要解决的问题。
我们先给稍微复杂一点的表达式,5 + 1 + (5 * 5)
,优先级高的优先放到后面,那第一步我们转换成了,5 + 1 + 5 5 *
,然后加减法从左往右,第二步就转换成了,5 1 + 5 5 * +
,第三步就转换成了,5 1 5 5 * + +
。
弄懂概念,我们就成功一半了,剩下的问题是如何计算后缀表达式
。根据转换过程中的优先级的符号在前面,我们就可以将此表达式,转换成 5 1 (5 * 5) + +,即为5 1 25 + +
,那么接着继续取出两个数,5 (1 +25)+,即为5 26 +
,再进一步转换,(5+26) ,即为31
,这个结果即为计算的结果 。仔细看,这个过程,再结合我们今天的专题,很容易就能想到,这是一个栈的思想。遇到数字就入栈
,遇到符号就出栈,出两个元素,当然顺序不能乱(尤其除和减),出的第一个元素我们设为n1
,第二个元素我们设为n2
,操作符我们设为x
,最终一定是n2 x n1
。然后计算后我们还要把结果入栈,以便于后续的操作。oK,理解了这些,写出代码就容易多了。
代码
int str_nums(char* str)
{
//求一下字符串的长度
int len = strlen(str);
//要转换成字符串需要正序遍历
int begin = 0;
int end = len-1;
//先假设是正数
int flag = 1;
if(str[0]=='-')
{
flag = -flag;
begin = 1;
}
int ret = 0;
for(int i = begin; i <= end; i++)
{
ret = ret*10 + (str[i] - '0');
}
return ret*flag;
}
bool is_nums(char* str)
{
int len = strlen(str);
char judge = str[len-1];
if(judge>='0'&&judge<='9')
{
return true;
}
else
{
return false;
}
}
int evalRPN(char ** tokens, int tokensSize)
{
Stack stack;
Init(&stack);
//将计算结果放在此变量中
int ret = 0;
int end = 0;
for(int i = 0; i < tokensSize; i++)
{
//判断是否是数字
char *nums = tokens[i];
if(is_nums(nums))
{
//是数字就入栈
int num = str_nums(nums);
Push(&stack,num);
}
else
{
int n1 = Pop(&stack);
int n2 = Pop(&stack);
//判断加减乘除,同时出栈
if(*nums == '+')
{
ret = n2 + n1;
}
else if(*nums == '-')
{
ret = n2 - n1;
}
else if(*nums == '*')
{
ret = n2 * n1;
}
else
{
ret = n2/n1;
}
//然后将结果再入栈
Push(&stack,ret);
}
}
//获取栈顶的元素
ret = Top(&stack);
//不要忘记,释放栈的空间
Stack_free(&stack);
return ret;
}
二、小行星碰撞
题目分析
思路分析
小行星碰撞,看起来就很有意思,不过需要我们分析清楚思路,什么时候会发生碰撞
,什么时候会同归于尽
,什么时候永远不会发生碰撞
。解决了这三个问题,代码就很轻松了。
先来解决一个大前提,我们为啥会采取栈的结构进行解决此问题
,我们从抽象的角度
考虑,栈的当前状态都是一致的,当遇到某种变量可能会打破这种状态时,栈会通过不断地取栈顶元素就为去维持当前的状态,直到将这种变量的影响消除,从而更新或者维护状态。
这里我们再具象化,当栈里面存的是不发生相撞的元素时,栈当前的状态是不相撞的,当遇到当前元素,可能会让栈的状态改变,从而发生相撞,我们就会用栈顶元素进行处理,直到栈的状态是不相撞的为止。
OK,来分析第一种情况:什么时候会发生相撞? 当然是相向而行了,如果背向而行,只会越走越远。那什么时候会发生相向而行呢?当然是栈不为空,且栈顶的元素大于0,且当前元素为负数
。那什么时候栈顶的元素不够处理这种情况呢?当前元素的相反数大于栈顶元素
。此时我们就要出栈了,用下一个栈顶元素,来处理这种情况。
接着分析,第二种情况:什么时候会发生同归于尽? 很明显,是在一种情况的特殊情况,当栈不为空,且栈顶的元素大于0,且当前元素为负数,且其相反数等于栈顶元素。
第三种情况:什么时候永远不会发生碰撞? 当背向而行时,肯定不会碰撞,即栈的元素小于0,当前元素大于0
。当同向而行时,即栈的元素和当前元素同号
。当栈的元素为空
时,也不会发生相撞。
OK,情况分析完了。
代码
int* asteroidCollision(int* asteroids, int asteroidsSize, int* returnSize)
{
Stack stack;
Init(&stack);
for(int i = 0; i < asteroidsSize; i++)
{
int num = asteroids[i];
//栈不为空时,且达成一直相撞条件的——当前元素小于0,栈元素大于0,且当前元素的绝对值较大
while(!is_empty(&stack)&&Top(&stack) > 0 && Top(&stack) < - num)
{
Pop(&stack);
}
//前提是栈不为空,当栈的元素大于0,并且与当前元素的相反数相等
if((!is_empty(&stack))&&(Top(&stack) > 0)&&(Top(&stack)==-num))
{
Pop(&stack);
}
//剩下的就是不满足相撞条件的,栈为空,栈的元素小于0,当前元素大于0,
else if(is_empty(&stack)||Top(&stack) < 0 || num > 0)
{
Push(&stack,num);
}
}
*returnSize = stack.size;
return stack.arr;
}
三、每日温度
题目分析
思路分析
如果能弄懂,栈的抽象解题思路,这道题,便会简单一些。首先要计算天数,我们存的是下标才好,栈里面的状态是目前为止气温没有升高
,当遇到温度高的,那么就要计算栈顶元素的与之的天数差,直到栈的状态是目前气温没有升高为止或者栈为空,然后直到所有的元素都入过栈为止,最后栈的状态是目前为止气温没有升高,那么剩余栈的元素的天数差就是0。
代码
int* dailyTemperatures(int* temperatures, int temperaturesSize, int* returnSize)
{
Stack stack;
Init(&stack);
int *arr = (int*)malloc(sizeof(int)*temperaturesSize);
*returnSize = temperaturesSize;
memset(arr,0,sizeof(int)*temperaturesSize);
for(int i = 0; i < temperaturesSize; i++)
{
//当栈不为空,并且当前的温度大于栈顶的温度。
//我们就计算栈顶元素的天气差,然后将栈顶元素出栈。
while(!is_empty(&stack)&&temperatures[Top(&stack)]\
< temperatures[i])
{
arr[Top(&stack)] = i - Top(&stack);
Pop(&stack);
}
//其余情况我们入栈即可
Push(&stack,i);
}
Stack_free(&stack);
return arr;
}
四、直方图最大矩形面积
题目分析
思路分析
首先我们要理解如何求这个问题,假如将每一个矩形看做求取最大矩形的高,如何求可能的最大的面积,这个问题很简单,那就是确定比这个矩形矮的两边最近界限,以上面的5为例,比之低的就是 1 和 2,因此我们可以确定宽度,即为两界限下标差减1。那如何问题就转换为了如何求这个界限的问题,这就利用到了栈,用栈(存的元素下标
)的状态表示当前不存在两边都小的情况
,即栈的元素递增
,当遇到比栈顶小或等于的元素,我们就可以计算,最小的宽度从而计算面积,将栈顶元素不断出栈,直到栈的状态满足递增或者栈为空为止。最后可能栈还会有元素,其符合递增顺序,且是整个数组中较小的元素,因此我们依次求宽度乘栈顶元素的高度即可,宽度即为元素个数减栈顶元素的下一个元素再减去1
,最后返回求得的最大面积即可。
代码
int largestRectangleArea(int* heights, int heightsSize)
{
Stack stack;
Init(&stack);
Push(&stack,0);
int max = 0;
for(int i = 1; i < heightsSize;i++)
{
int top = Top(&stack);
//处理栈不为空,且当前元素小于等于栈顶的元素
while(!is_empty(&stack)&&heights[i] <= heights[top])
{
Pop(&stack);
int wide = i-Top(&stack)-1;
int area = heights[top]*wide;
if(max<area)
{
max = area;
}
top = Top(&stack);
}
Push(&stack,i);
}
//最后处理栈中递增的元素
int top = Top(&stack);
while(!is_empty(&stack))
{
Pop(&stack);
int wide = (heightsSize-1)-Top(&stack);
int area = heights[top]*wide;
if(max < area)
{
max = area;
}
top = Top(&stack);
}
//不要忘记释放空间
Stack_free(&stack);
return max;
}
五、矩阵中最大的矩形
题目分析
思路分析
将二维矩阵转换成多个一维矩阵,不断地迭代,转为上一题的思路进行求解。
代码
int maximalRectangle(char** matrix, int matrixSize)
{
if(matrixSize == 0)
{
return 0;
}
char *str = matrix[0];
int len = strlen(str);
int *arr = (int*)malloc(sizeof(int)*len);
memset(arr,0,sizeof(int)*len);
int max = 0;
for(int i = 0; i < matrixSize; i++)
{
char *str = matrix[i];
for(int j = 0; j < len; j++)
{
if(str[j] == '0')
{
arr[j] = 0;
}
else
{
arr[j]++;
}
}
int ret = largestRectangleArea(arr,len);//上一题
max = max > ret ? max : ret;
}
return max;
}
总结
今天的分享就到这里了,如果觉得文章不错,点个赞鼓励一下吧!我们下篇文章再见
!