剑指offer(60-67题)详解
文章目錄
- 60 把二叉樹打印成多行
- 61 序列化二叉樹
- 62 二叉搜索樹第K個節(jié)點
- 63 數(shù)據流中的中位數(shù)
- 64 滑動窗口的最大值
- 65 矩陣中的路徑
- 66 機器人的運動范圍
- 67 剪繩子
歡迎關注個人數(shù)據結構專欄哈
微信公眾號:bigsai
劍指offer(1-10題)詳解
劍指offer(11-25題)詳解
劍指offer(26-33題)詳解
劍指offer(34-40題)詳解
劍指offer(41-50題)詳解
劍指offer(51-59題)詳解
劍指offer(60-67題)詳解
聲明:大部分題基本未參考題解,基本為個人想法,如果由效率太低的或者錯誤還請指正!
到這里,劍指offer就搞完了。2020-01-19到2020-02-29(大年初五)和100人一起,第一次組織吧,特記下!歡迎關注公眾號:bigsai下次再一起玩!
60 把二叉樹打印成多行
題目描述
從上到下按層打印二叉樹,同一層結點從左至右輸出。每一層輸出一行。
思路:
這題和上面59思路差不多,甚至更容易一些,對于層序遍歷。需要分層,所以用2個隊列進行區(qū)別一下層就可以了。
實現(xiàn)代碼為:
import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Queue;/* public class TreeNode {int val = 0;TreeNode left = null;TreeNode right = null;public TreeNode(int val) {this.val = val;}} */ public class Solution {ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) { ArrayList<ArrayList<Integer>>list=new ArrayList<ArrayList<Integer>>();if(pRoot==null)return list;Queue<TreeNode>q1=new ArrayDeque<TreeNode>();Queue<TreeNode>q2=new ArrayDeque<TreeNode>();q1.add(pRoot);while (!q1.isEmpty()||!q2.isEmpty()) {ArrayList<Integer>list2=new ArrayList<Integer>();while (!q1.isEmpty()) {TreeNode team=q1.poll();list2.add(team.val);if(team.left!=null)q2.add(team.left);if(team.right!=null)q2.add(team.right); }list.add(list2);q1.addAll(q2);q2.clear();//把q2全部加進q1。然后clear掉}return list; } }61 序列化二叉樹
題目描述
請實現(xiàn)兩個函數(shù),分別用來序列化和反序列化二叉樹
二叉樹的序列化是指:把一棵二叉樹按照某種遍歷方式的結果以某種格式保存為字符串,從而使得內存中建立起來的二叉樹可以持久保存。序列化可以基于先序、中序、后序、層序的二叉樹遍歷方式來進行修改,序列化的結果是一個字符串,序列化時通過 某種符號表示空節(jié)點(#),以 ! 表示一個結點值的結束(value!)。
二叉樹的反序列化是指:根據某種遍歷順序得到的序列化字符串結果str,重構二叉樹。
思路:
對于序列化的概念,大家肯定會想起面向對象啦,json之類的開發(fā)相關,序列化反序列化就是能夠將一種結構變成字符存儲,而反序列化就是能夠根據制定的某種規(guī)則構造出原結構相同的對象。
那么這題個人有兩個想法吧,但是實現(xiàn)只實現(xiàn)了一個。
思路一:
二叉樹是可以用數(shù)組存儲的,雖然用數(shù)組遇到不規(guī)則的二叉樹空間利用效率會很低,但是是可以存的。對于第n個節(jié)點。它的兩個兒子是2n和2n+1(對應數(shù)組的物理地址)。
- 所以實現(xiàn)上序列化你建立一個TreeNode[10000]數(shù)組,然后從根節(jié)點向后遍歷,如果有兒子放到對應位置。(當然這個實現(xiàn)上有點盲目性,不太好)。返回字符串如果有值返回對應值沒值的話可以用個特殊字串占位置。
- 而反序列化你只需要把字符串先放到對應數(shù)組中,然后建立TreeNode[10000]數(shù)組與上面的遍歷關聯(lián),如果有值那么就在這個位置建立TreeNode節(jié)點。再次遍歷的時候如果第n個節(jié)點的left指向2n,right指向2n+1位置.最終返回第1個位置的節(jié)點即可!反序列化完成。
思路二:
我們知道:一個中序帶著前序或者后序可以確定一個二叉樹!好了,我們就用前序可中序完成。前面講過二叉樹的幾種遍歷。在非遞歸遍歷的前序和中序可以共用一個過程!詳細可以參考數(shù)據結構專欄哈!
- 序列化:給你個根節(jié)點,非遞歸前序和中序序列可以搞出來吧?但是記住字符串要有個東西分割,不能直接加。比如19 8中間必須有個東西分隔開來,這個要注意。最后直接將兩個字符相加返回即可。
- 反序列化: 給你帶有前序和中序的字符串。分割出前序和中序序列。前面第四題是構建二叉樹就是用前序和中序構建二叉樹的,就是遞歸的妙用,至于還原具體的思路還請還對應劍指offer的題解!
實現(xiàn)代碼為:
import java.util.Stack; /* public class TreeNode {int val = 0;TreeNode left = null;TreeNode right = null;public TreeNode(int val) {this.val = val;} } */ public class Solution {static String Serialize(TreeNode root) {if(root==null)return "";String qian="";String zhong="";Stack<TreeNode>stack=new Stack<TreeNode>();while (!stack.isEmpty()||root!=null) {//前序的if(root!=null){qian+=" "+root.val;stack.push(root);root=root.left;}else {root=stack.pop();zhong+=" "+root.val;root=root.right;}}return qian.substring(1)+zhong; }static TreeNode Deserialize(String str) {if(str.equals(""))return null;String value[]=str.split(" ");int len=value.length;int qian[]=new int [len/2];int hou[]=new int [len/2];for(int i=0;i<len/2;i++){qian[i]=Integer.parseInt(value[i]);}for(int i=0;i<len/2;i++){hou[i]=Integer.parseInt(value[i+len/2]);}return creat(qian,hou,0,len/2-1,0,len/2-1); }private static TreeNode creat(int[] pre, int[] in, int preleft, int preright, int inleft, int inright) {if(preleft>preright||inleft>inright)return null;TreeNode node=new TreeNode(pre[preleft]);int mid=0;for(int i=inleft;i<=inright;i++){if(pre[preleft]==in[i]){mid=i;}}node.left=creat(pre, in, preleft+1, preleft+(mid-inleft), inleft, mid-1);node.right=creat(pre, in, preleft+(mid-inleft)+1, preright, mid+1, inright);return node;} }參考評論區(qū):
有個遞歸方法挺好的,值得參考:
62 二叉搜索樹第K個節(jié)點
題目描述
給定一棵二叉搜索樹,請找出其中的第k小的結點。例如, (5,3,7,2,4,6,8) 中,按結點數(shù)值大小順序第三小結點的值為4。
思路:
首先,二叉搜索樹是有序的!二叉搜索樹的中序遍歷是有序序列!(基礎知識和常識了)。所以你主要非遞歸中序把節(jié)點釋放到第K個就可以返回結束了。(也可使用遞歸版本)
實現(xiàn)代碼為:
import java.util.Stack; /* public class TreeNode {int val = 0;TreeNode left = null;TreeNode right = null;public TreeNode(int val) {this.val = val;} } */ public class Solution {TreeNode KthNode(TreeNode pRoot, int k){if(k==0)return null;Stack<TreeNode>stack=new Stack<TreeNode>();while (!stack.isEmpty()||pRoot!=null) {if(pRoot!=null){stack.push(pRoot);pRoot=pRoot.left;}else {pRoot=stack.pop();k--;if(k==0)return pRoot;pRoot=pRoot.right;}}if(k>0)return null;return pRoot; } }63 數(shù)據流中的中位數(shù)
題目描述
如何得到一個數(shù)據流中的中位數(shù)?如果從數(shù)據流中讀出奇數(shù)個數(shù)值,那么中位數(shù)就是所有數(shù)值排序之后位于中間的數(shù)值。如果從數(shù)據流中讀出偶數(shù)個數(shù)值,那么中位數(shù)就是所有數(shù)值排序之后中間兩個數(shù)的平均值。我們使用Insert()方法讀取數(shù)據流,使用GetMedian()方法獲取當前讀取數(shù)據的中位數(shù)。
思路:
構造類型的題,中位數(shù),也就是要我們維護一個有序數(shù)列取中間數(shù)。其實實現(xiàn)的方法也比較多吧。比如你可以用Arraylist。每次加入快速排序。也可以用優(yōu)先隊列進行堆排序維護。但是快排對已經有序序列效率不高,隊列取數(shù)是比較麻煩的。
當然,注意這個序列是你一手構造的,從開始為0,并且每次加入節(jié)點的序列都是有序的。其實插入排序更好,這個步驟就相當于插入排序的最后一部一樣。把最后一個找到合適位置插入就可以了。而序列是有序的,二分法+直接插入是一種很不錯的解法!
實現(xiàn)代碼為:
64 滑動窗口的最大值
題目描述
給定一個數(shù)組和滑動窗口的大小,找出所有滑動窗口里數(shù)值的最大值。例如,如果輸入數(shù)組{2,3,4,2,6,2,5,1}及滑動窗口的大小3,那么一共存在6個滑動窗口,他們的最大值分別為{4,4,6,6,6,5}; 針對數(shù)組{2,3,4,2,6,2,5,1}的滑動窗口有以下6個: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。
思路:
這題有技巧的,可以直接查找但是可能混亂一點。這題有興趣的可以了解線段樹,感覺有些思想有點像。雖然蠻干和我的題解在效率差別不大,但是這個思想感覺可以體會一下。
- (1)如果區(qū)間為1,那么最大的就是每個數(shù)。
- (2)如果區(qū)間為2,就是相鄰的2個進行比較滑動取最大。
- (3)如果區(qū)間為3,就是相鄰的3個進行比較!還是相鄰的(2)中的相鄰兩個最大兩個滑動。
- (4)如果區(qū)間為4,就是相鄰的4個進行比較!還是相鄰的(3)中的相鄰兩個最大兩個滑動。
- -----------
比如序列2 3 4 2 6 2 5 1
- 區(qū)間為1:2 3 4 2 6 2 5 1
- 區(qū)間為2:(23)3 (34)4 (42)4 (26)6 (62)6 (25)5 (51)5 7個窗口
- 區(qū)間為3:(234)3 (342)4 (426)6 (262)6 (625)6 (251)5 6個滑動。也等價于 (2334)3 (3442)4 (4226)6 (2662)6 (6225)6 (2551)5(區(qū)間為2的相互比較)。
當然在空間利用方面,其實不需要開辟新的數(shù)組,直接重復利用一個數(shù)組就行了,算完一輪進行下一輪,每次長度你記住少一就行了。
實現(xiàn)代碼為:
參考評論區(qū):
評論區(qū)的雙隊列方法:
65 矩陣中的路徑
題目描述
請設計一個函數(shù),用來判斷在一個矩陣中是否存在一條包含某字符串所有字符的路徑。路徑可以從矩陣中的任意一個格子開始,每一步可以在矩陣中向左,向右,向上,向下移動一個格子。如果一條路徑經過了矩陣中的某一個格子,則該路徑不能再進入該格子。 例如 a b c e s f c s a d e e 矩陣中包含一條字符串"bcced"的路徑,但是矩陣中不包含"abcb"路徑,因為字符串的第一個字符b占據了矩陣中的第一行第二個格子之后,路徑不能再次進入該格子。
思路:
基礎迷宮類dfs搜素題。它給的是一維數(shù)組,我們要根據位置對應轉換成二維的數(shù)組儲存地圖。為了方便我們進行搜素。另外要建立X[]Y[]表示上下左右四個移動方向。
而對于這類題我們的一般做法是:
- 二維的迷宮,用個boolean[][]數(shù)組判斷對應位置在當前是否走過。
- 本題要求找到這么一串符合的,我們可以遍歷首字母相同的,從首字母相同的進行深度優(yōu)先搜索,按照方向符合就往下搜索,到整個串路徑存在即可。
- 本題的搜索是屬于試探回溯的。所以在dfs搜索走完一個位置如果滿足條件,那么向四個滿足條件(不越界沒走過等)方向進行搜索,同時標記此位置在這條路徑不能再次使用。而這條搜索完需要將boolean數(shù)組還原。至于dfs和回溯如果沒基礎的可以百度學習一下。
另外就是注意下特殊值,或者不滿足的要判斷排除。
實現(xiàn)代碼為:
public class Solution {boolean judge=false;//判斷是否存在int x[]= {-1,0,1,0};int y[]= {0,1,0,-1};public boolean hasPath(char[] matrix, int rows, int cols, char[] str){if(str.length==0||matrix.length==0)return false;char map[][]=new char[rows][cols];boolean jud[][]=new boolean[rows][cols];for(int i=0;i<matrix.length;i++)//轉到二維數(shù)組{map[i/cols][i%cols]=matrix[i];}for(int i=0;i<rows;i++){for(int j=0;j<cols;j++){if(map[i][j]==str[0])//第一個相同{dfs(map,jud,i,j,0,str);if(judge)return true;}}}return false; }private void dfs(char[][] map, boolean[][] jud, int m, int n, int index, char[] str) {if(index==str.length-1) {if(map[m][n]==str[index])judge=true;}//有成功的else if(map[m][n]!=str[index]) {}//回溯停止else {//map[n][n]==str[index]jud[m][n]=true;for(int i=0;i<4;i++){if(m+x[i]>=0&&m+x[i]<map.length&&n+y[i]>=0&&n+y[i]<map[0].length&&map[m+x[i]][n+y[i]]==str[index+1]){if(!jud[m+x[i]][n+y[i]]){dfs(map, jud, m+x[i], n+y[i], index+1, str);}}}jud[m][n]=false;} } }66 機器人的運動范圍
題目描述
地上有一個m行和n列的方格。一個機器人從坐標0,0的格子開始移動,每一次只能向左,右,上,下四個方向移動一格,但是不能進入行坐標和列坐標的數(shù)位之和大于k的格子。 例如,當k為18時,機器人能夠進入方格(35,37),因為3+5+3+7 = 18。但是,它不能進入方格(35,38),因為3+5+3+8 = 19。請問該機器人能夠達到多少個格子?
思路:
這題也是簡單搜索題,但是處理方式可以用bfs,也可以用dfs。主要考察迷宮的通達性。所以你用dfs千萬不要將Boolean判斷數(shù)組復原,走過的地方只能走一次。至于這個規(guī)則我想很容易判斷,求余10和除10下去就可以計算每個位置是否滿足規(guī)則。
但是如果迷宮和K足夠大的話,那么橫縱坐標都這要這么計算n次,效率是不高的。你可以預處理的,每一行每一列先加上對應行和列。這樣搜索時候不需要一個個計算直接比較就可以了。當然如果K小或者迷宮小可能會造成一些無用計算,這里了解下就可以了(并未采用)。
實現(xiàn)代碼為:
public class Solution {int X[]= {-1,0,1,0};int Y[]= {0,1,0,-1};int num=0;public int movingCount(int threshold, int rows, int cols){boolean jud[][]=new boolean[rows][cols];dfs(0,0,rows,cols,threshold,jud);return num;}private void dfs(int x, int y, int rows, int cols, int threshold, boolean[][] jud) {int count=nadd(x,y); if(count>threshold) { }else {num++;jud[x][y]=true;for(int i=0;i<4;i++){if(x+X[i]>=0&&x+X[i]<rows&&y+Y[i]>=0&&y+Y[i]<cols&&!jud[x+X[i]][y+Y[i]])//不越界{dfs(x+X[i], y+Y[i], rows, cols, threshold, jud);}}}}private int nadd(int x, int y) {//計算個位數(shù)字之和int num=0;while (x>0) {num+=x%10;x/=10;}while (y>0) {num+=y%10;y/=10;}return num;}}67 剪繩子
題目描述
給你一根長度為n的繩子,請把繩子剪成整數(shù)長的m段(m、n都是整數(shù),n>1并且m>1),每段繩子的長度記為k[0],k[1],…,k[m]。請問k[0]xk[1]x…xk[m]可能的最大乘積是多少?例如,當繩子的長度是8時,我們把它剪成長度分別為2、3、3的三段,此時得到的最大乘積是18。
輸入描述:
輸入一個數(shù)n,意義見題面。(2 <= n <= 60)
輸出描述:
輸出答案。
示例1
輸入
8
輸出
18
思路:
看了評論區(qū)才發(fā)現(xiàn)原來這題分析的正解是什么,前面的自己做法雖然A了但可能是錯誤,但是這個想法仍有部分參考價值吧!
主流解法是貪心和dp吧。個人更推薦貪心感覺更容易理解。
鏈接:https://www.nowcoder.com/questionTerminal/57d85990ba5b440ab888fc72b0751bf8?f=discussion 來源:牛客網// // Created by yuanhao on 2019-9-3. //#include <iostream> #include <cmath>using namespace std;/*** 題目分析:* 先舉幾個例子,可以看出規(guī)律來。* 4 : 2*2* 5 : 2*3* 6 : 3*3* 7 : 2*2*3 或者4*3* 8 : 2*3*3* 9 : 3*3*3* 10:2*2*3*3 或者4*3*3* 11:2*3*3*3* 12:3*3*3*3* 13:2*2*3*3*3 或者4*3*3*3** 下面是分析:* 首先判斷k[0]到k[m]可能有哪些數(shù)字,實際上只可能是2或者3。* 當然也可能有4,但是4=2*2,我們就簡單些不考慮了。* 5<2*3,6<3*3,比6更大的數(shù)字我們就更不用考慮了,肯定要繼續(xù)分。* 其次看2和3的數(shù)量,2的數(shù)量肯定小于3個,為什么呢?因為2*2*2<3*3,那么題目就簡單了。* 直接用n除以3,根據得到的余數(shù)判斷是一個2還是兩個2還是沒有2就行了。* 由于題目規(guī)定m>1,所以2只能是1*1,3只能是2*1,這兩個特殊情況直接返回就行了。** 乘方運算的復雜度為:O(log n),用動態(tài)規(guī)劃來做會耗時比較多。*/ long long n_max_3(long long n) {if (n == 2) {return 1;}if (n == 3) {return 2;}long long x = n % 3;long long y = n / 3;if (x == 0) {return pow(3, y);} else if (x == 1) {return 2 * 2 * (long long) pow(3, y - 1);} else {return 2 * (long long) pow(3, y);} }//給你一根長度為n的繩子,請把繩子剪成m段(m、n都是整數(shù),n>1并且m>1),每段繩子的長度記為k[0],k[1],...,k[m]。請問k[0]xk[1]x...xk[m]可能的最大乘積是多少?例如,當繩子的長度是8時,我們把它剪成長度分別為2、3、3的三段,此時得到的最大乘積是18。 // //輸入描述: //輸入一個數(shù)n,意義見題面。(2 <= n <= 100) // // //輸出描述: //輸出答案。 //示例1 //輸入 //8 //輸出 //18 int main() {long long n = 0;cin >> n;cout << n_max_3(n) << endl;return 0; } 鏈接:https://www.nowcoder.com/questionTerminal/57d85990ba5b440ab888fc72b0751bf8?f=discussion 來源:牛客網/*** @param target* @return** 分類:最值型、劃分型** 考慮最后一步:* 必然有一個點,把target分成兩段,兩段分別構成最小子問題。* 兩段的最大值的乘積,也是target所求的最大值。* 設劃分點為i,f[i]表示長度為i的繩子的乘積最大值。** 轉移方程:* f[i] = MAX{f[j]*f[i-j]}|0<j<i** 那么我們求的就是f[target]** 初始值:* f[0]=1* f[1]=1** 邊界情況:* 無** 計算順序:* i從1到target* j從1到i*/public int cutRope(int target) {int[] f = new int[target+1];//初始化f[0] = 0;f[1] = 1;for (int i = 1; i <= target; i++) {/*** 處理不分割的情況,因為繩子不能不被分割*/if(i==target) {f[i] = 1;}else {f[i] = i;}for (int j = 1; j < i; j++) {f[i] = Math.max(f[i],f[j]*f[i-j]);}}return f[target];}到這里,劍指offer就搞完了。2020-01-19到2020-02-29(大年初五),特記!歡迎關注公眾號:bigsai下次再一起玩!
總結
以上是生活随笔為你收集整理的剑指offer(60-67题)详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 剑指offer(34-40题)详解
- 下一篇: 浅谈迷宫搜索类的双向bfs问题(例题解析