31、递归

本文详细介绍了Java中的递归与方法嵌套,通过四个实例——递归求解斐波那契数列、打印九九乘法表、猴子吃桃问题和删除文件夹下的资源,阐述了递归的基本概念和实现条件。强调了递归出口的重要性,以及避免内存溢出和栈溢出的注意事项。

定义

在 Java 中,就是在方法定义中调用本方法自身的现象
但是 Math.max(Math.max(a,c),b); 这不叫递归,则叫方法嵌套

注意点(实现递归的三个必须条件)

1、递归一定要有出口(举例中的 n),否则死循环:重点中的重点
2、递归次数不要太多,否则容易内存溢出
3、方法定义中调方法本身

举例

1、递归

public static void main(String[] args) {
	System.out.println(recursion(12));
}
 
public static int recursion(int month) {
// 倒序
// 12月时是10月和11月之和
// recursion(12) = recursion(10)+recursion(11)
// recursion(11) = recursion(10)+recursion(9)
if (month == 1 || month == 2) {
	return 1;// 太难了,把钱还我吧
} else {
	return recursion(month - 1) + recursion(month - 2);
}

2、九九乘法表

public static void main(String[] args) {
	print99(9);
}
	
public static void print99(int i) {
	if (i==1) {
		System.out.println("1=1*1");
	} else {
		print99(i-1);
		for (int j = 1; j <=i; j++) {
			System.out.print(i*j+"="+j+"*"+i+"\t");
		}
		System.out.println();
	}
}

3、猴子吃桃问题。猴子第一天摘下若干个桃子,当即吃了一半,还不过瘾,又多吃了一个。第二天又将剩下的桃子吃掉一半,又多吃了一个。以后每天都吃了前一天剩下的一半零一个。到第10天,只剩下一个桃子了。试求第一天共桃子?

public static void main(String[] args) {
	System.out.println(eatPeach(10));
}

public static int eatPeach(int i) {
	if (i==1) {
		return 1;
	} else {
		return 2*eatPeach(i-1)+2;
	}
}

4、删除某个文件夹下的所有文件夹和文件

public static void main(String[] args) {
	File file = new File("H:\\1223HomeWork");
	delResources(file);
}

public static void delResources(File file) {
	File[] files = file.listFiles();
	if (files != null) {
		for (File file2 : files) {
			if (file2.isDirectory()) { //判断是否为文件夹
				delResources(file2);   //是文件夹就迭代
			} else {				//不是文件夹就删除
				file2.delete();
			}
			//删除空文件夹
			file2.delete();
		}
	}else {
		System.out.println("该目录: "+file.getAbsolutePath()+"为空");
	}
}
我们要解决的问题是: 给定一个最多 31 位的数字字符串,通过重新排列得到**最小的合法偶数**(无前导 0,且为偶数)。若无法构造,则返回 `-1`。 现在要求:**使用递归方式实现核心逻辑**,并且仍然只能使用 `<iostream>` 和 `<string>` 头文件。 --- ### ✅ 解题思路(递归版): 我们将采用 **递归生成所有可能的排列组合** 的思想,但因为字符串长度最多为 31,全排列不可行(31! 太大),所以我们不能暴力递归所有排列。 但是注意:我们只需要**最小的偶数**,不需要所有排列。因此可以优化: > ❌ 不能用纯暴力递归生成所有排列(时间爆炸) > ✅ 但我们可以在 **排序后贪心地尝试构造候选解**,并用递归来辅助“选择下一个数字”的过程 —— 实际上更适合用回溯法。 然而题目要求“使用递归”,我们可以将关键子任务写成递归形式,例如: - 字符串排序(递归冒泡或归并) - 构造最小前缀(递归查找非零字符、拼接) - 或者使用递归回溯来生成候选排列(仅当长度较小时可行) 但由于长度最大为 31,**完全递归回溯所有排列不可行**。 --- ### 🔧 折中方案(合理使用递归 + 贪心剪枝): 我们结合以下方法: 1. 使用递归实现字符串排序(如递归版本的归并排序)。 2. 使用递归函数从剩余字符中构造最小有效前缀(避免前导0)。 3. 主体逻辑仍为枚举偶数结尾 + 贪心构造最小前缀,但在处理字符串操作时使用递归。 这样既满足“使用递归”的要求,又保证效率可行。 --- ### ✅ C++ 代码(仅用 `<iostream>`、`<string>`,含递归实现) ```cpp #include <iostream> #include <string> using namespace std; // 递归合并两个已排序的子串(辅助函数) void merge(string &s, int left, int mid, int right) { string temp = ""; int i = left, j = mid + 1; while (i <= mid && j <= right) { if (s[i] <= s[j]) { temp += s[i++]; } else { temp += s[j++]; } } while (i <= mid) temp += s[i++]; while (j <= right) temp += s[j++]; for (int k = 0; k < temp.length(); ++k) { s[left + k] = temp[k]; } } // 递归归并排序 void mergeSort(string &s, int left, int right) { if (left >= right) return; int mid = left + (right - left) / 2; mergeSort(s, left, mid); mergeSort(s, mid + 1, right); merge(s, left, mid, right); } // 递归查找第一个非零字符的位置 int findFirstNonZero(const string &s, int start, int n) { if (start >= n) return -1; if (s[start] != '0') return start; return findFirstNonZero(s, start + 1, n); } // 递归构造最小有效前缀(无前导0) string buildSmallestPrefix(const string &s) { int n = s.length(); if (n == 0) return ""; if (n == 1) return s; // 如果首字符不是 '0',直接返回 if (s[0] != '0') return s; // 否则找第一个非零字符 int firstNonZero = findFirstNonZero(s, 0, n); if (firstNonZero == -1) return "0"; // 全是0 string result = ""; result += s[firstNonZero]; // 把前面的所有0和其他字符加回来(除了 firstNonZero) for (int i = 0; i < firstNonZero; ++i) { result += s[i]; } for (int i = firstNonZero + 1; i < n; ++i) { result += s[i]; } return result; } // 主求解函数 string solve(string num) { int n = num.size(); mergeSort(num, 0, n - 1); // 递归排序 string best = ""; // 枚举每一位作为末尾偶数 for (int i = 0; i < n; ++i) { int d = num[i] - '0'; if (d % 2 != 0) continue; // 构造剩余字符串 string rem = ""; for (int j = 0; j < n; ++j) { if (i != j) rem += num[j]; } string prefix = ""; if (rem.empty()) { prefix = ""; // 单独处理 } else { mergeSort(rem, 0, rem.length() - 1); // 再排一次 prefix = buildSmallestPrefix(rem); // 递归构造最小前缀 } string candidate = prefix + num[i]; // 检查前导零:长度大于1时不能以0开头 if (candidate[0] == '0' && candidate.length() > 1) { continue; } // 更新最优解 if (best == "" || candidate < best) { best = candidate; } } return best == "" ? "-1" : best; } int main() { int T; cin >> T; while (T--) { string n; cin >> n; cout << solve(n) << endl; } return 0; } ``` --- ### 📌 解释: 1. **`mergeSort`**:递归实现归并排序,替代 `std::sort`。 2. **`findFirstNonZero`**:递归查找第一个非 `'0'` 字符。 3. **`buildSmallestPrefix`**:调用递归查找函数,构造最小有效前缀。 4. **主体逻辑不变**:枚举偶数结尾 → 剩余字符排序 → 构造最小前缀 → 拼接候选 → 取最小值。 5. 所有操作仅依赖 `<iostream>` 和 `<string>`。 --- ### ✅ 示例输入: ``` 3 312 135 204 ``` ### 输出: ``` 132 -1 204 ``` 正确无误。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值