算法入门-递归1

第四部分:递归

前情:什么情况判断应该使用递归?

在编程中,使用递归的情况通常包括以下几种场景:

  1. 问题可以被分解为相似的子问题

    • 递归特别适合那些可以将问题分解为相似的小问题的情形。例如,计算斐波那契数列、合并排序、快速排序等。

  2. 树形结构的问题

    • 处理树形结构的算法(如遍历二叉树、查找、插入以及删除操作)通常使用递归,因为树本身具有递归的性质。

  3. 简化代码

    • 有些问题通过递归实现的代码比通过迭代实现的代码更可读,更容易理解。例如,遍历图或链表结构时,递归的实现可能更直观。

  4. 自然定义的算法

    • 一些算法本质上是递归的,例如动态规划中的某些解法,计算阶乘或最小公倍数等。

  5. 需回溯的场景

    • 回溯算法通常使用递归,针对组合、排列、子集等问题,递归能够更自然地表示选择和撤销的过程。

24.两两交换链表中的节点(中等)

题目:给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例 1:

输入:head = [1,2,3,4]
输出:[2,1,4,3]

第一种思路:

  • 如果链表中没有节点,或者链表中只有一个节点,此时无法进行交换。

  • 如果链表中至少有两个节点,则在两两交换链表中的节点之后,原始链表的头节点变成新的链表的第二个节点,原始链表的第二个节点变成新的链表的头节点。链表中的其余节点的两两交换可以递归地实现。在对链表中的其余节点递归地两两交换之后,更新节点之间的指针关系,即可完成整个链表的两两交换。

形象的思考过程:

  • 假设链表是:1 -> 2 -> 3 -> 4

  • 第一次调用:

    • 当前 head 是 1,node 是 2。之后会递归处理 3 -> 4 的交换。

  • 递归下去:

    • 对于子链表 3 -> 4,将会变成 4 -> 3。

  • 连接回来:

    • 完成 1 和 2 的交换后,原链表将变成 2 -> 1 -> 4 -> 3。

  • 这样,最终链表的结果就是 2 -> 1 -> 4 -> 3,递归一次又一次返回,构建出最终的结果。

思路分解:

  1. 基本情况(递归终止条件)

    • 首先检查链表是否为空或只有一个节点。如果是,直接返回头节点,因为没有节点需要交换。这确保了递归的终止条件。

    if (head == null || head.next == null) {  
        return head; // 递归基础情况:无节点或只有一个节点  
    }  

  2. 定义变量

    • 找到当前两节点中的第二个节点,即 node = head.next。在这里,node 是新链表的头节点。

  3. 递归调用

    • 对于剩余的链表,递归调用 swapPairs(node.next) 来处理剩下的节点。这里的 node.next 代表的是当前节点 node 的下一个节点,这样可以确保每次递归中处理的都是未交换的节点。

    head.next = swapPairs(node.next); // 递归处理后续的节点对  

  4. 更新指针关系

    • node(当前的第二个节点)指向 head(当前的第一个节点)。这样完成了当前两个节点的交换。

    node.next = head; // 更新指针,完成交换  

  5. 返回新头节点

    • 最后,返回新的头节点,即 node。这样,在向上返回的过程中,会逐级构建交换后的链表。

                

 return node; // 返回新的链表头  
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {  
    public ListNode swapPairs(ListNode head) {  
        // 首先检查头节点是否为 null  
        if (head == null || head.next == null) {  
            return head; // 如果为空或只有一个节点,直接返回  
        }  

        // 存储原始链表头节点的下一个节点,即新链表的头节点  
        ListNode node = head.next;  
        // 重新链接  
        head.next = swapPairs(node.next); // 递归调用以处理下一个节点对  
        node.next = head; // 将当前节点与其前一个节点连接  
        return node; // 返回新的头节点  
    }  
}

390.消除游戏(中等)

题目:列表 arr 由在范围 [1, n] 中的所有整数组成,并按严格递增排序。请你对 arr 应用下述算法:

  • 从左到右,删除第一个数字,然后每隔一个数字删除一个,直到到达列表末尾。

  • 重复上面的步骤,但这次是从右到左。也就是,删除最右侧的数字,然后剩下的数字每隔一个删除一个。

  • 不断重复这两步,从左到右和从右到左交替进行,直到只剩下一个数字。

给你整数 n ,返回 arr 最后剩下的数字。

示例 1:

输入:n = 9
输出:6
解释:
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
arr = [2, 4, 6, 8]
arr = [2, 6]
arr = [6]

第一种思路:

这部分主要在练习递归类型的题目,所有就直接往递归方面想了,确实在敲的过程中慢慢更熟练其逻辑思路了,但是写完之后点击提交显示“最后执行的输入n =10000003373 / 3377 个通过的测试用例”,不是几个意思啊,我当然知道数值过大会超时,但是归入递归里的题目你整这么大的数做什么,建议递归但是不能用递归是吧!!!

虽然如此,但是案例基本都通过了,说明我的思路应该没有问题:

  1. 初始化数据结构

    • 使用一个动态数组(List)来存储从 1n 的数字。

  2. 递归删除元素

    • 定义一个递归函数 delete,它接受当前数组和一个控制删除方向的变量 flog

    • flog 变量用于指示是从左到右 (flog = 1) 还是从右到左 (flog = -1) 进行删除。

  3. 基准条件

    • 如果数组只剩下一个元素,直接返回这个元素,这作为递归的终止条件。

  4. 根据方向删除元素

    • flog1 时,遍历数组并每次删除一个元素(从头到尾)。

    • flog-1 时,倒序遍历数组并逐步删除元素(从尾到头)。

  5. 切换删除方向

    • 在每次递归调用结束后,切换 flog 的值,以改变后续的删除方向。

!!!!这里需要注意第四步的根据方向删除元素,从头到尾 i++ 是因为ArrayList集合在删除元素后下标会动态的变化,所以一次操作 i 只用移动一个单位,但是从尾到头时,操作删除后ArrayList集合各元素的下标没有发生变化,所有 i 此时就要一次移动两个单位了。
class Solution {  
    // 主函数,返回最后剩下的数字  
    public int lastRemaining(int n) {  
        // 创建一个包含从 1 到 n 的数字的列表  
        List<Integer> arr = new ArrayList<>();  
        for (int i = 0; i < n; i++)  
            arr.add(i + 1); // 将数字添加到列表中  

        // 标记变量,控制删除的方向:1表示从左到右,-1表示从右到左  
        int flog = 1;  
        // 进行删除操作,并返回最后剩下的数字  
        int re = delete(arr, flog);  
        return re; // 返回结果  
    }  

    // 递归删除函数  
    public int delete(List<Integer> arr, int flog) {  
        // 如果列表中只剩下一个数字,返回该数字  
        if (arr.size() == 1) return arr.get(0);  

        // 如果 flog 为 1,执行从左到右的删除  
        if (flog == 1) {  
            // 遍历列表,删除每个第一个数字  
            for (int i = 0; i < arr.size(); i++)  
                arr.remove(i); // 删除每隔一个的数字  
        } else {  
            // 如果 flog 为 -1,执行从右到左的删除  
            for (int i = arr.size() - 1; i >= 0; i -= 2)  
                arr.remove(i); // 删除每隔一个的数字  
        }  

        // 切换删除方向  
        flog = flog * -1;   
        // 递归调用 delete 函数  
        return delete(arr, flog);  
    }  
}

官方的解答只有一种用数学分析的,这里就不介绍了。

  • 26
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值