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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

剑指offer(60-67题)详解

發布時間:2025/3/20 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 剑指offer(60-67题)详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

    • 60 把二叉樹打印成多行
    • 61 序列化二叉樹
    • 62 二叉搜索樹第K個節點
    • 63 數據流中的中位數
    • 64 滑動窗口的最大值
    • 65 矩陣中的路徑
    • 66 機器人的運動范圍
    • 67 剪繩子

歡迎關注個人數據結構專欄哈
微信公眾號: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個隊列進行區別一下層就可以了。

實現代碼為:

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 序列化二叉樹

題目描述

請實現兩個函數,分別用來序列化和反序列化二叉樹

二叉樹的序列化是指:把一棵二叉樹按照某種遍歷方式的結果以某種格式保存為字符串,從而使得內存中建立起來的二叉樹可以持久保存。序列化可以基于先序、中序、后序、層序的二叉樹遍歷方式來進行修改,序列化的結果是一個字符串,序列化時通過 某種符號表示空節點(#),以 ! 表示一個結點值的結束(value!)。

二叉樹的反序列化是指:根據某種遍歷順序得到的序列化字符串結果str,重構二叉樹。

思路
對于序列化的概念,大家肯定會想起面向對象啦,json之類的開發相關,序列化反序列化就是能夠將一種結構變成字符存儲,而反序列化就是能夠根據制定的某種規則構造出原結構相同的對象

那么這題個人有兩個想法吧,但是實現只實現了一個。

思路一
二叉樹是可以用數組存儲的,雖然用數組遇到不規則的二叉樹空間利用效率會很低,但是是可以存的。對于第n個節點。它的兩個兒子是2n和2n+1(對應數組的物理地址)。

  • 所以實現上序列化你建立一個TreeNode[10000]數組,然后從根節點向后遍歷,如果有兒子放到對應位置。(當然這個實現上有點盲目性,不太好)。返回字符串如果有值返回對應值沒值的話可以用個特殊字串占位置。
  • 而反序列化你只需要把字符串先放到對應數組中,然后建立TreeNode[10000]數組與上面的遍歷關聯,如果有值那么就在這個位置建立TreeNode節點。再次遍歷的時候如果第n個節點的left指向2n,right指向2n+1位置.最終返回第1個位置的節點即可!反序列化完成。

思路二
我們知道:一個中序帶著前序或者后序可以確定一個二叉樹!好了,我們就用前序可中序完成。前面講過二叉樹的幾種遍歷。在非遞歸遍歷的前序和中序可以共用一個過程!詳細可以參考數據結構專欄哈!

  • 序列化:給你個根節點,非遞歸前序和中序序列可以搞出來吧?但是記住字符串要有個東西分割,不能直接加。比如19 8中間必須有個東西分隔開來,這個要注意。最后直接將兩個字符相加返回即可。
  • 反序列化: 給你帶有前序和中序的字符串。分割出前序和中序序列。前面第四題是構建二叉樹就是用前序和中序構建二叉樹的,就是遞歸的妙用,至于還原具體的思路還請還對應劍指offer的題解!

實現代碼為:

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

參考評論區:
有個遞歸方法挺好的,值得參考:

鏈接:https://www.nowcoder.com/questionTerminal/cf7e25aa97c04cc1a68c8f040e71fb84?answerType=1&f=discussion 來源:牛客網public class SerializeTree {int index = -1;/*** 分別遍歷左節點和右節點,空使用#代替,節點之間,隔開** @param root* @return*/public String Serialize(TreeNode root) {if (root == null) {return "#";} else {return root.val + "," + Serialize(root.left) + "," + Serialize(root.right);}}/*** 使用index來設置樹節點的val值,遞歸遍歷左節點和右節點,如果值是#則表示是空節點,直接返回** @param str* @return*/TreeNode Deserialize(String str) {String[] s = str.split(",");//將序列化之后的序列用,分隔符轉化為數組index++;//索引每次加一int len = s.length;if (index > len) {return null;}TreeNode treeNode = null;if (!s[index].equals("#")) {//不是葉子節點 繼續走 是葉子節點出遞歸treeNode = new TreeNode(Integer.parseInt(s[index]));treeNode.left = Deserialize(str);treeNode.right = Deserialize(str);}return treeNode;}public static void main(String[] args) {TreeNode treeNode1 = new TreeNode(1);TreeNode treeNode2 = new TreeNode(2);TreeNode treeNode3 = new TreeNode(3);TreeNode treeNode4 = new TreeNode(4);TreeNode treeNode5 = new TreeNode(5);TreeNode treeNode6 = new TreeNode(6);treeNode1.left = treeNode2;treeNode1.right = treeNode3;treeNode2.left = treeNode4;treeNode3.left = treeNode5;treeNode3.right = treeNode6;SerializeTree serializeTree = new SerializeTree();String str = serializeTree.Serialize(treeNode1);TreeNode treeNode = serializeTree.Deserialize(str);} }

62 二叉搜索樹第K個節點

題目描述

給定一棵二叉搜索樹,請找出其中的第k小的結點。例如, (5,3,7,2,4,6,8) 中,按結點數值大小順序第三小結點的值為4。

思路
首先,二叉搜索樹是有序的!二叉搜索樹的中序遍歷是有序序列!(基礎知識和常識了)。所以你主要非遞歸中序把節點釋放到第K個就可以返回結束了。(也可使用遞歸版本)

實現代碼為:

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 數據流中的中位數

題目描述

如何得到一個數據流中的中位數?如果從數據流中讀出奇數個數值,那么中位數就是所有數值排序之后位于中間的數值。如果從數據流中讀出偶數個數值,那么中位數就是所有數值排序之后中間兩個數的平均值。我們使用Insert()方法讀取數據流,使用GetMedian()方法獲取當前讀取數據的中位數。

思路
構造類型的題,中位數,也就是要我們維護一個有序數列取中間數。其實實現的方法也比較多吧。比如你可以用Arraylist。每次加入快速排序。也可以用優先隊列進行堆排序維護。但是快排對已經有序序列效率不高,隊列取數是比較麻煩的。

當然,注意這個序列是你一手構造的,從開始為0,并且每次加入節點的序列都是有序的。其實插入排序更好,這個步驟就相當于插入排序的最后一部一樣。把最后一個找到合適位置插入就可以了。而序列是有序的,二分法+直接插入是一種很不錯的解法!


實現代碼為:

import java.util.ArrayList; import java.util.List; public class Solution {List<Integer> list = new ArrayList<Integer>();public void Insert(Integer num) {int l=0,r=list.size()-1;int mid=(l+r)/2;while (l<r) {if(list.get(mid)<num){l=mid+1;mid=(l+r)/2;}else if (list.get(mid)>num) {r=mid;mid=(l+r)/2;}else {l=r=mid;}}list.add(l, num);}public Double GetMedian() {if(list.size()%2==1){return (double)list.get(list.size()/2);}else {return ((double) (list.get((list.size()-1)/2)+list.get(list.size()/2))/2);}} }

64 滑動窗口的最大值

題目描述

給定一個數組和滑動窗口的大小,找出所有滑動窗口里數值的最大值。例如,如果輸入數組{2,3,4,2,6,2,5,1}及滑動窗口的大小3,那么一共存在6個滑動窗口,他們的最大值分別為{4,4,6,6,6,5}; 針對數組{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)如果區間為1,那么最大的就是每個數。
  • (2)如果區間為2,就是相鄰的2個進行比較滑動取最大。
  • (3)如果區間為3,就是相鄰的3個進行比較!還是相鄰的(2)中的相鄰兩個最大兩個滑動。
  • (4)如果區間為4,就是相鄰的4個進行比較!還是相鄰的(3)中的相鄰兩個最大兩個滑動。
  • -----------

比如序列2 3 4 2 6 2 5 1

  • 區間為1:2 3 4 2 6 2 5 1
  • 區間為2:(23)3 (34)4 (42)4 (26)6 (62)6 (25)5 (51)5 7個窗口
  • 區間為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(區間為2的相互比較)。

當然在空間利用方面,其實不需要開辟新的數組,直接重復利用一個數組就行了,算完一輪進行下一輪,每次長度你記住少一就行了。


實現代碼為:

import java.util.ArrayList; public class Solution {public static ArrayList<Integer> maxInWindows(int [] num, int size){ArrayList<Integer>list=new ArrayList<Integer>();if(size==0)return list;int len=num.length;while (size-->1) {for(int i=0;i<len-1;i++){num[i]=Math.max(num[i], num[i+1]);}len--;}System.out.println(len);for(int i=0;i<len;i++){list.add(num[i]);}return list;}}

參考評論區:
評論區的雙隊列方法:

鏈接:https://www.nowcoder.com/questionTerminal/1624bc35a45c42c0bc17d17fa0cba788?f=discussion 來源:牛客網/*** 雙隊列方法* 滑動窗口的最大值總是保存在隊列首部,隊列里面的數據總是從大到小排列。** [@param num* @param size* @return](/profile/547241) */public ArrayList maxInWindows(int[] num, int size) {ArrayList res = new ArrayList();if (num == null || num.length == 0 || size == 0 || size > num.length) {return res;}Deque deque = new LinkedList();for (int i = 0; i < num.length; i++) {if (!deque.isEmpty()) {// 如果隊列頭元素不在滑動窗口中了,就刪除頭元素if (i >= deque.peek() + size) {deque.pop();}// 如果當前數字大于隊列尾,則刪除隊列尾,直到當前數字小于等于隊列尾,或者隊列空while (!deque.isEmpty() && num[i] >= num[deque.getLast()]) {deque.removeLast();}}deque.offer(i); // 入隊列// 滑動窗口經過一個滑動窗口的大小,就獲取當前的最大值,也就是隊列的頭元素if (i + 1 >= size) {res.add(num[deque.peek()]);}}return res;}

65 矩陣中的路徑

題目描述

請設計一個函數,用來判斷在一個矩陣中是否存在一條包含某字符串所有字符的路徑。路徑可以從矩陣中的任意一個格子開始,每一步可以在矩陣中向左,向右,向上,向下移動一個格子。如果一條路徑經過了矩陣中的某一個格子,則該路徑不能再進入該格子。 例如 a b c e s f c s a d e e 矩陣中包含一條字符串"bcced"的路徑,但是矩陣中不包含"abcb"路徑,因為字符串的第一個字符b占據了矩陣中的第一行第二個格子之后,路徑不能再次進入該格子。

思路
基礎迷宮類dfs搜素題。它給的是一維數組,我們要根據位置對應轉換成二維的數組儲存地圖。為了方便我們進行搜素。另外要建立X[]Y[]表示上下左右四個移動方向。

而對于這類題我們的一般做法是:

  • 二維的迷宮,用個boolean[][]數組判斷對應位置在當前是否走過。
  • 本題要求找到這么一串符合的,我們可以遍歷首字母相同的,從首字母相同的進行深度優先搜索,按照方向符合就往下搜索,到整個串路徑存在即可。
  • 本題的搜索是屬于試探回溯的。所以在dfs搜索走完一個位置如果滿足條件,那么向四個滿足條件(不越界沒走過等)方向進行搜索,同時標記此位置在這條路徑不能再次使用。而這條搜索完需要將boolean數組還原。至于dfs和回溯如果沒基礎的可以百度學習一下。

另外就是注意下特殊值,或者不滿足的要判斷排除。

實現代碼為:

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++)//轉到二維數組{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的格子開始移動,每一次只能向左,右,上,下四個方向移動一格,但是不能進入行坐標和列坐標的數位之和大于k的格子。 例如,當k為18時,機器人能夠進入方格(35,37),因為3+5+3+7 = 18。但是,它不能進入方格(35,38),因為3+5+3+8 = 19。請問該機器人能夠達到多少個格子?

