数据结构与算法(七) -- 算法示例解题

325 阅读8分钟

一、进制转换

题目: 将一个十进制数据转换为八进制

分析: 在我们学习计算机基础知识的时候, 知道怎么将一个十进制转换为二进制. 对十进制数不断的除以二并对二取余数. 得到的余数反过来就是想要的结果.

这里转换为八进制同理.

  1. 首先定义一个栈, 每一次取余就把余数进栈,并且把数据除以二
  2. 当数据为0时, 证明数据已经除成0了
  3. 直接出栈即为我们想要的数据

代码:

void conversion(int N){

    int stackSize = 10;//栈大小
    int *stack = (int *)malloc(stackSize * sizeof(int));//栈空间
    int top = -1;//栈顶
    
    int v = N;
    
    while (v) {
        //栈满了扩充
        if (v == stackSize - 1) {
            stack = realloc(stack, stackSize * 2);
            stackSize = stackSize * 2;
        }
        //取余
        stack[++top] = v % 8;
        v = v / 8;
    }
    
    //打印出栈里的结果
    while (top != -1) {
        printf("%d", stack[top--]);
    }
    printf("\n");
}

二、有效的括号(LeetCode 20)

题目链接

给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。

分析:

  1. 括号都是成对的出现, 每出现一个左括号就一定会有一个右括号
  2. 碰到左括号就入栈, 右括号出栈
  3. 当碰到右括号出栈的时候, 判断栈顶的括号是否匹配当前右括号
bool isValid(char * s){
    //长度
    int len = (int)strlen(s);
    //括号是成对出现, 奇数长度字符串一定是错误的
    if (len % 2 != 0) {
        return false;
    }
    //栈空间只需要小于等于字符串的一半. 因为只需要入栈字符串长度一半的括号,入栈超过这个长度都是错误的
    int *stack = (int *)malloc(len / 2 * sizeof(int));
    int top = -1;

    for (int i = 0; i < len; i++) {
        
        switch (s[i]) {
            case '(':
            case '[':
            case '{':
                //满栈还继续入账肯定是错误的
                if (top == len / 2 - 1) {
                    return false;
                }
                stack[++top] = s[i];
                break;
            case ')':
                if (stack[top--] != '(') {
                    return false;
                }
                break;
            case ']':
                if (stack[top--] != '[') {
                    return false;
                }
                break;
            case '}':
                if (stack[top--] != '{') {
                    return false;
                }
                break;
            default:
                break;
        }
    }
    return true;
}

三、每日温度(LeetCode 739)

题目链接

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

提示:气温 列表长度的范围是[1, 30000]。每个气温的值的均为华氏度,都是在[30, 100]范围内的整数。 解题关键: 实际上就是找当前元素 从[i,TSize] 找到大于该元素时. 数了几次. 首先最后一个元素默认是0,因为它后面已经没有元素了.

3.1、暴力法

分析:

  1. 从左到右按顺序遍历到倒数第二个, 最后一个后面没有元素, 默认为0
  2. 当前温度与前一个温度一样时, 可以取前一个温度的记录值减一, 特殊判断是否为0
  3. 从i+1个开始向后遍历,直到找到比i这个位置数据大的
int *dailyTemperatures(int* T, int TSize, int* returnSize){
    
    int *result = (int *)alloca(sizeof(int) * TSize);
    result[TSize - 1] = 0;
    
    for (int i = 0; i < TSize - 1; i++) {
        //当这个气温与前一个气温相同, 则可以直接读取前一个气温的记录值减去一, 得判断一下是否为0
        if (i > 0 && T[i] == T[i - 1]) {
            result[i] = result[i] == 0 ? 0 : result[i - 1] - 1;
            break;
        }
        for (int j = i + 1; j < TSize; j++) {
            if (T[i] < T[j]) {
                result[i] = j - i;
                break;
            }
            if (j == TSize - 1) {
                result[i] = 0;
            }
        }
    }
    return result;
}

3.2、跳跃对比法

分析:

  1. 从倒数第二个记为i向左遍历, 最后一天的默认为0
  2. 从[i+1, TSize]开始遍历记为j.
  3. 若T[i] < T[j], 则记录j-i
  4. 若result[j] == 0, 则后面不会存在比T[j]更大的数,达到减少遍历的效果

代码:

//每日气温跳跃对比法
int *dailyTemperatures(int* T, int TSize, int* returnSize){
    
    int *result = (int *)alloca(sizeof(int) * TSize);
    result[TSize - 1] = 0;
    
    for (int i = TSize - 2; i >= 0; i--) {
        for (int j = i + 1; j < TSize; j++) {
            if (T[i] < T[j]) {
                result[i] = j - i;
            } else if (result[j] == 0) {
                result[i] = 0;
                break;
            }
        }
    }
    return result;
}

3.2、栈方法

分析:

  1. 初始化一个栈, 用来记录温度的下标index
  2. 从左向右遍历, 第一次直接入栈
  3. 从第二个开始i, 拿到温度, 空栈则直接入栈, 否则与栈顶做对比, 低于则i入栈, 高于则出栈
  4. 出栈后再继续与栈顶对比, 低于则入栈, 高于则出栈 , 重复此步骤
  5. 直到栈为空或者碰到栈顶元素大于当前元素,则停止对比, 直接入栈

代码:

//每日气温栈
int *dailyTemperatures_3(int* T, int TSize, int* returnSize){
    //此处返回的记录长度即等于传过来的天气长度
    *returnSize = TSize;
    //返回的数据
    int *result = (int *)malloc(sizeof(int) * TSize);
    //清空
    for (int i = 0; i < TSize; i++) {
        result[i] = 0;
    }
    //栈
    int *stack = (int *)malloc(sizeof(int) * TSize);
    int top = -1;
    //遍历
    for (int i = 0; i < TSize - 1; i++) {
        
        while (top >= 0 && T[stack[top]] < T[i]) {
            //记录温差数据
            result[stack[top]] = i - stack[top];
            //出栈操作
            top--;
        }
        //入栈
        stack[++top] = i;
    }
    free(stack);
    return result;
}

四、杨辉三角(LeetCode 118)

题目链接

给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。

在杨辉三角中,每个数是它左上方和右上方的数的和。

分析:

  1. 需要控制两层循环
  2. 外层循环i : 由图可知,每一层代表外部循环, 一层中的头尾均为1
  3. 内存循环j : 每一层的内部循环triangle[i][j] = triangle[i - 1][j] + triangle[i - 1][j - 1]

//numRows: 行数
//returnSize: 数组大小, 指有多少行
//returnColumnSizes: 每一行数组大小 [2, 3, 4...]
int** generate(int numRows, int* returnSize, int** returnColumnSizes){
    *returnSize = numRows;
    *returnColumnSizes = (int*)malloc(numRows * sizeof(int));
    int **res = (int **)malloc(sizeof(int *) * numRows);
    
    for (int i = 0; i < numRows; i++) {
        res[i]=(int *)malloc(sizeof(int) * (i + 1));
        res[i][0] = 1;
        res[i][i] = 1;
        (*returnColumnSizes)[i] = i + 1;
        for (int j = 1; j < i; j++) {
            res[i][j] = res[i - 1][j] + res[i - 1][j - 1];
        }
    }
    return res;
}

五、爬楼梯(LeetCode 70)

题目链接

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

5.1、递归求解

分析:

  1. 每一次走无非两种情况, 要么一步要么两步
  2. 拿出每一次走步来模拟要是是1要么是2
  3. 递归这个走步函数

代码:

int ClimbStairs(int n){
    
    if (n<1)  return 0;
    if (n == 1) return 1;
    if (n == 2) return 2;
    
    return ClimbStairs(n-1) + ClimbStairs_1(n-2);
}

5.2、动态规划法

分析:

0 1 2 3 4 5 6
方法 0 1 2 3 5 8 13
  1. 假设n层有f(n)种可能
  2. 假设走1步, 剩下n-1层就有f(n-1)种方法
  3. 假设走2步, 剩下n-2层就有f(n-2)种方法
  4. 已经走了n阶可以转化为上面两种走法的问题的和 f(n) = f(n-1) + f(n-2)

代码:

int ClimbStairs(int n){
    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 394)

题目链接

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: 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".

分析:

  1. 以 ] 为界限分割成一个组合, 每次遍历不是 ] , 则入栈
  2. 遇到 ] 后出栈, 直到碰到 [, 记录下每次出栈的字符
  3. 遇到 [ 后, 继续出栈, 记录数字, 直到遇到不在0-9范围内的字符,或者栈为空
  4. 根据记录的数字循环次数创建记录的字符
  5. 继续向下遍历查找 ]

代码:

char * decodeString(char * s){
    int len = (int)strlen(s);
    int stackSize = len;
    //+1是为存最后的\0
    char *stack = (char *)malloc(sizeof(char) * stackSize + 1);
    int top = -1;
    for (int i = 0; i < len; i++) {
        if (s[i] != ']') {
            //入栈
            if (top == stackSize - 1) {
                stack = (char *)realloc(stack, stackSize * 2 + 1);
                stackSize = stackSize * 2;
            }
            stack[++top] = s[i];
        } else {
            
            //遍历 [ 与 ] 中间的字母
            int zmSize = 50;
            char *tempZM = (char *)malloc(sizeof(char) * zmSize);
            int tempZMTop = -1;
            while (stack[top] != '[') {
                if (tempZMTop == zmSize - 1) {
                    tempZM = (char *)realloc(tempZM, zmSize * 2);
                    zmSize = zmSize * 2;
                }
                tempZM[++tempZMTop] = stack[top--];
            }
            //此时栈顶是 [
            top--;
            
            //遍历获取数字
            char strOfInt[11];
            int tempTop = top;//记录当前栈顶
            //遍历到数字的其实位置
            while ((top > -1) && (stack[top] >= '0') && (stack[top] <= '9')) {
                top--;
            }
            //存储数字字符
            for (int j = top + 1; j <= tempTop; ++j) {
                strOfInt[j - (top + 1)] = stack[j];
            }
            //在数字字符末尾补\0
            strOfInt[tempTop - (top + 1) + 1] = '\0';
            //char 转 int
            int sum = atoi(strOfInt);
            //根据记录数字循环创建字符
            for (int j = 0; j < sum; j++) {
                //此时字符是倒叙的
                for (int k = tempZMTop; k > -1; k--) {
                    if (top == stackSize - 1) {
                        stack = (char *)realloc(stack, stackSize * 2 + 1);
                        stackSize = stackSize * 2;
                    }
                    stack[++top] = tempZM[k];
                }
            }
            free(tempZM);
        }
    }
    //末尾补0
    stack[++top] = '\0';
    return stack;
}