日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

算法进阶面试题07——求子数组的最大异或和(前缀树)、换钱的方法数(递归改dp最全套路解说)、纸牌博弈、机器人行走问题

發(fā)布時(shí)間:2024/3/13 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 算法进阶面试题07——求子数组的最大异或和(前缀树)、换钱的方法数(递归改dp最全套路解说)、纸牌博弈、机器人行走问题 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

第一題

給定一個(gè)數(shù)組,求子數(shù)組的最大異或和。

一個(gè)數(shù)組的異或和為,數(shù)組中所有的數(shù)異或起來的結(jié)果。

?

簡單的前綴樹應(yīng)用

?

暴力方法:

?

?

先計(jì)算必須以i結(jié)尾的子數(shù)組的異或和,然后再計(jì)算機(jī)i+1的,以此類推...

?

最暴力的解

public static int getMaxEor1(int[] nums) {int maxEor = Integer.MAX_VALUE;for (int i = 0; i < nums.length; i++) {for (int start = 0; start <= i; start++) {int curEor = 0;for (int k = start; k <= start; k++) {curEor ^= nums[k];}Math.max(maxEor, curEor);}}return maxEor;}

?

怎么優(yōu)化?

?

異或的運(yùn)算法則為:0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0(同為0,異為1),這些法則與加法是相同的,只是不帶進(jìn)位,所以異或常被認(rèn)作不進(jìn)位加法。

0~i = eor

0~start-1 = eor2

strart~i = eor^eor2

例如

1 0 0 1 1 1 0 1 1 eor

1 0 0 1 1 0 0 0 0 eor2

0 0 0 0 0 1 0 1 1

?

優(yōu)化方案,準(zhǔn)備一個(gè)dp輔助數(shù)組記錄結(jié)果。(增加空間)

//記憶化搜索優(yōu)化(利用之前的計(jì)算結(jié)果)public static int getMaxEor2(int[] nums) {int maxEor = Integer.MAX_VALUE;int[] dp = new int[nums.length];int eor = 0;for (int i = 0; i < nums.length; i++) {eor ^= nums[i];Math.max(maxEor,eor);for (int start = 0; start <= i; start++) {int curEor = eor ^ dp[start - 1];Math.max(maxEor,curEor);}dp[i] = eor;}return maxEor;}

?

結(jié)論:

?

?

O(n)的方法

思路:求以i結(jié)尾,異或和最大的子數(shù)組。0~i、1~i、2~i全部計(jì)算出來得到結(jié)果,效率很低。

黑盒直到i,里面存了0~0、0~1、0~2...0~i-1的結(jié)果,0~i的結(jié)果在eor時(shí)刻更新的,i希望黑盒可以告訴他,這里面哪個(gè)值和eor異或出來最大,那就是答案。

例如eor和0~3異或和是最大的,那么以i結(jié)尾的,4~i就是最大的。

?

?

黑盒可以告訴你,0~start ^ eor(0~i) 是最大的,就能得出start^i是最大的。

黑盒用前綴樹做,以4位二進(jìn)制舉例,假設(shè)0~0、0~1、0~2的值,分別加入到前綴樹中

假設(shè)求以3結(jié)尾情況下,最大異或和,0~3異或結(jié)果為0010,我特別希望異或后符號(hào)位還是0,后面的盡量1,所以就在前綴樹里面尋找適合的路。

?

?

符號(hào)位盡量為0,后面的位是0走1,1走0,沒得選就將就著走,盡量保持最大化值。

按前綴樹的走法,每次選最優(yōu),可以找到最大值。

?

符號(hào)位為1的情況下,求補(bǔ)碼,取反再加一

例如:

1 1 1 1

1 0 0 0 + 1

-1

1 0 1 1

1 1 0 0 + 1

-5

當(dāng)符號(hào)位是1的時(shí)候,希望選1的路,讓其1^1變成0,除了符號(hào)位選擇的路有講究之外,不管正負(fù),接下來的選擇是一樣的,后面的位盡量都變成1。

所以選擇符號(hào)位的時(shí)候,希望是和符號(hào)位的值是一樣的,1選1,0選0

?

