文章目录

  • 哈希函数与哈希表等
    • 1、设计RandomPool结构
    • 2、详解布隆过滤器
      • 位图:
    • 3、详解一致性哈希原理---虚拟节点技术
  • 有序表与并查集等
    • 1、岛问题
    • 2、并查集:
    • 3、KMP算法
      • 3.1 题目一
  • KMP和Manacher算法
    • 1、Manacher算法
      • 1.1 字符串str中,最长回文子串的长度如何求解?
    • 2、窗口的最大值最小值更新结构
    • 3、单调栈结构
      • 3.1题目一
  • 滑动窗口单调栈等
    • 1、树形dp套路
      • 1.1二叉树节点间的最大距离问题
      • 1.2 派对最大快乐值
    • 2、Morris遍历
      • 2.1遍历细节
      • 2.2遍历
    • 3、总结
    • 4、大数据题目的解题技巧
      • 4.1题目一
  • 6 大数据题目等
    • 1、暴力递归到动态规划
      • 1.1机器人达到指定位置方法数
    • 2、换钱的最少货币数
      • 2.1题目一
      • 2.2题目二
  • 7、暴力递归上
    • 1.1排成一条线的纸牌博弈问题
    • 1.2象棋中马的跳法
    • 1.3Bob的生存概率
    • 2、第六节课的2.2题目

哈希函数与哈希表等

1、设计RandomPool结构

​ 【题目】

​ 设计一种结构,在该结构中有如下三个功能:

​ insert(key):将某个key加入到该结构,做到不重复加入

​ delete(key):将原本在结构中的某个key移除

​ getRandom(): 等概率随机返回结构中的任何一个key。

​ 【要求】

​ Insert、delete和getRandom方法的时间复杂度都是O(1)

