暴力递归
public class HanNuoTa {
public static void hannuo(int n) {
if(n>0) {
function(n,"左","右","中");
}
}
public static void function(int i, String start, String end, String other) {
if(i==1) {
System.out.println("Move 1 from"+start+"to"+end);
}else {
function(i-1,start,other,end);//第一步,把i-1个从初始移动到其他
System.out.println("Move "+i+"from "+start+"to "+end);//第二步,把第i个从初始移动到目的地
function(i-1,other,end,start);//第三步:把之前的i-1个从其他移动到目的地
}
}
public static void main(String[] args) {
int n=4;
hannuo(n);
}
}
abc,ab,ac,a,bc,b,c,“”(我没写代码)
打印一个字符串的全排列,可以出现重复的排列,下面是它的代码
/*字符串的全排列*/
public class StringsQuanPaiLie {
public static ArrayList<String> QuanPaiLie(String str){
ArrayList<String> res=new ArrayList<>();
if(str==null||str.length()==0) {
return res ;
}
char[] chs=str.toCharArray();
process(chs,0,res);
res.sort(null);
return res;
}
public static void process(char[] str, int i, ArrayList<String> res) {
if(i==str.length) {//如果来到了末尾
res.add(String.valueOf(str));//str承载了所有之前的选择
}
for(int j=i;j<str.length;j++) {//i往后所有的字符都可以来到i位置,
swap(str,i,j);
process(str,i+1,res);
swap(str,i,j);
}
}
public static void swap(char[] str, int i, int j) {
char tmp=str[i];
str[i]=str[j];
str[j]=tmp;
}
public static void main(String[] args) {
ArrayList<String> result=QuanPaiLie("abbb");
for(int i=0;i<result.size();i++) {
System.out.println(i+"---"+result.get(i));
}
}
}
运行结果:
打印一个字符串的全排列,不出现重复的排列,下面是它的代码
/*字符串的全排列,不重复*/
public class StringsQuanPaiLieUnique {
public static ArrayList<String> QuanPaiLieUnique(String str){
ArrayList<String> res=new ArrayList<>();
if(str==null||str.length()==0) {
return res ;
}
char[] chs=str.toCharArray();
process(chs,0,res);
res.sort(null);
return res;
}
public static void process(char[] str, int i, ArrayList<String> res) {
if(i==str.length) {//如果来到了末尾
res.add(String.valueOf(str));//str承载了所有之前的选择
}
boolean[] visit=new boolean[26];//visit[0,1,2...25]表示a,b,c,d...z试过还是没试过
for(int j=i;j<str.length;j++) {//i往后所有的字符都可以来到i位置,
if(!visit[str[j]-'a']) {//如果当前字符没试过,就试,注册上
visit[str[j]-'a']=true;
swap(str,i,j);
process(str,i+1,res);
swap(str,i,j);
}
}
}
public static void swap(char[] str, int i, int j) {
char tmp=str[i];
str[i]=str[j];
str[j]=tmp;
}
public static void main(String[] args) {
ArrayList<String> result=QuanPaiLieUnique("abbb");
for(int i=0;i<result.size();i++) {
System.out.println(i+"---"+result.get(i));
}
}
}
运行结果:
先手函数定义
后手函数定义
代码:
/*系统栈结构逆序,能帮助理解递归
* https://www.bilibili.com/video/BV13g41157hK?p=10
* 1:39:40*/
public class SystemStackReverse {
public static void reverse(Stack<Integer> stack) {
if(stack.isEmpty()) {
return;
}
int i=f(stack);
reverse(stack);
stack.push(i);
}
public static int f(Stack<Integer> stack) {
int result=stack.pop();
if(stack.isEmpty()) {
return result;
}else {
int last=f(stack);
stack.push(result);
return last;
}
}
public static void main(String[] args) {
Stack<Integer> test=new Stack<Integer>();
test.push(1);
test.push(2);
test.push(3);
test.push(4);
test.push(5);
test.push(6);
reverse(test);
while(!test.isEmpty()) {
System.out.println(test.pop());
}
}
}
代码片
/*规定1和A对应、2和B对应、3和C对应...
那么一个数字字符串比如"111",就可以转化为"AAA”、“KA"和"AK"
给定一个只有数字字符组成的字符串str,返回有多少种转化结果。
*/
public class NumToChar {
public static int number(String s) {
char[] intChar=s.toCharArray();
return process(intChar,0);
}
public static int process(char[] str,int i) {
if(i==str.length) {//如果i来到了字符串结束的位置,说明可以作为一种结果返回
return 1;
}
if(str[i]=='0') {
return 0;//如果当前来到了字符0,说明后面走不通了
}
if(str[i]=='1') {
int res=process(str,i+1);//i自己作为单独的部分,后续有多少种方法
if(i+1<str.length) {
res+=process(str,i+2);//(i和i+1)作为单独的部分,后续有多少种方法
}
return res;
}
if(str[i]=='2') {
int res=process(str,i+1);//i自己作为单独的部分,后续有多少种方法
if(i+1<str.length&&(str[i+1]>='0'&&str[i+1]<='6')) {
//i和i+1作为单独的部分并且没有超过26,后续有多少种方法
res+=process(str,i+2);
}
return res;
}
//str[i]=='3'-'9'时
return process(str,i+1);
}
public static void main(String[] args) {
System.out.println(number("11"));
}
}
思路:就是试,0号货要或不要,1号货要或不要…,【测试代码有错误,就不展示了】
暴力递归上
递归解法
/**
* 马要跳到(a,b)位置,总共走K步
* 总共9列10行*/
public class HorseJump {
public static int getWays(int x,int y,int k) {
return process(x,y,k);
}
//从(0,0)出发,跳step步来到(a,b)位置的方法数有多少种,a是行,b是列,(a,b)是目标位置
public static int process(int a, int b, int step) {
if(a<0||a>8||b<0||b>9) {
return 0;//无法到达
}
if(step==0){//已经跳完,不能动了
return (a==0&&b==0)?1:0;
}
//不越界也有步数可以跳
return process(a-1,b+2,step-1)//从以下位置跳一步就可以到达(a,b),从(0,0)出发跳step-1步到达这些位置就可以了
+process(a-2,b+1,step-1)
+process(a+1,b+2,step-1)
+process(a+2,b+1,step-1)
+process(a+1,b-2,step-1)
+process(a+2,b-1,step-1)
+process(a-1,b-2,step-1)
+process(a-2,b-1,step-1);
}
public static void main(String[] args) {
//测试从(0,0)出发走step=10,到达(6,6)的方法数有多少种
int res=getWays(6,6,10);
System.out.println(res);
}
}
改为动态规划
如果是一个体的话,要的是最高层的值,也是一个面,(具体是那=哪一个值,由x,y决定)
public static int dpWays(int x,int y,int step) {//走step步到达(x,y),有多少种走法
if(x<0||x>8||y<0||y>9||step<0) {
return 0;
}
int[][][] dp=new int[9][10][step+1];
dp[0][0][0]=1;
for(int h=1;h<=step;h++) {//上面的层由下面的层推出
for(int r=0;r<9;r++) {
for(int c=0;c<10;c++) {
dp[r][c][h]+=getValue(dp,r-1,c+2,h-1);
dp[r][c][h]+=getValue(dp,r+1,c+2,h-1);
dp[r][c][h]+=getValue(dp,r+2,c+1,h-1);
dp[r][c][h]+=getValue(dp,r+2,c-1,h-1);
dp[r][c][h]+=getValue(dp,r+1,c-2,h-1);
dp[r][c][h]+=getValue(dp,r-1,c-2,h-1);
dp[r][c][h]+=getValue(dp,r-2,c-1,h-1);
dp[r][c][h]+=getValue(dp,r-2,c+1,h-1);
}
}
}
return dp[x][y][step];
}
public static int getValue(int[][][] dp, int row, int col, int step) {//给定一个dp表
if(row<0||row>8||col<0||col>9) {//解决越界问题,如果越界,就让它累加0
return 0;
}
return dp[row][col][step];//如果不越界,就让它累加
}
public static void main(String[] args) {
//测试从(0,0)出发走step=10,到达(7,7)的方法数有多少种
int x=7;
int y=7;
int step =10;
System.out.println(getWays(x,y,step));//一般方法
System.out.println(dpWays(x,y,step));//动态规划
}
给定一个行数和列数,表示这个格子有多大
这个人可以往左往右往上往下(各个方向概率相等),越界会死掉,问这个人生存下来的概率是多大
public class BobLive {
//(N,M)指区域的大小,(i,j)指bob目前所在的位置,K是所走的步数
public static String bob1(int N,int M,int i,int j,int K) {
long all=(long) Math.pow(4, K);//先求出总数,不考虑越界死掉的时候,bob可以往上往下往左往右,是4的k次方
long live=process(N,M,i,j,K);//在算出bob活下来的方法数
long gcd=gcd(all,live);//求最大公约数
return String.valueOf(live/gcd)+"/"+(all/gcd);//把分子越掉最大公约数,也把分母约掉最大公约数
}
//N*M的区域,Bob从(row,col)位置出发,走rest步之后,获得的生存方方法数
public static long process(int N, int M, int row, int col, int rest) {
if(row<0||row==N||col<0||col==M) {
return 0;
}
//row,col没越界,又已经走完的情况下 ,活下来了
if(rest==0) {
return 1;
}
//还没走完,row,col也没越界
long live =process(N,M,row-1,col,rest-1);
live+=process(N,M,row+1,col,rest-1);
live+=process(N,M,row,col-1,rest-1);
live+=process(N,M,row,col+11,rest-1);
return live;
}
public static long gcd(long m, long n) {
return n==0?m:gcd(n,m%n);
}
//三维尝试做动态规划(做体),二维尝试做动态规划(做表),画出图来,最终想要的位置标出星号,
//通过basecase决定哪些位置可以直接出答案,分析普遍位置依赖的时候,二维表三维标也是一样的,那个位置先推,那个位置后推,
//规定好搭积木的顺序,最终把需要的积木块返回
public static void main(String[] args) {
int N=5, M=6, i=2,j=2,K=5;
System.out.println(bob1(N,M,i,j,K));
}
}
给定一个数组,每个位置的值代表面值,如【2,3,5,10】,面值类型无重复,,面值张数有任意张,要使用这些面值凑够1000,问方法数有多少种
普通递归解法:
for(int zhang=0;arr[index]*zhang<=rest;zhang++) {//0位置的面值使用0张的方法数,0位置的面值使用1张的方法数,...
ways+=process(arr,index+1,rest-arr[index]*zhang);//递归循环,1位置的面值使用0张的方法数,1位置的面值使用1张的方法数...,
}
完整代码:
//arr里都是正整数,没有重复值,每个值代表一种货币,每一种都可以使用无限张
//最终要找零钱数是aim
//找零方法数返回
public static int way1(int[] arr,int aim) {
return process(arr,0,aim);
}
//可以自由使用arr[index...]所有的货币
//需要搞定的钱数是rest
//返回找零 的方法数
public static int process(int[] arr, int index, int rest) {
if(index==arr.length) {
return rest==0?1:0;
}
//arr[index] 0张1张...不超过rest的钱数
int ways=0;
for(int zhang=0;arr[index]*zhang<=rest;zhang++) {//0位置的面值使用0张的方法数,0位置的面值使用1张的方法数,...
ways+=process(arr,index+1,rest-arr[index]*zhang);//递归循环,1位置的面值使用0张的方法数,1位置的面值使用1张的方法数...,
}
return ways;
}
测试代码:
public static void main(String[] args) {
int[] mianZhiArr= {2,3,5,10};
int aim=10;
System.out.println(way1(mianZhiArr,aim));
}
改为动态规划,让aim=10,数组长度为4,则aim范围是0到10,index是0到4,行列都补充了终止位置,
//从左往右,从下往上填
for(int row=N-1;row>=0;row--) {//行数从N-1行开始填,
for(int col=0;col<=aim;col++) {
int ways=0;
for(int zhang=0;arr[row]*zhang<=col;zhang++) {
ways+=dp[row+1][col-arr[row]*zhang];//调用递归行为改为拿表行为就可以了
//col是由当前的货币值和选择的张数决定的
}
dp[row][col]= ways;//ways最终给当前你要求的格子,即结果数
}
}
完整代码
public static int dp1(int[] arr, int aim) {//arr代表行,index,aim代表列,剩余的钱
if(arr==null||arr.length==0) {
return 0;
}
int N=arr.length;
int[][] dp=new int[N+1][aim+1];//一张dp表
dp[N][0]=1;//最后一行只有0位置是1,其他位置都是0
//从左往右,从下往上填
for(int row=N-1;row>=0;row--) {//行数从N-1行开始填,
for(int col=0;col<=aim;col++) {
int ways=0;
for(int zhang=0;arr[row]*zhang<=col;zhang++) {
ways+=dp[row+1][col-arr[row]*zhang];//调用递归行为改为拿表行为就可以了
//col是由当前的货币值和选择的张数决定的
}
dp[row][col]= ways;//ways最终给当前你要求的格子,即结果数
}
}
return dp[0][aim];
}
测试代码
public static void main(String[] args) {
int len=10;
int max=10;
int testTime=10000;//对这两个方法测试10000次
for(int i=0; i<testTime;i++) {
int[] arr=genetatrRandomArray(len,max);
int aim=(int) (Math.random()*3*max)+max;
System.out.println(way1(arr,aim));
System.out.println(dp1(arr,aim));
if(way1(arr,aim)!=dp1(arr,aim)) {
System.out.println("出错了");
break;
}
}
}
优化想法及代码,所求位置只需要它左边和下边这两个格子,因为左边的额那个格子=b+c+d+…,所求位置的格子=a+b+c+d+…,
暴力递归下
有序表的时间复杂度都是O(log(N))级别的–红黑树、AVL、SB、跳表都可以实现有序表,前三个红黑树、AVL(平衡二叉树)、SB是平衡搜索二叉树系列,跳表实单链表改写。
BST是指搜索二叉树
首先要实现搜索二叉树的增删改查(从头结点出发,大的话就往右划,小的话往左划,默认搜索二叉树是没有重复结点的),如果考虑平衡性,就是平衡搜索二叉树。
添加一个结点:
搜索一个结点:
***删除一个结点:***首先搜索该节点是否存在,
1.如果该节点没有左右孩子,让他的父节点指针悬空即可
2.如果该节点有左右孩子中的一个,让他的孩子替换他的环境即可
3.如果如果该节点左右孩子都有,如果该节点左右孩子都有,无法判断哪个孩子替换它的环境,可以让左树的最右节点替换他的环境,也可以让右树的最左孩子替换他的环境。
假设是右树,让右树一直往左遍历 ,直到找到没有左孩子的结点(当前是4的这个结点)为止,让这个节点来替换要删除的结点。把4的有孩子那一坨(A)给他的父节点6,让后把4放在3的位置(3剥离出去)
当前的搜索二叉树没有平衡性,没有平衡性会出现的问题,时间复杂度没办法维持在log(N)的水平,取决于用户给你的数据状况
log(N)(好情况)
log(NN)*(坏情况)
AVL:平衡二叉树,是最严格的平衡性,因为它的任何一个结点的左右子树高度差不超过1,怎样做到(通过左旋和右旋来维持他的平衡性)
这样的话就是带自平衡操作的搜索二叉树(外面包住搜索二叉树的那个域),并不知道左旋右旋怎么用
AVL是在往包一层,首先是搜索二叉树,其次有左旋右旋的操作,AVL实现的是怎么用这个操作
红黑树,首先是搜索二叉树,其次有左旋右旋的操作,有自己关于平衡性的定义,在这种定义下怎么维持这种平衡性。
SB树:首先是搜索二叉树,其次有左旋右旋的操作,有自己关于平衡性的定义,在这种定义下怎么维持这种平衡性。怎莫用:有自己的行为操作
现在了解左旋右旋的操作
左边少了就左旋,右边少了就右旋,通过左旋右旋可以使树变得平衡一点
破坏平衡的4中类型:LL,RR,LR,RL
LL 型,左边孩子过长,导致的平衡性失效,这样的话,做一个右旋就行
RR,右边孩子过长,导致的平衡性失效,这样的话,做一个左旋就行
LR型,左树的右孩子过长,(解决办法:想办法让X变成头部)
1.先让Y这棵树左旋,让X上去
2.再让Z这棵树右旋,让X上去
RL型,是右树的左孩子过长,(想办法让C成为头部)
1.让B这棵树右旋,使C上去
2.再让Z这棵树左旋,使C上去
SetsBalance树(SB树)定义:任意一个结点都不比他侄子结点小,跟AVL树一样也有4种类型《LL,RR,LR,RL》
红黑树:1点要么是黑要么是红,
2.头和叶子结点是黑的,
3.红黑结点不相邻,
4.cur是当前头结点,每条到结束结点的路中黑色结点一样多
跳表(我不懂):是实现有序表的一种方式,用户随意给数据,
现在假设要加入70,有2层,永远从最高层开始,找到<=70最右的结点,所以来到20
70没有5层结点,20自己内部来到4层,在4层上要找到<=70最右的结点,就往右来到了50
70没有4层,在50内部跳转到3层。70也没有3层,所以在50内部跳转到2层。70也没有2层,所以在50内部跳转到1层。有1层,所以挂上去,把指针指向70
效果就是50结点中第0层和第1层会指向70。70结点中第0层和第1层会指向100
3个月到3年