先附上地址方便对照着看:代码随想录
五、双指针法
这里面的题目都是之前的,双指针法确实很有用,尤其适用于需要多次遍历或者在一次遍历中需要对两端进行操作的情况,使用双指针法可以大大减少时间复杂度,考试的时候也可以注意下往这方面想的思路
六、栈和队列
栈:先进后出,队列:先进先出
一、用栈实现队列:C语言真是啥也没有啊……
感觉对于C语言而言无论是栈还是队列,都是数组或链表实现,没有定义好的数据结构还是挺麻烦
这道题直接去看原文好了,也没什么好分析的
二、用队列实现栈:同上,机试没必要关心这个,通常考的都是应用题
三、有效的括号:熟悉的括号匹配,用栈解决的经典问题。
首先分析括号不匹配的情况:左括号多了、右括号多了、以及左右括号不是同种括号
左括号多了:遍历完字符串但栈不为空
右括号多了:还没遍历完字符串但栈已经空了
左右括号不是同一种:遍历途中右括号和栈顶括号不是同种括号
然后分析步骤:
step 1:定义栈和栈顶指针并初始化
step 2 :遍历字符串,如果是左括号就存入栈中,如果是右括号就进行匹配,匹配时,若栈为空或括号类型不对那么直接返回false,要注意匹配完成后栈顶指针需要移动
step 3:字符串遍历完成,检查栈顶指针是否归零,如果不为0表示栈中还有元素,那就是左括号多了,也要返回false,否则返回true
直接搬代码过来了,注释写的很充分
//辅助函数:判断栈顶元素与输入的括号是否为一对。若不是,则返回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;
}
bool isValid(char * s){
int strLen = strlen(s);
//开辟栈空间
char stack[5000];
int stackTop = 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;
//当前字符与栈顶元素为一对括号,将栈顶元素出栈
else
stackTop--;
}
//若栈中有元素,返回False。若没有元素(stackTop为0),返回True
return !stackTop;
}
四、删除字符串中的相邻重复项:这道题也能用栈是我没想到的,挺像消消乐的,本来想递归删除的但是除了树那一块还是少用递归吧。
思路很简单:遍历字符串,每遍历一个字符就把栈顶元素和当前字符进行比较,如果相同的话就删除栈顶元素,最后返回栈
步骤如下:
step 1:定义栈和栈顶指针并初始化
step 2:遍历字符串,如果栈中有元素且遍历元素与栈顶元素相同,则将栈顶元素出栈,否则将遍历元素入栈
step 3:由于定义的栈是字符串,所以在末尾添上 '\0',然后返回栈
char* removeDuplicates(char* s) {
int len = strlen(s);
char *stack = (char *)malloc(sizeof(char) * len + 1 ); //step 1,注意多加1用来放'\0'
int stackTop = 0;
for(int i=0;i<len;i++){ //step 2
if(stackTop>0 && s[i]==stack[stackTop-1])
stackTop--;
else
stack[stackTop++]=s[i];
}
stack[stackTop]='\0'; //step 3
return stack;
}
五、逆波兰表达式求值:又是栈的经典应用,求后缀表达式的值,非常适合用栈计算
后缀表达式的运算在数据结构中有学过:遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中。
那么基于这个运算方法分析步骤:
step 1:创建 int 类型栈并初始化栈顶指针
step 2:遍历字符串,如果当前字符是数字,就转化为 int 类型存入栈中。如果是运算符,那么栈顶指针 -1 ,然后用现在栈顶指针所指的元素和栈顶指针回退之前的元素进行运算,也就是不需要弹出来数字了,直接在栈里进行运算。
step 3:遍历结束后,返回当前栈顶指针所指的元素。
但是有几个地方特别注意:
一个是在比较遍历字符和运算符时,需要用到 strcmp 库函数,不能简单写成 tokens[i] == '+' 或 tokens[i] == "+",前面一种可以运行但结果错误,后一种无法运行,因为这是字符串之间的比较!
另一个就是把字符串转化为 int 类型,需要使用 atoi 库函数,包含在<stdlib.h>这个库中,如果不记得这个函数的话就得手搓字符串转换了,还得考虑负号,很麻烦,后面我专门整理一期机试方便好用的库函数。
int evalRPN(char** tokens, int tokensSize) {
int *stack = (int *)malloc(sizeof(int) * 20000); //step 1
int stackTop = -1;
for(int i=0;i<tokensSize;i++){ //step 2
if(strcmp(tokens[i],"+") == 0){
stackTop--;
stack[stackTop] += stack[stackTop+1];
}
else if(strcmp(tokens[i],"-") == 0){
stackTop--;
stack[stackTop] -= stack[stackTop+1];
}
else if(strcmp(tokens[i],"*") == 0){
stackTop--;
stack[stackTop] *= stack[stackTop+1];
}
else if(strcmp(tokens[i],"/") == 0){
stackTop--;
stack[stackTop] /= stack[stackTop+1];
}
else{
stack[++stackTop] = atoi(tokens[i]);
}
}
return stack[stackTop]; //step 3
}
六、滑动窗口最大值:看到这道题我就想到双指针法,但是这是个固定的窗口,没啥用啊,然后我的思路是用排序硬解这道题,然后超时了,看完题解才想到居然能用队列解这道题,机试要是有这种题我大概率寄了
直接说最佳方法:双端队列+单调队列:核心思路就是维护一个单调队列,使得队首元素是此时窗口的最大值,然后在队首出队的时候把最大值记录进答案。
注释已经很详细了,步骤不多赘述。
int* maxSlidingWindow(int* nums, int numsSize, int k, int* returnSize) {
int n = numsSize;
int queue[n]; //队列
int front = 0, rear = -1; //队首 队尾
int left = 0, right = 0; //窗口左下标 窗口右下标
while (right < n) { //窗口右移至终点
while (rear >= front && nums[right] > queue[rear]) rear--; //维护队列的单调性(非递增),即保证队首元素就是当前窗口的最大值
queue[++rear] = nums[right++]; //入队下一个窗口可能的最大值
if (left + k <= right) { //窗口大小大于k
if (nums[left] == queue[front]) front++; //如果最大值已经在窗口的左边,则将它永久出队
else nums[left] = queue[front]; //否则记录最大值进原数组中
left++; //左框右移
}
}
*returnSize = n - k + 1;
return nums; //返回原数组
}
七、前K个高频元素:这道题虽然说可以用堆排序来解,但堆这个数据结构不适合C语言啊,那既然是排序,为什么不用快速排序呢,只是这里要用到结构体
步骤如下:
step 1:定义返回数组、数组大小、结构体数组并初始化,再对初始数组nums进行排序
step 2:遍历计算各个数字出现的次数,注意相同元素在 nums 中排序后一定是相邻的,所以相邻元素存储结构体的时候要注意判断,若是相同元素就直接让 cnt++
step 3:对结构体数组按出现次数降序排序,这里注意结构体排序函数,可以直接传入结构体指针进行排序,这里要是用const void 指针不够直观
step 4:存储 cnt 最大的前k个数字到数组中并返回
struct Times{
int num;
int cnt;
};
//数组排序
int cmp(const void* a,const void* b){
return *(int*)a - *(int*)b;
}
//结构体排序
int cmps(struct Times *a,struct Times *b){
return b->cnt - a->cnt;
}
int* topKFrequent(int* nums, int numsSize, int k, int* returnSize) {
*returnSize = k;
int* res = (int*)malloc(sizeof(int)*k);
struct Times time[numsSize];
int index=0;
qsort(nums,numsSize,sizeof(int),cmp); //step 1
time[index].num = nums[0];
time[index].cnt = 1;
for(int i=1;i<numsSize;i++){ //step 2
if(nums[i]==nums[i-1])time[index].cnt++;
else{
time[++index].num = nums[i];
time[index].cnt = 1;
}
}
qsort(time,index+1,sizeof(struct Times),cmps); //step 3
for(int i=0;i<k;i++){ //step 4
res[i] = time[i].num;
}
return res;
}
八、总结:C语言机试但凡涉及到数据结构都是一件比较麻烦的事,普通的栈和队列还好,如果是循环队列或双端队列确实容易把人绕进去,其实我更建议多多使用C语言的结构体,利用结构体本身的一些特性去解决实际问题,毕竟大多数机试题目都是偏应用而非复现某些算法。