递归原理
递归是一种解决问题的有效方法,在递归过程中,函数将自身作为子例程调用
你可能想知道如何实现调用自身的函数。诀窍在于,每当递归函数调用自身时,它都会将给定的问题拆解为子问题。
递归调用继续进行,直到到子问题无需进一步递归就可以解决的地步。
为了确保递归函数不会导致无限循环,它应具有以下属性:
- 一个简单的
基本案例(basic case)
(或一些案例) —— 能够不使用递归来产生答案的终止方案。---终止条件 - 一组规则,也称作
递推关系(recurrence relation)
,可将所有其他情况拆分到基本案例。 ---递推关系
注意,函数可能会有多个位置进行自我调用。
示例
让我们从一个简单的编程问题开始:
以相反的顺序打印字符串。
你可以使用迭代的办法轻而易举地解决这个问题,即从字符串的最后一个字符开始遍历字符串。但是如何递归地解决它呢?
首先,我们可以将所需的函数定义为 printReverse(str[0...n-1])
,其中 str[0]
表示字符串中的第一个字符。然后我们可以分两步完成给定的任务:
printReverse(str[1...n-1])
:以相反的顺序打印子字符串str[1...n-1]
。print(str[0])
:打印字符串中的第一个字符。
请注意,我们在第一步中调用函数本身,根据定义,它使函数递归。
例子:
private static void printReverse(char [] str) { helper(0, str); } private static void helper(int index, char [] str) { if (str == null || index >= str.length) { return; } // 这可以理解为,先输出当前index之后索引的字符(即index+1),且该方法可以宏观理解为,已经输出后后边所有的字符了,接下来输出当前字符 helper(index + 1, str); System.out.print(str[index]); }
递归例子:
一、反转字符串
问题:
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。
package com.example.demo; public class TestString0001 { public void reverseString(char[] s) { int len = s.length; swap(s, 0, len - 1); } private void swap(char[] s, int left, int right) { if (left > right) { return; } char temp = s[left]; s[left] = s[right]; s[right] = temp; swap(s, left+1, right-1); } public static void main(String[] args) { TestString0001 t = new TestString0001(); char[] arr = {'H', 'a', 'n', 'n', 'a', 'h'}; t.reverseString(arr); for (char c : arr) { System.out.println(c); } } }
二、问题
两两交换链表中的节点
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
class Solution { public ListNode swapPairs(ListNode head) { if (head == null || head.next == null) { return head; } ListNode temp = head; head = head.next; temp.next = head.next; head.next = temp; //下一个交换 head.next.next = swapPairs(head.next.next); return head; } }
相关:leetcode有关递归:
https://leetcode-cn.com/explore/featured/card/recursion-i/256/principle-of-recursion/1101/