-
采用递归算法解题
-
定义
什么是递归? 若在⼀个函数,过程或数据结构定义的内部⼜直接(或间接)出现定义本身的应⽤; 则称为他们是 递归的. 或者是递归定义.
在实际项目开发中不建议使用递归实现某种功能,应为递归比较浪费空间应为每一次递归的实现中,系统都会重新为变量分配空间而不是覆盖原来的空间
-
使用递归的场景
- 定义就是递归的
例如阶乘,
1 阶乘Fact(n)
(1) 若n = 0,则返回1;
(2) 若n > 1,则返回 n*Fact(n-1);
2 ⼆阶斐波拉契数列Fib(n)
(1) 若n = 1或者n = 2, 则返回1;
(2) 若n > 2,则Fib(n-1) + Fib(n-2);对于类似这种复杂问题,若能够分解成⼏个简单且解法相同或类似的⼦问题,来求解,便称为递归求解. 这种求解方式叫做”分治法“ 使用分治法的三个条件:
- 能将⼀个问题转换变成⼀个⼩问题,⽽新问题和原问题解法相同或类同. 不同的仅仅是处理的对象, 并且 这些处理更⼩且变化有规律的.
- 可以通过上述转换⽽使得问题简化
- 必须有⼀个明确的递归出⼝, 或称为递归边界.
- 数据结构是递归的
其数据结构本身具有递归的特性.
例如,对于链表,其结点LNode的定义由数据域data和指针域next 组成,⽽指针域next是⼀种指向LNode类型的指针,即LNode的定义中⼜⽤到了其⾃身. 所以链表是⼀种递归的数据结构; - 问题的解法是递归的
有⼀类问题,虽然问题本身并没有明显的递归结构,但是采样递归求解⽐迭代求解更简单, 如Hanoi塔问题,⼋ 皇后问题,迷宫问题.
- 定义就是递归的
-
例题
/** 爬楼梯问题 假设你正在爬楼梯。需要n 阶你才能到达楼顶。每次你可以爬1 或2 个台阶。你有多少种不 同的⽅法可以爬到楼顶呢?注意:给定n 是⼀个正整数å 解题思路: 可先将大问题化成小问题(可以先想爬一层的方法数2层的方法数),例如以下层数和方法映射 层数 方法 1 1 2 2 3 3 4 5 5 8 可发现其实爬上n层的楼梯需要的方法数就等于f(n-1)+f(n-2) 就可以现冲1层2层算出3层的方法、2层3层算出4层的方法直到n层 */ int stairs(int n){ if(n == 1 || n == 2){ return n; }else{ return stairs(n-1) + stairs(n - 2); } }
-
-
采用栈思想解决问题
-
使用场景
指的是利⽤栈的特性(先进后出)去解决问题
- 数据是线性的
- 问题中常常涉及到数据的来回⽐较,匹配问题;例如,每⽇温度,括号匹配,字符串解码,去掉重复字⺟等问题.
- 问题中涉及到数据的转置,例如进制问题.链表倒序打印问题等
- 注意并不是说栈思想只是⼀个解决的的参考思想.并不是万能的.它适⽤于以上这样的情况下去解决问题; 利⽤栈思想解决问题时,⾸先需要透彻的解析问题之后,找到问题解决的规律.才能使⽤它解决;
-
例题:
- 括号匹配检验:
/* 假设表达式中允许包含两种括号:圆括号与⽅括号,其嵌套 顺序随意,即([]()) 或者[([][])]都是正 确的.⽽这[(]或者(()])或者([()) 都是不正确的格式. 检验括号是否匹配的⽅法可⽤"期待的急迫程度"这个概念 来描述.例如,考虑以下括号的判断: [ ( [ ] [ ] ) ] 解题思路: 用利用栈思想,第一个符号先入栈,第二个符号入栈的时候先判断是否和栈顶元素的符号正好匹配, 如果匹配则出栈,否则继续入栈,最后只需判断栈是否为空即可(为空则说明匹配成功) */ Status matchingBrackets(char *data){ Stack s; if(!creatStack(&s)) return ERROR; if(strlen(data) <= 0) return ERROR; pushData(&s, data[0]); for(int i = 1; i < strlen(data); i ++){ if(s.length == 0){ pushData(&s, data[i]); }else{ switch (getTop(s)) { case '(': if(data[i] == ')'){ popData(&s); }else{ pushData(&s, data[i]); } break; case '[': if(data[i] == ']'){ popData(&s); }else{ pushData(&s, data[i]); } break; default: break; } } } if(s.length == 0){ printf("匹配成功\n"); return TRUE; }else{ printf("匹配不成功\n"); return FALSE; } }- 每日气温问题(这里可以用栈思想解决问题,单并不是最优解,这里还给出了暴力法解决,跳跃法解决这两个方法不是利用的栈思想)
/** 题⽬: 根据每⽇⽓温列表,请重新⽣成⼀个列表,对应位 置的输⼊是你需要再等待多久温度才会 升⾼超过该⽇的天数。如果之后都不会升⾼,请在该位置0 来代替。例如,给定⼀个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。 */ /** (1)暴力法解决 解题思路: 两层遍历,第一层冲0开始遍历整个数组,第二层冲i+1开始遍历只要遇到比自己大的元素(或者大于 等于数组的长度)第二层循环截止,如果找到比自己大的元素则索引相减得出天数 */ Status temperature(int *t, int len, int **result){ *result = (int *)calloc(len, sizeof(int)); if(!result) return ERROR; for(int i = 0; i < len; i ++){ for (int j = i+1; j < len; j++) { if(t[i] < t[j]){ (*result)[i] = j-i; break; } } } return OK; } //(2)跳跃法解决 /** 思路提示:从右到左的计算,那么如果遇到计算过的位置则不需要重复计算. 例如,当计算75时, 遍历到71时,则直接使⽤计算好的71上对应的值2. 那么我就直接 跳2步再进⾏⽐较. 利⽤已有的结果,减少遍历次数. */ Status temperature2(int *t, int len, int **result){ *result = (int *)calloc(len, sizeof(int)); if(!result) return ERROR; for(int i = len-2; i >= 0; i --){ int j = i+1; while (1) { if(t[i] < t[j]){ (*result)[i] = j - i; break; } if((*result)[j] == 0) break; j += (*result)[j]; } } return OK; } //(3)栈思想解决问题 /** 解题思路: 1. 栈中存储的是元素的索引值index; 2.将当前元素和栈顶元素⽐较; 如果栈为空,那么直接将当前元素索引index 存储到栈中; 如果栈顶元素>当前元素,则将当前元素索引index 存储到栈中; 如果栈顶元素<当前元素,则将当前元素索引index-栈顶元素index,计算完毕则将当前栈顶元素移除,将当前元素索引index 存储到栈中 */ Status temperature3(int *t, int len, int **result){ *result = (int *)calloc(len, sizeof(int)); Stack s; if(!creatStack(&s)) return ERROR; if(!result) return ERROR; pushData(&s, 0); for(int i = 1; i < len; i ++){ while (1) { if(s.length > 0 && t[getTop(s)] < t[i]){ (*result)[getTop(s)] = i - getTop(s); popData(&s); }else{ pushData(&s, i); break; } } } return OK; }- 字符串编码问题
/** 编码规则为: k[encoded_string],表示其中⽅括号内部的encoded_string 正好重复k 次。 注意k 保证为正整数。你可以认为输⼊字符串总是有效的;输⼊ 字符串中没有额外的空格, 且输⼊的⽅括号总是符合格式要求的。此外,你可以认为 原始数据不包含数字,所有的数字只 表示重复的次数k ,例如不会出现像3a 或2[4] 的输⼊。 例如: s = "12[a]2[bc]", 返回"aaabcbc". s = "3[a2[c]]", 返回"accaccacc". s = "2[abc]3[cd]ef", 返回"abcabccdcdcdef". 解题思路: 栈思想解决问题,先入栈元素如果元素不是 】则一直入栈当遇见】 停止入栈,现在开始出栈(这里的出栈只是将栈顶的索引 建议并不是将对应的内容也置空)出栈的同时将对应的 元素保存在一个新的栈里面 一直到 【 此时出栈的区域此时就找到了【】里面的字母同事栈顶元 素是【,再出栈一次 将现在的栈顶索引保存下来,开始判断如果栈顶元素不是 数字则出栈,最后找到的索引区域就是数字的区域 然后拿到数字循环将【】中的字母入栈,最后得出结果 */ char * decodeString(char *t){ int len = (int)strlen(t); int stackSize = 50; char * stack = (char *)malloc(sizeof(char) * stackSize); int top = -1; char strInt[11]; for(int i = 0; i < len; i ++){ if(t[i] != ']'){ if(top == stackSize -1){ stack = realloc(&stack, (stackSize += 50)*sizeof(char)); } stack[top + 1]=t[i]; top ++; }else{ int tempSize = 10; char* temp = (char*)malloc(tempSize * sizeof(char)); int tempOfTop = -1; int curTop = top; while (1) { if(strlen(stack) == 0) break; if(stack[top] == '['){ break; }else{ if(tempOfTop == tempSize + 1){ temp = realloc(&temp, (tempSize += 10) * sizeof(char)); } temp[tempOfTop + 1] = stack[top]; tempOfTop ++; top --; } } top = top - 1; curTop = top; while (1) { if(top < 0) break; if(stack[top] >= '0' && stack[top] <= '9'){ top --; }else{ break; } } for(int i = top+1; i <= curTop; i ++){ strInt[i - (top+1)] = stack[i]; } strInt[curTop - top] = '\0'; if(strlen(strInt) == 0){ strInt[0] = '1'; } int charNum = atoi(strInt); for(int i = 0; i < charNum; i ++){ for(int t = 0; t <= tempOfTop; t ++){ if(top == stackSize + 1){ stack = realloc(&stack, (stackSize += 50)*sizeof(char)); } stack[top + 1] = temp[tempOfTop-t]; top ++; } } free(temp); temp = NULL; } } char * ans = realloc(stack, (top + 1)*sizeof(char)); ans[top+1] = '\0'; return ans; }- 去重字母问题
/** 去除重复字⺟问题 给你⼀个仅包含⼩写字⺟的字符串,请你去除字符串中重 复的字⺟,使得每个字⺟只出现⼀次。需保 证返回结果的字典序最⼩(要求不能打乱其他字符的相对 位置) 解题思路: 首先一层遍历计算出所有字母出现的次数(这里可以创建 一个有26个存储单元的数组,对应的字母次数就存储在对 应的位置 例如a 0 b 1 c 2 d 3)创建一个新栈,判断栈为空入栈,不为空比较两个字母 的ASCII码,当前元素小于栈顶元素,找到栈顶元素的重复 次数 如果大于0出栈当前元素入栈,否则判断栈内是否有该元素 如果没有直接入栈 */ char *removeDuplicate(char * str){ if(strlen(str) <= 1) return str; int indexNum[26] = {0}; int i; for(i = 0; i < strlen(str); i ++){ indexNum[str[i] - 'a'] ++; } char *result = (char *)malloc(sizeof(char) * strlen(str) + 1); int top = -1; for(i = 0; i < strlen(str); i ++){ if(top == -1){ //当前为空栈 result[++top] = str[i]; }else{ if(str[i] > result[top]){ //当前元素大于栈顶元素 result[++top] = str[i]; }else{ //当前元素小于栈顶元素 while (1) { int flag = 0; for(int j = 0; j <= top; j ++){ if(result[j] == str[i]){ flag = 1; break; } } if(indexNum[result[top] - 'a'] > 1){ if(flag){ //栈内存在该元素直接对应计数减一 indexNum[result[top] - 'a'] --; }else{ //后面有重复元素且目前栈内无该元素 则该元素计数减一 indexNum[result[top] - 'a'] --; top --; } }else{ //后面没有重复元素并且栈内没有改元素直接入栈 if(!flag){ result[++top] = str[i]; } break; } if(top == -1){ result[++top] = str[i]; break; } } } } } result[++top] = '\0'; return result; }
-
-
BF算法
-
定义
BF算法即暴风算法,是普通的模式匹配算法。BF算法的思想:将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果
-
例题
/** 字符串匹配问题 题目: 有一个主串S = {a, b, c, a, c, a, b, d, c}, 模式串T = { a, b, d } ; 请找到模 式串在主串中第一次出现的位置; 提示: 不需要考虑字符串大小写问题, 字符均为小写字母; 解题思路: BF算法; BF算法即暴风算法,是普通的模式匹配算法。BF算法的思 想:将目标串S的第一个字符与模式串T的第一个字符进行 匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第 一个字符,依次比较下去,直到得出最后的匹配结果 步骤: 两层遍历第一层遍历主串遍历范围[0,主串长度-模式串长度] 【i】 第二层遍历遍历模式串遍历范围[0,模式串长度)i 【j】 在第二程遍历中对比主串中的第i+j个字符和模式串中的第 j个字符 如果全部相等则返回i */ //BF算法 int matchStr(char * str,char *model){ if(strlen(model) > strlen(str)) return -1; int flag = 0; for(int i = 0; i <= strlen(str) - strlen(model); i ++){ flag = 0; for(int j = 0; j < strlen(model); j ++){ if(str[i+j] != model[j]){ flag = 1; break; } } if(!flag){ return i; } } return -1; } -
-
RK算法
-
定义
基本思想: 如果两个字符串hash后的值不相同,则它们肯定不相同;如果它们hash后的值相同,它们不一定相同。 RK算法的基本思想就是:将模式串P的hash值跟主串S中的每一个长度为|P|的子串的hash值比较。如果不同,则它们肯定不相等;如果相同,则再诸位比较之。
-
例题
/** 字符串匹配问题(RK算法解题) 解题思路: RK算法: 精髓在于:1、将字符串转成数字进行比较,省去遍历模式串逐个对比字符的步骤 2、边遍历边比较,计算出主串中的一段字符串的数值之后就和模式串 的数值进行比较,如果相等直接返回。这样省去计算主串后面的数值 3、计算数值时可借用上一个数值的后面(模式串长度-1)位进行计算 例如 234 345 456 345 = (234 -200)*10+5 456=(345-300)*10+6 4、最后防止哈希冲突可以遍历字符串在进行字符比较确认 */ int matchStr2(char * str,char *model){ //1.计算模式串的哈希值 unsigned int modelHash = 0; int i = 0; for(i = 0;i < strlen(model); i ++){ modelHash += (model[i] - 'a' + 1) * pow(26, strlen(model)-i-1); } unsigned int strHash = 0; for(i = 0;i < strlen(str) - strlen(model) + 1; i++){ if(i == 0){ //第一次需要算出具体哈希值 for(int j = i;j < strlen(model); j ++){ strHash += (str[j] - 'a' + 1) * pow(26, strlen(model)-i-j-1); } }else{ //可借用上一个哈希值算出现在的哈希值 //例如 234 345 456 345 = (234 -200)*10+5 456=(345-300)*10+6 strHash = (strHash - (str[i-1] - 'a' + 1)*pow(26, strlen(model) - 1))*26 +(str[i+strlen(model)-1] - 'a' + 1); } if(strHash == modelHash){ //防止哈希冲突 int flag = 0; for(int n = i;n < strlen(model); n ++){ if(str[n] != model[n-i]){ flag = 1; } } if(!flag){ return i+1; } } } return -1; } -