本文目录
1. 走楼梯
问题描述:
有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶、3阶。
请实现一个方法,计算小孩有多少种上楼的方式。
为了防止溢出,请将结果Mod 1000000007给定一个正整数int n,请返回一个数,代表上楼的方式数。
保证n小于等于100000。
思路解析:
递归方程:f(x) = f(x-1)+f(x-2)+f(x-3)
迭代方程:dp[i]=dp[i-1]+dp[i-2]+dp[i-3]
代码:
package 逐步生成式递归;
import java.util.Scanner;
/**
* @author: DreamCode
* @file: 走楼梯.java
* @time: 2022年3月24日-上午9:01:26
*/
public class 走楼梯 {
static long[] rec;
static long MODE = 1000000007;
public static void main(String[] args) {
int n = 1000;
rec = new long[n + 1];
rec[1] = 1;
rec[2] = 2;
rec[3] = 4;
long ans = recursion(n); // 递归
long ans2 = iteration(n); // 迭代
System.out.println(ans);
System.out.println(ans2);
}
private static long iteration(int n) {
// TODO 迭代形式
long[] dp = new long[n + 1];
dp[1] = 1;
dp[2] = 2;
dp[3] = 4;
for (int i = 4; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3];
dp[i] %= MODE;
}
return dp[n];
}
private static long recursion(int n) {
// TODO 递归方程;f(x) = f(x-1)+f(x-2)+f(x-3)
if (rec[n] != 0) {
return rec[n];
}
long res;
res = (recursion(n - 1) + recursion(n - 2) + recursion(n - 3)) % MODE;
rec[n] = res;
return res;
}
}
2. 机器人走格子
问题描述:
有一个X*Y的网格,一个机器人只能走格点且只能向右或向下走,要从左上角走到右下角。
请设计一个算法,计算机器人有多少种走法。
给定两个正整数int x,int y,请返回机器人的走法数目。保证x+y小于等于12。
思路解析:
递归方程:f(x,y)=f(x-1,y)+f(x,y-1)
迭代方程:dp(i,j) = dp(i-1,j) + dp(i,j-1)
代码:
package 逐步生成式递归;
/**
* @author: DreamCode
* @file: 机器人走格子.java
* @time: 2022年3月24日-上午9:32:57
*/
public class 机器人走格子 {
static long[][] rec;
public static void main(String[] args) {
int x = 1000;
int y = 1000;
rec = new long[x + 1][y + 1];
long ans1 = recursion(x, y); // 递归
long ans2 = iteration(x, y); // 迭代
System.out.println(ans1);
System.out.println(ans2);
}
private static long iteration(int x, int y) {
// TODO 迭代形式(动态规划)
long[][] dp = new long[x + 1][y + 1];
for (int i = 1; i <= x; i++) { // 所有列为1的都只有一种走法
dp[i][1] = 1;
}
for (int i = 1; i <= y; i++) { // 所有行为1的都只有一种走法
dp[1][i] = 1;
}
for (int i = 2; i <= x; i++) {
for (int j = 2; j <= y; j++) {
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[x][y];
}
private static long recursion(int x, int y) {
// TODO 递归方式:f(x,y)=f(x-1,y)+f(x,y-1)
if (rec[x][y] != 0) {
return rec[x][y];
}
long res;
if (x == 1) { // 只有一列
res = 1;
rec[x][y] = res;
return res;
}
if (y == 1) { // 只有一行
res = 1;
rec[x][y] = res;
return res;
}
res = recursion(x - 1, y) + recursion(x, y - 1);
rec[x][y] = res;
return res;
}
}
3. 非空子集(递归|迭代)
问题描述:
请编写一个方法,返回某集合的所有非空子集。
给定一个int数组A和数组的大小int n,请返回A的所有非空子集。
保证A的元素个数小于等于20,且元素互异。各子集内部从大到小排序,子集之间字典逆序排序
思路解析:
常规子集生成(递归增量构造法)算法;f(n)是在f(n-1)的基础上逐步生成的
代码:
package 逐步生成式递归;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* @author: DreamCode
* @file: 子集生成.java
* @time: 2022年3月25日-下午2:14:16
*/
public class 非空子集生成 {
public static void main(String[] args) {
int n = 3;
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = i + 1;
}
Set<Set<Integer>> ans_recursion = getSubSet_recursion(arr, n - 1); // 递归
System.out.println(ans_recursion);
Set<Set<Integer>> ans_iteration = getSubSet_iteration(arr); // 迭代
System.out.println(ans_iteration);
}
private static Set<Set<Integer>> getSubSet_iteration(int[] arr) {
// TODO 逐步迭代
Set<Set<Integer>> old_rec = new HashSet<>();
old_rec.add(new HashSet<>()); // 初始化有一个空集
for (int i = 0; i < arr.length; i++) {
Set<Set<Integer>> new_rec = new HashSet<>(); // 新的集合
for (Set<Integer> set : old_rec) { // 遍历旧集合的每一个元素
new_rec.add(set); // 不选择arr[i]
HashSet<Integer> temp = (HashSet<Integer>) ((HashSet<Integer>) set).clone();
temp.add(arr[i]); // 选择当前元素加入集合
new_rec.add(temp); // 将新的集合加入到new_rec
}
old_rec = new_rec;
}
return old_rec;
}
private static Set<Set<Integer>> getSubSet_recursion(int[] arr, int n) {
// TODO 递归增量递增法
Set<Set<Integer>> new_rec = new HashSet<>();
if (n == -1) { // 出口
new_rec.add(new HashSet<Integer>());
return new_rec;
}
Set<Set<Integer>> old_rec = getSubSet_recursion(arr, n - 1);
for (Set<Integer> set : old_rec) { // 遍历每一个f(n-1)
new_rec.add(set); // 不选当前的arr[n]
HashSet<Integer> temp = (HashSet<Integer>) ((HashSet<Integer>) set).clone(); // set为引用类型,修改时需要备份
temp.add(arr[n]);
new_rec.add(temp); // 选择当前元素
}
return new_rec;
}
}
4. 非空子集(二进制)
问题描述:
请编写一个方法,返回某集合的所有非空子集。
给定一个int数组A和数组的大小int n,请返回A的所有非空子集。
保证A的元素个数小于等于20,且元素互异。各子集内部从大到小排序,子集之间字典逆序排序
思路解析:
二进制法构造非空子集;二进制的每一位数字代表当前位置选与不选
代码:
package 逐步生成式递归;
import java.util.ArrayList;
import java.util.Arrays;
/**
* @author: DreamCode
* @file: 非空子集生成_二进制.java
* @time: 2022年3月25日-下午2:48:32
*/
public class 非空子集生成_二进制 {
public static void main(String[] args) {
int[] arr = { 1, 3, 2 };
ArrayList<ArrayList<Integer>> ans = getSubSet(arr);
System.out.println(ans);
}
private static ArrayList<ArrayList<Integer>> getSubSet(int[] arr) {
// TODO 二进制法构造非空子集;二进制的每一位数字代表当前位置选与不选
Arrays.sort(arr); // 对arr进行升序排序
int n = arr.length; // arr数组的长度(元素的个数)
ArrayList<ArrayList<Integer>> ans = new ArrayList<>();
for (int num = (int) (Math.pow(2, n) - 1); num > 0; num--) { // 为方便字典序,选择从大到小的遍历,不含有空集则不能等于0
ArrayList<Integer> list = new ArrayList<>(); // 当前数的选择集合
for (int i = n - 1; i >= 0; i--) { // 向右移动n-1~0位,再与1做&,得到1代表当前位选则,否则代表不选
if (((num >> i) & 1) == 1) { // 当前位置选择
list.add(arr[i]);
}
}
ans.add(list);
}
return ans;
}
}
5. 全排列(迭代)
思路解析:
逐个迭代的方法,逐个元素字符选取,插入到字符串的两两字符之间;避免产生重复子集元素,故选取set存储
代码:
package 逐步生成式递归;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
/**
* @author: DreamCode
* @file: 全排列.java
* @time: 2022年3月25日-下午9:30:33
*/
public class 全排列_迭代法 {
public static void main(String[] args) {
String A = "ABC";
int n = 3;
Set<String> ans = permutation(A, n);
// 将HashSet排序,将HashSet转换为TreeSet,descendingSet()实现降序排序
System.out.println(new TreeSet<>(ans).descendingSet());
}
private static Set<String> permutation(String string, int n) {
// TODO 逐步生成大法-迭代法
Set<String> rec_old = new HashSet<>();
rec_old.add("");
for (int i = 0; i < n; i++) {// 遍历每一个字符
Set<String> rec_new = new HashSet<>(); // 新的存储容器
char c = string.charAt(i);
for (String s : rec_old) { // 访问上一趟集合中的每个字符串
rec_new.add(c + s); // s之前插入字符
for (int j = 1; j < s.length(); j++) { // s的每一个空格插入字符
String subString = s.substring(0, j) + c + s.substring(j); // substring(i,j):取左不取右
rec_new.add(subString);
}
rec_new.add(s + c); // s之后插入字符
}
rec_old = rec_new; // 更新旧的容器
}
return rec_old;
}
}
6. 全排列(递归回溯法)
思路解析:
逐步递归回溯。逐个交换,递归回溯;这次选用ArrayList存储;
简单、但是不能保证字典序。
如果原数组含有大量重复元素,会造成重复排列
代码:
package 逐步生成式递归;
import java.util.ArrayList;
import java.util.TreeSet;
/**
* @author: DreamCode
* @file: 全排类_交换法_递归回溯.java
* @time: 2022年3月25日-下午10:15:33
*/
public class 全排类_交换法_递归回溯 {
static ArrayList<String> container = new ArrayList<>();
public static void main(String[] args) {
String string = "ABC";
int n = 3;
permutation(string.toCharArray(), 0);
System.out.println(new TreeSet<>(container).descendingSet());
}
private static void permutation(char[] string, int k) {
// TODO 将第i位置字符逐个交换到第k位,递归回溯
if (k == string.length) {// 当前已经构成一个完整的字符串
container.add(new String(string));
return;
}
for (int i = k; i < string.length; i++) {
swap(string, i, k); // 将第i位与第k位进行交换
permutation(string, k + 1); // 递归进入下一层
swap(string, i, k); // 回溯
}
}
private static void swap(char[] string, int i, int k) {
// TODO 交换string字符串的i位与k位
char t = string[i];
string[i] = string[k];
string[k] = t;
}
}
7. 全排列(前缀法)
思路解析:
全排列_前缀法_递归回溯;每次根据字符数组从头扫描,只要该字符可用,就将其加到前缀的后面。
字符可用的前提:该字符在前缀中出现的次数小于在数组中出现的次数;
递归出口:前缀长度=字符数组长度
复杂、但是能保证字典序,对于求第K个是最佳选择;
代码:
package 逐步生成式递归;
import java.util.ArrayList;
import java.util.List;
/**
* @author: DreamCode
* @file: 全排列_前缀法_递归回溯.java
* @time: 2022年3月25日-下午10:33:05
*/
public class 全排列_前缀法_递归回溯 {
static List<String> container = new ArrayList<>();
static long k = 0; // 记录当前是第几个排列
public static void main(String[] args) {
String string = "ABCDEFGHIJKLMNOPQRS";
permutation("", string.toCharArray());
System.out.println(container);
}
private static void permutation(String prefix, char[] charArray) {
// TODO 前缀法
if (charArray.length == prefix.length()) {
container.add(prefix);
k++;
if(k==100) {
System.out.println(prefix);
System.exit(0);
}
return;
}
//每次都从头扫描,只要该字符可用,我们就附加到前缀后面,前缀变长了
for (int i = 0; i < charArray.length; i++) {
char c = charArray[i];
if(count(prefix,c)<count(new String(charArray),c)) {
permutation(prefix+c, charArray);
}
}
}
private static int count(String string, char c) {
// TODO 计算在string字符串中字符c出现的次数
int res=0;
for(int i=0;i<string.length();i++) {
if(string.charAt(i)==c) {
res++;
}
}
return res;
}
}
8. 全排列(抽取法)
思路解析:
整体和递归回溯法相似,但是在选择元素的时候有所不同。当前元素与前一个元素相同,必须保证前一个元素已经选择才能选择当前元素,不然会有重复
代码:
package 第7届;
/**
* @author: DreamCode
* @file: G_剪邮票.java
* @time: 2022年3月13日-下午5:42:19
*/
public class G_剪邮票 {
static int[] record = { 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1 }; // 全排列的数组,为1的位置为选择的卡片
static boolean[] vis = new boolean[12]; // 记录当前的卡片是否选中的标记数组
static int ans = 0; // 种类数答案
public static void main(String[] args) {
permutation(0, new int[12]); // 全排列
System.out.println(ans);
}
private static void permutation(int k, int[] arr) {
// TODO 全排列算法:抽取式
if (k == 12) { // 排完一轮
ans++;
return;
}
for (int i = 0; i < 12; i++) {
// 当前元素与前一个元素相同,必须保证前一个元素已经选择才能选择当前元素,不然会有重复
if (i > 0 && record[i - 1] == record[i] && vis[i - 1] == false) {
continue;
}
if (vis[i] == false) { // 当前元素未抽取
vis[i] = true;
arr[k] = record[i];
permutation(k + 1, arr);
vis[i] = false; // 回溯
}
}
}
}
9. 合法括号
思路解析:
输出合法的括号组合,输入括号对数,输出所有合法组合
输入:3
输出:()()(),((())),(()()),()(()),(())(),
判断一个字符串是否合法
思路解析:
f(n)=对f(n-1)中的每一个元素左边加括号,右边加括号,外层加括号
代码:
package 逐步生成式递归;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
/**
* @author: DreamCode
* @file: 合法括号.java
* @time: 2022年3月25日-上午9:43:12
*/
public class 合法括号 {
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
int n=scanner.nextInt();
Set<String> ans= recursion(n);
for(String string:ans) {
System.out.print(string+"、");
}
}
private static Set<String> recursion(int n) {
Set<String> ans = new HashSet<>();
if(n==1) {
ans.add("()");
return ans;
}
Set<String> ans_n_1 = recursion(n-1);
for(String string:ans_n_1) {
ans.add("()"+string); //加左括号
ans.add(string+"()"); //加右括号
ans.add("("+string+")"); //外层加括号
}
return ans;
}
}
10. 硬币表示
问题描述:
假设我们有8种不同面值的硬币{1,2,5,10,20,50,100,200},用这些硬币组合构成一个给定的数值n。
例如n=200,那么一种可能的组合方式为 200 = 3 * 1 + 1*2 + 1*5 + 2*20 + 1 * 50 + 1 * 100.
问总共有多少种可能的组合方式? (这道题目来自著名编程网站ProjectEuler) 类似的题目还有:[华为面试题] 1分2分5分的硬币三种,组合成1角,共有多少种组合 1x + 2y + 5*z=10 [创新工厂笔试题]
有1分,2分,5分,10分四种硬币,每种硬币数量无限,给定n分钱,有多少组合可以组成n分钱
1 5 10 25 分 n,多少种组合方法.
思路解析:
递归方式:f(n,a)=f(n-k*coins[a],a-1)+k
迭代方式:动态规划
代码:
package 逐步生成式递归;
/**
* @author: DreamCode
* @file: 硬币表示.java
* @time: 2022年3月24日-上午9:51:15
* @思路:
*/
public class 硬币表示 {
/**
* 假设我们有8种不同面值的硬币{1,2,5,10,20,50,100,200},用这些硬币组合构成一个给定的数值n。
* 例如n=200,那么一种可能的组合方式为 200 = 3 * 1 + 1*2 + 1*5 + 2*20 + 1 * 50 + 1 * 100.
* 问总共有多少种可能的组合方式? (这道题目来自著名编程网站ProjectEuler) 类似的题目还有:
*
* [华为面试题] 1分2分5分的硬币三种,组合成1角,共有多少种组合 1*x + 2*y + 5*z=10 [创新工厂笔试题]
* 有1分,2分,5分,10分四种硬币,每种硬币数量无限,给定n分钱,有多少组合可以组成n分钱
*
* 1 5 10 25 分 n,多少种组合方法.
*/
static long[][] rec;
static int[] coins = { 1, 2, 5, 10, 20, 50, 100, 200 };
public static void main(String[] args) {
int n = 1000;
rec = new long[n + 1][coins.length];
long ans1 = recursion(n, coins.length - 1); // 递归
long ans2 = iteration(n); // 递推(动态规划)二维
long ans3 = iteration2(n); // 递推(动态规划)一维
System.out.println(ans1);
System.out.println(ans2);
System.out.println(ans3);
}
private static long iteration2(int n) {
// TODO 迭代方式(动态规划)一维
int[] dp = new int[n + 1]; // 记录每种面值可以兑换的方法数
dp[0] = 1; // 面值为0只有一种兑换方法
for (int i = 0; i < coins.length; i++) {
for (int j = coins[i]; j <= n; j++) {
dp[j] = dp[j] + dp[j - coins[i]];
}
}
return dp[n];
}
private static long iteration(int n) {
// TODO 迭代方式(动态规划)二维
int[][] dp = new int[coins.length][n + 1];
for (int i = 0; i <= n; i++) { // 只有“1”这种硬币的兑换方式都只有1种
dp[0][i] = 1;
}
for (int i = 0; i < coins.length; i++) { // 不管有哪些硬币,兑换“0”都只有一种方法
dp[i][0] = 1;
}
for (int i = 1; i < coins.length; i++) {
for (int j = 1; j <= n; j++) { // 用前i种硬币兑换j的面值
for (int k = 0; k <= j / coins[i]; k++) { // 当前兑换i种硬币的个数
dp[i][j] += dp[i - 1][j - k * coins[i]];
}
}
}
return dp[coins.length - 1][n];
}
private static long recursion(int n, int available) {
// TODO 递归方式:f(n,a)=f(n-k*coins[a],a-1)+k
if (rec[n][available] != 0) {
return rec[n][available];
}
if (available == 0 || n == 0) { // 当前只有“1”这一种硬币,必然只有一种兑换方法
return 1;
}
long res = 0;
for (int k = 0; k * coins[available] <= n; k++) {
res += recursion(n - k * coins[available], available - 1);
}
rec[n][available] = res;
return res;
}
}