public static class Pool<K> {private HashMap<K, Integer> keyIndexMap;            //字符串-index  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)) {this.keyIndexMap.put(key, this.size);this.indexKeyMap.put(this.size++, key);}}public void delete(K key) {if (this.keyIndexMap.containsKey(key)) {int deleteIndex = this.keyIndexMap.get(key);int lastIndex = --this.size;K lastKey = this.indexKeyMap.get(lastIndex);this.keyIndexMap.put(lastKey, deleteIndex);this.indexKeyMap.put(deleteIndex, lastKey);this.keyIndexMap.remove(key);this.indexKeyMap.remove(lastIndex);}}public K getRandom() {if (this.size == 0) {return null;}int randomIndex = (int) (Math.random() * this.size); // 0 ~ size -1return this.indexKeyMap.get(randomIndex);}}

2、详解布隆过滤器

位图:

int a = 0;
//a    32 bit
int[] arr = new int[10];
//arr[0] int 0 ~31int i = 178;
int numIndex = 178 / 32;
int bitIndex = 178 % 32;
//拿到178位的状态
int s = ((arr[numIndex] >> (bitIndex))  & 1);//请把178位的状态改为1
arr[numIndex] = arr[numIndex] | (1 << (bitIndex));i = 178;//请把178位的状态改为1
arr[numIndex] = arr[numIndex] &(~  (1 << bitIndex)
public static class BitMap {private long[] bits;public BitMap(int max) {bits = new long[(max + 64) >> 6];}public void add(int num) {bits[num >> 6] |= (1L << (num & 63));}public void delete(int num) {bits[num >> 6] &= ~(1L << (num & 63));}public boolean contains(int num) {return (bits[num >> 6] & (1L << (num & 63))) != 0;}}public static void main(String[] args) {// 表示 0~ 31 谁出现了,谁没出现
//      int a = 0;
//      int num = 7;
//      // 请把7位描黑!
//      //   0000000000010000000
//      //   0000000000010000000
//      a |= 1 << 7;
//      a |= 1 << 13;
//      a |= 1 << 29;
//      // 7  13  29
//      // 请告诉我,7有没有进去
//      boolean has =( a & (1 << 7)) != 0;
//
//      int[] set = new int[10];// set : 10个数// 每个数,32位// 0~319int num = 176;// set[0] : 0~31// set[1] : 32~int team = num / 32;set[team] |= 1 << (num % 32);//        System.out.println("测试开始!");
//      int max = 2000000;
//      BitMap bitMap = new BitMap(max);
//      HashSet<Integer> set = new HashSet<>();
//      int testTime = 6000000;
//      for (int i = 0; i < testTime; i++) {//          int num = (int) (Math.random() * (max + 1));
//          double decide = Math.random();
//          if (decide < 0.333) {//              bitMap.add(num);
//              set.add(num);
//          } else if (decide < 0.666) {//              bitMap.delete(num);
//              set.remove(num);
//          } else {//              if (bitMap.contains(num) != set.contains(num)) {//                  System.out.println("Oops!");
//                  break;
//              }
//          }
//      }
//      for (int num = 0; num <= max; num++) {//          if (bitMap.contains(num) != set.contains(num)) {//              System.out.println("Oops!");
//          }
//      }
//      System.out.println("测试结束!");}

考虑样本量(n)与失误率(p)

需要空间:m = - ( n * lnp) / (ln2)平方 哈希函数个数:k = ln2 * m / n ≈ 0.7 * m / n

3、详解一致性哈希原理—虚拟节点技术

有序表与并查集等

1、岛问题

​ 一个矩阵中只有0和1两种值,每一个位置都和自己的上下左右四个位置相连如果有一片1连在一起这个部分叫做岛,这个矩阵岛屿的数目

public static int isLands(int[][] m)
{if(m == null || m[0] == null){return 0;}int res = 0;int M = m.length;int N = m[0].length;for(int i = 0;i < M;i++){for(int j = 0;j < i;j++){if(a[i][j] == 1){res++;infect(m,i,j,N,M);}}}return res;
}public static void infect(int[][] m,int i,int j,int N,int M)
{if(i < 0 || i > N|| j < 0 || j > M || m[i][j] != 1){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、并查集:

public static class Node<V> {V value;public Node(V v) {value = v;}}public static class UnionFind<V> {public HashMap<V, Node<V>> nodes;public HashMap<Node<V>, Node<V>> parents;public HashMap<Node<V>, Integer> sizeMap;public UnionFind(List<V> values) {nodes = new HashMap<>();parents = new HashMap<>();sizeMap = new HashMap<>();for (V cur : values) {Node<V> node = new Node<>(cur);nodes.put(cur, node);parents.put(node, node);sizeMap.put(node, 1);}}// 给你一个节点,请你往上到不能再往上,把代表返回public Node<V> findFather(Node<V> cur) {Stack<Node<V>> path = new Stack<>();while (cur != parents.get(cur)) {path.push(cur);cur = parents.get(cur);}while (!path.isEmpty()) {parents.put(path.pop(), cur);}return cur;}public boolean isSameSet(V a, V b) {return findFather(nodes.get(a)) == findFather(nodes.get(b));}public void union(V a, V b) {Node<V> aHead = findFather(nodes.get(a));Node<V> bHead = findFather(nodes.get(b));if (aHead != bHead) {int aSetSize = sizeMap.get(aHead);int bSetSize = sizeMap.get(bHead);Node<V> big = aSetSize >= bSetSize ? aHead : bHead;Node<V> small = big == aHead ? bHead : aHead;parents.put(small, big);sizeMap.put(big, aSetSize + bSetSize);sizeMap.remove(small);}}public int sets() {return sizeMap.size();}}

3、KMP算法

3.1 题目一

字符串str1和字符串str2,str1是否包含str2,若包含返回str2在str1中的开始位置

public static int getIndexOf(String s1, String s2) {if (s1 == null || s2 == null || s2.length() < 1 || s1.length() < s2.length()) {return -1;}char[] str1 = s1.toCharArray();char[] str2 = s2.toCharArray();int x = 0;int y = 0;// O(M) m <= nint[] next = getNextArray(str2);// O(N)while (x < str1.length && y < str2.length) {if (str1[x] == str2[y]) {x++;y++;} else if (next[y] == -1) { // y == 0x++;} else {y = next[y];}}return y == str2.length ? x - y : -1;}public static int[] getNextArray(char[] str2) {if (str2.length == 1) {return new int[] { -1 };}int[] next = new int[str2.length];next[0] = -1;next[1] = 0;int i = 2; // 目前在哪个位置上求next数组的值int cn = 0; // 当前是哪个位置的值再和i-1位置的字符比较while (i < next.length) {if (str2[i - 1] == str2[cn]) { // 配成功的时候next[i++] = ++cn;} else if (cn > 0) {cn = next[cn];} else {next[i++] = 0;}}return next;}

KMP和Manacher算法

1、Manacher算法

1.1 字符串str中,最长回文子串的长度如何求解?

public static int manacher(String s) {if (s == null || s.length() == 0) {return 0;}// "12132" -> "#1#2#1#3#2#"char[] str = manacherString(s);// 回文半径的大小int[] pArr = new int[str.length];int C = -1;// 讲述中:R代表最右的扩成功的位置// coding:最右的扩成功位置的,再下一个位置int R = -1;int max = Integer.MIN_VALUE;for (int i = 0; i < str.length; i++) { // 0 1 2// R第一个违规的位置,i>= R// i位置扩出来的答案,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;public static char[] manacherString(String str) {char[] charArr = 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 ? '#' : charArr[index++];}return res;}/*伪代码*/for(int i = 0;i < str.length;i++){if(i在外部){i暴力扩}else{if(i 的回文区域在L - R内){pArr[i] = 某个表达式}else if(回文区域有一部风在外部){pArr[i] = 某个表达式}else{i的回文区域L-R左边界重合以R外侧字符扩增}}}

2、窗口的最大值最小值更新结构

由一个代表题目,引出一种结构

【题目】

有一个整型数组 arr 和一个大小为 w 的窗口从数组的最左边滑到最右边,窗口每次向右边滑一个位置。

例如,数组为[4,3,5,4,3,3,6,7门,窗口大小为3时:

​ [4 3 5 ] 4 3 3 6 7

​ 4 [ 3 5 4 ] 3 3 6 7

​ 4 3 [ 5 4 3 ] 3 6 7

​ 4 3 5 [ 4 3 3 ] 6 7

​ 4 3 5 4 [ 3 3 6 ] 7

​ 4 3 5 4 3 [ 3 6 7 ]

窗口中最大值为5窗口中最大值为5窗口中最大值为5窗口中最大值为4窗口中最大值为6窗口中最大值为7

如果数组长度为 n ,窗ロ大小为 w ,则一共产生 n - w +1个窗口的最大值。

请实现一个函数。输入:整型数组 arr ,窗口大小为 W 。

输出:一个长度为 n - w +1的数组 res , res [ i ]表示每一种窗口状态下的以本题为例,结果应该返回(5,5,5,4,6.7}。

// 暴力的对数器方法public static int[] right(int[] arr, int w) {if (arr == null || w < 1 || arr.length < w) {return null;}int N = arr.length;int[] res = new int[N - w + 1];int index = 0;int L = 0;int R = w - 1;while (R < N) {int max = arr[L];for (int i = L + 1; i <= R; i++) {max = Math.max(max, arr[i]);}res[index++] = max;L++;R++;}return res;}public static int[] getMaxWindow(int[] arr, int w) {if (arr == null || w < 1 || arr.length < w) {return null;}// qmax 窗口最大值的更新结构// 放下标LinkedList<Integer> qmax = new LinkedList<Integer>();int[] res = new int[arr.length - w + 1];int index = 0;for (int R = 0; R < arr.length; R++) {while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[R]) {qmax.pollLast();}qmax.addLast(R);if (qmax.peekFirst() == R - w) {qmax.pollFirst();}if (R >= w - 1) {res[index++] = arr[qmax.peekFirst()];}}return res;}

3、单调栈结构

在数组(有重复值的与无重复值的)中想找到一个数,左边和右边比这个数小(大)、且离这个数最近的位置。

如果对每一个数都想求这样的信息,能不能整体代价达到O(N)?需要使用到单调栈结构

public static int[][] getNearLessNoRepeat(int[] arr) {int[][] res = new int[arr.length][2];Stack<Integer> stack = new Stack<>();for (int i = 0; i < arr.length; i++) {while (!stack.isEmpty() && arr[stack.peek()] > arr[i]) {int popIndex = stack.pop();int leftLessIndex = stack.isEmpty() ? -1 : stack.peek();res[popIndex][0] = leftLessIndex;res[popIndex][1] = i;}stack.push(i);}while (!stack.isEmpty()) {int popIndex = stack.pop();int leftLessIndex = stack.isEmpty() ? -1 : stack.peek();res[popIndex][0] = leftLessIndex;res[popIndex][1] = -1;}return res;}public static int[][] getNearLess(int[] arr) {int[][] res = new int[arr.length][2];Stack<List<Integer>> stack = new Stack<>();for (int i = 0; i < arr.length; i++) {while (!stack.isEmpty() && arr[stack.peek().get(0)] > arr[i]) {List<Integer> popIs = stack.pop();// 取位于下面位置的列表中,最晚加入的那个int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1);for (Integer popi : popIs) {res[popi][0] = leftLessIndex;res[popi][1] = i;}}if (!stack.isEmpty() && arr[stack.peek().get(0)] == arr[i]) {stack.peek().add(Integer.valueOf(i));} else {ArrayList<Integer> list = new ArrayList<>();list.add(i);stack.push(list);}}while (!stack.isEmpty()) {List<Integer> popIs = stack.pop();// 取位于下面位置的列表中,最晚加入的那个int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1);for (Integer popi : popIs) {res[popi][0] = leftLessIndex;res[popi][1] = -1;}}return res;}

3.1题目一

定义:正数数组中累积和与最小值的乘积,假设叫做指标A。给定一个数组,请返回子数组中,指标A最大的值。

public static int max1(int[] arr) {int max = Integer.MIN_VALUE;for (int i = 0; i < arr.length; i++) {for (int j = i; j < arr.length; j++) {int minNum = Integer.MAX_VALUE;int sum = 0;for (int k = i; k <= j; k++) {sum += arr[k];minNum = Math.min(minNum, arr[k]);}max = Math.max(max, minNum * sum);}}return max;}public static int max2(int[] arr) {int size = arr.length;int[] sums = new int[size];sums[0] = arr[0];for (int i = 1; i < size; i++) {sums[i] = sums[i - 1] + arr[i];}int max = Integer.MIN_VALUE;Stack<Integer> stack = new Stack<Integer>();for (int i = 0; i < size; i++) {while (!stack.isEmpty() && arr[stack.peek()] >= arr[i]) {int j = stack.pop();max = Math.max(max, (stack.isEmpty() ? sums[i - 1] : (sums[i - 1] - sums[stack.peek()])) * arr[j]);}stack.push(i);}while (!stack.isEmpty()) {int j = stack.pop();max = Math.max(max, (stack.isEmpty() ? sums[size - 1] : (sums[size - 1] - sums[stack.peek()])) * arr[j]);}return max;}public static int[] gerenareRondomArray() {int[] arr = new int[(int) (Math.random() * 20) + 10];for (int i = 0; i < arr.length; i++) {arr[i] = (int) (Math.random() * 101);}return arr;}

滑动窗口单调栈等

第八节课 二叉树递归套路里有—1.1与1.2都有

1、树形dp套路

1.1二叉树节点间的最大距离问题

从二叉树的节点 a 出发,可以向上或者向下走,但沿途的节点只能经过一次,到达节点 b 时路径上的节点个数叫作 a 到 b 的距离,那么二叉树任何两个节点之间都有距离,求整棵树上的最大距离

public static class Node {public int value;public Node left;public Node right;public Node(int data) {this.value = data;}}public static int maxDistance(Node head) {int[] record = new int[1];return posOrder(head, record);}public static class ReturnType{public int maxDistance;public int h;public ReturnType(int m, int h) {this.maxDistance = m;;this.h = h;}}public static ReturnType process(Node head) {if(head == null) {return new ReturnType(0,0);}ReturnType leftReturnType = process(head.left);ReturnType rightReturnType = process(head.right);//三个可能最大值int includeHeadDistance = leftReturnType.h + 1 + rightReturnType.h;int p1 = leftReturnType.maxDistance;int p2 = rightReturnType.maxDistance;//int resultDistance = Math.max(Math.max(p1, p2), includeHeadDistance);//头结点所以+int hitself  = Math.max(leftReturnType.h, leftReturnType.h) + 1;return new ReturnType(resultDistance, hitself);}public static int posOrder(Node head, int[] record) {if (head == null) {record[0] = 0;return 0;}int lMax = posOrder(head.left, record);int maxfromLeft = record[0];int rMax = posOrder(head.right, record);int maxFromRight = record[0];int curNodeMax = maxfromLeft + maxFromRight + 1;record[0] = Math.max(maxfromLeft, maxFromRight) + 1;return Math.max(Math.max(lMax, rMax), curNodeMax);}

1.2 派对最大快乐值

派对的最大快乐值

员工信息的定义如下:

​ class Employee {

​ public int happy; // 这名员工可以带来的快乐值

​ List subordinates; // 这名员工有哪些直接下级

}

公司的每个员工都符合 Employee 类的描述。整个公司的人员结构可以看作是一棵标准的、没有环的多叉树。树的头节点是公司唯一的老板。除老板之外的每个员工都有唯一的直接上级。叶节点是没有任何下属的基层员工(subordinates列表为空),除基层员工外,每个员工都有一个或多个直接下级。

这个公司现在要办party,你可以决定哪些员工来,哪些员工不来。但是要遵循如下规则。

​ 1.如果某个员工来了,那么这个员工的所有直接下级都不能来

​ 2.派对的整体快乐值是所有到场员工快乐值的累加

​ 3.你的目标是让派对的整体快乐值尽量大

给定一棵多叉树的头节点boss,请返回派对的最大快乐值。

增强版解释:

​ 两种情况:

  1. 自己来:自己的快乐值 + 下级各个不来参加的情况下每个树的最大快乐值
  2. 自己不来:0 + Math.max(下级在来的情况下整棵树的最大值,下级在不来情况下整棵树的最大值)
public static int maxHappy(int[][] matrix) {int[][] dp = new int[matrix.length][2];boolean[] visited = new boolean[matrix.length];int root = 0;for (int i = 0; i < matrix.length; i++) {if (i == matrix[i][0]) {root = i;}}process(matrix, dp, visited, root);return Math.max(dp[root][0], dp[root][1]);}public static void process(int[][] matrix, int[][] dp, boolean[] visited, int root) {visited[root] = true;dp[root][1] = matrix[root][1];for (int i = 0; i < matrix.length; i++) {if (matrix[i][0] == root && !visited[i]) {process(matrix, dp, visited, i);dp[root][1] += dp[i][0];dp[root][0] += Math.max(dp[i][1], dp[i][0]);}}}

2、Morris遍历

​ 一种遍历二叉树的方式,并且时间复杂度O(N),额外空间复杂度O(1)

​ 通过利用原树中大量空闲指针的方式,达到节省空间的目的

2.1遍历细节

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)
​ 3)cur为空时遍历停止

2.2遍历

  1. ​ 先序遍历: 只过一次 直接打印 ; 过两次 第一次打印
  2. ​ 中序遍历 : 只过一次 直接打印 ; 两次第二次打印
  3. ​ 后序遍历: 逆序打印左树右边界 ; 单打整棵树右边界(逆序)

搜索二叉树:左树的值小于节点,右树的值大于节点 ; 中序遍历这棵树是升序就是的

public static class Node {public int value;Node left;Node right;public Node(int data) {this.value = data;}}//中序public static void morrisIn(Node head) {if (head == null) {return;}Node cur1 = head;Node cur2 = null;//3情况while (cur1 != null) {cur2 = cur1.left;//2情况if (cur2 != null) {//不断向右侧  在有指针为空  或者有指针已经指向cur停止 否则循环while (cur2.right != null && cur2.right != cur1) {cur2 = cur2.right;}//a情况if (cur2.right == null) {cur2.right = cur1;cur1 = cur1.left;continue;} else {//b情况cur2.right = null;}}System.out.print(cur1.value + " ");//1情况cur1 = cur1.right;}System.out.println();}//先序public static void morrisPre(Node head) {if (head == null) {return;}Node cur1 = head;Node cur2 = null;while (cur1 != null) {cur2 = cur1.left;if (cur2 != null) {while (cur2.right != null && cur2.right != cur1) {cur2 = cur2.right;}if (cur2.right == null) {cur2.right = cur1;System.out.print(cur1.value + " ");cur1 = cur1.left;continue;} else {cur2.right = null;}} else {//无左子树System.out.print(cur1.value + " ");}cur1 = cur1.right;}System.out.println();}//后序遍历public static void morrisPos(Node head) {if (head == null) {return;}Node cur1 = head;Node cur2 = null;while (cur1 != null) {cur2 = cur1.left;if (cur2 != null) {while (cur2.right != null && cur2.right != cur1) {cur2 = cur2.right;}if (cur2.right == null) {cur2.right = cur1;cur1 = cur1.left;continue;} else {cur2.right = null;printEdge(cur1.left);}}cur1 = cur1.right;}printEdge(head);System.out.println();}public static void printEdge(Node head) {Node tail = reverseEdge(head);Node cur = tail;while (cur != null) {System.out.print(cur.value + " ");cur = cur.right;}reverseEdge(tail);}public 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;}

3、总结

​ 所想方法需要做第三次的信息的强整合则用二叉树的递归套路

​ 所想方法不需要第三次 最优解则可以Morris

4、大数据题目的解题技巧

​ 1)哈希函数可以把数据按照种类均匀分流

​ 2)布隆过滤器用于集合的建立与查询,并可以节省大量空间

​ 3)一致性哈希解决数据服务器的负载管理问题

​ 4)利用并查集结构做岛问题的并行计算

​ 5)位图解决某一范围上数字的出现情况,并可以节省大量空间

​ 6)利用分段统计思想、并进一步节省大量空间

​ 7)利用堆、外排序来做多个处理单元的结果合并

之前的课已经介绍过前4个内容,本节内容为介绍解决大数据题目的后3个技巧

4.1题目一

​ 32位无符号整数的范围是0~4,294,967,295,现在有一个正好包含40亿个无符号整数的文件,所以在整个范围中必然存在没出现过的数。可以使用最多1GB的内存,怎么找到所有未出现过的数?

【进阶】 内存限制为 10MB,但是只用找到一个没出现过的数即可

6 大数据题目等

1、暴力递归到动态规划

动态规划就是暴力尝试减少重复计算的技巧整,而已

​ 这种技巧就是一个大型套路

​ 先写出用尝试的思路解决问题的递归函数,而不用操心时间复杂度

​ 这个过程是无可替代的,没有套路的,只能依靠个人智慧,或者足够多的经验

但是怎么把尝试的版本,优化成动态规划,是有固定套路的,大体步骤如下

​ 1)找到什么可变参数可以代表一个递归状态,也就是哪些参数一旦确定,返回值就确定了

​ 2)把可变参数的所有组合映射成一张表,有 1 个可变参数就是一维表,2 个可变参数就是二维表,…

​ 3)最终答案要的是表中的哪个位置,在表中标出

​ 4)根据递归过程的 base case,把这张表的最简单、不需要依赖其他位置的那些位置填好值