public static class Node {//前綴樹節(jié)點(diǎn)public Node[] nexts = new Node[2];//只有兩個(gè)路,0/1}public static class NumTrie {//前綴樹public Node head = new Node();public void add(int num) {Node cur = head;//位移,整數(shù)是31位for (int move = 31; move >= 0; move--) {//提取出每個(gè)進(jìn)制里面的數(shù)字//例如:0101 >> 3 = 0//在和1進(jìn)行與運(yùn)算//0 0 0 0//0 0 0 1//0 0 0 0 //取出了第一位為0int path = ((num >> move) & 1);//查看是否有路,沒有就新建cur.nexts[path] = cur.nexts[path] == null ? new Node() : cur.nexts[path];cur = cur.nexts[path];}}//num 0~i eor結(jié)果,選出最優(yōu)再返回public int maxXor(int num) {Node cur = head;int res = 0;for (int move = 31; move >= 0; move--) {int path = (num >> move) & 1;//如果考察符號(hào)位希望和path是一樣的 1^1=0 0^0=0//其他位置,希望是相反的 1^0=1 0^1=1int best = move == 31 ? path : (path ^ 1);//期待best = cur.nexts[best] != null ? best : (best ^ 1);//實(shí)際//當(dāng)前位的最優(yōu)選擇,左移當(dāng)前位的數(shù)值后,加入結(jié)果(或一下)res |= (path ^ best) << move;//設(shè)置每一位的答案cur = cur.nexts[best];//下一層}return res;}}public static int maxXorSubarray(int[] arr) {if (arr == null || arr.length == 0) {return 0;}int max = Integer.MIN_VALUE;int eor = 0;NumTrie numTrie = new NumTrie();numTrie.add(0);//0和0異或for (int i = 0; i < arr.length; i++) {eor ^= arr[i];// 0 .. i//這個(gè)黑盒超好用//放入0~i eor,返回以i結(jié)尾下最大的異或和子數(shù)組的異或值max = Math.max(max, numTrie.maxXor(eor));numTrie.add(eor);}return max;}// for testpublic static int comparator(int[] arr) {if (arr == null || arr.length == 0) {return 0;}int max = Integer.MIN_VALUE;for (int i = 0; i < arr.length; i++) {int eor = 0;for (int j = i; j < arr.length; j++) {eor ^= arr[j];max = Math.max(max, eor);}}return max;}// for testpublic static int[] generateRandomArray(int maxSize, int maxValue) {int[] arr = new int[(int) ((maxSize + 1) * Math.random())];for (int i = 0; i < arr.length; i++) {arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());}return arr;}// for testpublic static void printArray(int[] arr) {if (arr == null) {return;}for (int i = 0; i < arr.length; i++) {System.out.print(arr[i] + " ");}System.out.println();}// for testpublic static void main(String[] args) {int testTime = 500000;int maxSize = 30;int maxValue = 50;boolean succeed = true;for (int i = 0; i < testTime; i++) {int[] arr = generateRandomArray(maxSize, maxValue);int res = maxXorSubarray(arr);int comp = comparator(arr);if (res != comp) {succeed = false;printArray(arr);System.out.println(res);System.out.println(comp);break;}}System.out.println(succeed ? "Nice!" : "Fucking fucked!");}

?

?

希望每次都走最優(yōu)的,符號(hào)位希望是0。

?

開始第六課內(nèi)容 dp最全套路解說

暴力遞歸怎么改動(dòng)態(tài)規(guī)劃(基礎(chǔ)班最后一節(jié)有方法論的講述)

換錢的方法數(shù)

【題目】

給定數(shù)組arr,arr中所有的值都為正數(shù)且不重復(fù)。每個(gè)值代表

一種面值的貨幣,每種面值的貨幣可以使用任意張,再給定一

個(gè)整數(shù)aim代表要找的錢數(shù),求換錢有多少種方法。

【舉例】

arr=[5,10,25,1],aim=0。

組成0元的方法有1種,就是所有面值的貨幣都不用。所以返回1。

arr=[5,10,25,1],aim=15。

組成15元的方法有6種,分別為3張5元、1張10元+1張5元、1張

10元+5張1元、10張1元+1張5元、2張5元+5張1元和15張1元。所

以返回6。

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

任何方法都無法組成2元。所以返回0。

嘗試版本寫出后,后面就是搭積木。

?

使用0張200的,后面湊出1000的方法數(shù)a

使用1張200的,后面湊出 800的方法數(shù)b

使用2張200的,后面湊出 600的方法數(shù)c

使用3張200的,后面湊出 400的方法數(shù)d

使用4張200的,后面湊出 200的方法數(shù)e

...

全部加起來就是答案。

?

public static int coins1(int[] arr, int aim) {if (arr == null || arr.length == 0 || aim < 0) {return 0;}return process1(arr, 0, aim);}//index:任意使用index之后的所有錢public static int process1(int[] arr, int index, int aim) {int res = 0;if (index == arr.length) {res = aim == 0 ? 1 : 0;} else {for (int zhang = 0; arr[index] * zhang <= aim; zhang++) {res += process1(arr, index + 1, aim - arr[index] * zhang);}}return res;}

?

?

process1暴力遞歸方法

?

?

返回值一樣,都要重復(fù)計(jì)算

?

?

第一個(gè)優(yōu)化版本,如果index和aim固定的,只要是后面要計(jì)算600那返回值一定是確定的,是個(gè)無后效性問題,前面怎么選擇不影響后面的操作。

利用一個(gè)map存儲(chǔ)之前的結(jié)果(緩存)。下次調(diào)用,直接取出,不用這么暴力的重復(fù)計(jì)算。(記憶化搜索)

?

//--map優(yōu)化版本//key index_aim//value 組合數(shù)public static HashMap<String, Integer> answer = new HashMap<>();public static int processMap(int[] arr, int index, int aim) {int res = 0;if (index == arr.length) {res = aim == 0 ? 1 : 0;} else {for (int zhang = 0; arr[index] * zhang <= aim; zhang++) {int nextAim = aim - arr[index] * zhang;String nextCalc = (index + 1) + "_" + nextAim;if (answer.containsKey(nextCalc)) {res += answer.get(nextCalc);} else {res += processMap(arr, index + 1, nextAim);}}}answer.put(index + "_" + aim, res);return res;}

?

?

接下來直接計(jì)算所有的變化(dp),準(zhǔn)備一個(gè)二維表 0~n是index的長度,n是終止位置。

可以裝下所有返回值。

?

?

最終需要得到0 aim的值

?

?

接下來思考一下,哪些位置的值是可以直接確定的,不依賴其他位置的(看遞歸中的basekey)

?

?

接著開始看位置依賴,也就是怎么調(diào)用遞歸的

index + 1, aim - arr[index] * zhang

一個(gè)具體的[index,aim]位置,需要他的下一行(假設(shè)是五塊)

Aim - 0張5塊、1張5塊、2張5塊...一直到越界的所以位置累加起來,就是他的值。

和題目已經(jīng)沒什么關(guān)系,就是看遞歸函數(shù)可以畫出這個(gè)圖。

?

?

從最后一行逐行逐個(gè)推算,可以算出最后的答案就是,他的下面+下面位置-5+下面位置-5*2...一直到越界,因?yàn)樽钌厦嬉涣械慕痤~數(shù)是5,所以最后結(jié)果是1+1+2=4。

