【算法通关】递归:汉诺塔、合并链表、反转链表、两两交换、快速幂全解

文章目录

    • [1. 汉诺塔问题](#1. 汉诺塔问题)
    • [2. 合并两个有序链表](#2. 合并两个有序链表)
    • [3. 反转链表](#3. 反转链表)
    • [4. 两两交换链表中的节点](#4. 两两交换链表中的节点)
    • [5. 快速幂](#5. 快速幂)

1. 汉诺塔问题

题目链接:汉诺塔问题

题目描述:

题解思路:递归

将 n 个盘子从 A 柱移到 C 柱(以 A 为起点、C 为目标、B 为辅助)拆分为三个步骤,其中包含两个结构完全相同的子问题:

  1. 子问题一:将上面 n-1 个盘子从 A 柱移到 B 柱(以 A 为起点、B 为目标、C 为辅助)
  2. 独立操作:将最底层唯一的最大盘子从 A 柱直接移到 C 柱
  3. 子问题二:将n-1 个盘子从 B 柱移到 C 柱(以 B 为起点、C 为目标、A 为辅助)

两个子问题与原问题的解题逻辑完全一致,仅盘子数量、柱子角色不同,符合递归的拆分要求。

每一层递归只处理固定数量的盘子移动,执行完整的三步流程:

  1. 先递归调用自身,完成上层 n-1 个盘子的转移,为最大盘子腾出移动空间
  2. 执行唯一的直接移动操作,将当前最底层的最大盘子移到目标柱
  3. 再次递归调用自身,将之前转移走的 n-1 个盘子,从辅助柱移到目标柱,叠在最大盘子之上

递归过程自顶向下拆分问题,自底向上逐步完成移动,最终合并为完整解。当盘子数量 n = 1 时,无需再拆分,直接将这一个盘子从起始柱移到目标柱即可。

示例代码:

java 复制代码
class Solution {
    public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
        movePlant(A,B,C,A.size());
    }

    public void movePlant(List<Integer> start, List<Integer> temp, List<Integer> target, int size){
        if(size == 1){
            target.add(start.remove(start.size() - 1));
            return;
        }

        // a 借助 c 将n-1个盘子放到b
        movePlant(start,target,temp,size-1);
        // a 剩下的一个盘子 放到c
        target.add(start.remove(start.size()-1));
        // b 借助a 将 n-1 个盘子放到c
        movePlant(temp,start,target,size-1);
    }
}

2. 合并两个有序链表

题目链接:21. 合并两个有序链表

题目描述:

题解思路:递归

  1. 递归函数:接收两个有序链表的头节点,将它们合并为一个新的有序链表,并返回合并后链表的头节点。
  2. 函数体逻辑 :比较两个链表当前头节点的值,选择值较小的节点作为合并后链表的头节点,然后将该节点的 next 指针指向「剩余两个链表」递归合并后的结果。
  3. 递归出口:当其中一个链表为空时,直接返回另一个非空链表(空链表也会被正确处理)。

示例代码:

java 复制代码
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if(list1 == null) return list2;
        if(list2 == null) return list1;

        if(list1.val <= list2.val) {
            list1.next = mergeTwoLists(list1.next,list2);
            return list1;
        }
        else {
            list2.next = mergeTwoLists(list2.next,list1);
            return list2;
        }

    }
}

3. 反转链表

题目链接:206. 反转链表

题目描述:

题解思路:递归

  1. 递归函数:接收一个链表的头指针,完成链表逆序操作,并返回逆序后链表的头节点。
  2. 函数体逻辑:先递归处理「当前节点之后的子链表」并完成逆序,再将当前节点添加到已逆序的子链表末尾。
  3. 递归出口:当当前节点为空,或当前链表仅含一个节点时,无需逆序,直接返回当前节点。

示例代码:

java 复制代码
class Solution {
    public ListNode reverseList(ListNode head) {
        if(head == null || head.next == null) return head;

        ListNode newNode = reverseList(head.next);
        head.next.next = head;
        head.next = null;

        return newNode;
    }
}

4. 两两交换链表中的节点

题目链接:24. 两两交换链表中的节点

题目描述:

题解思路:递归

  1. 递归函数:接收一个链表,完成链表节点的两两交换,并返回交换后链表的头节点。
  2. 函数体逻辑:先递归处理「第二个节点之后的子链表」,再将当前的两个节点进行交换,最后将交换后的当前节点组与已处理好的后续子链表连接。
  3. 递归出口:当当前节点为空,或当前链表仅含一个节点时,无需交换,直接返回当前节点。

示例代码:

java 复制代码
class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head == null || head.next == null) return head;

        ListNode newNode = swapPairs(head.next.next);
        ListNode temp = head.next;
        temp.next = head;
        head.next = newNode;
        
        return temp;
    }
}

5. 快速幂

题目链接:50. Pow(x,n)

题目描述:

题解思路:快速幂

这道题递归的核心是:不直接算 xⁿ,而是把它拆成规模更小、解法完全相同的子问题,递归解决后再合并答案。

就是求 x 的 n 次方 = 求 x 的 n/2 次方 × 自己(再根据奇偶决定是否多乘一个 x,n是奇数则多乘一个 x)

负数处理:

x 的负 n 次方 = 1 / (x 的正 n 次方),先把负指数变成正指数,仍然用上面完全相同的递归逻辑计算,最后取倒数即可。

示例代码:

java 复制代码
class Solution {
    public double myPow(double x, int n) {
        return n < 0 ? 1.0 / pow(x,-n) : pow(x,n);
    }

    public double pow (double x, int n){
        if(n == 0) return 1.0;

        double tmp = pow(x,n/2);
        return n % 2 == 0 ? tmp*tmp : tmp*tmp*x;
    }
}
相关推荐
CyberMuse5 小时前
408习题集-数据结构101
算法
迷海5 小时前
力扣原题《有效的数独游戏》,纯手搓,已验证
算法·leetcode·游戏
freshman_y5 小时前
经典的C语言题型
c语言·开发语言·算法
small_wh1te_coder5 小时前
拷打字节技术总监: 详解c语言嵌入式多线程编程中的头文件 #总结 上下篇合 #
c语言·开发语言·算法·操作系统·嵌入式
字节高级特工5 小时前
C++从入门到熟悉:深入剖析const和constexpr
前端·c++·人工智能·后端·算法
Cathy Bryant5 小时前
聊聊拓扑学
笔记·算法·数学建模·拓扑学·高等数学
Lisssaa5 小时前
打卡第二十七天
算法
XWalnut5 小时前
LeetCode刷题 day2
算法·leetcode·职场和发展
Tisfy5 小时前
LeetCode 2946.循环移位后的矩阵相似检查:模拟(左即是右)
算法·leetcode·矩阵·题解
SadSunset5 小时前
第四章:Redis 数据结构与命令
数据结构·数据库·redis