​ 5)根据递归过程非base case的部分,也就是分析表中的普遍位置需要怎么计算得到,那么这张表的填写顺序也就确定了

​ 6)填好表,返回最终答案在表中位置的值

1.1机器人达到指定位置方法数

【题目】

假设有排成一行的 N 个位置,记为 1~N,N 一定大于或等于2。开始时机器人在其中的M位置上(M 一定是 1~N 中的一个),机器人可以往左走或者往右走,如果机器人来到1位置,那么下一步只能往右来到 2 位置;如果机器人来到 N 位置,那么下一步只能往左来到N-1位置。规定机器人必须走 K 步,最终能来到 P 位置(P 也一定是1~N 中的一个)的方法有多少种。给定四个参数 N、M、K、P,返回方法数。 【举例】

N=5,M=2,K=3,P=3

上面的参数代表所有位置为 1 2 3 4 5。机器人最开始在2 位置上,必须经过3步,最后到达 3 位置。走的方法只有如下 3 种:

​ 1)从2到1,从1到2,从2到3

​ 2)从2到3,从3到2,从2到3

​ 3)从2到3,从3到4,从4到3

所以返回方法数 3。 N=3,M=1,K=3,P=3

上面的参数代表所有位置为 1 2 3。机器人最开始在 1 位置上,必须经过3 步,最后到达3位置。怎么走也不可能,所以返回方法数 0。

