左神算法提升班
10.哈希函数和哈希表
1.认识哈希表和哈希函数的实现
哈希函数特征
- 离散性
- 均匀性
2.设计RandomPool结构
package hash;
import java.util.HashMap;
public class RandomPool {
public static class Pool<K>{
private HashMap<K, Integer> keyIndexMap;
private HashMap<Integer, K> indexKeyMap;
private int size;
public Pool() {
this.keyIndexMap = new HashMap<K, Integer>();
this.indexKeyMap = new HashMap<Integer, K>();
this.size = 0;
}
public void insert(K key){
if(!this.keyIndexMap.containsKey(key)){
keyIndexMap.put(key,this.size);
indexKeyMap.put(this.size++, key);
}
}
public void delete(K key){
if(this.keyIndexMap.containsKey(key)){
int index = this.keyIndexMap.get(key);
int lastIndex = --this.size;
K lastKey = indexKeyMap.get(lastIndex);
this.keyIndexMap.put(lastKey, index);
this.indexKeyMap.put(index, lastKey);
this.keyIndexMap.remove(key);
this.indexKeyMap.remove(lastIndex);
}
}
public K getRandom(){
if(this.size == 0){
return null;
}
int random = (int) Math.random() * this.size;
return this.indexKeyMap.get(random);
}
}
}
3.详解布隆过滤器
n 样本量
p 预期失误率
k 哈希函数个数
m 过滤器大小
package hash;
public class BitMap {
public static void main(String[] args) {
int a = 0;
int[] arr = new int[10]; // 32bit * 10 320bit
int i = 178; //向取得第178个bit的状态
int numIndex = 178 / 32;
int bitIndex = 178 % 32;
int s = ( arr[numIndex] >> (bitIndex) & 1);
//把第 i 为的状态改为 1
arr[numIndex] = arr[numIndex] | (1 << bitIndex);
//把第 i 为的状态改为 0
arr[numIndex] = arr[numIndex] & (~ (1 << bitIndex) );
}
}
详解一致性哈希原理
11.有序表,并查集
1.岛问题
package sortTable;
public class Islands {
public static int countIslands(int[][] m){
if(m == null || m[0] == null){
return 0;
}
int N = m.length;
int M = m[0].length;
int res = 0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
if(m[i][j] == 1){
//感染
res++;
infect(m, i, j, N, M);
}
}
}
return res;
}
private static void infect(int[][] m, int i, int j, int N, int M) {
if(m[i][j] != 1 || i < 0 || i >= N || j < 0 || j >= M){
return;
}
m[i][j] = 2;
infect(m, i-1, j, N, M);
infect(m, i+1, j, N, M);
infect(m, i, j-1, N, M);
infect(m, i, j+1, N, M);
}
}
2.并查集
package sortTable;
import java.util.HashMap;
import java.util.List;
import java.util.Stack;
public class UnionFind {
public static class Element<V>{
public V value;
public Element(V value) {
this.value = value;
}
}
public static class UnionFindSet<V> {
public HashMap<V, Element<V>> elementMap;
// key 对应的父结点是 value
public HashMap<Element<V>, Element<V>> fatherMap;
// key 某个集合的代表元素 value表示该集合的大小
public HashMap<Element<V>, Integer> sizeMap;
public UnionFindSet(List<V> list){
elementMap = new HashMap<>();
fatherMap = new HashMap<>();
sizeMap = new HashMap<>();
for (V value : list) {
Element<V> element = new Element<V>(value);
elementMap.put(value, element);
fatherMap.put(element, element);
sizeMap.put(element,1);
}
}
// 给定一个ele,往上一直找,把代表元素返回
private Element<V> findHead(Element<V> element){
Stack<Element<V>> path = new Stack<>();
while(fatherMap.get(element) != element){
path.push(element);
element = fatherMap.get(element);
}
while (!path.isEmpty()){
fatherMap.put(path.pop(), element);
}
return element;
}
public boolean isSameSet(V a, V b){
if(elementMap.containsKey(a) && elementMap.containsKey(b)){
return findHead(elementMap.get(a)) == findHead(elementMap.get(b));
}
return false;
}
public void union(V a, V b){
if(elementMap.containsKey(a) && elementMap.containsKey(b)){
Element<V> aF = findHead(elementMap.get(a));
Element<V> bF = findHead(elementMap.get(b));
if(aF != bF){
Element<V> big = sizeMap.get(aF) > sizeMap.get(bF) ? aF : bF;
Element<V> small = big == aF ? bF : aF;
fatherMap.put(small, big);
sizeMap.put(big, sizeMap.get(small) + sizeMap.get(big));
sizeMap.remove(small);
}
}
}
}
}
3.KMP
字符串str1和str2,str1是否包含str2,如果包含返回str2在str1中的位置 时间复杂的O(N)
- 暴力解法
- next数组解法
package promoteClass.kmp;
public class KMP {
public static int getIndexOf(String s, String m) {
if(s == null || m == null || m.length() < 1 || m.length() > s.length()){
return -1;
}
char[] str1 = s.toCharArray();
char[] str2 = m.toCharArray();
int i = 0;
int j = 0;
int[] next = getNextArray(str2);
while(i < str1.length && j < str2.length){
if(str1[i] == str2[j]){
i++;
j++;
}else if (next[j] == -1){
i++;
}else {
j = next[j];
}
}
return j == str2.length ? i - j : -1;
}
private static int[] getNextArray(char[] str) {
if(str.length == 1){
return new int[]{-1};
}
int[] next = new int[str.length];
next[0] = -1;
next[1] = 0;
int i = 2;
int cn = 0;
while(i < str.length){
if(str[i - 1] == str[cn]){
next[i++] = ++cn;
}else if(cn > 0){//cn 来到 next[cn]位置处
cn = next[cn];
}else {
next[i++] = 0;
}
}
return next;
}
public static void main(String[] args) {
String str1 = "afatatedd";
String str2 = "tedd";
System.out.println(getIndexOf(str1, str2));
}
}
12.KMP,Manacher算法
1.Manacher算法解决的问题
最长回文子串
package promoteClass.kmp;
public class Manacher {
public static char[] manacherString(String str) {
char[] chars = str.toCharArray();
char[] res = new char[str.length() * 2 - 1];
int index = 0;
for(int i = 0; i != res.length; i++){
res[i] = (i & 1) == 0 ? '#' : chars[index++];
}
return res;
}
public static int maxLcpsLength(String s) {
if (s.length() == 0 || s==null){
return 0;
}
char[] str = manacherString(s);// 1221 ----> #1#2#2#1#
int[] pArr = new int[str.length];//回文半径数组
int C = -1; //中心
int R = -1; // 半径
int max = Integer.MIN_VALUE;
for(int i = 0; i != str.length; i++){
pArr[i] = R > i ? Math.min(pArr[2 * C - i], R - i) : 1;
while (i + pArr[i] < str.length && i - pArr[i] > -1){
if(str[i + pArr[i]] == str[i - pArr[i]]){
pArr[i]++;
}else {
break;
}
}
if(i + pArr[i] > R){
R = i + pArr[i];
C = i;
}
max = Math.max(max, pArr[i]);
}
return max - 1;
}
}
2.窗口问题
有一个整型数组arr和一个大小为w的窗口从数组的最左边滑到右边,窗口每次向右滑动一个位置,每滑动一次窗口产生一个窗口内的最大值,返回这个最大值数组
package promoteClass.kmp;
import java.util.LinkedList;
public class SlidingWindowMaxArray {
public static class WindowMax {
private int L;
private int R;
private int[] arr;
private LinkedList<Integer> qmax;
public WindowMax(int[] a){
this.arr = a;
L = -1;
R = 0;
qmax = new LinkedList<>();
}
public void addNumFromRight(){
if(R == arr.length){
return;
}
while(!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[R]){
qmax.pollLast();
}
qmax.addLast(R);
R++;
}
public void removeFromLeft() {
if (L > R - 1){
return;
}
L++;
if(qmax.peekFirst() == L) {
qmax.pollFirst();
}
}
public Integer getMax() {
if(!qmax.isEmpty()) {
return arr[qmax.pollFirst()];
}
return null;
}
}
public static int[] getMaxWindow(int[] arr, int w){
if(arr == null || w < 1 || arr.length < w){
return null;
}
LinkedList<Integer> qmax = new LinkedList<>();
int[] res = new int[arr.length - w + 1];
int index = 0;
for (int i = 0; i < arr.length; i++) {
while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[i]) {// 把小于arr[i]的数从队列移除
qmax.pollLast();
}
qmax.addLast(i);
if(qmax.peekFirst() == i - w){ // i - w 等于 队列 第一个数 说明队列的第一个元素已经不在窗口内 弹出
qmax.pollFirst();
}
if (i >= w - 1){//窗口形成了
res[index++] = arr[qmax.pollFirst()];
}
}
return res;
}
}
3.单调栈结构
给你一个数组arr,返回每一个数最近的大于这个数的位置,没有返回-1
package promoteClass.kmp;
import java.util.*;
//数 组 返回每一个数最近的大于这个数的位置,没有返回-1
public class GetBorderMaxIndex {
public static void insert(Stack<ArrayList<Integer>> stack, int index){
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(index);
stack.push(list2);
}
// 合并
public static void merge(Stack<ArrayList<Integer>> stack, int index){
ArrayList<Integer> arrayList = stack.pop();
arrayList.add(index);
stack.push(arrayList);
}
//出栈
public static void popFormIndex(Stack<ArrayList<Integer>> stack, int[] arr, HashMap<Integer, ArrayList<Integer>> map, int index) {
// 出栈
while (!stack.isEmpty() && arr[stack.peek().get(stack.peek().size() - 1)] < arr[index]){//栈不为空 且 栈顶 小于要入栈的
//arr[i] > 栈顶 此时 出栈
ArrayList<Integer> list = stack.pop();
for (Integer popIndex : list) {
int rightIndex = index; //右边的最大值位置 ,只要还要入栈,就不用考虑右边的最大值是否存在
int leftIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1); // 左边最大位置 栈为空 时不存在
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(leftIndex);
arrayList.add(rightIndex);
map.put(popIndex, arrayList);
}
}
}
public static void popFormIndex(Stack<ArrayList<Integer>> stack, HashMap<Integer, ArrayList<Integer>> map){
while (!stack.isEmpty()) {
ArrayList<Integer> list = stack.pop();
for (Integer popIndex : list) {
//int rightIndex = -1; //此时右边大的数没有
int leftIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1);
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(leftIndex);
arrayList.add(-1);//此时右边大的数没有
map.put(popIndex, arrayList);
}
}
}
public static HashMap<Integer, ArrayList<Integer>> getBorderMaxIndex(int[] arr){
if(arr == null || arr.length == 1) {
return null;
}
HashMap<Integer, ArrayList<Integer>> res = new HashMap<>();
Stack<ArrayList<Integer>> stack = new Stack<>();
ArrayList<Integer> list1 = new ArrayList<>();
list1.add(0);
stack.push(list1);
int i = 1;
while(!stack.isEmpty() && i < arr.length){
// arr[i] < 栈顶 直接入栈
if(arr[i] < arr[stack.peek().get(stack.peek().size() - 1)]){
insert(stack, i);
i++;
}else if(arr[i] == arr[i - 1]){
//相等,把 i 位置 和 栈中前一个合并
merge(stack, i);
i++;
}else {
//出栈
popFormIndex(stack, arr, res, i);
insert(stack, i);
i++;
}
}
// 处理栈中的元素
popFormIndex(stack, res);
return res;
}
public static void main(String[] args) {
int[] arr = {3,5,5,4,2,5};
HashMap<Integer, ArrayList<Integer>> map = getBorderMaxIndex(arr);
for (int i = 0; i < arr.length; i++) {
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList = map.get(i);
for (Integer integer : arrayList) {
System.out.println(integer);
}
}
}
}
定义:数组中累积和与最小值的乘积,假设叫做指标A,给定一个数组,请返回子数组中,指标A的最大值
13.滑动窗口,单调栈结构
1.二叉树节点间的最大距离
从二叉树的节点a出发,可以向上或者向下走,但沿途的节点只能经过一次,到达节点b时路径上的节点个数叫作a到b的距离,那么二叉树任何两个节点之间都有距离求整棵树上的最大距离。 在基础班讲过
package promoteClass.kmp;
public class MaxDistanceInTree {
public static class Node{
public int value;
public Node left;
public Node right;
public Node(int value) {
this.value = value;
}
}
public static int maxDistance(Node head) {
return process(head).maxDistance;
}
public static class Info {
public int maxDistance;
public int height;
public Info(int dis, int h) {
this.maxDistance = dis;
this.height = h;
}
}
public static Info process(Node head){
if(head == null){
return new Info(0, 0);
}
Info leftInfo = process(head.left);
Info rightInfo = process(head.right);
int p1 = leftInfo.maxDistance;
int p2 = rightInfo.maxDistance;
int p3 = leftInfo.height + 1 + rightInfo.height;
int maxDistance = Math.max(p3, Math.max(p1, p2));
int height = Math.max(leftInfo.height, rightInfo.height) + 1;
return new Info(maxDistance, height);
}
}
2.排队的最大快乐值
派对的最大快乐值员工信息的定义如下:
class Employee {
public int happy;//这名员工可以带来的快乐值
List subordinates; //这名员工有哪些直接下级}
公司的每个员工都符合Employee类的描述。整个公司的人员结构可以看作是一棵标准的、没有环的多叉树。树的头节点是公司唯一的老板。除老板之外的每个员工都有唯一的直接上级。叶节点是没有任何下属的基层员工(subordinates列表为空),除基层员工外,每个员工都有一个或多个直接下级。这个公司现在要办party,你可以决定哪些员工来,哪些员工不来。但是要遵循如下规则。
1.如果某个员工来了,那么这个员工的所有直接下级都不能来
2.派对的整体快乐值是所有到场员工快乐值的累加
3.你的目标是让派对的整体快乐值尽量大
给定一棵多叉树的头节点boss,请返回派对的最大快乐值。
package promoteClass.kmp;
import jdk.nashorn.internal.ir.IfNode;
import java.util.List;
public class GetMaxHappy {
public static class Employee{
public int happy;
public List<Employee> nexts;
}
public static class Info {
public int laiMaxHappy;;
public int buMaxHappy;
public Info(int laiMaxHappy, int buMaxHappy) {
this.laiMaxHappy = laiMaxHappy;
this.buMaxHappy = buMaxHappy;
}
}
public static int maxHappy(Employee boss) {
Info headInfo = process(boss);
return Math.max(headInfo.buMaxHappy, headInfo.laiMaxHappy);
}
public static Info process(Employee x){
if(x.nexts.isEmpty()){
return new Info(x.happy, 0);
}
int lai = x.happy;
int bu = 0;
for (Employee next : x.nexts) {
Info nextInfo = process(next);
lai += nextInfo.buMaxHappy;
bu += Math.max(nextInfo.buMaxHappy, nextInfo.laiMaxHappy);
}
return new Info(lai, bu);
}
}
3.Morris遍历
Morris遍历细节
假设来到当前节点cur,开始时cur来到头节点位置
1)如果cur没有左孩子,cur向右移动(cur = cur.right)
2)如果cur有左孩子,找到左子树上最右的节点mostRight:
a.如果mostRight的右指针指向空,让其指向cur,然后cur向左移动(cur = cur.left)
b.如果mostRight的右指针指向cur,让其指向null,然后cur向右移动(cur = cur.right)
- cur为空时遍历停止
package promoteClass.kmp;
public class MorrisTraversal {
public static class Node{
public int value;
public Node left;
public Node right;
public Node(int value) {
this.value = value;
}
}
public static void process(Node head) {
if(head == null){
return;
}
process(head.left);
process(head.right);
}
public static void morris(Node head) {
if(head == null){
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null){
mostRight = cur.left;
if(mostRight != null) {//cur 有左子树
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;//找出左子树的最优结点
}
if(mostRight.right == null){//最右结点,指向cur
mostRight.right = cur;
cur = cur.left;
continue;
}else {//第二次来 指向null
mostRight.right = null;
}
}
cur = cur.right;
}
}
//先序
public static void morrisPre(Node head) {
if(head == null){
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null){
mostRight = cur.left;
if(mostRight != null) {
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if(mostRight.right == null){
System.out.println(cur.value);
mostRight.right = cur;
cur = cur.left;
continue;
}else {
mostRight.right = null;
}
}else {//没有左子树
System.out.println(cur.value);
}
cur = cur.right;
}
}
//中序
public static void morrisIN(Node head) {
if(head == null){
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null){
mostRight = cur.left;
if(mostRight != null) {
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if(mostRight.right == null){
mostRight.right = cur;
cur = cur.left;
continue;
}else {
mostRight.right = null;
}
}
System.out.println(cur.value);
cur = cur.right;
}
}
//后序
public static void morrisPos(Node head) {
if(head == null){
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null){
mostRight = cur.left;
if(mostRight != null) {
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if(mostRight.right == null){
mostRight.right = cur;
cur = cur.left;
continue;
}else {
mostRight.right = null;
printEdge(cur.left);
}
}
cur = cur.right;
}
}
public static void printEdge(Node X) {
Node tail = reverseEdge(X);
Node cur = tail;
while(cur != null){
System.out.println(cur.value);
cur = cur.right;
}
reverseEdge(tail);
}
private static Node reverseEdge(Node from) {
Node pre = null;
Node next = null;
while (from != null){
next = from.right;
from.right = pre;
pre = from;
from = next;
}
return pre;
}
}
4.未出现过得数
32位无符号整数的范围是0~4,294,967,295,现在有一个正好包含40亿个无符号整数的文件,所以在整个范围中必然存在没出现过的数。可以使用最多1GB的内存,怎么找到所有未出现过的数?
【进阶】
内存限制为10MB,但是只用找到一个没出现过的数即可
1.创建一个大小为2^32/8大小的int数组,用位信息来表示
2.例如创造一个大小为1024大小的数组,然后假设A=40亿/1024,统计出现次数,当数组某个位置小于A时,迭代
2.树形dp问题
14.二叉树的Mirrors遍历
1.找出重复的url
有一个包含100亿个URL的大文件,每个URL占用64B,找出其中重复的url
布隆过滤器或者哈希函数分流到小文件
某搜索公司一天用户的搜索词汇是百亿级的,设计出一种求每天热门Top100词汇
哈希函数分流+堆结构
2.找出出现2次的数
32位无符号数的范围是0-4294967295,现在有40个无符号数,可以使用最多1GB的内存,找出出现了两次的数
定义一个大小为2^32/4大小的int数组,每个数用2个bit位,00代表0次,01代表1次,10代表2次,11代表2次以上
找出40亿个数的中位数,限制内存大小
例如创造一个大小为1024大小的数组,然后假设A=40亿/1024,统计出现次数,然后我们将数组从下标0累加,当累加和为20亿时,将这个数继续划分
3.位运算
给定2个有符号32位整数a和b,返回a和b较大的 (要求 不能做比较判断)
//保证参数a不是1就是0的情况下
// 把 1 --> 0
// 0 ---> 1
public static int flip(int n){
return n ^ 1;
}
// 把 n >= 0 1
// n < 0 0
public static int sign(int n){
return flip( ( n >> 31 ) & 1);
}
//不能解决溢出问题
public static int getMax1(int a, int b) {
int c = a - b;
int scA = sign(c); // c >= 0 scA = 1 否则 scA = 0
int scB = flip(scA); // scA = 1 ---> scB = 0
return a * scA + b * scB;//返回较大的那一个
}
public static int getMax2(int a, int b) {
int c = a - b;
int sa = sign(a);
int sb = sign(b);
int sc = sign(c);
int difSab = sa ^ sb; //a和 b的符号不一样为 1 一样为 0
int sameSab = flip(difSab); // a 和 b 的符号一样为1 不一样为 0
/*
返回a的条件
1.a 和 b 的符号相同 a - b 大于 0 即 sc = 1 返回a
2.a 和 b 的符号不同 a > 0 即 sa = 1 返回 a
*/
int returnA = difSab * sa + sameSab * sc; // returnA 表示返回 a 的情况
/*
difSab 和 sameSab 是互斥的 即 difSab = 0 那么 sameSab = 1
returnA = 1 * sa + 0 * sc
或者
returnA = 0 * sa + 1 * sc
*/
int returnB = flip(returnA);
return a * returnA + b * returnB;
}
4.判断幂
判断一个32位正数是不是2的幂,4的幂
package promoteClass.kmp;
//判断幂问题
public class Power {
public static boolean is2Power(int n) {
return (n & (n - 1)) == 0;
}
public static boolean is4Power(int n){
return (n & (n - 1)) == 0 && (n & 0x55555555 ) != 0;
}
}
给定两个有符号32位整数a和b,不能使用算术运算符,分别实现a和b的加、减、乘、除运算
如果给定a、 b执行加减乘除的运算结果就会导致数据的溢出,那么你实现的函数不必对此负责,除此之外请保证计算过程不发生溢出
package promoteClass.kmp;
public class AddMinusMMultiDivideByBit {
public static int add(int a, int b){
int sum = 0;
while(b != 0){
sum = a ^ b;//无进位信息相加结果
b = (a & b) << 1; //进位信息
a = sum;
}
return sum;
}
public static int negNum(int n){
return add(~n, 1);
}
//减法
public static int minus(int a, int b){
// a - b = a + (-b) -b = ~n + 1
return add(a, negNum(b));
}
// 乘法
public static int multi(int a, int b){
int res = 0;
while (b != 0){
if((b & 1) != 0){
res = add(res, a);
}
a <<= 1;
b >>>= 1; //无符号右移
}
return res;
}
public static boolean isNeg(int n) {
return n < 0;
}
public static int div(int a, int b) {
int x = isNeg(a) ? negNum(a) : a; // a为负数取反加1
int y = isNeg(b) ? negNum(b) : b;
int res = 0;
for (int i = 31; i > -1; i = minus(i, 1)) {
if ((x >> i) >= y){
res |= (1 << i);
x = minus(x, y << i);
}
}
return isNeg(a) ^ isNeg(b) ? negNum(res) : res;//若a,b是一正一负,结果取反加1
}
public static int divide(int a, int b){
if (b == 0){
throw new RuntimeException("divide is 0");
}
if(a == Integer.MIN_VALUE && b == Integer.MIN_VALUE) {
return 1;
}else if (b == Integer.MIN_VALUE) {
return 0;
}else if (a == Integer.MIN_VALUE) {
int res = div(add(a, 1), b);
return add(res, divide(minus(a, multi(res, b)), b));
}else {
return div(a,b);
}
}
public static void main(String[] args) {
System.out.println(add(1, -2));
System.out.println(minus(5, 2));
System.out.println(multi(6, 2));
}
}
15.大数据题目
1.机器人走路问题
给你一个N,表示共有 1 - N 个格子,S表示起始位置,E表示终止位置,K表示总共要走的步数,机器人从S开始走,每次都可以向左或者向右走一步,走K步后到达终止位置则表示一种走法,则机器人一共有多少种不同的走法? 在1位置只能向右走,在N位置只能向左走
package promoteClass.bigData;
public class RobotWalk {
public static int walkWays1(int N, int E, int S, int K) {
return f1(N, E, K, S);
}
/*
暴力递归
一共是 1 ~ N 个位置
最终的目标是P
当前在cur位置
剩余rest步
*/
private static int f1(int N, int E, int rest, int cur) {
if (rest == 0) {
return cur == E ? 1 : 0;
}
if (cur == 1) {
return f1(N, E, rest - 1, 2);
}
if (cur == N) {
return f1(N, E, rest - 1, N - 1);
}
return f1(N, E, rest - 1, cur - 1) + f1(N, E, rest - 1, cur + 1);
}
// 优化 建立一个递归缓存
public static int walkWays2(int N, int E, int S, int K) {
//建立一个dp缓存,已经计算过的递归就直接取值
int[][] dp = new int[K + 1][N + 1];
for (int i = 0; i <= K; i++) {
for (int j = 0; j <= N; j++) {
dp[i][j] = -1;
}
}
return f2(N, E, K, S, dp);
}
private static int f2(int N, int E, int rest, int cur, int[][] dp) {
if (dp[rest][cur] != -1) {
return dp[rest][cur];
}
//建立缓存
if (rest == 0) {
dp[rest][cur] = cur == E ? 1 : 0;
return dp[rest][cur];
}
if (cur == 1) {
dp[rest][cur] = f1(N, E, rest - 1, 2);
return dp[rest][cur];
}
if (cur == N) {
dp[rest][cur] = f1(N, E, rest - 1, N - 1);
return dp[rest][cur];
}
dp[rest][cur] = f1(N, E, rest - 1, cur - 1) + f1(N, E, rest - 1, cur + 1);
return dp[rest][cur];
}
// 动态规划
// public static int dpWay(int N, int P, int M, int K) {
// int[][] dp = new int[N][N];
// return 0;
// }
}
2.硬币问题
给你一个数组arr和一个目标金钱aim,让你每次从arr数组中选取一个数来达到我们的目标aim,不可以重复取,求最少要拿几次硬币才可以达到aim
package promoteClass.bigData;
public class CoinsMin {
// 有多少种组合
public static int min1(int[] arr, int aim) {
return f(arr, 0, 0, aim);
}
/*
arr[] 硬币都在其中,固定参数
aim 最终要达成的目标 固定参数
pre 你已经选择了多少
*/
public static int f(int[] arr, int index, int pre, int aim) {
if (index == arr.length) {
return pre == aim ? 1 : 0;
}
return f(arr, index + 1, pre, aim) + f(arr, index + 1, pre + arr[index], aim);
}
// 硬币数最少的组合
public static int coinsMin1(int[] arr, int aim) {
return process1(arr, 0, aim);
}
public static int process1(int[] arr, int index, int rest) {
if (rest < 0) {
return -1;
}
if (rest == 0) {
return 0;
}
//rest > 0
if (index == arr.length) {
return -1;
}
// rest > 0 并且还有硬币
int p1 = process1(arr, index + 1, rest);
int p2Next = 1 + process1(arr, index + 1, rest - arr[index]);
if (p1 == -1 && p2Next == -1) {
return -1;
} else {
if (p1 == -1) {
return p2Next + 1;
}
if (p2Next == -1) {
return p1;
}
return Math.min(p1, p2Next + 1);
}
}
// 硬币数最少的组合
public static int coinsMin2(int[] arr, int aim) {
int[][] dp = new int[arr.length + 1][aim + 1];
for (int i = 0; i <= arr.length; i++) {
for (int j = 0; j <= aim; j++) {
dp[i][j] = -2;
}
}
return process2(arr, 0, aim, dp);
}
public static int process2(int[] arr, int index, int rest, int[][] dp) {
if (rest < 0) {
return 0;
}
if (dp[index][rest] != -2) {
return dp[index][rest];
}
if (rest == 0) {
dp[index][rest] = 0;
} else if (index == arr.length) {
dp[index][rest] = -1;
} else {
int p1 = process2(arr, index + 1, rest, dp);
int p2Next = 1 + process2(arr, index + 1, rest - arr[index], dp);
if (p1 == -1 && p2Next == -1) {
dp[index][rest] = -1;
} else {
if (p1 == -1) {
dp[index][rest] = p2Next + 1;
} else if (p2Next == -1) {
dp[index][rest] = p1;
} else {
dp[index][rest] = Math.min(p1, p2Next + 1);
}
}
}
return dp[index][rest];
}
public static int coinsMin3(int[] arr, int aim) {
int N = arr.length;
int[][] dp = new int[N + 1][aim + 1];
for (int row = 0; row <= N; row++) {
dp[row][0] = 0;
}
for (int col = 0; col <= aim; col++) {
dp[N][col] = -1;
}
for (int index = N - 1; index >= 0; index--) {
for (int rest = 1; rest <= aim; rest++) {
int p1 = dp[index + 1][rest];
int p2Next = -1;
if (rest - arr[index] >= 0) {
p2Next = dp[index + 1][rest - arr[index]];
}
if (p1 == -1 && p2Next == -1) {
dp[index][rest] = -1;
} else {
if (p1 == -1) {
dp[index][rest] = -1;
} else if (p2Next == -1) {
dp[index][rest] = p1;
} else {
dp[index][rest] = Math.min(p1, p2Next + 1);
}
}
}
}
return dp[0][aim];
}
// for test
public static int[] generateRandomArray(int len, int max) {
int[] arr = new int[(int) (Math.random() * len) + 1];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * max) + 1;
}
return arr;
}
public static void main(String[] args) {
int len = 10;
int max = 10;
int testTime = 10000;
for (int i = 0; i < testTime; i++) {
int[] arr = generateRandomArray(len, max);
int aim = (int) (Math.random() * 3 * max) + max;
if (coinsMin1(arr, aim) != coinsMin2(arr, aim) && coinsMin2(arr, aim) != coinsMin3(arr, aim)) {
System.out.println("error");
break;
}
}
}
}
16.暴力递归(上)
1.挑卡片问题
给你一个arr数组,甲乙两人分别依次从数组两边挑选一个数
// 动态规划
public static int dp(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
// f , s 两个二维数组的左下部分是无效的
int[][] f = new int[arr.length][arr.length];
int[][] s = new int[arr.length][arr.length];
for (int i = 0; i < arr.length; i++) {
f[i][i] = arr[i];
}
/*
* 表示无效数据区域
f:
arr[0][0]
* arr[1][1]
* * arr[2][2]
* * * arr[3][3]
s:
0
* 0
* * 0
* * * 0
*/
int row = 0;
int col = 1;
while (col < arr.length) {
int i = row;
int j = col;
while (i < arr.length && j < arr.length) {
f[i][j] = Math.max(arr[i] + s[i + 1][j], arr[j] + s[i][j - 1]);
s[i][j] = Math.min(f[i + 1][j], f[i][j + 1]);
i++;
j++;
}
col++;
}
return Math.max(f[0][arr.length - 1], s[0][arr.length - 1]);
}
2.马跳棋盘问题
有一个坐标轴,长度为8,高度为9.马的起始位置在(0,0),给定一个终点位置(x,y),给你一个k,表示马要跳的步数,问有多少种不同的跳法可以到达终点位置
package promoteClass.bigData;
public class HorseJump {
//棋盘默认的大小是 8 * 9
public static int getWays(int x, int y, int k){
return process(x, y, k);
}
//从(0,0)位置出发要去往(x, y)位置
// 必须跳 step 步 返回跳法的总数
private static int process(int x, int y, int step) {
if(x < 0 || x > 8 || y < 0 || y > 9){
return 0;
}
if(step == 0) {
return (x == 0 && y == 0) ? 1 : 0;
}
return process(x + 1, y + 2, step - 1)
+ process(x + 1, y - 2, step - 1)
+ process(x - 1, y + 2, step - 1)
+ process(x - 1, y - 2, step - 1)
+ process(x + 2, y + 1, step - 1)
+ process(x + 2, y - 1, step - 1)
+ process(x - 2, y + 1, step - 1)
+ process(x - 2, y - 1, step - 1);
}
// 三维动态规划
public static int dpWasy(int x, int y, int step) {
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 - 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 - 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) {
if(row < 0 || row > 8 || col < 0 || col > 9) {
return 0;
}
return dp[row][col][step];
}
public static void main(String[] args) {
System.out.println(getWays(7, 7, 10));
System.out.println(dpWasy(7, 7, 10));
}
}
3.Bob的死亡概率
给你五个数 N, M, i, j, K. 有一个N * M大小的格子,(i,j)表示bob现在的坐标,走K步,每一步可以往上下左右四个方向,走出格子bob就死亡了,求bob的存活率
package promoteClass.bigData;
public class BobDie {
// 暴力递归
public static String bob1(int N ,int M, int i, int j, int K) {
long all = (long) Math.pow(4, K);
long live = process(N, M, i, j, K);
long gcd = gcd(all, live);
return String.valueOf(live / gcd) + "/" + (all / gcd);
}
private static long process(int N, int M, int row, int col, int rest) {
// 越界
if(row < 0 || row == N || col < 0 || col == M) {
return 0;
}
// 步数走完没有越界
if (rest == 0) {
return 1;
}
//
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 - 1, rest - 1);
return live;
}
// 求最大公约数
private static long gcd(long m, long n) {
return n == 0 ? m : gcd(n, m % n);
}
//动态规划
public static String bob2(int N, int M, int i, int j, int K) {
int[][][] dp = new int[N + 2][M + 2][K + 1];
for (int row = 1; row <= N; row++) {
for (int col = 1; col <= M; col++) {
dp[row][col][0] = 1;
}
}
for (int rest = 1; rest <= K; rest++) {
for (int row = 1; row <= N; row++) {
for (int col = 1; col <= M; col++) {
dp[row][col][rest] = dp[row - 1][col][rest - 1];
dp[row][col][rest] += dp[row + 1][col][rest - 1];
dp[row][col][rest] += dp[row][col + 1][rest - 1];
dp[row][col][rest] += dp[row][col - 1][rest - 1];
}
}
}
long all = (long) Math.pow(4, K);
long live = dp[i + 1][j + 1][K];
long gcd = gcd(all, live);
return String.valueOf(live / gcd) + "/" + (all / gcd);
}
}
4.换零钱问题
给你数组arr,表示有哪些面额的零钱,aim表示要换取得总量,你可以使用任意张数的零钱,问一共有多少种不同的换法
package promoteClass.bigData;
public class CoinsWays {
// 暴力递归方法
public static int way1(int[] arr, int aim) {
return process(arr, 0, aim);
}
public static int process(int[] arr, int index, int rest) {
if (arr.length == index){
return rest == 0 ? 1 : 0;
}
int ways = 0;
for (int zhang = 0; arr[index] * zhang <= rest; zhang++){
ways += process(arr, index + 1, rest - arr[index] * zhang);
}
return ways;
}
// 动态规划
public static int way2(int[] arr, int aim){
if(arr == null || arr.length == 0) {
return 0;
}
int N = arr.length;
int[][] dp = new int[N + 1][aim + 1];
dp[N][0] = 1;
for (int index = N - 1; index >= 0; index--){
for (int rest = 0; rest <= aim; rest++){
int ways = 0;
for (int zhang = 0; arr[index] * zhang <= rest; zhang++){
ways += dp[index + 1][rest - zhang * arr[index]];
}
dp[index][rest] = ways;
}
}
return dp[0][aim];
}
// 枚举行为查看邻近有没有可以代替枚举的位置
// 斜率优化
public static int way3(int[] arr, int aim){
if(arr == null || arr.length == 0) {
return 0;
}
int N = arr.length;
int[][] dp = new int[N + 1][aim + 1];
dp[N][0] = 1;
for (int index = N - 1; index >= 0; index--){
for (int rest = 0; rest <= aim; rest++){
// 每个格子都需要下方的格子
dp[index][rest] = dp[index + 1][rest];
if (rest - arr[index] >= 0){
dp[index][rest] += dp[index][rest - arr[index]];
}
}
}
return dp[0][aim];
}
// for test
public static int[] generateRandomArray(int len, int max) {
int[] arr = new int[(int) (Math.random() * len) + 1];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * max) + 1;
}
return arr;
}
public static void main(String[] args) {
int len = 10;
int max = 10;
int testTime = 10000;
for (int i = 0; i < testTime; i++) {
int[] arr = generateRandomArray(len, max);
int aim = (int) (Math.random() * 3 * max) + max;
if (way1(arr, aim) != way3(arr, aim)) {
System.out.println("error");
break;
}
}
}
}
17.暴力递归(下)
1.何为具有平衡度的数
有序表的时间复杂度为O(logN)
4种实现方式
- 红黑树
- AVL
- Size-Balance树
- 跳表
介绍树的左旋,树的右旋
- LL型 左孩子左树不平衡 左旋
- RR型 右孩子右树不平衡 右旋
- LR型 左孩子右树不平衡
- RL型 右孩子左树不平衡
2.介绍SB树及其实现
平衡性:
每棵子树的大小,不小于其兄弟的子树大小
平衡性:
每棵子树的大小,不小于其兄弟的子树大小
既每棵叔叔树的大小,不小于其任何侄子树的大小具体实现与调整细节
3.红黑树
- 每个结点都是红或者黑
- 头结点和叶节点(null)为黑
- 任意2个红结点不相邻
- 任意一个子树,从头结点出发,到每个叶节点的路径上黑一样多