?

public static int coins3(int[] arr, int aim) {if (arr == null || arr.length == 0 || aim < 0) {return 0;}int[][] dp = new int[arr.length][aim + 1];for (int i = 0; i < arr.length; i++) {dp[i][0] = 1;}for (int j = 1; arr[0] * j <= aim; j++) {dp[0][arr[0] * j] = 1;}int num = 0;for (int i = 1; i < arr.length; i++) {for (int j = 1; j <= aim; j++) {num = 0;for (int k = 0; j - arr[i] * k >= 0; k++) {num += dp[i - 1][j - arr[i] * k];}dp[i][j] = num;}}return dp[arr.length - 1][aim];}

?

最后一步的優(yōu)化(建立起空間感后)

計(jì)算A,需要對(duì)B12345進(jìn)行累加,其實(shí)A的前三個(gè)位置的同層C,已經(jīng)計(jì)算過12345,那其實(shí)只需要進(jìn)行C+B就行。

?

?

二維三維四維問題完全一樣。

?

public static int coins4(int[] arr, int aim) {if (arr == null || arr.length == 0 || aim < 0) {return 0;}int[][] dp = new int[arr.length][aim + 1];//填好可以直接設(shè)置的值for (int i = 0; i < arr.length; i++) {dp[i][0] = 1;}//例如第一行是5,那么5、10、15...位置肯定為1for (int j = 1; arr[0] * j <= aim; j++) {dp[0][arr[0] * j] = 1;}//代碼實(shí)現(xiàn)是從表格的上往下填for (int i = 1; i < arr.length; i++) {for (int j = 1; j <= aim; j++) {dp[i][j] = dp[i - 1][j];//每個(gè)位置一定包含下面的和//加上左邊的位置。dp[i][j] += j - arr[i] >= 0 ? dp[i][j - arr[i]] : 0;}}return dp[arr.length - 1][aim];}//最優(yōu)解,連dp都是一維的,現(xiàn)在理解起來有點(diǎn)超自然public static int coins5(int[] arr, int aim) {if (arr == null || arr.length == 0 || aim < 0) {return 0;}int[] dp = new int[aim + 1];for (int j = 0; arr[0] * j <= aim; j++) {dp[arr[0] * j] = 1;}for (int i = 1; i < arr.length; i++) {for (int j = 1; j <= aim; j++) {dp[j] += j - arr[i] >= 0 ? dp[j - arr[i]] : 0;}}return dp[aim];}public static void main(String[] args) {int[] coins = {10, 5, 1, 25};int aim = 2000;long start = 0;long end = 0;start = System.currentTimeMillis();System.out.println(coins1(coins, aim));end = System.currentTimeMillis();System.out.println("cost time : " + (end - start) + "(ms)");start = System.currentTimeMillis();System.out.println(coinsOther(coins, aim));end = System.currentTimeMillis();System.out.println("cost time : " + (end - start) + "(ms)");aim = 20000;start = System.currentTimeMillis();System.out.println(coins2(coins, aim));end = System.currentTimeMillis();System.out.println("cost time : " + (end - start) + "(ms)");start = System.currentTimeMillis();System.out.println(coins3(coins, aim));end = System.currentTimeMillis();System.out.println("cost time : " + (end - start) + "(ms)");start = System.currentTimeMillis();System.out.println(coins4(coins, aim));end = System.currentTimeMillis();System.out.println("cost time : " + (end - start) + "(ms)");start = System.currentTimeMillis();System.out.println(coins5(coins, aim));end = System.currentTimeMillis();System.out.println("cost time : " + (end - start) + "(ms)");}

?

?

之前是通過遞歸獲取值,現(xiàn)在是在dp里面獲取值。

?

解決類似題目:

排成一條線的紙牌博弈問題

【題目】

給定一個(gè)整型數(shù)組arr,代表數(shù)值不同的紙牌排成一條線。玩家A和玩家B依次拿走每張紙牌,規(guī)定玩家A先拿,玩家B后拿,但是每個(gè)玩家每次只能拿走最左或最右的紙牌,玩家A和玩家B都絕頂聰明。請(qǐng)返回最后獲勝者的分?jǐn)?shù)。