//最直接的暴力尝试 递归
public static int ways1(int N, int M, int K, int P) {// 参数无效直接返回0if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {return 0;}// 总共N个位置,从M点出发,还剩K步,返回最终能达到P的方法数return walk(N, M, K, P);}// N : 位置为1 ~ N,固定参数// cur : 当前在cur位置,可变参数// rest : 还剩res步没有走,可变参数// P : 最终目标位置是P,固定参数// 该函数的含义:只能在1~N这些位置上移动,当前在cur位置,走完rest步之后,停在P位置的方法数作为返回值返回public static int walk(int N, int cur, int rest, int P) {// 如果没有剩余步数了,当前的cur位置就是最后的位置// 如果最后的位置停在P上,那么之前做的移动是有效的// 如果最后的位置没在P上,那么之前做的移动是无效的if (rest == 0) {return cur == P ? 1 : 0;}// 如果还有rest步要走,而当前的cur位置在1位置上,那么当前这步只能从1走向2// 后续的过程就是,来到2位置上,还剩rest-1步要走if (cur == 1) {return walk(N, 2, rest - 1, P);}// 如果还有rest步要走,而当前的cur位置在N位置上,那么当前这步只能从N走向N-1// 后续的过程就是,来到N-1位置上,还剩rest-1步要走if (cur == N) {return walk(N, N - 1, rest - 1, P);}// 如果还有rest步要走,而当前的cur位置在中间位置上,那么当前这步可以走向左,也可以走向右// 走向左之后,后续的过程就是,来到cur-1位置上,还剩rest-1步要走// 走向右之后,后续的过程就是,来到cur+1位置上,还剩rest-1步要走// 走向左、走向右是截然不同的方法,所以总方法数要都算上return walk(N, cur + 1, rest - 1, P) + walk(N, cur - 1, rest - 1, P);}//计划搜索   加入傻缓存// 1-N的位置     目标E     剩余步数S   当前位置Kpublic static int walkway(int N,int E,int S,int K){int[][] dp = new int[K + 1][N + 1];for(int i = 0;i <= K;i++){for(int j = 0; <= N;j++){dp[i][j] = -1;}}return f1(N,E,S,K,dp);}public static int f1(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 cur ==E ? 1 :0;  }if(cur == 1){dp[rest][cur] = f1(N,E,rest - 1,2,dp);   }else if(cur == N){dp[rest][cur] = f1(N,E,rest - 1,cur - 1,dp); }else {dp[rest][cur] = f1(N,E,rest - 1,cur + 1,dp) + f1(N,E,rest - 1,cur - 1,dp); }return dp[rest][cur];}public static int ways2(int N, int M, int K, int P) {// 参数无效直接返回0if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {return 0;}int[][] dp = new int[K + 1][N + 1];dp[0][P] = 1;for (int i = 1; i <= K; i++) {for (int j = 1; j <= N; j++) {if (j == 1) {dp[i][j] = dp[i - 1][2];} else if (j == N) {dp[i][j] = dp[i - 1][N - 1];} else {dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1];}}}return dp[K][M];}public static int ways3(int N, int M, int K, int P) {// 参数无效直接返回0if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {return 0;}int[] dp = new int[N + 1];dp[P] = 1;for (int i = 1; i <= K; i++) {int leftUp = dp[1];// 左上角的值for (int j = 1; j <= N; j++) {int tmp = dp[j];if (j == 1) {dp[j] = dp[j + 1];} else if (j == N) {dp[j] = leftUp;} else {dp[j] = leftUp + dp[j + 1];}leftUp = tmp;}}return dp[M];}

2、换钱的最少货币数

2.1题目一

【题目】

给定数组 arr,arr 中所有的值代表硬币的面值可以重复。每一个值代表一枚硬币,给定一个整数 aim,代表要找的钱数,求组成aim的最少硬币数。

暴力递归:

 public static int minCoins1(int[] arr, int aim) {process(arr,0,aim);}
public static int process(int [] arr,int index,int rest)
{if(rest < 0){return -1;}if(rest == 0){return 0;}if(index == arr.length){return -1;}//rest > 0而且有银币int p1 = process(arr,index + 1,rest);int p2Next = process(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 minCoins1(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; <= aim;j++){dp[i][j] = -2;}}process2(arr,0,aim);}
public static int process2(int [] arr,int index,int rest,int[][] dp)
{if(rest < 0){return -1;}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 = 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;}if(p2Next == -1){dp[index][rest] =  p1;}else{dp[index][rest] = Math.min(p1,p2Next+ 1);}     }}return    dp[index][rest];
}

