一、记忆缓存
遇到出现同样的参数多次递归调用时,可以添加记忆缓存,以空间换时间。
原理说明:
1、如下面案例中的递归,调用时每次都从n执行到0(n一次衰减),则可能存在n-1的阶乘次重复参数调用,这里将重复部分都缓存起来,达到了跟动态规划类似的效果。
下面的力扣案例,没加缓存记忆会执行超时,增加后顺利通过。
#案例
3193. 统计逆序对的数目
给你一个整数 n
和一个二维数组 requirements
,其中 requirements[i] = [endi, cnti]
表示这个要求中的末尾下标和 逆序对 的数目。
整数数组 nums
中一个下标对 (i, j)
如果满足以下条件,那么它们被称为一个 逆序对 :
i < j
且nums[i] > nums[j]
请你返回 [0, 1, 2, ..., n - 1]
的
排列
perm
的数目,满足对 所有 的 requirements[i]
都有 perm[0..endi]
恰好有 cnti
个逆序对。
由于答案可能会很大,将它对 109 + 7
取余 后返回。
示例 1:
输入:n = 3, requirements = [[2,2],[0,0]]
输出:2
解释:
两个排列为:
[2, 0, 1]
- 前缀
[2, 0, 1]
的逆序对为(0, 1)
和(0, 2)
。 - 前缀
[2]
的逆序对数目为 0 个。
- 前缀
[1, 2, 0]
- 前缀
[1, 2, 0]
的逆序对为(0, 2)
和(1, 2)
。 - 前缀
[1]
的逆序对数目为 0 个。
- 前缀
示例 2:
输入:n = 3, requirements = [[2,2],[1,1],[0,0]]
输出:1
解释:
唯一满足要求的排列是 [2, 0, 1]
:
- 前缀
[2, 0, 1]
的逆序对为(0, 1)
和(0, 2)
。 - 前缀
[2, 0]
的逆序对为(0, 1)
。 - 前缀
[2]
的逆序对数目为 0 。
示例 3:
输入:n = 2, requirements = [[0,0],[1,0]]
输出:1
解释:
唯一满足要求的排列为 [0, 1]
:
- 前缀
[0]
的逆序对数目为 0 。 - 前缀
[0, 1]
的逆序对为(0, 1)
。
提示:
2 <= n <= 300
1 <= requirements.length <= n
requirements[i] = [endi, cnti]
0 <= endi <= n - 1
0 <= cnti <= 400
- 输入保证至少有一个
i
满足endi == n - 1
。 - 输入保证所有的
endi
互不相同。
二、状态记录
部分算法题目中,在从顶层到最底层的一次递归中时,数组中前面层使用过的元素不允许再使用。该场景下即可使用数据状态记录的方法。
使用一个相同大小的状态数组boolean[n],数组初始化为false,对于目标数组使用过的元素,对应状态数组下同下标visit[i]赋值true,递归完,再将其赋值回false。
#案例
HJ67 24点游戏算法
描述
给出4个1-10的数字,通过加减乘除运算,得到数字为24就算胜利,除法指实数除法运算,运算符仅允许出现在两个数字之间,本题对数字选取顺序无要求,但每个数字仅允许使用一次,且需考虑括号运算
此题允许数字重复,如3 3 4 4为合法输入,此输入一共有两个3,但是每个数字只允许使用一次,则运算过程中两个3都被选取并进行对应的计算操作。
输入描述:
读入4个[1,10]的整数,数字允许重复,测试用例保证无异常数字。
输出描述:
对于每组案例,输出一行表示能否得到24点,能输出true,不能输出false
示例1
输入:
7 2 1 10
复制输出:
true
import java.io.*;/*知识点:递归、深度优先搜索、回溯算法
*/
public class Main {static int count =0;public static void main(String[] args) throws IOException {BufferedReader br = new BufferedReader(new InputStreamReader(System.in));String str;while ((str = br.readLine()) != null) {String[] numstr = str.split(" ");int[] nums = new int[4]; // 存放数字int[] visit = newint[4]; // 存放对应位置数字的使用状态(1代表已使用)boolean flag = false;for (int i = 0; i < 4; i++) {nums[i] = Integer.parseInt(numstr[i]); // 读取数字}for (int i = 0; i < 4; i++) {visit[i] = 1; // 把当前数字标记为已使用if (dfs(nums, visit, nums[i])) { // 进入递归flag = true;break;}}System.out.println(flag);}}public static boolean dfs(int[] nums, int[] visit, int temp) {count++;for (int i = 0; i < nums.length; i++) {if (visit[i] == 0) { // 如果是未使用的数字visit[i] = 1; // 标记为已使用if (dfs(nums, visit, temp + nums[i]) // 递归判断|| dfs(nums, visit, temp - nums[i])|| dfs(nums, visit, temp * nums[i])|| dfs(nums, visit, temp / nums[i])) {// 如果存在满足条件的,终止循环return true;}// 不存在满足条件的,说明当前的数字顺序不符要求,进行回溯,把标记重置为0visit[i] = 0;}}// 数字都已使用且结果为24,返回真if (temp == 24) {return true;}// 不存在24,返回假return false;}
}