【舉例】

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

開始時(shí)玩家A只能拿走1或4。如果玩家A拿走1,則排列變?yōu)閇2,100,4],接下來玩家B可以拿走2或4,然后繼續(xù)輪到玩家A。如果開始時(shí)玩家A拿走4,則排列變?yōu)閇1,2,100],接下來玩家B可以拿走1或100,然后繼續(xù)輪到玩家A。玩家A作為絕頂聰明的人不會(huì)先拿4,因?yàn)槟?之后,玩家B將拿走100。所以玩家A會(huì)先拿1,讓排列變?yōu)閇2,100,4],接下來玩家B不管怎么選,100都會(huì)被玩家A拿走。玩家A會(huì)獲勝,分?jǐn)?shù)為101。所以返回101。

arr=[1,100,2]。

開始時(shí)玩家A不管拿1還是2,玩家B作為絕頂聰明的人,都會(huì)把100拿走。玩家B會(huì)獲勝,分?jǐn)?shù)為100。所以返回100。

?

?

先想暴力,加入緩存,建立空間感,再改動(dòng)態(tài)規(guī)劃。

練遞歸,學(xué)習(xí)怎么去試。底下的優(yōu)化全都是套路。

?

分析: ?f(i, j) 表示 在arr[i~j]中A 先手時(shí)能夠獲得的最大分?jǐn)?shù),s(i, j) 表示 A后手時(shí)能夠獲得的最大分?jǐn)?shù)。

首先分析f(i, j)。 A可先取arr[i], 取完后剩余arr[i+1, j]。此時(shí)相當(dāng)于A后手在[i+1, j]的情況了。

也可先取arr[j], 取完后剩余arr[i, j - 1]。 此時(shí)相當(dāng)于A后手在[i, j -1]的情況了。

則 f(i, j) = max{arr[i] + s(i+1, j), arr[j] + s(i, j-1)}

再分析s(i, j)。B可先取arr[i] 或 arr[j] 。取完后相當(dāng)于A先手的情況了。只是在這種情況下,B會(huì)留下最差解。

s(i, j) = min{arr[i] + f(i+1, j), arr[j] + f(i, j-1)};