dp:

public static int minCoins1(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 = 1;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 && p2 Next == -1){dp[index][rest] = -1;}else{if(p1 == -1){dp[index][rest] = p2Next + 1;}if(p2Next == -1){dp[index][rest] = p1;}dp[index][rest] = Math.min(p1,p2Next + 1);}}}return dp[0][aim];
}

2.2题目二

题目】

给定数组 arr,arr 中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数 aim,代表要找的钱数,求组成aim的最少货币数。

【举例】

arr=[5,2,3],aim=20。

4 张 5 元可以组成 20 元,其他的找钱方案都要使用更多张的货币,所以返回4。

arr=[5,2,3],aim=0。

不用任何货币就可以组成 0 元,返回 0。

arr=[3,5],aim=2。

根本无法组成 2 元,钱不能找开的情况下默认返回-1。

 public static int minCoins1(int[] arr, int aim) {if (arr == null || arr.length == 0 || aim < 0) {return -1;}return process(arr, 0, aim);}// 当前考虑的面值是arr[i],还剩rest的钱需要找零// 如果返回-1说明自由使用arr[i..N-1]面值的情况下,无论如何也无法找零rest// 如果返回不是-1,代表自由使用arr[i..N-1]面值的情况下,找零rest需要的最少张数public static int process(int[] arr, int i, int rest) {// base case:// 已经没有面值能够考虑了// 如果此时剩余的钱为0,返回0张// 如果此时剩余的钱不是0,返回-1if (i == arr.length) {return rest == 0 ? 0 : -1;}// 最少张数,初始时为-1,因为还没找到有效解int res = -1;// 依次尝试使用当前面值(arr[i])0张、1张、k张,但不能超过restfor (int k = 0; k * arr[i] <= rest; k++) {// 使用了k张arr[i],剩下的钱为rest - k * arr[i]// 交给剩下的面值去搞定(arr[i+1..N-1])int next = process(arr, i + 1, rest - k * arr[i]);if (next != -1) { // 说明这个后续过程有效res = res == -1 ? next + k : Math.min(res, next + k);}}return res;}public static int minCoins2(int[] arr, int aim) {if (arr == null || arr.length == 0 || aim < 0) {return -1;}int N = arr.length;int[][] dp = new int[N + 1][aim + 1];// 设置最后一排的值,除了dp[N][0]为0之外,其他都是-1for (int col = 1; col <= aim; col++) {dp[N][col] = -1;}for (int i = N - 1; i >= 0; i--) { // 从底往上计算每一行for (int rest = 0; rest <= aim; rest++) { // 每一行都从左往右dp[i][rest] = -1; // 初始时先设置dp[i][rest]的值无效if (dp[i + 1][rest] != -1) { // 下面的值如果有效dp[i][rest] = dp[i + 1][rest]; // dp[i][rest]的值先设置成下面的值}// 左边的位置不越界并且有效if (rest - arr[i] >= 0 && dp[i][rest - arr[i]] != -1) {if (dp[i][rest] == -1) { // 如果之前下面的值无效dp[i][rest] = dp[i][rest - arr[i]] + 1;} else { // 说明下面和左边的值都有效,取最小的dp[i][rest] = Math.min(dp[i][rest],dp[i][rest - arr[i]] + 1);}}}}return dp[0][aim];}