思路
這題也是簡單搜索題,但是處理方式可以用bfs,也可以用dfs。主要考察迷宮的通達性。所以你用dfs千萬不要將Boolean判斷數組復原,走過的地方只能走一次。至于這個規則我想很容易判斷,求余10和除10下去就可以計算每個位置是否滿足規則。

但是如果迷宮和K足夠大的話,那么橫縱坐標都這要這么計算n次,效率是不高的。你可以預處理的,每一行每一列先加上對應行和列。這樣搜索時候不需要一個個計算直接比較就可以了。當然如果K小或者迷宮小可能會造成一些無用計算,這里了解下就可以了(并未采用)。

實現代碼為:

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) {//計算個位數字之和int num=0;while (x>0) {num+=x%10;x/=10;}while (y>0) {num+=y%10;y/=10;}return num;}}

67 剪繩子

題目描述

給你一根長度為n的繩子,請把繩子剪成整數長的m段(m、n都是整數,n>1并且m>1),每段繩子的長度記為k[0],k[1],…,k[m]。請問k[0]xk[1]x…xk[m]可能的最大乘積是多少?例如,當繩子的長度是8時,我們把它剪成長度分別為2、3、3的三段,此時得到的最大乘積是18。
輸入描述:
輸入一個數n,意義見題面。(2 <= n <= 60)

輸出描述:

輸出答案。
示例1
輸入
8
輸出
18

思路

看了評論區才發現原來這題分析的正解是什么,前面的自己做法雖然A了但可能是錯誤,但是這個想法仍有部分參考價值吧

主流解法是貪心和dp吧。個人更推薦貪心感覺更容易理解。

鏈接:https://www.nowcoder.com/questionTerminal/57d85990ba5b440ab888fc72b0751bf8?f=discussion 來源:牛客網// // Created by yuanhao on 2019-9-3. //#include <iostream> #include <cmath>using namespace std;/*** 題目分析:* 先舉幾個例子,可以看出規律來。* 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]可能有哪些數字,實際上只可能是2或者3。* 當然也可能有4,但是4=2*2,我們就簡單些不考慮了。* 5<2*3,6<3*3,比6更大的數字我們就更不用考慮了,肯定要繼續分。* 其次看2和3的數量,2的數量肯定小于3個,為什么呢?因為2*2*2<3*3,那么題目就簡單了。* 直接用n除以3,根據得到的余數判斷是一個2還是兩個2還是沒有2就行了。* 由于題目規定m>1,所以2只能是1*1,3只能是2*1,這兩個特殊情況直接返回就行了。** 乘方運算的復雜度為:O(log n),用動態規劃來做會耗時比較多。*/ 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都是整數,n>1并且m>1),每段繩子的長度記為k[0],k[1],...,k[m]。請問k[0]xk[1]x...xk[m]可能的最大乘積是多少?例如,當繩子的長度是8時,我們把它剪成長度分別為2、3、3的三段,此時得到的最大乘積是18。 // //輸入描述: //輸入一個數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题)详解的全部內容,希望文章能夠幫你解決所遇到的問題。

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