LeetCode-刷题总结:递归思想

结论:假设一个问题可以分解为若干个子问题A、B、C、D...,且A问题需要在B、C、D...已经解决的基础上求解。

1)我们可以假设B问题已经解决,考虑A如何在B问题已经解决的前提下,如何处理(即寻找递推关系)

2)有了递推关系,我们就可以由A==>B==>C...==>最后一个最基本的子问题,我们处理最基本的子问题(即寻找终止条件)

基本上,所有的递归问题都可以用递推公式来表示。我们不需要按照程序进行的步骤去,一步一步嵌套调用,这样细节问题只会越想越复杂。

 

例1: (比较明显的能递推公式的问题)

问题:斐波那契数列的第n项

递推公式:

f(n)=f(n-1)+f(n-2) 其中,f(0)=0,f(1)=1

终止条件:

if (n <= 2) return 1;

递归代码:

int f(int n) {
  if (n <= 2) return 1;
  return f(n-1) + f(n-2);
}

例2:(不那么明显的有递推公式的问题)

问题:逆序打印一个数组

递推公式:

假设令F(n)=逆序遍历长度为n的数组
那么F(n)= 打印数组中下标为n的元素 + F(n-1)

终止条件:

if (n <0) return ;

递归代码:

public void Print(int[] nums,int n){
    if(n<0) return;
    System.out.println(nums[n]);
    Print(nums,n-1);
}

到这里,不知道大家对写递归有没有一些理解了。其实写递归不能总想着去把递归平铺展开,这样脑子里就会循环,一层一层往下调,然后再一层一层返回,试图想搞清楚计算机每一步都是怎么执行的,这样就很容易被绕进去。对于递归代码,这种试图想清楚整个递和归过程的做法,实际上是进入了一个思维误区。只要找到**递推公式**,我们就能很轻松地写出递归代码。

到这里,我想进一步跟大家说明我这个思路是比较能够容易出代码的,那么就树的遍历问题来和大家讲。递归总是和树分不开,其中,最典型的便是树的遍历问题。刚开始学的时候,不知道大家是怎么理解先/中/后序遍历的递归写法的,这里我提供我的思路供参考,以前序遍历为例:

问题:二叉树的先序遍历

递推公式:

令F(Root)为问题:遍历以Root为根节点的二叉树,
令F(Root.left)为问题:遍历以F(Root.left)为根节点的二叉树
令F(Root.right)为问题:遍历以F(Root.right)为根节点的二叉树
那么其递推公式为:
F(Root)=遍历Root节点+F(Root.left)+F(Root.right)

递归代码:

public void preOrder(TreeNode node){
    if(node==null) return;
    System.out.println(node.val);
    preOrder(node.left);
    preOrder(node.righr);
}

递归是一种关于某个重复动作(完成重复性的功能)的形式化描述**。具体点讲,如果一个问题 A 可以分解为若干子问题 B、C、D,**你可以假设子问题 B、C、D 已经解决,在此基础上思考如何解决问题 A**。而且,你只需要思考问题 A 与子问题 B、C、D 两层之间的关系即可,不需要一层一层往下思考子问题与子子问题,子子问题与子子子问题之间的关系(也就是说,递归只能考虑当前层和下一层的关系,不能继续往下深入)。我们需要屏蔽掉递归细节,理解为完成了某种功能的形式化描述即可。

好了,那我们来分析这个题。

问题:单向链表的反转

递推公式:

令F(node)为问题:反转以node为头节点的单向链表;
一般,我们需要考虑F(n)和F(n-1)的关系,那么这里,如果n代表以node为头节点的单向链表,那么n-1就代表以node.next为头节点的单向链表.
所以,我们令F(node.next)为问题:反转以node.next为头节点的单向链表;
那么,F(node)和F(node.next)之间的关系是?这里我们来简单画个图,假设我们反转3个节点的链表:
1 -> 2 -> 3
那么,F(node=1)=F(node=2)+?
这里假设子问题F(node=2)已经解决,那么我们如何解决F(node=1):
很明显,我们需要反转node=2和node=1, 即 node.next.next=node; 同时 node.next=null;
所以,这个问题就可以是:F(node=1)=F(node=2)+ 反转node=2和node=1

递归代码:

    public ListNode reverseList(ListNode head) {
       if(head == null || head.next == null){
           return head;
       }
       ListNode node = reverseList(head.next);//下一个节点已经反转结束
       head.next.next = head;//将未反转的节点,放置已经反转的最后
       head.next = null;//将最后一个反转的指向null;
       return node;

    }

后续添加递归的例子:

剑指 Offer 25. 合并两个排序的链表

输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

示例1:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
//递归
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1 == null ){//考虑l1为空的特殊情况
            return l2;
        }
        if(l2 == null){//考虑l2为空的特殊情况
            return l1;
        }
        if(l1.val < l2.val){
            l1.next = mergeTwoLists(l1.next,l2);//l1后接已经已经合并好的链表
            return l1;
        }else{
            l2.next = mergeTwoLists(l1,l2.next);//l2后接已经合并号的链表
            return l2;
        }
    }   
}
//时间复杂度:O(m+n)
//空间复杂度:O(1)

 判断二叉树 A 中是否包含子树 B

题目描述:输入两棵二叉树 AB,判断 B 是不是 A 的子结构。(ps:我们约定空树不是任意一个树的子结构)

思路:若根节点相等,利用递归比较他们的子树是否相等,若根节点不相等,则 利用递归分别在左右子树中查找。

package Q18;
//判断二叉树 A 中是否包含子树 B
//输入两棵二叉树 A,B,判断 B 是不是 A 的子结构。(ps:我们约定空 树不是任意一个树的子结构)
//思路:若根节点相等,利用递归比较他们的子树是否相等,若根节点不相等,则 利用递归分别在左右子树中查找。
public class Q18Demo {

	public static void main(String[] args) {
		TreeNode node1 = new TreeNode(1);
		TreeNode node2 = new TreeNode(2);
		TreeNode node3 = new TreeNode(3);
		TreeNode node4 = new TreeNode(4);
		TreeNode node5 = new TreeNode(5);
		TreeNode node6 = new TreeNode(6);
		node1.left = node2;
		node1.right = node3;
		node2.left = node4;
		node3.left = node6;
		node3.right = node5;
		TreeNode node11 = new TreeNode(3);
		TreeNode node12 = new TreeNode(5);
		TreeNode node13 = new TreeNode(6);
		node11.left = node12;
		node11.right = node13;
		boolean result;
		Solution s = new Solution();
		result = s.hasSubTree(node1, node11);
		System.out.println(result);
	}

}

class Solution {
    public boolean hasSubTree(TreeNode source, TreeNode target) {
    	if(target == null){
    		return true;
    	}
    	if(source == null){
    		return false;
    	}
    	if(doesTree1HaveTree2(source, target)){//如果根节点与target相等,且其左右子树相等。
    		return true;
    	}
    	//否则 遍历其 左右子节点
    	return hasSubTree(source.left, target) || hasSubTree(source.right, target);
    }
    public static boolean doesTree1HaveTree2(TreeNode source,TreeNode target){
    	if(source == null && target == null){//终止条件:两者都为空
    		return true;
    	}
    	if(source == null || target == null){//两者有一个为空,则不含有
    		return false;
    	}
    	if(source.val != target.val){//值不相等,则返回false
    		return false;
    	}
    	return doesTree1HaveTree2(source.left, target.left) && doesTree1HaveTree2(source.right, 
    			target.right);//左子树 和 右子数必须相等
    }
}


剑指 Offer 27. 二叉树的镜像

请完成一个函数,输入一个二叉树,该函数输出它的镜像。

例如输入:

     4
   /   \
  2     7
 / \   / \
1   3 6   9
镜像输出:

     4
   /   \
  7     2
 / \   / \
9   6 3   1

 

示例 1:

输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode mirrorTree(TreeNode root) {
        if(root == null) return null;
        TreeNode tmp = root.left;
        root.left = mirrorTree(root.right);
        root.right = mirrorTree(tmp);
        return root;
    }
}

剑指 Offer 33. 二叉搜索树的后序遍历序列

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

 

参考以下这颗二叉搜索树:

     5
    / \
   2   6
  / \
 1   3
示例 1:

输入: [1,6,3,2,5]
输出: false
示例 2:

输入: [1,3,2,6,5]
输出: true

class Solution {
    //二叉搜索树满足左子节点值小于父节点值,右子节点大于父节点值
    //思路通过数组值,来确定哪一颗二叉搜索树
    public boolean verifyPostorder(int[] postorder) {
        return recur(postorder,0,postorder.length - 1);
    }
    public boolean recur(int[] postorder,int i,int j){
        if(i >= j) return true;
        int p = i;//遍历postorder,找到右子树的第一个节点
        while(postorder[p] < postorder[j]) p++;
        int m = p;//索引m为所要找的右子树第一个节点
        while(postorder[p] > postorder[j]) p++;//看其是否满足右子树都大于根节点
        return p==j && recur(postorder,i,m-1) && recur(postorder,m,j-1);
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值