7、暴力递归上

1.1排成一条线的纸牌博弈问题

【题目】

给定一个整型数组 arr,代表数值不同的纸牌排成一条线。玩家A 和玩家B 依次拿走每张纸牌,规定玩家 A 先拿,玩家 B 后拿,但是每个玩家每次只能拿走最左或最右的纸牌,玩家A和玩家 B 都绝顶聪明。请返回最后获胜者的分数。

【举例】

arr=[1,2,100,4]。

开始时,玩家 A 只能拿走 1 或 4。如果玩家 A 拿走 1,则排列变为[2,100,4],接下来玩家B可以拿走 2 或 4,然后继续轮到玩家 A。如果开始时玩家A 拿走4,则排列变为[1,2,100],接下 来玩家 B 可以拿走 1 或 100,然后继续轮到玩家 A。玩家A 作为绝顶聪明的人不会先拿4,因为 拿 4 之后,玩家 B 将拿走 100。所以玩家 A 会先拿1,让排列变为[2,100,4],接下来玩家 B 不管 怎么选,100 都会被玩家 A 拿走。玩家 A 会获胜,分数为101。所以返回101。arr=[1,100,2]。

开始时,玩家 A 不管拿 1 还是 2,玩家 B 作为绝顶聪明的人,都会把100 拿走。玩家B会获胜,分数为 100。所以返回 100。

