【学习记录】栈与队列习题 20200417

242 阅读9分钟

本篇主要对栈与队列的运用进行了一些练习,其中涉及到了一些常见的算法题目。
代码在这,大家可以参照代码来阅读会更加容易理解。


进制转换

将一个十进制数转为 N 进制,输出结果。

进制转换的思路可以概括为:辗转相除,余数倒序。
其中倒序这个点可以使用栈来实现。

void conversion(int num){
    ListStack s;
    ItemType e;
    
    int num_temp = num;
    
    stackInit(&s);
    
//    将辗转相除的余数入栈
    while (num_temp) {
        stackPush(&s, num_temp % SYSTEMNUM);
        num_temp = num_temp / SYSTEMNUM;
    }
    
    
    printf("十进制下 %d 转为 %d 进制编码为:", num, SYSTEMNUM);
    while (!isStackEmpty(s)) {
        stackPop(&s, &e);
        printf("%d", e);
    }
    
    printf(" .\n");
    
}

括号匹配检验

假设字符串中仅包含两种括号:圆括号与方括号,其中的嵌套顺序随意,即 () 或者 [([][])] 都是正确的,而 [(] 或者 ([()) 都不是正确的格式。设计算法检验括号是否匹配可用。

匹配的思路便是先记下等待与之相对的元素出现,而且在最内层的括号一定是直接成对的(左括号下一个一定就是右括号)。所以这个“先记下”和"配对"暗合和入栈出栈的思想。
思路:
1.首字符入栈;
2.查看栈顶,如果栈顶是 ( 恰好当前元素就是 ),那么栈顶出栈(这一组就匹配好了,不用管了)。如果不是,那么入栈当前元素(他需要别人和它匹配);
3.使用相同的逻辑判断 [;
4.多组判断中,以 # 间隔,那么一定要以 ( 或 [ 开始,否则即为不合法字符串;
5.最后遍历处理完毕之后若栈中没有待匹配的字符,那么即为合法。

int execute(ListStack stack, char* str){
    stackPush(&stack, str[0]);
    
    ItemType e;
    
    for (int i = 1; i < strlen(str); i++) {
        char top;
        stackGetTop(stack, &top);
        stackPrint(stack);
        switch (top) {
            case '(':
                if (str[i] == ')') {
                    stackPop(&stack, &e);
                } else {
                    stackPush(&stack, str[i]);
                }
                break;
            case '[':
                if (str[i] == ']') {
                    stackPop(&stack, &e);
                } else {
                    stackPush(&stack, str[i]);
                }
                break;
            case '#':
                if (str[i] == '(' || str[i] == '[') {
                    stackPush(&stack, str[i]);
                }
                break;
                
            default:
                return -1;
                break;
        }
    }
    
    if (isStackEmpty(stack)) {
        return 0;
    }
    
    return -1;
}

每日温度问题

题目: 根据每日气温列表,请重新生成一个列表,对应位置的输入是你需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置0来代替。例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。

提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。

这个和上一题有一些类似。 思路:
1.遍历所有温度,标记当前温度;
2.查看栈顶,如果当前温度比栈顶温度高,那么栈顶温度就找到匹配的温度了,计算差值后出栈。重复出栈符合条件的温度。
3.当前温度没有比栈顶高,那么说明当前温度是一个较低的温度,入栈,等待后续升温进行匹配;


void dailyTemperatures(int *data, int size, int *result, int *resSize){
    *result = (int)malloc(sizeof(int) * size);
    
//    注意此处是使用了栈思想,不需要真的实现一个堆栈
//    使用堆栈记录 data 的下标
    int *stack = malloc(sizeof(int) * size);
    *resSize = size;

    int top = 0;
    int tIndex;
    
//    初始化结果搜赋值为 0 其中最后一位到最后也是为 0。
    for (int i = 0; i < size; i++) {
        result[i]  = 0;
    }
    
    for (int i = 0; i < size; i++) {
//        若某一天温度大于栈顶元素了,那么即为温度升高了,所求天数则为下标之差。
//        然后还需要看一下当前温度是不是比新的栈顶高。
        while (top > 0 && data[i] > data[stack[top - 1]]) {
            printf("栈顶 %2d \n", top);
            
            tIndex = stack[top - 1];
            result[tIndex] = i - tIndex;
            top--;
        }
//        直到栈为空或者说当前温度没有栈顶高,
//        那么这个温度就是一个较低的温度他需要入栈等下一次升温再出栈
        stack[top] = i;
        top++;
    }
}

杨辉三角

打印杨辉三角:
1
1 1
1 2 1
1 3 3 1

思路:
1.默认 [i][0] = 1; [i][i] = 1;
2.在 1. 完成的基础上,[i][j] = [i-1][j-1] + [i-1][j].

int** yanghuiTriangle(int row, int *resSize){
    int **result = (int **)malloc(sizeof(int*) * row);
    *resSize = row;
    
    for (int i = 0; i < row; i++) {
        result[i] = (int *)malloc(sizeof(int)*(i+1));
        result[i][0] = 1;
        result[i][i] = 1;
        
        for (int j = 1; j < i; j++) {
            result[i][j] = result[i-1][j-1] + result[i-1][j];
        }
    }
    
    return result;
}

斐波那契数列---爬楼梯

在你面前有一个n阶的楼梯,你一步只能上1阶或2阶。
请问计算出你可以采用多少种不同的方式爬完这个楼梯。

这就是一个菲波那切数列的应用。可以使用递归或者动态规划的方法解决。


//递归算法
int fibonacci_1(int n){
    if (n < 1) {
        return 0;
    }
    if (n == 1) {
        return 1;
    }
    if (n == 2) {
        return 2;
    }
    
    return fibonacci_1(n-1) + fibonacci_1(n-2);
}

//动态规划算法
int fibonacci_2(int n){
    if (n < 1) {
        return 0;
    }
    if (n == 1) {
        return 1;
    }
    int *sum = (int *)malloc(sizeof(int) * (n+1));
    sum[0] = 0;
    sum[1] = 1;
    sum[2] = 2;
    for (int i = 3; i <= n; i++) {
        sum[i] = sum[i-1] + sum[i-2];
    }
    return sum[n];
}

字符串编码

题目: 字符串编码LeetCode-中等
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。

例如:
s = "3[a]2[bc]", 返回 "aaabcbc".
s = "3[a2[c]]", 返回 "accaccacc".
s = "2[abc]3[cd]ef", 返回 "abcabccdcdcdef".

思路详见代码注释。

char * decodeString(char *s){
    int len = (int)strlen(s);
    int stackSize = 50;
    char *stack = (char*)malloc(stackSize *sizeof(char));
    
    int top = -1;
    
//    遍历输入字符,将‘]’之前的字符全部入栈。
    for (int i = 0; i < len; ++i) {
        if (s[i] != ']') {
//            如果超容了,就扩容。
            if (top == stackSize - 1) {
                stack = realloc(stack, (stackSize+=50) * sizeof(char));
            }
            stack[++top] = s[i];
        }else{
            int tempSize = 10;
//            这是一个新的栈用来保存需要重复的目标字符串
            char *temp = (char *)malloc(sizeof(char) * tempSize);
            
//            目标字符串暂存栈的栈顶
            int top_temp = -1;
            
//            相当于截取了'['和']'之间的字符(也就是需要重复的目标字符)全部存放在了 temp 这个栈里面。
            while (stack[top] != '[') {
                temp[++top_temp] = stack[top--];
            }
            
            char str_int[11];
//            这是记录了数字字符结束的下标
            int curTop = top;
            top--;
//            把是数字的字符的 下标起始 找到,其实这个时候字符里应该只有数字字符了。
            while (top != -1 && stack[top] >= '0' && stack[top] <= '9') {
                top--;
            }
            
//            把数字提出来
            for (int j = top + 1; j < curTop; ++j){
                str_int[j - (top + 1)] = stack[j];
            }
            //为字符串strOfInt数组加一个字符结束后缀'\0',因为这个数组有 11 位,所以你要标志它的尾。
            str_int[curTop - (top + 1)] = '\0';
            
//            转成数字
            int curNum = atoi(str_int);
            
//            这个地方把原来的 stack 内容重置了,最后的结果就放在这里
            for (int k = 0; k < curNum; ++k) {
                
                int kk = top_temp;
                while (kk != -1) {
                    ++top;
                    stack[top] = temp[kk];
                    kk--;
                }
            }
            free(temp);
            temp = NULL;
        }
    }
    
    //realloc 动态内存调整;
    //void *realloc(void *mem_address, unsigned int newsize);
    //构成字符串stack后, 在stack的空间扩容.
    
    char *res = realloc(stack, (top + 1) * sizeof(char));
    
    res[++top] = '\0';
    
    free(stack);
    return res;
}

字典序去除重复字符

题目:• 去除重复字母(LeetCode-困难)
给你一个仅包含小写字母的字符串,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证返回结果的字典序最小(要求不能打乱其他字符的相对位置)

示例1:
输入:"bcabc"
输出:"abc"

示例2:
输入:"cbacdcbc"
输出:"acdb"


/*
 解题关键:
 字典序: 字符串之间比较和数字比较不一样; 字符串比较是从头往后挨个字符比较,那个字符串大取决于两个字符串中 【第一个对应不相等】 的字符; 例如 任意一个a开头的字符串都大于任意一个b开头的字符串;例如字典中apple 大于 book;
 题目的意思,你去除重复字母后,需要按最小的字典序返回.并且不能打乱其他字母的相对位置;
 例如 bcabc 你应该返回abc, 而不是bca,cab;
 例如 cbacdcbc 应该返回acdb,而不是cbad,bacd,adcb
 例如 zab,应该返回zab,而不是abz;
 
 思路:
 1. s 为空或者长度为 1,需要特殊判断。
 2. record 数组记录字母出现的次数。
 3. stack 堆栈,用以存储结果和虚招次序。
 4. 遍历字符串 s; 当前字符记为 keyChar ,
 5. 判断 0 ~ top 中是否有有 keyChar,
 6. 若已经存在了;record 中该字符的次数若是 > 1 那么需要将次数-1,若次数 = 1,那么说明keyChar 是最后一次了,那么不能出栈了。
 7. 若不存在;那么说明这是这个字符最后一次出现了,那么将所有字典序比 keyChar 大的并且后面还会出现的字符全部出栈,再将 kayChar 入栈,这时的位置就是 keyChar 的正确位置。
        条件为:top > -1 && 字典序比 keyChar 大 && 后面还会出现的字符(次数>1)
 8. top++ keyChar 入栈。
 
 */
char * removeLetters(char *s){
    int len = (int)strlen(s);
    
//    先处理特殊情况
    if (s == NULL || len == 0) {
        return "";
    }
    
    if (len == 1) {
        return s;
    }
    
//    标记数组统计所有字符是否出现了。
    char record[26] = {0};
//    为了完成字典序的限制,需要使用堆栈。多一个位置以便最后放结尾标志
    char *stack = (char*)malloc((len + 1) *sizeof(char));
    
//    memset 将 stack 中 规定长度内的空间都填充为 0。
    memset(stack, 0, (len + 1) * sizeof(char));
    
    int top = -1;
    
//    统计每个字符的频次
    for (int i = 0; i < len; i++) {
         record[s[i] - 'a']++;
    }
    
//    为了完成字典序的限制,需要使用堆栈。
    for (int i = 0; i < len; i++) {
//        标记堆栈中是否已经存在该字符
        int isExist = 0;
        for (int j = 0; j <= top; j++) {
            if (s[i] == stack[j]) {
                isExist = 1;
                break;
            }
        }
//        如果存在的话,那么说明该字符已经入栈了,这一次就不入了,同时 标记次数 要减一
        if (isExist == 1) {
            record[s[i] - 'a']--;
        } else {
//            如果不存在,那么当前字符就要入栈了
//            入栈的时候,如果 栈顶有字符 而且 栈顶字符的字典序比当前字符还大 而且 栈顶字符后面还会出现,
//            那么就让栈顶字符把位置让出来给当前字符,因为这样可以让整个字符的字典序更小。
//            所以将符合上述条件的字符全部出栈,同时这些字符的 标记次数 要减一。
            while (top > -1 && stack[top] > s[i] && record[stack[top] - 'a'] > 1) {
                record[stack[top] - 'a']--;
//                弹栈
                top--;
            }
            
            top++;
//            压栈,这个位置就是当前字符的正确位置
            stack[top] = s[i];
        }
    
    }
//    在结尾补上一个结束标记
    stack[++top] = '\0';
    
    return stack;
}