”逐步生成结果“类问题之数值型
自下而上的递归(递归,数学归纳,动态规划)
-解决简单情况下的问题
-推广到稍复杂情况下的问题
...
-如果递推次数很明显,用迭代
-如果有封闭形式,可以直接求解
f(n)=f(n-1)+f(n-2)
迭代也叫递归,但不是编程语言的递归
上楼梯
有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶、3阶
请实现一个方法,计算小孩有多少种上楼梯得的方式
为了防止溢出,请将结果Mod 1000000007
给定一个正整数int n,返回一个数,代表上楼的方式数。
保证n小于等于100000
static final int mod = 1000000007;
public static long recursion(int n){
if(n<0)return 0;//小于0走不了
if(n==0||n==1)retrun 1;//从0直接一步到第三阶,从1直接一步到三第阶
if(n==2)return 2;//从0到2有两种方式,0->1->2,0->2,然后从2阶一步到三阶,但是总共有两种方式
return recursion(n-1)%mod+recursion(n-2)%mod+recursion(n-3)%mod;%mod;
}
//循环迭代,代码长,但性能高
public static int recursion2(int n){
if(n<0)return 0;
if(n==0||n==1)return 1;
if(n==2)return 2;
if(n==3)return 4;
int x1=1;
int x2=2;
int x3=3;
for(int i=4;i<=n;i++){
int x_1=x1;
x1=x2%mod;
x2=x3%mod;
x3=((x1+x2)%mod+x1)%modl//注意此处
}
return x3;
}
机器人走方格
有一个X*Y的网络,一个机器人只能走格点,且只能向右或向下走,要从左上角走到右下角
请设计一个算法,计算机器人有多少种走法
给定两个正整数int x,int y,请返回机器人的走法数目,保证x+y小于等于12
与走楼梯类似
【1】【1】【1】
【1】【1+1=2】【2+1=3】
【1】【1+2=3】【3+3=6】
递推公式
DP公式 f(x,y)=f(x,y-1)+f(x-1,y);
public static void main(String[] args){ System.out.println(solve(2,3)); }
public static int solve(int x,int y){
if(x==1||y==1)return 1;
return solve(x-1,y)+solve(x,y-1);
}
public static int solve1(int m,int n){
int[][] state=new int[m-1][n-1];
for(int i=1;i<=n;i++){
int[1][i]=1;
}
for(int i=1;i<=m;i++){
int[i][1]=1;
}
for(int i-2;i<=m;i++){
for(int j=2;<=n;j++){
state[i][j]=state[i][j]+state[i-1][j];
}
}
return state[][];
}
边界初始化为1,然后依此递推
题解:硬币问题
假设我们有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,可以有多少种组合方法
1 5 10 25->n
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ......25
1 1 1 1 2 2 2 2 2 4 4 4 4 4 6 ......
15->0*10->0*5
->1*5
->2*5
->3*5
->1*10->0*5
->1*5
f(25)=f(要1个25,剩0,1 5 10)+f(要0个25,剩25,1 5 10)
f(50)=f(要2个25,剩0,1 5 10)+f(要1个25,剩25,1 5 10)+f(要0个25,剩50,1 5 10)
参数
出口
最大面值用多少个
//递归形式
public int countWays(int n){
if(n<=0)return 0;
return countWays(n,new int[]{1,5,10,15},3);
}
private int countWaysCore(int n,int[] coins,int cur){
if(cur==0)return 1;//到最小的硬币就只有一种选法了
int res=0;
//不选coins[cur]
//要一个
//要两个
//coin[cur]最大面值
for(int i=0;i*coins[cur]<=n;i++){
int shengyu=n-i*coins[cur];
res+=countWaysCore(shengyu,coins,cur-1);//cur-1下一个最大值
}
return res;
}
//若是最小的硬币不是1,则上述代码,似乎没有判断,选完最小的,但是不足以达到n总数,当这种情况下,则应该返回0
//dp形式
int[][] state;
private int countWays1(int n){
int[] coins={1,5,10,25};
int[][] dp=new int[4][n+1];//前i种面值,组合出面值j
for(int i=0;i<4;i++){
dp[i][0]=1;//存储面值0,只有一种,第一列初始化为1
}
for(int j=0;j<n+1;j++){
dp[0][j]=1;//用1来凑任何面值都只有一种凑法,第一行初始化为1
}
for(int i=1;i<4;i++){
for(int j=0;j<n+1;j++){
for(int k=0;k<=j/coins[i];++k){
dp[i][j]+=dp[i-1][j-k*coins[i]];//从0个开始选,数组更新结果的总和
}
}
}
}
为什么写出递归
-因为递归有更强的表达力
递归=递推={递归,迭代}
本质为数学归纳
记忆型递归,迭代都叫做动态规划
“逐步生成结果”类问题之非数值型
题解:合法括号
实现一种算法,打印n对括号的全部有效组合(即左右括号正确配对)
n 组合
1 ()
2 ()(), (())
3 ()()(),()()(),(()()) ()(()),(())(),((()))
左,右,包一生三
s(n)={对s(n-1)中每个元素 添左,添右,添外
//逐步生成之递归算法
public Set<String>parenthesis(int n){
Set<String>s_n=new HashSet();//HashSet可以去重
Set<String> s_n_i=parenthesis(n-1);
for(String eOfN_1:s_n_1){
s_n.add("()"+eOfN_1);//填左
s_n.add(eOfN_1+"()");//添右
s_n.add("("+eOfN-1+")");//添外
}
return s_n
}//也是从底层开始的
//迭代形式
private See<String>parenthesis(int n){
Set<String>res=new HashSet();//保存上次迭代的状态
res.add("()");
if(n==1){
return res;//若是n=1直接就退出了
}
for(int i=2;i<=n;i++){
for(String e:res){
Set<String>res_new=new HashSet<>();
for(String e:res){
res_new.add(e+"()");
res_new.add("()"+e);
res_new.add("("+e+")");
}
res=res_new;//每一层结束后,更新
}
return res; //返回最后一层res
}
}
题解:非空子集(子集生成)
给定一个int数组A和数组大小int n,请返回A的所有非空子集
保证A的元素个数小于等于20,且元素互异
各子集内部从大到小排序,子集之间字典逆序排序
{A,B,C}-->{A},{B},{C},{A,B},{A,C},{B,C},{A,B,C}
原有集合的子集
C1+C2+C3
3 3 3
{A}<-{}->{}
{A}<-{A}->{A,B} {B}<-{}->{}
{A}<-{A}->{A,C} {A,B}<-{A,B}->{A,B,C} {B}<-{B}->{B,C} {}<-{}->{C}
选与不选
public Set<Set<Integer>>getSubSets3(int[] A,int n){
return getSubsets3Core(A,n,n-1);
}
private Set<Set<Integer>>getSubSet3Core(int[] A,int n,int cur){
if(cur==0){//处理第一个元素
Set<Set<Integer>>nil=new HashSet<>();//空集
Set<Set<Integer>>first =new HashSet<>();//包含第一个元素的集合
first.add(A[0]);
newSet.add(nil);
newSet.add(first);
return newSet;
}
Set<Set<Integer>> oldSet=getSubset3Core(A,n,cur-1);
Set<Set<Integer>> newSet=new HashSet<>();//消重
for(Set<Integer> set:oldSet){
//对于每个子集,cur这个元素可以加进去,也可以不加进去
newSet.add(set);//不加进去,保留原样
Set<Integer>clone =(Set<Integer>)((HashSet)set).clone();//克隆
clone.add(A[cur]);
newSet.add(clone);//加进去
}
return newSet;
}
//最后会有一个空集,需要处理一下,将第一个元素去掉就可以了
//用迭代器删除
Iterator<Set<Integer>> iterator=set.iterator();
if(iterator.hasNext()){//判断下一个是否含有元素,下标从-1开始
iterator.next();//元素到下一个
ieterator.remove();//删除当前元素
}
//逐步生成迭代大法
public Set<Set<Integer>> getSubsets2(int[] A,int n){
Set<Set<Integer>> res=new HashSet<>();
res.add(new HashSet<>());//初始化为空集//匿名对象,也可以加入集合中,显示空集
//从第一个元素开始处理
for(int i=0;i<n;i++){
Set<Set<Integer>> res_new=new HashSet<>();//新建一个大集合
res_new addAll(res);//把原来集合中的每个子集都加入到新集合中
//遍历之前的集合全部克隆一遍
for(Set e:res){
Set clone=(Set)((HashSet)e).clone();
clone.add(A[i]);//把当前元素加进去
res_new.add(clone);//把克隆的子集加到大集合中
}//将整体不选的加入大集合,拷贝整体不选的,整体加上选的元素,再将整体选的加入大集合,
res=res_new;
}
return res;
}
子集生成之二进制
{A,B,C}
111
110
101
100
011
010
001
000
去掉空集就是2^3-1
1~2^3-1
S=new Set
number中二进制为1
S.add(对应的元素)
public ArrayList<ArrayList<integer>> getSubsets(int[] A,int n){
Arrays.Sort(A);//正序排序
ArrayList<ArrayList<Integer>> res=new ArrayList<>();//大集合
for(int i=Case11_NExpoment.ex(2,n)-1;i>0;i--){//大数字-1
ArrayList<Integer>S=new ArrayList<>();//对每个i建立一个集合
for(int j=n-1;j>0;j--){//检查哪个位上的二进制为1,从高位开始检查,高位对应着数组靠后的元素
if(((i>>j)&1)==1){
s.add(A[j]);
}
}
res.add(s);
}
return res;
}
题解:全排列I
{A,B,C}
每一个都要选,但是顺序不同
ABC ACB
BAC BCA
CAB CBA
[3]*[2]*[1]=6
N!当N>7的时候就全面超越2^n-1
每个|都代表可以插入的位置
1 |A|
2 |B|A| |A|B|
3 CBA BCA BAC CAB ACB ABC
//逐步生成打法 迭代法
public ArrayList<String>getPermutation0(String A){
int n=A.length();
ArrayList<String> res=new ArrayList<>();
res.add(A.charAt(0)+"");//初始化,包含一个字符
for(int i=1;i<n;i++){//从第二个字符开始
ArrayList<String>res_new=new ArrayList<>();
char c=A.charAt(i);//新字符
for(String str:res){//访问上一趟集合中的每个字符串
//插入到每个位置,形成一个新串
String newStr=c+str;//加在前面
res_new.add(newStr);
newStr=Str+c;//加在后面
res_new.add(new);
//加在中间,中间有很多位置,因此需要一个循环来依此加入
for(int j=1;j<str.length();j++){//数组是.length,字符串String 和BuilderString BufferString都是.length()
//将字符串拆开,再插入
newStr=str.substring(0,j)+c+str.substring(j);//substring本身是一个单词,中间的S不大写
res_new.add(newStr);
}
}
res=res_new;
}
retrun res;
}
全排列II
回溯[A,B,C]
打乱顺序
每个元素都尝试着放到头部
[ABC]
A[BC] B[] C[]
B[C] C[B]
C B
[A][B][C] [A][C][B]
多路递归(回溯)
for(i=k...N-1){
swap(k,i);//交换
f(arr,k+1);//递归结束
swap(k,i);//换回来
}
ArrayList<String> res=new ArrayList<>();
public ArrayList<String> getPermutation(String A){
char[] arr=A.toCharArray();
Arrays.sort(arr);//abcd
getPermutationCore(arr,0);
return res;
}
private void getpermutationCore(char[] arr,int k){
if(k==arr.length){
res.ade(new String(arr));
}
//从k位开始的每个字符,都尝试放在新排列的k这个位置上
for(int i=k;i<arr.length;i++){
swap(arr,k,i);//把后缀每个字符换到k位
getpermutationCore(arr,k+1);
swap(arr,k,i);//回溯
}
}
//交换位置
static void swap(char[] arr,int i,int j){
char temp=char[i];
char[i]=char[j];
char[j]=temp;
}
全排列III
前缀法
A A B C-->先找到A,再找A,再找B,再找C AABC
一个排列结束,变回前缀AAB此时下标从C开始,继续往下找,遍历一遍没找到
则前缀回到了AA,从B开始继续找,找到C,前缀变为AAC,再找到B,AAC,又一个排列结束
final static int k=3;
static int count=0;
public static void permutation(String prefix,char[] arr){
//prefix前缀,相当于一个筐,当筐填满了,就是一个排列完成了,然后退回上一个前缀,继续找,一遍找完位置,若没找到,则在回到上一个前缀。继续下一个找
if(prefix.length()==arr.length){//前缀的长度==字符集的长度,一个排列就完成了
count++;
if(count==k){
System.out.println("--------:"+prefix);
System.exit(0);
}
}
//每次都从头扫描,只要该字符可用,我们就附加到前缀后面,前缀变长了
for(int i=0;i<arr.length;i++){
char ch=arr[i];
//这个字符串可用,在pre中出现次数<在字符集中的出现次数
if(count(prefix,ch)<count(arr,ch)){
permutation(prefix+ch,arr);
}
}
}
private static int count(char[] arr,char ch){
int cnt=0;
for(char c:arr){
if(c==ch)cnt++;
}
return cnt;
}
交换法,--简介,重复,无法维持字典序
前缀法,--复杂,可维持字典序
封闭式举例
-汉诺塔移动次数
-斐波那契数列第n项
-上楼梯
运算的次数
f(n)=2f(n-1)+1
f(n-1)=2^(n-1)+1
f(n)=2(2^(n-1))+1
1 f(3)
2 f(4)
3 f(5)
. .
. .
n-2 f(n)
f(n)=f(n-1)+f(n-2)+f(n-3)
[f1,f2,f3] * [0 0 1]-->[f2,f3,f4]
[1 0 1]
[0 1 1]
1 4
2 5
3 6
..
n-3 n