1、“逐步生成结果”类问题–数值型
自下而上的递归(递推、数学归纳、动态规划)
1、走楼梯
1、问题
2、思路
(1)第一步选择(4台阶情况):
第一步走1梯:剩下的就是3梯的情况
第一步走2梯:剩下的就是2梯的情况
第一步走3梯:剩下的就是1梯的情况
(2)递推公式 n>3 f(n)=f(n-3)+f(n-2)+f(n-1)
2、代码
package com.lanqiao.eight;
public class tizi {
public static void main(String[] args) {
System.out.println(f(5));
}
public static int f(int n)
{
if(n==0) return 1;//注意!,比如f(3)第一步走三梯,直接是f(0)这也是一种情况,所以应该是1
if(n==1) return 1;
if(n==2) return 2;
if(n==3) return 4;
return f(n-3)+f(n-2)+f(n-1);
}
}
3、注意点
(1)注意f(0)=1
2、机器人走方格
1、题目
2、思想
(1)递推公式解法
(1)只有一行或一列:只有1种走法
(2)2行2列:2种走法(也是向下:单行单列,向右:单行单列)
(3)2行3列:向下走一个就是2行2列的情况(2),向右走一个就是单行单列的情况(1)
3行2列:向右走就是2行2列的情况(2),向下走就是单行单列的情况
(4)3行3列:右走:2行3列,下走:3行2列
(5)递推公式:f(x,y)=f(x,y-1)+f(x-1,y)
(2)直接迭代解法
每个格子都是相邻两个格子相加
二维数组行数代表有多少行,列数数字代表有多少列
3、代码
package com.lanqiao.eight;
public class wangge {
public static void main(String[] args) {
System.out.println(f(3,3));
System.out.println(f2(3,3));
}
public static int f(int x,int y)
{
if(x==1||y==1) return 1;
return f(x,y-1)+f(x-1,y);
}
public static int f2(int x,int y)
{
int[][] ans = new int[x+1][y+1];
for(int i=1;i<=x;i++)//第一列填充满
{
ans[i][1]=1;
}
for (int i = 1; i <=y; i++) {
ans[1][i]=1;
}
for(int i=2;i<=x;i++)
{
for(int j=2;j<=y;j++)
{
ans[i][j]=ans[i][j-1]+ans[i-1][j];
}
}
return ans[x][y];
}
}
1、“逐步生成结果”类问题–非数值型
1、逐步生成括号
1、问题
给出一个数字,相当于给出几个括号,判断排列方式
2、思想
(1)Set集合特点:不会存储相同元素(去重)
(2)Set两个实现类:HashSet TreeSet(按红黑树有序排列其中元素)
(3)Set使用方法参照:https://www.cnblogs.com/xiaxj/p/7891963.html
(4)借助Set的去重特点,向其中不断加入元素,遍历到所有情况
3、代码
package com.lanqiao.eight;
import java.util.HashSet;
import java.util.Set;
public class kuohao {
public static void main(String[] args) {
Set<String> ans = f(3);
for(String e:ans)
{
System.out.println(e);
}
}
public static Set<String> f(int n)
{
Set<String> ans = new HashSet<String>();
ans.add("()");
if(n==1) return ans;
for(int i=2;i<=n;i++)
{
Set<String> ans1 = new HashSet<String>();
for(String e:ans)
{
ans1.add(e+"()");
ans1.add("()"+e);
ans1.add("("+e+")");
}
ans = ans1;
}
return ans;
}
}
2、列出所有子集
1、问题
2、思路
(1)进行递归思考时,一般是从1~n进行递推思考
(2)从下往上逐步减小cur,对每个元素是否加入到当前序列分情况考虑。
(3)边界条件cur=0即考虑第一个元素,此时要考虑将空集加入到集合中,因为只有加了空集才会出现{2},{3},{4}等这些情况
方法2:
(1)二进制法则,根据元素个数计算出应该有的自己个数2^n-1,根据子集个数进行循环
(2)将自己转为2进制,通过左移查看每一位是1还是0,是1就将这一位的元素添加到当前集合中
3、代码
package com.lanqiao.eight;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
public class ziji {
public static void main(String[] args) {
int[] arr = {1,2,3};
// Set<Set<Integer>> ans = getSet(arr, arr.length-1, arr.length-1);
// for(Set set:ans)
// {
// System.out.print("{");
// for(Object a:set)
// {
// System.out.print(a+",");
// }
// System.out.print("}");
// System.out.println();
// }
ArrayList<ArrayList<Integer>> ans2 = getSet2(arr, arr.length);
for(ArrayList<Integer> a:ans2)
{
System.out.print("{");
for(Integer i:a)
{
System.out.print(i+" ");
}
System.out.print("}");
System.out.println();
}
}
/**
*
* @param arr 要处理的元素
* @param n 元素个数
* @param cur 当前处理元素下标
* @return
*/
public static Set<Set<Integer>> getSet(int[] arr,int n,int cur)
{
Set<Set<Integer>> ans = new HashSet<>();
if(cur==0)//处理到第一个元素,添加空集
{
Set<Integer> nil = new HashSet<>();//建立空集,HashSet不用任何例子
Set<Integer> first = new HashSet<>();
first.add(arr[0]);
ans.add(nil);
ans.add(first);
return ans;
}
Set<Set<Integer>> last = getSet(arr,n,cur-1);
for(Set<Integer> set:last)//对得到的上个集合中每个集合判定当前元素加还是不加
{
ans.add(set);//保留上个集合原样
Set<Integer> clone = (Set<Integer>)((HashSet)set).clone();
clone.add(arr[cur]);//关键点:如果是原set添加则会导致上面添加的set也会改变:地址引用
ans.add(clone);
}
return ans;
}
public static ArrayList<ArrayList<Integer>> getSet2(int[] arr,int n)
{
ArrayList<ArrayList<Integer>> ans = new ArrayList<>();
for(int i=(int)Math.pow(2, arr.length);i>0;i--)
{
ArrayList<Integer> s = new ArrayList<>();
for(int j =n-1;j>=0;j--)
{
if(((i>>j)&1)==1)//
{
s.add(arr[j]);
}
}
ans.add(s);
}
return ans;
}
}
4、注意点
(1)建立空集,HashSet不用任何例子
(2)如果是原set添加则会导致上面添加的set也会改变:地址引用,所以调用clone方法克隆一个Set
3、全排列
1、问题
给定一个字符串,列出其全排列的情况
2、思想
思想1:
(1)先将第一个字符加入到结果数组中,对后面每个字符分别放在数组中已放好元素的前、后、中间三种情况,直到最后一个数组完成
思想2:
(1)利用回溯思想
(2)先将字符串转为字符数组,对字符数组中每一对元素进行一次翻转,翻转完成对下一对进行翻转,翻转到最后一个字符,加入到结果数组,并返回上一层,将翻转字符数组恢复。
(3)注意要恢复已经翻转的数组
(4)回溯法参照:https://blog.csdn.net/qq_43313035/article/details/93124719
排列树:
3、代码
package com.lanqiao.eight;
import java.util.ArrayList;
import java.util.Arrays;
import com.lanqiao.vidio.Utils;
public class quanpailie {
public static void main(String[] args) {
ArrayList<String> ans = getArr2("abc");
for(String s:ans)
{
System.out.print(s+" ");
}
}
public static ArrayList<String> getArr(String s)
{
ArrayList<String> ans = new ArrayList<>();
int n = s.length();
ans.add(s.charAt(0)+"");//注意!char转String方法
for(int i=1;i<n;i++)
{
ArrayList<String> newArr = new ArrayList<>();
for(int j = 0;j<ans.size();j++)
{
String newStr = s.charAt(i)+ans.get(j);//加在前面
newArr.add(newStr);
newStr = ans.get(j)+s.charAt(i);//加在后面
newArr.add(newStr);
for(int k=1;k<ans.get(j).length();k++)//加在中间
{
newStr = ans.get(j).substring(0,k)+s.charAt(i)+ans.get(j).substring(k);//subString是从中间截取到最后
newArr.add(newStr);
}
}
ans=newArr;
}
return ans;
}
//2、回溯法
static ArrayList<String> ans = new ArrayList<>();
public static ArrayList<String> getArr2(String s)
{
char[] arrList = s.toCharArray();
Arrays.sort(arrList);
paiLie(arrList,0);
return ans;
}
private static void paiLie(char[] arr, int k) {
if(k==arr.length)
{
ans.add(new String(arr));
}
for(int i=k;i<arr.length;i++)
{
Utils.swap(arr, k, i);
paiLie(arr,k+1);
Utils.swap(arr, k, i);
}
}
}
3、封闭形式直接解
1、汉诺塔移动次数
(1)n个圆盘,先将n-1个从a->c f(n)=f(n-1)
(2) 将最后一个盘子挪到b f(n)=f(n-1)+1
(3) 将c柱的n-1个盘子挪到b f(n)=2f(n-1)+1
最终总结出f(n)=2^n -1
数学归纳法:
得出封闭式解,可以直接算
2、斐波那契数列第n项
可以参考:https://blog.csdn.net/qq_38930523/article/details/80303694
总结:斐波那契第n项就是
[1,1] *第二个矩阵的n-2次方次
3、上楼梯
思想:
两个矩阵乘一次是 f2,f3,f1+f2+f3=f4 得到f4
乘右边两次是 f3,f4,f5 得到f5
…不断乘右边矩阵最后得到结果