递归进阶

循环与递归进阶用法

注意:避免栈溢出(出口的情况一定要罗列完毕)

例1:在n个球中取m个 n是总球数 m是被取球数。求可以有多少种取法?

准备使用递归解决问题,第一步该是构建框架。

如下:

public static void main(String[] args)
{
    int k = fn(10, 3);//例如10个里面取3个
    System.out.println(k);
}

此时大概知道需要构造出一个fn(),还需传入n,m。然后我们可以使用递归的想法得到,fn(n - 1, m - 1) + fn(n - 1, m)。最后构造出口,主要考虑被取球数大于/等于/为0所有的球

即m>n,n=m,m=0三种情况。

public static int fn(int n, int m)
{
    if (n < m) return 0;//当取球数大于总球数时,没有取法
    if (n == m) return 1;//当总球数与被取球数一致时,只有一种取法,即全取完
    if (m == 0) return 1;//当被取球数为0时只有一种取法,即什么也不去取
    //n个里有个特殊球x,取法划分,包不包含x
    return fn(n - 1, m - 1) + fn(n - 1, m);
}

例2:n个元素的全排列(全排列即所有的排列的可能)

由例1的思路咱们先确定框架

public static void main(String[] args)
{
    char[] data="ABCDEF".toCharArray();
    fn(data,0);
}

然后我们想到可以定义k作为交换位置,以此为界,与其后面的元素交换,进行尝试。

可以得到有如下代码

for (int i = k; i <data.length ; i++)
{
    {
       char t=data[k];
       data[k]=data[i];
       data[i]=t;
    }
    fn(data,k+1);
}

但是数组循环之后,原有的顺序就被打乱了,此处应该有一个回溯的步骤,修改之后应该如下图。

//交换第一个数组元素,就是第K个与第i个交换
for (int i = k; i <data.length ; i++)
{
    {//试探
       char t=data[k];
       data[k]=data[i];
       data[i]=t;
    }
    fn(data,k+1);
    {//回溯 恢复原来的位置
        char t=data[k];
        data[k]=data[i];
        data[i]=t;
    }
}

解决了上述问题后,问题实则已经解决了十之八九。所以到了该考虑出口的问题了。我们可以看出当k(交换位置)与data的长度相同时,就可以输出结果了。综合上述的分析我们可以定义有如下fn()。

public static void fn(char[] data,int k)
{
    if (k==data.length)
    {
        for (int i = 0; i < data.length; i++)
        {
            System.out.print(data[i]+" ");

        }
        System.out.println("\t");//为了输出结果的美观
    }

    //交换第一个数组元素,就是第K个与第i个交换
    for (int i = k; i <data.length ; i++)
    {
        {//试探
           char t=data[k];
           data[k]=data[i];
           data[i]=t;
        }
        fn(data,k+1);
        {//回溯 恢复原来的位置
            char t=data[k];
            data[k]=data[i];
            data[i]=t;
        }
    }
}

例3:两个字符串公共子串的长度

思路还是如上,先定义有如下的框架。

public static void main(String[] args)
{

    int k = fn("abc", "abcd");
    System.out.println(k);
}

然后就需要想到定义fn(),传入s1,s2。当准备工作完成后,我们可以从输入的字符串中,看出s1,s2的首字母是相同的。所以可以先将其首字符进行比较,返回fn(s1.substring(1), s2.substring(1))+1;进行再次调用。我们也需要考虑其他情况的出现,所以其他情况可以将其归类成首字符之后的比较,可以将其写成 Math.max(fn(s1.substring(1), s2), fn(s1, s2.substring(1)));并将其返回出去。

关键是出口的发现,其实由我的输入,很容易看出s1与s2的长度相同时,两个字符串的公共子串长度便是0.所以得到如下fn()

public static int fn(String s1, String s2)
{
    if (s1.length() == 0 || s2.length() == 0) return 0;
    if (s1.charAt(0) == s2.charAt(0))

        return fn(s1.substring(1), s2.substring(1))+1;
     else

        return Math.max(fn(s1.substring(1), s2), fn(s1, s2.substring(1)));

}

个人总结:确定框架之后,确定fn()这步实是难以把握。需要以练习,练出感觉。发现“出口”的意识还是欠缺。总之,继续学习,加强练习。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值