《五月集训》第十四日——栈

前言

这是五月集训的第十四日,今日的训练内容是

解题报告

1.力扣1441

原题链接

1441. 用栈操作构建数组

题目概述

给你一个目标数组 target 和一个整数 n。每次迭代,需要从 list = {1,2,3…, n} 中依序读取一个数字。

请使用下述操作来构建目标数组 target :

Push:从 list 中读取一个新元素, 并将其推入数组中。
Pop:删除数组中的最后一个元素。
如果目标数组构建完成,就停止读取更多元素。
题目数据保证目标数组严格递增,并且只包含 1 到 n 之间的数字。

请返回构建目标数组所用的操作序列。

题目数据保证答案是唯一的。

解题思路

虽然这题描述的方法是栈,但其实本题可以不使用栈的方式就成功实现。首先构建好一个用于返回的数组,其中包含的是操作的名称,因此需要用数组保存每一个字符串,需要用到二维数组了。来分析一下,这题是不用考虑进栈出栈的顺序的,只要当前的数字不是需要的数字,就直接执行进栈出栈就可以了,如果是需要的数组就只进栈不出栈就可以了。

源码剖析

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
char ** buildArray(int* target, int targetSize, int n, int* returnSize){
    char **ret = NULL;
    ret=(char**)malloc(sizeof(char*)*(2*n - 1));
    int i=0,x=1,size = 0;
    while(x<=target[targetSize-1]){ 
        while(target[i]!=x){
            ret[size] = "Push";
            size++;
            x++;
            ret[size] = "Pop";
            size++;
        }
        if(target[i]=x){          //当元素相等时直接进栈
            ret[size] = "Push";
            size++;
            if(i<targetSize-1) i++;
            x++;
        }
    }
    *returnSize = size;
    return ret;
}

2.力扣1021

原题链接

1021. 删除最外层的括号

题目概述

有效括号字符串为空 “”、“(” + A + “)” 或 A + B ,其中 A 和 B 都是有效的括号字符串,+ 代表字符串的连接。

例如,“”,“()”,“(())()” 和 “(()(()))” 都是有效的括号字符串。
如果有效字符串 s 非空,且不存在将其拆分为 s = A + B 的方法,我们称其为原语(primitive),其中 A 和 B 都是非空有效括号字符串。

给出一个非空有效字符串 s,考虑将其进行原语化分解,使得:s = P_1 + P_2 + … + P_k,其中 P_i 是有效括号字符串原语。

对 s 进行原语化分解,删除分解中每个原语字符串的最外层括号,返回 s 。

解题思路

构建一个栈,因为只记录栈内元素的个数,不记录数据,因此用一个变量初始化之后就可以充当是栈了。然后每次出现左括号时就使其数值加一,每出现右括号的时候数值减一,这样在碰到第一个括号开始栈内的数据在碰到与之对应的右括号之前都大于等于1,这就是基本的实现思路了,接下来稍微介绍一下如何实现不申请额外空间的做法。除去用于循环的下标之外,再定义一个新数组的下标,当栈的数值第一次等于一的时候,也就是说刚计入第一个左括号的时候。将 k 的值左移一位与原来的下标 i 岔开一位,这样 k 变量始终追不上下标 i 于是在使用 k 对前面部分进行重新赋值的时候就完全不需要担心会覆盖掉原来的数组导致数据的丢失。当栈内数据个数等于 0 的时候说明这时遇到了最外层的右括号,同样左移 k 一位,和上面类似。

源码剖析

char * removeOuterParentheses(char * s){
    int stack = 0;              //定义一个栈
    int i,k=0,len=strlen(s);    //i是原下标,k是返回用的下标
    for(i = 0;i<len;++i,++k){
        if(s[i]=='('){
            stack++;
            s[k]=s[i];
            if(stack==1) k--;
        }
        if(s[i]==')'){
            stack--;
            if(stack==0) k--;
            else s[k]=s[i];
        }
    }
    s[k] = '\0';
    return s;
}

详细的介绍已经在解题思路中写出,这里提出一点需要注意的地方。由于经过了几次位移 k ,最终 k 的下标与 i 应该是有一段距离的1,那么后续的其他位仍然有原来的数据保存着,这时候为了避免读到后面的数据,直接使得 k 下标的后一位赋值为'\0'就可以了,代码中写的是 s[k] = '\0'; 因为循环结束后 k 的值会再加一,刚好拿来填充下一位。

3.力扣1700

原题链接

1700. 无法吃午餐的学生数量

题目概述

学校的自助午餐提供圆形和方形的三明治,分别用数字 0 和 1 表示。所有学生站在一个队列里,每个学生要么喜欢圆形的要么喜欢方形的。
餐厅里三明治的数量与学生的数量相同。所有三明治都放在一个 栈 里,每一轮:

如果队列最前面的学生 喜欢 栈顶的三明治,那么会 拿走它 并离开队列。
否则,这名学生会 放弃这个三明治 并回到队列的尾部。
这个过程会一直持续到队列里所有学生都不喜欢栈顶的三明治为止。

给你两个整数数组 students 和 sandwiches ,其中 sandwiches[i] 是栈里面第 i​​​​​​ 个三明治的类型(i = 0 是栈的顶部), students[j] 是初始队列里第 j​​​​​​ 名学生对三明治的喜好(j = 0 是队列的最开始位置)。请你返回无法吃午餐的学生数量。

解题思路

这题其实核心的东西其实也就是两点:把students[]数组变成循环队列;统计在students[]数组中的遍历次数,一旦有遍历完了所有有效数据的时候结束循环。虽然代码写的判断比较多,不够简洁,但是代码的效率还不错,达到了双百。这题我使用了一个指针来实现循环队列的功能:代码中的所有的i++的部分都用以下的代码代替来实现循环队列的效果:

if(i==studentsSize-1) i = 0;
else i++;

源码剖析

bool if_break(int b,int c,int d,int amax){     //预设一个判断是不是要break的函数,用起来稍微简洁一些
    if((b<amax)&&(c>0)&&(d<=c)) return false;
    else return true;
}
int countStudents(int* students, int studentsSize, int* sandwiches, int sandwichesSize){
    int ans = studentsSize;
    int i=0,j=0,cul=0;                        //i是student指针,j是sandwich指针,cul用于统计队列循环次数
    while(1){
        cul = 0;
        while(cul<=ans){                     //这说明队伍没背遍历完,也就是说不是所有人都不吃栈顶这个
            if(students[i]==sandwiches[j]){
                students[i] = 2;              //排除出这一项
                if(i==studentsSize-1) i = 0;
                else i++;
                j++;
                ans--;
                break;
            }
            if(if_break(j,ans,cul,studentsSize)) break;
            if(students[i]!=sandwiches[j]){
                if(students[i]==2){          //说明这一项被排除出去了
                    if(i==studentsSize-1) i = 0;
                    else i++;
                }else{
                    if(i==studentsSize-1) i = 0,cul++;
                    else i++,cul++;
                }
            }
            if(if_break(j,ans,cul,studentsSize)) break;
        }
        if(if_break(j,ans,cul,studentsSize)) break;
    }
    return ans;
}

首先写了一个用来随时判断break;的函数,其实就是判断各种越界情况用的。随后定义了一个变量 ans 这不仅是最终用于返回的变量,同时也与循环的过程息息相关。前面的部分可以看注释。对下面进行着重介绍:在students[i]!=sandwiches[j]的时候,首先要判断一下,当前的这一项是不是已经被上面排除了(也就是是否等于 2 )如果是的话就只是移动指针而不去变动计数器,反之如果不是被排除项的话说明这本身就是不符合条件的解,因此就要在移动指针的同时计数器也要加一。

4.力扣1381

原题链接

1381. 设计一个支持增量操作的栈

题目概述

请你设计一个支持下述操作的栈。

实现自定义栈类 CustomStack :

CustomStack(int maxSize):用 maxSize 初始化对象,maxSize 是栈中最多能容纳的元素数量,栈在增长到 maxSize 之后则不支持 push 操作。
void push(int x):如果栈还未增长到 maxSize ,就将 x 添加到栈顶。
int pop():弹出栈顶元素,并返回栈顶的值,或栈为空时返回 -1 。
void inc(int k, int val):栈底的 k 个元素的值都增加 val 。如果栈中元素总数小于 k ,则栈中的所有元素都增加 val 。

解题思路

除去最后一个功能之外,其余的功能都是《数据结构》这门课所介绍到的对于栈的操作,全都较为简单。

源码剖析

typedef struct {
    int stack[1001];
    int pos;
    int maxSize;
} CustomStack;


CustomStack* customStackCreate(int maxSize) {
    CustomStack* obj=malloc(sizeof(CustomStack));
    obj->pos=0;
    obj->maxSize = maxSize;
    return obj;
}

void customStackPush(CustomStack* obj, int x) {
    if(obj->pos<obj->maxSize){
        obj->stack[obj->pos++] = x;
    }
}

int customStackPop(CustomStack* obj) {
    if(obj->pos<1){
        return -1;
    }
    return obj->stack[--obj->pos];
}

void customStackIncrement(CustomStack* obj, int k, int val) {
    int minNum = fmin(obj->pos,k);
    for(int i=0;i<minNum;i++){
        obj->stack[i]+=val;
    }
}

void customStackFree(CustomStack* obj) {
    free(obj);
}

/**
 * Your CustomStack struct will be instantiated and called as such:
 * CustomStack* obj = customStackCreate(maxSize);
 * customStackPush(obj, x);
 
 * int param_2 = customStackPop(obj);
 
 * customStackIncrement(obj, k, val);
 
 * customStackFree(obj);
*/
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值