首先博弈的先后手问题 是比较难以考虑的,先手函数调用的后手函数,后手函数调用的先手函数,而且 要明白先后手的情况是相对而言的,继而我们在改动暴力递归到dp的时候就要考虑缓存的先手dp的数组缓存后手信息,反之一样。

他也是范围性的尝试 正方形,左下半部分 无效

//暴力尝试
public static int win1(int[] arr) {if (arr == null || arr.length == 0) {return 0;}return Math.max(f(arr, 0, arr.length - 1), s(arr, 0, arr.length - 1));}//先手函数public static int f(int[] arr, int i, int j) {if (i == j) {return arr[i];}return Math.max(arr[i] + s(arr, i + 1, j), arr[j] + s(arr, i, j - 1));}//后手函数public static int s(int[] arr, int i, int j) {if (i == j) {return 0;}return Math.min(f(arr, i + 1, j), f(arr, i, j - 1));}public static int win2(int[] arr) {if (arr == null || arr.length == 0) {return 0;}int[][] f = new int[arr.length][arr.length];int[][] s = new int[arr.length][arr.length];for (int j = 0; j < arr.length; j++) {f[j][j] = arr[j];for (int i = j - 1; i >= 0; i--) {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]);}}return Math.max(f[0][arr.length - 1], s[0][arr.length - 1]);}

1.2象棋中马的跳法

【题目】

请同学们自行搜索或者想象一个象棋的棋盘,然后把整个棋盘放入第一象限,棋盘的最左下角是(0,0)位置。那么整个棋盘就是横坐标上9条线、纵坐标上10条线的一个区域。给你三个参数,x,y,k,返回如果“马”从(0,0)位置出发,必须走k步,最后落在(x,y)上的方法数有多少种?

考虑:由于象棋的特殊性,他必须以特定的位置才能到达x,y 那么以x,y 反推需要到达那个位置才能到达x,y 则下述的函数的八个位置才能到达。base case 很好确定 递归暴力很好实现

public static int getWays(int x, int y, int step) {return process(x, y, step);}//目的地:x,y位置   步数step//返回方法数目public 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 + 2, y + 1, step - 1)+ process(x + 2, y - 1, 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);}public static int dpWays(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 + 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) {if (row < 0 || row > 8 || col < 0 || col > 9) {return 0;}return dp[row][col][step];}

1.3Bob的生存概率

【题目】

给定五个参数n,m,i,j,k。表示在一个N*M的区域,Bob处在(i,j)点,每次Bob等概率的向上、下、左、右四个方向移动一步,Bob必须走K步。如果走完之后,Bob还停留在这个区域上,就算Bob存活,否则就算Bob死亡。请求解Bob的生存概率,返回字符串表示分数的方式。

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));}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;}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;}public 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));}

