一:拿起武器:数学归纳法
Step1:验证p(1)成立
Step2:证明如果p(k)成立,那么p(k+1)也成立
Step3:联合Step1 与 Step2,证明由 p(1)--->p(n)成立
例如:证明 1 + 3 + 5 + 7 +......+ (2n - 1) = n^2
第一步:证明p(1) : p(1) = 1 = 1^2
第二步:假设p(k)正确------>证明p(k+1)
p(n-1) = (n-1)^2 p(n) = p(n-1)+(2n-1) = (n-1)^2+(2n-1) = n^2
第三步:证毕p(n)正确
二:递归函数设计的三个重要部分
1. 重要:给 递归函数 一个明确的语义信息
2. 实现边界条件的逻辑程序
3. 假设递归函数调用返回结果是正确的,实现本层函数逻辑
三:学以致用:递归求阶乘
int f(int n) { // f(n)代表n的阶乘的结果 if (n == 1) { // 边界条件 return 1; } return f(n - 1) * n; // 利用f(n - 1)的值,计算f(n)的值 (n - 1)! * n }
四:习题
1. ✌路飞吃桃
/********************************************************************* 路飞买了一堆桃子不知道个数,第一天吃了一半的桃子,还不过瘾,又多吃了一个。 以后他每天吃剩下的桃子的一半还多一个,到n天只剩下一个桃子了。路飞想知道一 开始买了多少桃子。 输入: 输入一个整数n(2 <= n <= 30) 输出: 输出买的桃子的数量 *********************************************************************/ #include <stdio.h> // 1) int main(int argc, char *argv[]) { int n, sum = 1; scanf("%d", &n); for (int i = 1; i < n; i++) { sum = (sum + 1) * 2; } printf("%d\n", sum); return 0; } // 2) 递归 int fun(int day) { // 能吃day天的桃子的数量有多少个 if (day == 1) { // 边界条件 return 1; } // 假设递归函数调用返回结果是正确的,实现本层函数逻辑 return (fun(day - 1) + 1) * 2; }
2. ✌弹簧板
/**************************************************************************** 有一个小球掉落在一串连续的弹簧板上,小球落到某一个弹簧板后,会被弹到某一个地点, 直到小球被弹到弹簧板以外的地方。 假设有n个连续的弹簧板,每个弹簧板占一个单位距离,a[i]代表第i个弹簧板会把小球向 前弹a[i]个距离。比如位置1的弹簧能让小球前进2个距离到达位置3,如果小球落到某个弹簧板 后,经过一系列弹跳会被弹出弹簧板,那么小球就能从这个弹簧板弹出来。 现在小球掉到了1号弹簧板上面,那么这个小球会被弹起多少次,才会弹出弹簧板。1号弹簧 板也算一次。 输入: 第一行输入一个n代表一共有n(1 <= n <= 100000)个弹簧板。 第二行输入n个数字,中间用空格分开。第1个数字a[i](0 < a[i] <= 30)代表第i个弹 簧板可以让小球移动的距离。 输出: 输出一个整数,表示小球被弹起的次数。 样例输入1: 5 2 2 3 1 2 样例输出1: 2 样例输入2: 5 1 2 3 1 2 样例输出2: 4 ****************************************************************************/ #include <stdio.h> // 递归 // f(i) 小球从i位置开始被弹出弹簧板的次数 int f(int *a, int i, int n) { // 给 递归函数 一个明确的语义信息 if (i >= n) { // 实现边界条件的逻辑程序 return 0; } // 假设递归函数调用返回结果是正确的,实现本层函数逻辑 return f(a, i + a[i], n) + 1; } // 循环 int f(int *a, int i, int n) { if (i >= n) { return 0; } int num = 0; while (i < n) { i += a[i]; num++; } return num; } int main(int argc, char *argv[]) { int n; scanf("%d", &n); int a[n]; for (int i = 0; i < n; i++) { scanf("%d", &a[i]); } int time = f(a, 0, n); printf("%d\n", time); return 0; }
3. ✌递归实现指数型枚举
1. 如何按照字典序去输出:从小到大枚举每个位置的数字
2. 如何保证每个位置的数字都大于它前面的数字:在枚举的过程中,对于每个位置的枚举的过程,都传入另外一个数字,这个数字标记了当前的位置最小可以选取的那个数字是几
3. 如何输出结果
/*************************************************************** 从1——n这n个整数中随机选取任意多个,每种方案里的数从小到大排列, 按字典序输出所有可能的选择方案。 输入: 输入一个整数n(1 <= n <= 10) 输出: 每行一组方案,每组方案中两个数之间用空格分隔。 注意每行最后一个数后没有空格。 样例输入: 3 样例输出: 1 1 2 1 2 3 1 3 2 2 3 3 ****************************************************************/ #include <stdio.h> int a[10]; void print_one_result(int n) { for (int i = 0; i <= n; i++) { if (i) { putchar(' '); } printf("%d", a[i]); } printf("\n"); } // i:当前枚举的是第i位的值 j:当前这个位置最小可以选取的数字 n:最大可以选取的数字 void f(int i, int j, int n) { // 给 递归函数 一个明确的语义信息 if (j > n) { // 实现边界条件的逻辑程序(可删除) return; } // 假设递归函数调用返回结果是正确的,实现本层函数逻辑 for (int k = j; k <= n; k++) { a[i] = k; print_one_result(i); f(i + 1, k + 1, n); } } int main(int argc, char *argv[]) { int n; scanf("%d", &n); f(0, 1, n); return 0; }
/*************************************************************** 从1——n这n个整数中随机选取任意多个,每种方案里的数从小到大排列, 按字典序输出所有可能的选择方案。 输入: 输入一个整数n(1 <= n <= 10) 输出: 每行一组方案,每组方案中两个数之间用空格分隔。 注意每行最后一个数后没有空格。 样例输入: 3 样例输出: 1 1 2 1 2 3 1 3 2 2 3 3 ****************************************************************/ #include <stdio.h> int path[10]; int pathSize; void print_one_result(int n) { for (int i = 0; i < n; i++) { if (i) { putchar(' '); } printf("%d", path[i]); } printf("\n"); } void f(int startindex, int n) { if (startindex > n) { return; } for (int i = startindex; i <= n; i++) { path[pathSize++] = i; print_one_result(pathSize); f(i + 1, n); // 回溯 pathSize--; } } int main(int argc, char *argv[]) { int n; scanf("%d", &n); pathSize = 0; f(1, n); return 0; }
4. ✌递归实现组合型枚举
/********************************************************* 从1——n这n个整数中随机选取m个,每种方案里的数从小到大排列, 按字典序输出所有可能的选择方案。 输入: 输入两个整数n,m(1 <= m <= n <= 10) 输出: 每行一组方案,每组方案中两个数之间用空格分隔。 注意每行最后一个数后没有空格。 样例输入: 3 2 样例输出: 1 2 1 3 2 3 *********************************************************/ #include <stdio.h> int a[10]; void print_one_result(int n) { for (int i = 0; i < n; i++) { if (i) { putchar(' '); } printf("%d", a[i]); } printf("\n"); } // i:当前枚举的是第i位的值 j:当前这个位置可以选取的最小数字 void f(int i, int j, int n, int m) { if (i == m) { //边界 print_one_result(i); return; } // 剪枝优化:m-i表示枚举到m还需要多少个,n-k+1表示后面还剩下的数字数量 for (int k = j; m - i <= n - k + 1; k++) { a[i] = k; f(i + 1, k + 1, n, m); } } int main(int argc, char* argv[]) { int n, m; // n:最大可以选取的数字 m:随机选取多少个 scanf("%d%d", &n, &m); f(0, 1, n, m); return 0; }
5. ✌递归实现排列型枚举
/****************************************************************
从1——n这n个整数排成一排并打乱次序,按字典序输出所有可能的选择方案。
输入:
输入一个整数n(1 <= n <= 8)
输出:
每行一组方案,每组方案中两个数之间用空格分隔。
注意每行最后一个数后没有空格。
样例输入:
3
样例输出:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
*****************************************************************/
#include <stdio.h>
int a[10], vis[10] = {0};
void print_one_result(int n) {
for (int i = 0; i < n; i++) {
if (i) {
putchar(' ');
}
printf("%d", a[i]);
}
printf("\n");
}
void f(int i, int n) { // i:当前枚举的是第i位的值 n:最大可以选取的数字
if (i == n) { // 边界
print_one_result(i);
return;
}
for (int k = 1; k <= n; k++) {
if (vis[k]) { // 标记位思想
continue;
}
a[i] = k;
vis[k] = 1;
f(i + 1, n);
// 回溯
vis[k] = 0;
}
}
int main(int argc, char* argv[]) {
int n;
scanf("%d", &n);
f(0, n);
return 0;
}
6. ✌递归乘法
代码实现:
int func(int A, int B) { if (B == 1) { return A; } return func(A, B - 1) + A; } int multiply(int A, int B) { return func(A, B); }
7. 反转链表
代码实现:
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ struct ListNode* reverseList(struct ListNode *head) { if (head == NULL || head->next == NULL) { return head; } // 记录当前节点(头结点)的下一个节点,就是反转以后的尾节点 struct ListNode *tail = head->next; struct ListNode *new_head = reverseList(head->next); head->next = tail->next; tail->next = head; return new_head; }
8. 反转链表 ||
代码实现:
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ struct ListNode* reverseBetween(struct ListNode *head, int left, int right) { if (left == 1 && right == 1) { return head; } if (left != 1) { head->next = reverseBetween(head->next, left - 1, right - 1); } else { struct ListNode *tail = head->next; struct ListNode *new_head = reverseBetween(head->next, left, right - 1); head->next = tail->next; tail->next = head; head = new_head; } return head; }
9. ✌两数相加
代码实现:
int c = 0; struct ListNode* addTwoNumbers(struct ListNode *l1, struct ListNode *l2) { if (l1 == NULL && l2 == NULL && c == 0) { // 边界条件 return NULL; } l1 = l1 != NULL ? (c += l1->val, l1->next) : l1; l2 = l2 != NULL ? (c += l2->val, l2->next) : l2; struct ListNode *p = (struct ListNode*)malloc(sizeof(struct ListNode)); p->val = c % 10; c /= 10; p->next = addTwoNumbers(l1, l2); return p; }
10. 从链表中移除节点
代码实现:
- 该节点为空,那么递归函数返回空指针
- 该节点不为空,那么先对它的右侧节点进行移除操作,得到一个新的子链表,如果子链表的表头节点值大于该节点的值,那么移除该节点,否则将该节点作为子链表的表头节点,最后返回该子链表
struct ListNode *removeNodes(struct ListNode *head) { if (head == NULL) { return NULL; } head->next = removeNodes(head->next); if (head->next != NULL && head->val < head->next->val) { return head->next; } else { return head; } }
11. ✌合并两个有序链表
代码实现:
struct ListNode* mergeTwoLists(struct ListNode *list1, struct ListNode *list2) { if (list1 == NULL) { return list2; } else if (list2 == NULL) { return list1; } else if (list1->val < list2->val) { list1->next = mergeTwoLists(list1->next, list2); return list1; } else { list2->next = mergeTwoLists(list1, list2->next); return list2; } }
12. 二叉树的最近公共祖先
代码实现:
/** * Definition for a binary tree node. * struct TreeNode { * int val; * struct TreeNode *left; * struct TreeNode *right; * }; */ struct TreeNode* lowestCommonAncestor(struct TreeNode *root, struct TreeNode *p, struct TreeNode *q) { if (p == root || q == root || root == NULL) { return root; } struct TreeNode *l = lowestCommonAncestor(root->left, p, q); struct TreeNode *r = lowestCommonAncestor(root->right, p, q); if (l != NULL && r != NULL) { return root; } if (l == NULL && r != NULL) { return r; } else if (l != NULL && r == NULL) { return l; } else { return NULL; } }