1. 什么是递归?
递归是指在函数的定义中使用函数自身的过程。简单来说,递归是通过将大问题分解为更小的子问题来解决问题的一种方法。递归函数在执行时会反复调用自身,直到达到某个终止条件。
2. 递归解决什么问题?
递归的使用可以简化问题的解决过程,特别适用于那些具有递归结构或可分解为子问题的复杂问题。递归的主要优势在于它提供了一种自然而简洁的方式来解决这些问题。
3. 递归的步骤
- 定义递归函数的基本情况(终止条件):递归函数需要定义一个或多个基本情况,即当满足某个条件时,递归停止并返回结果。这是确保递归函数不会无限循环的关键。
- 将大问题分解为子问题:递归函数应该通过将大问题分解为一个或多个更小的子问题来解决。每个子问题都是原始问题的规模更小的版本。
- 在递归函数中调用自身:在解决子问题时,递归函数会调用自身来处理子问题。递归函数的调用应该是在满足递归终止条件之前的步骤。
- 组合子问题的结果:递归函数在处理完子问题后,需要将子问题的结果组合起来得到原始问题的解。
4. 使用递归的注意事项
- 确保递归终止条件的正确性:递归函数必须定义一个或多个终止条件,以避免无限递归。如果终止条件不正确或者缺失,递归函数可能会导致无限循环,最终耗尽计算资源。
- 确保每次递归调用都能向终止条件靠近:递归调用的参数或输入应该是可以缩小问题规模的,这样每次递归调用都能使问题向终止条件靠近。否则,递归函数可能会陷入无尽的递归循环。
- 注意递归的性能问题:递归函数可能会生成大量的函数调用和堆栈帧,消耗较多的内存和计算资源。在某些情况下,使用迭代等其他方法可能更高效。
5. 示例
-
合并两个有序链表
public class MergeSortedLists { public ListNode mergeLists(ListNode l1, ListNode l2) { // 处理递归终止条件 if (l1 == null) { return l2; } if (l2 == null) { return l1; } // 比较两个链表头节点的值,并选择较小的节点作为合并后链表的头节点 if (l1.val < l2.val) { l1.next = mergeLists(l1.next, l2); // 递归调用mergeLists函数 return l1; } else { l2.next = mergeLists(l1, l2.next); // 递归调用mergeLists函数 return l2; } } }
定义一个递归函数mergeLists,该函数接受两个有序链表的头节点作为参数,并返回合并后的链表的头节点。
在函数内部,首先处理递归终止条件。如果其中一个链表为空,直接返回另一个链表的头节点。
比较两个链表的头节点的值,将较小值的节点作为合并后链表的头节点,并将其next指针指向递归调用mergeLists函数的结果。
递归调用mergeLists函数,传入较小节点的next指针和另一个链表的头节点作为参数。
返回合并后的链表的头节点。
-
判断链表是否有环
public class Solution { public boolean hasCycle(ListNode head) { // 判断链表是否为空或只有一个节点,不构成环 if (head == null || head.next == null) { return false; } ListNode slow = head; // 慢指针 ListNode fast = head.next; // 快指针 return hasCycleRecursive(slow, fast); } private boolean hasCycleRecursive(ListNode slow, ListNode fast) { // 如果快指针到达链表末尾,说明没有环 if (fast == null || fast.next == null) { return false; } // 如果快指针追上慢指针,说明存在环 if (fast == slow || fast.next == slow) { return true; } // 继续移动指针 return hasCycleRecursive(slow.next, fast.next.next); } }
首先,在 hasCycle() 方法中检查链表是否为空或只有一个节点,这种情况下不会形成环,直接返回 false。
然后,将慢指针 slow 初始化为链表头,快指针 fast 初始化为 slow 的下一个节点。
接下来,调用 hasCycleRecursive() 方法进行递归判断。在递归方法中,首先判断快指针是否到达链表末尾,如果是,则链表不包含环,返回 false。然后,判断快指针是否追上慢指针或者快指针的下一个节点是否追上慢指针,如果是,则链表包含环,返回 true。最后,继续递归调用 hasCycleRecursive() 方法,将慢指针指向下一个节点,快指针指向下下个节点,继续进行判断。
注意,这里使用了两个条件进行判断,即快指针是否追上慢指针,或者快指针的下一个节点是否追上慢指针。这是因为快指针每次移动两步,而慢指针每次移动一步,所以在形成环的情况下,两个指针可能会有一个节点的差距。
最后,在主函数中调用 hasCycle() 方法,传入链表的头节点,即可判断链表是否存在环。