递归
如同干活,重复的工作,我只干一遍,剩下的让别人干
重复的事,代码只写一遍,剩下的递归调用函数
重复、变化、边界
变换的量作为函数参数
切蛋糕思维
切完后,子问题有与原问题相同的形式
求阶乘
public static int t1(int n) {
if(n == 1) {
return 1;
}
return n * t1(n - 1);
}
打印i~j
public static void t2(int i,int j) {
System.out.println(i);
if (i == j) {
return;
}
t2(i+1,j);
}
数组求和
public static int t3(int [] arr,int begin) {
if (begin == arr.length - 1) {
return arr[begin];
}
return arr[begin] + t3(arr, begin + 1);
}
反转字符串
public static String t4(String src , int end) {
if (end == 0) {
//charAt是String类的一个方法,用于获取字符串中指定索引处的字符
return "" + src.charAt(0);
}
return src.charAt(end) + t4(src,end - 1);
}
斐波那契数列
特点:每一项都是前两项的和
求第n项是什么数
public static int t5(int n) {
//不能只写其中一个条件
//因为如果想求第三个数,那么下面的return会t5(2) + t5(1),这样既有2又有1
if (n == 1||n == 2) {
//不管是第一个还是第二个都是1
return 1;
}
//求的是n位置上的数,=n - 1位置的数 + n - 2位置的数
return t5(n - 1) + t5(n - 2);
}
时间复杂度O(2^n)
如果return Kt5(n - 1) + t5(1);就是O(K^n),k是几就是几的n次方
最大公约数
辗转相除法
//这里的m和n的大小随意,eg:2 4和4 2都行
public static int t6(int m,int n) {
//这里的n就是每次算的m % n,
//如果结果是0,则这里的n就是最大公约数
//看t6(n,(m % n))传参,把n的值传给m了,所以return m
if (n == 0) {
return m;
}
return t6(n,(m % n));
}
插入排序
public static void t7(int [] arr,int k) {
// 从小到大排
if (k == 0) {
return;
}
// 因为是递归调用,所以这样写实际上是从第一个元素开始,一点一点排序
// 对前k-1个元素排序
t7(arr,k - 1);
// 把k位置的元素插入对应的位置
// 取出这个元素
int x = arr[k];
int index = k - 1;
// 找位置
// 因为序列是有序的,所以没找到比x大的元素时就一直找
while (index > -1 && x < arr[index]) {
// 因为是数组,不能真的插入,只能是空出地方,比他大的数往后挪1
arr[index + 1] = arr[index];
index--;
}
arr[index + 1] = x;
}
汉诺塔
把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘
63个盘子移动的时候看作一个盘子,因为是递归最终移动的实际上是一个盘子,递归返回,一点点递归到64
先以真正的目标(B)为辅助,再以辅助(C)为辅助
//这里的移动其实就是打印输出,输出是按from是源头,to是目标位置,help是辅助空间
//我们只需要变换输出的from,to,help的值即可完成每步
//每一步三者的值是什么,传参的时候体现出来,即可实现
public static void t8(int N,String from,String to,String help) {
if (N == 1) {
// 就剩一个盘子时,直接把这个盘子放目标空间就行
// 实际上也是从这个盘子开始移动的
System.out.println("move" + N + "from" + from + "to" + to);
} else {
// 看md写的流程,目标位置是挪到B,C是辅助空间
// 第一步:挪是挪到C,B作为辅助空间,所以help先做目标空间,to做辅助空间
t8(N - 1,from,help,to);
// 第二步:打印N到目标
System.out.println("move" + N + "from" + from + "to" + to);
// 第三步:help作为源头,to作为目标位置,from是源头
t8(N - 1,help,to,from);
}
}
时间复杂度O(2^n)
递归实现二分查找
public static int t9(int [] arr,int low,int high,int key) {
// low和high相当于c++的指针,这里当索引用
if (low > high) {
return -1;
}
// 为了不溢出,移位效率更高
// 如果用(low + high) / 2,low和high很大时会溢出int类型的最大限度
// >>会使二进制向右移,也就相当于除以2
// 所以low + ((high - low) >> 1) = low + ((high - low) / 2) = 1/2*low + 1/2*high
int mid = low + ((high - low) >> 1);
int midValue = arr[mid];
if (midValue < key) {
// 去后面找
return t9(arr,mid + 1,high,key);
} else if (midValue > key) {
// 去前面找
return t9(arr,low,mid - 1,key);
} else {
// 相等就返回
return mid;
}
}
递归实现希尔排序
按增量分组,一组一组的进行插入排序
public static void t10(int[] arr) {
// 不断缩小增量
for (int interval = arr.length/2; interval > 0; interval /= 2) {
// 从第interval个元素,逐个对其所在组进行直接插入排序操作
// 从第interval个元素(组内第二个元素)开始排,先与他前一个元素比较大小(所以不从开头开始排)
// 不是一组一组的排序,是从第interval个元素开始,后面的每个数都取出来和自己对应的组排序
for (int i = interval; i < arr.length; i++) {
int temp = arr[i];
// 只操作组内元素,所以要插入元素的上一个元素的索引是i - 步长
int j = i - interval;
while (j > -1&&temp < arr[j]) {
// 把他前一个元素移到他后一位
arr[j + interval]=arr[j];
// 继续向前找
j -= interval;
}
// 循环结束,找到比他小的那个位置的索引了,插在他后面
arr[j+interval] = temp;
}
}
}
jdk的排序
Arrays.sort(arr);
//这个比前面讲的冒泡,插入,希尔等都快
例题
上楼梯
最后一步是上到n,算出走到n-1,n-2,n-3的次数加一起
public static int t11(int n) {
// 如果剩下1阶,执行if (n == 1) return 1;这三句返回什么是计算过的
// 如果剩下2阶,执行if (n == 2) return 2;
// ………………
// 如果剩下超过3阶,就要递归了
if(n == 3) return 4;
if (n == 1) return 1;
if (n == 2) return 2;
// 分别代表跨1阶,2阶,3阶三种方式
// 递归出来是一个树状图,会把每种情况都走一遍,求出总和就是总走法
return t11(n - 1) + t11(n - 2) + t11(n - 3);
}
旋转数组的最小数
用二分法划(midValue),最小数一定在无序的那一边,且挨着最大值
public static int t13(int[] arr) {
int begin = 0;
int end = arr.length - 1;
// 考虑没有旋转这种特殊的旋转
if(arr[begin] < arr[end]) {
return arr[begin];
}
// 这里begin和end最后总是 ……|……|begin|end|……|……|……这样挨在一起,所以while循环写<>>而带=
while (begin + 1 < end) {
int mid = left + ((right - left) >> 1);
// 左侧有序,找右侧
if (arr[mid] > arr[begin]) {
// 不能像递归二分那样写begin + 1= mid;因为可能错过最小值
// 递归二分分了> < =但这个代码把=合并了
begin = mid;
}
// 右侧有序
else if (arr[mid] < arr[begin]){
end = mid ;
} else {
System.out.println("挨个遍历找");
}
}
// 找到了(只能输出end)
return arr[end];
}
在有空字符串的有序字符串数组中查找
因为有序,所以用二分法
这里是查找指定的元素,不是直接找最小的,所以我们可以按”递归实现二分法“的思路写
public static int t14(String[] arr, String target) {
int low = 0;
int high = arr.length - 1;
// 没找到l
// 只有递归的写法才需要下面的代码,正常写,不可能刚开始low > high,以后也不会这样,所以不需要写
// 这个判断应该写在循环里
// if(low > high) {
// return -1;
// }
// low high 相等,虽然说直接返回就行,
// 但是因为这种情况写到while里了,也要进这个循环才能返回,所以这里不能写low < high
while (low <= high) {
int mid = low + ((high - low) >> 1);
// 如果mid定位到空字符串,没法比较,就让mid往一个方向移即可
while (arr[mid].equals("")) {
mid +=1;
// 如果移动的过程中,mid>high了说明没有这个字符串
if(mid > high) {
return -1;
}
}
// target在左侧,去左边查
if(arr[mid].compareTo(target) > 0) {
high = mid - 1;
} else if(arr[mid].compareTo(target) < 0) {
// target在右侧,去右边查
low = mid + 1;
} else {
return mid;
}
}
// 如果没找到就退出while,返回-1
return -1;
}
最长连续递增子序列
设俩指针A,B,先都指向头节点
让B一直往后移,遇到比所指的小的元素,就结算length=B-A
把A移到B的位置,再重复上面的动作
再算长度,比上一个长,就把长的赋值给length……
高效求a^n(二分幂算法)
普通求法就是每次循环*a,也就是n是一点一点加上去的
快速幂算法”或“二分幂算法”
现在我们改变n的增加方法,让n按2^x这个速度加
也就是每次循环让n不断*2
假设我们要计算 210:
-
初始时,
n = 10
,res = 2
,ex = 1
。 -
循环第一次迭代:
ex
变为2,res
变为 22=4。 -
循环第二次迭代:
ex
变为4,res
变为 42=16。 -
循环第三次迭代:
ex
变为8,res
变为 162=256。此时,(ex<<1)
不再小于等于n
,循环结束。 -
递归调用t15(2, 10 - 8) = t15(2, 2)
对于t15(2, 2)
,它直接返回 22=4(因为n == 2
时,res
初始化为2,然后直接返回)。 -
最后,将
res = 256
与t15(2, 2) = 4
相乘,得到 210=256×4=1024。
public static int t15(int a,int n) {
if(n==0) return 1;
int res = a;
// 记录本次递归n的数目,初始=1
int ex = 1;
// ex<<1:n不断*2,一直*到倍数比需要的总n大
// 因为n不一定是2的倍数
// 如果是2的倍数,本次就能算出来
// 不是2的倍速,剩下的没×的n就让下次递归处理
// 如果ex*2比n还大,则可以进行ex*2的算法,就运算
for (;(ex<<1) <= n;ex<<= 1) {
// n*2了,也就是res的指数*2,代表(res)^2
res *= res;
}
// 处理剩余的n
return res*t15(a,n - ex);
}