递归算法的时间优化举例

题目

【问题描述】
小明想知道,满足以下条件的正整数序列的数量:

  1. 第一项为 n;
  2. 第二项不超过 n;
  3. 从第三项开始,每一项小于前两项的差的绝对值。
    请计算,对于给定的 n,有多少种满足条件的序列。
    【输入格式】
    输入一行包含一个整数 n。
    【输出格式】
    输出一个整数,表示答案。答案可能很大,请输出答案除以10000的余数。
    【样例输入】
    4
    【样例输出】
    7
    【样例说明】
    以下是满足条件的序列:
    4 1
    4 1 1
    4 1 2
    4 2
    4 2 1
    4 3
    4 4
    【评测用例规模与约定】
    对于 20% 的评测用例,1 <= n <= 5;
    对于 50% 的评测用例,1 <= n <= 10;
    对于 80% 的评测用例,1 <= n <= 100;
    对于所有评测用例,1 <= n <= 1000。
    思路
    f(pre,cur) = f(cur,1) + f(cur,2) + … +f(cur,abs(pre-cur)-1) + 1
    pre表示前一个数,cur代表当前的数,选定之后,序列种数等于以cur为前序,以1到abs-1为当前的序列数的总和再加1.
    如f(5,2) = f(2,1)+f(2,2).

无优化(n=25时就已经将近1s了)

时间复杂度:n的三次方(第二个数最多接近n,想象成一条线。第三个数最多也接近n,此时可想成一个面。此后逐步嵌套,最多n次,此时可想成n个面构成一个高度为n的立方体。因此时间复杂度为n的三次方)

import java.util.Scanner;
public class Nine {
	    static final int MOD = 10000;
	    static int N;
	    static long ans;
	    static Scanner sc;
	    public static void main(String[] args) {
	        sc = new Scanner(System.in);
	            work();  
	    }

	    static void work() {
	        ans = 0;
	        N = sc.nextInt();
			long ago = System.currentTimeMillis();
	        for (int x = 1; x <= N; ++x) ans = (ans + dfs(N, x)) % MOD;	//数列中的第二个数
	        System.out.println(ans);
			System.err.println(System.currentTimeMillis() - ago);
	    }

	    static long dfs(int pre, int cur) {
	        // 询问状态
	        long ans = 1;
	        for (int j = 1; j < Math.abs(pre - cur); j++) {	//数列中第三个到最后一个满足条件的数
	            ans = (ans + dfs(cur, j)) % MOD;
	        }
	        return ans;
	    }
}

优化1:记忆型递归(当n=600时大概是1s)

时间复杂度:仍然为n的三次方,只是对某些位置进行了记忆,减少了某些重复运算

import java.util.Scanner;
public class Nine {
	    static final int MOD = 10000;
	    static int N;
	    static long ans;
	    static Scanner sc;
	    static long[][] mem = new long[1001][1000];
	    public static void main(String[] args) {
	        sc = new Scanner(System.in);
	            work();  
	    }

	    static void work() {
	        ans = 0;
	        N = sc.nextInt();
			long ago = System.currentTimeMillis();
	        for (int x = 1; x <= N; ++x) ans = (ans + dfs(N, x)) % MOD;	//数列中的第二个数
	        System.out.println(ans);
			System.err.println(System.currentTimeMillis() - ago);
	    }

	    static long dfs(int pre, int cur) {
	        // 询问状态
	    	  if (mem[pre][cur] != 0)	//优化1在这里,记忆型递归,因为相同的pre,cur被重复计算了很多次
	              return mem[pre][cur];
	          long ans = 1;
	          for (int j = 1; j < Math.abs(pre - cur); j++) {	//数列中第三个到最后一个满足条件的数
	              ans = (ans + dfs(cur, j)) % MOD;
	          }
	          mem[pre][cur] = ans;
	          return ans;
	    }
}

优化2:每次解答树只展开两个节点,减少一层循环,虽然增加了递归,但是充分利用了记忆

时间复杂度:n的平方
解空间是N的平方(详细为N*N)表格,但是每次都要循环加总,所以成了N的立方,在同样的解空间下,避免循环加总,即可优化到N的平方
重新考虑状态的转移:
如果我们用f(i,j)表示前一个数是i,当前数是1到j的合法序列的个数;有f(i,j) = 1 + f(i,j-1) + f(j,abs(i-j)-1)即分为两个部分:
1)i作为前一个数,从1到j-1为当前数的合法序列的个数已经计算好
2)求以j为尾数,后面选择1到abs(i-j)-1的合法序列的个数。
如 f(10,5)=f(10,4)+f(5,4);而不是枚举1到5;这样每次解答树只展开两个节点,相当于减少一层循环,虽然解答树的层次还是很深,但是由于记忆的存在,解空间仍然是N的平方。可在100ms内解决。

import java.util.Scanner;
public class Nine {
	    static final int MOD = 10000;
	    static int N;
	    static long ans;
	    static long[][] mem = new long[1001][1000];
	    static Scanner sc;
	    
	    public static void main(String[] args) {
	        sc = new Scanner(System.in);
	            work();
	    }
	    
	    static void work() {
	        ans = 0;
	        N = sc.nextInt();
	        long ago = System.currentTimeMillis();
	        System.out.println(dfs(N, N));	//优化2:去掉了这里的循环
	        System.err.println(System.currentTimeMillis() - ago);
	    }
	    
	    static long dfs(int pre, int cur) {
	        if (cur <= 0) return 0;
	        // 询问状态
	        if (mem[pre][cur] != 0)
	            return mem[pre][cur];
	        mem[pre][cur] = (1 + dfs(pre, cur - 1) + dfs(cur, Math.abs(pre - cur) - 1)) % MOD;	//优化2:每次解答树只展开两个节点
	        return mem[pre][cur];
	    }	 
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值