(OK)递归函数设计技巧

一:拿起武器:数学归纳法

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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值