日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

左神---基础提升笔记

發布時間:2024/3/13 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 左神---基础提升笔记 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 哈希函數與哈希表等
    • 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,請返回派對的最大快樂值。

增強版解釋:

? 兩種情況:

  • 自己來:自己的快樂值 + 下級各個不來參加的情況下每個樹的最大快樂值
  • 自己不來: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遍歷

  • ? 先序遍歷: 只過一次 直接打印 ; 過兩次 第一次打印
  • ? 中序遍歷 : 只過一次 直接打印 ; 兩次第二次打印
  • ? 后序遍歷: 逆序打印左樹右邊界 ; 單打整棵樹右邊界(逆序)
  • 搜索二叉樹:左樹的值小于節點,右樹的值大于節點 ; 中序遍歷這棵樹是升序就是的

    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 ? 10; return cur ==E ? 10; }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題目

    出現枚舉類型的優化題目

    總結

    以上是生活随笔為你收集整理的左神---基础提升笔记的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。