2、第六节课的2.2题目

出现枚举类型的优化题目

左神---基础提升笔记相关推荐

  1. 左神基础算法笔记-四

    1. 二叉树先序.中序.后序遍历的非递归实现 先序遍历:使用栈实现,栈中先压入二叉树根节点,然后循环以下过程,先弹出一个节点,随后压入该节点的右孩子,再压入该节点的左孩子--(这样弹出时就是左孩子先弹 ...

  2. 判断是否为二叉排序树的递归算法_左神直通BAT算法笔记(基础篇)

    时间复杂度 空间复杂度 经典例题 找出B中不属于A的数 荷兰国旗问题 矩阵打印问题 岛问题 经典结构和算法 字符串 KMP算法 前缀树 数组 冒泡排序 选择排序 插入排序 归并排序 快速排序 堆排序 ...

  3. 左程云算法笔记总结-基础篇

    基础01(复杂度.基本排序) 认识复杂度和简单排序算法 时间复杂度 big O 即 O(f(n)) 常数操作的数量写出来,不要低阶项,只要最高项,并且不要最高项的系数 一个操作如果和样本的数据量没有关 ...

  4. 算法与数据结构——算法基础——二叉树(java)(b站左程云课程笔记整理)

    二叉树 了解一个二叉树的递归序.先序.中序.后序 递归序:每个数会被打印三次(可以理解为前中后) 先序:头左右 中序:左头右 后序:左右头 public static class Node {publ ...

  5. b站唐老师人工智能基础知识笔记

    b站唐老师人工智能基础知识笔记 0.机器学习(常用科学计算库的使用)基础定位.目标定位 1.机器学习概述 1.1.人工智能概述 1.2.人工智能发展历程 1.3.人工智能主要分支 1.4.机器学习工作 ...

  6. 6-DoF问题相关基础知识笔记

    6-DoF问题相关基础知识笔记 一.什么是6-DoF,即6个自由度是什么? 二.PnP算法 三.BOP挑战与官方数据集简介 BOP数据集 BOP toolkit BOP挑战的介绍页面 四.相关论文 C ...

  7. 逆向脱壳破解分析基础学习笔记七 堆栈图(重点)

    本文为本人 大神论坛 逆向破解脱壳学习笔记之一,为本人对以往所学的回顾和总结,可能会有谬误之处,欢迎大家指出. 陆续将不断有笔记放出,希望能对想要入门的萌新有所帮助,一起进步 堆栈图 首先给定一段反汇 ...

  8. Task 06 数据增强;模型微调;目标检测基础 学习笔记

    Task 06 数据增强:模型微调:目标检测基础 学习笔记 数据增强 图像增广 在5.6节(深度卷积神经网络)里我们提到过,大规模数据集是成功应用深度神经网络的前提.图像增广(image augmen ...

  9. ASP.Net MVC开发基础学习笔记(5):区域、模板页与WebAPI初步

    http://blog.jobbole.com/85008/ ASP.Net MVC开发基础学习笔记(5):区域.模板页与WebAPI初步 2015/03/17 · IT技术 · .Net, Asp. ...

最新文章

  1. AI科研绘图3:排版
  2. 使用idea 在springboot添加本地jar包的方法 部署的时候本地jar没有包含的解决方法
  3. Codeforces 919D Substring (拓扑图DP)
  4. 十三、深入Python字典和集合
  5. 反汇编学习笔记2 函数的本质
  6. Hibernate HQL基础 调用数据库存储过程
  7. 为什么苹果内购总是失败_IOS用户支付失败 购买无法完成解决教程
  8. C语言中二维数组名与数组地址、首行地址、首行首元素地址关系与区别详解(初学者必须掌握)
  9. CentOS下gitlab迁移和升级
  10. vm14安装mac10教程(亲测;转载)
  11. 一个手机号码如何注册多个百度账号
  12. 51单片机流水灯方法大全
  13. 洛谷 P3403 跳楼机
  14. linux6 64位,CentOS 6.0 X64官方正式版系统(64位)
  15. 论文阅读_Show, Attend and Tell: Netural Image Caption Generation with Visual Attention
  16. 手机数据恢复华为荣耀X1智能手机32G内置存储手机严重损坏恢复照片聊天记录文档...
  17. 创建Silverlight Bussiness Application时报错的解决
  18. 【第三篇:利用ChatGPT编写贪食蛇小游戏】
  19. 26进制(A到Z表示1到26,例27:AA,2019:BYQ)
  20. 张量学习(1):张量的基本概念

热门文章

  1. 如何安装最新igraph 现在已经无坑了
  2. 赵小楼:《天道》《遥远的救世主》深度解析(22)丁元英为什么不问肖亚文以后有什么打算?
  3. jenkins api使用_使用管理API和Jenkins作为IBM App Connect Professional部署自动化的持续集成引擎
  4. java mocked,JMockit 中被 Mocked 的对象属性及方法的默认值
  5. 佛祖保佑永无BUG 神兽护体 代码注释(各种版本)
  6. 伦敦 quant_伦敦统一用户组7
  7. FLUENT中关于边界和域的操作
  8. 业务层Service的作用
  9. 【教程】Ubuntu20.04 + VirtualBox 各种软件环境安装
  10. 每一天的邂逅......