?

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) {//先拿的//如果i == j,即arr[i...j]上只有一張紙牌,當(dāng)然會(huì)被先拿紙牌的人拿走,所以可以返回arr[i];if (i == j) {return arr[i];}//拿了其中一個(gè)之后,當(dāng)前玩家成了后拿的那個(gè)人//因?yàn)楫?dāng)前的玩家會(huì)做出最好的選擇,所以會(huì)拿走最好的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) {//后拿的//如果i == j,即arr[i...j]上只有一張紙牌,作為后拿的人必然什么也得不到,所以返回0;if (i == j) {return 0;}//因?yàn)閷?duì)手會(huì)拿走最好的,所以當(dāng)前玩家只能拿最差的return Math.min(f(arr, i + 1, j), f(arr, i, j - 1));}

?

?

改動(dòng)態(tài)規(guī)劃,兩張表。分析遞歸函數(shù),i和j的變化范圍(index變化范圍)。

i肯定不會(huì)大于j(下半?yún)^(qū)域直接不用填),先看目標(biāo)位置(打星星的地方),然后填上固定的值,i==j是對(duì)角線,填上對(duì)應(yīng)的值。

?

?

然后看普遍位置是怎么依賴的。

F(i,j)依賴與他相對(duì)稱的S(i,j)的

s(arr, i + 1, j), s(arr, i, j - 1);

同理S的點(diǎn)也依賴F的同樣位置。

?

?

通過對(duì)角線一行行直到把終止位置(那個(gè)小星星)推出來。

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];//一邊設(shè)置對(duì)角線,一邊計(jì)算值,對(duì)角線慢慢向上走for (int j = 0; j < arr.length; j++) {f[j][j] = arr[j];//設(shè)計(jì)的很好,以列為首要遍歷條件,再逐行向下計(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]);}public static void main(String[] args) {int[] arr = { 1, 9, 1 };System.out.println(win2(arr));System.out.println(win1(arr));}

?

?

?

2017年阿里的題目:

一個(gè)長度為N的路,1~N

一個(gè)機(jī)器人在M位置,他可以走P步,如果在1只能走右,在N只能走左,請(qǐng)問機(jī)器人走P步后他停在K位置上的走法有多少種。

?

?

?

public static int walk(int N, int curPosition, int remainSteps, int K) {if (N < 1 || curPosition < 1 || curPosition > N || remainSteps < 0 || K > N) {return 0;}if (remainSteps == 0) {return curPosition == K ? 1 : 0;}int count = 0;if (curPosition == 1) {count = walk(N, curPosition + 1, remainSteps - 1, K);} else if (curPosition == N) {count = walk(N, curPosition - 1, remainSteps - 1, K);} else {count = walk(N, curPosition + 1, remainSteps - 1, K) + walk(N, curPosition - 1, remainSteps - 1, K);}return count;}

?

?

改動(dòng)態(tài)規(guī)劃,可變參數(shù)是M(機(jī)器人位置)和P(可以走的步數(shù))

?

?

最后需要獲取的位置是(M,P)

普遍依賴,

?

curPosition + 1, remainSteps - 1 curPosition - 1, remainSteps - 1

?

?

楊輝三角形

?

?

計(jì)算到最后一排,看落到M上的數(shù)是幾就返回幾。

會(huì)撞墻的楊輝三角形(指的是在1和N的情況)

?

public static int dpWalk(int N, int curPosition, int remainSteps, int K) {int[][] dp = new int[remainSteps + 1][N + 1];dp[0][K] = 1;for (int i = 1; i <= remainSteps; i++) {for (int j = 1; j <= N; j++) {dp[i][j] += j - 1 < 1 ? 0 : dp[i - 1][j - 1];dp[i][j] += j + 1 > N ? 0 : dp[i - 1][j + 1];}}return dp[remainSteps][curPosition];}public static void main(String[] args) {System.out.println(walk(5, 3, 0, 3));System.out.println(walk(5, 3, 2, 3));System.out.println(dpWalk(5, 3, 0, 3));System.out.println(dpWalk(5, 3, 2, 3));}

?

只要你會(huì)試,什么都無所謂。

?

首先是遞歸試法,發(fā)現(xiàn)有多余的計(jì)算浪費(fèi),衍生出記憶化搜索,加上空間感后,用空間換了時(shí)間,即是動(dòng)態(tài)規(guī)劃。

如果你對(duì)編程感興趣或者想往編程方向發(fā)展,可以關(guān)注微信公眾號(hào)【筑夢(mèng)編程】,大家一起交流討論!小編也會(huì)每天定時(shí)更新既有趣又有用的編程知識(shí)!
?

總結(jié)

以上是生活随笔為你收集整理的算法进阶面试题07——求子数组的最大异或和(前缀树)、换钱的方法数(递归改dp最全套路解说)、纸牌博弈、机器人行走问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。