剑指offer(11-25题)详解
文章目錄
- 11 二進制種1的個數★
- 12 數值的正數次方
- 13 調整數組順序使奇數位于偶數前面
- 14 鏈表中倒數第K個節點
- 15 反轉鏈表
- 16 合并兩個排序的鏈表
- 17 樹的子結構
- 18 二叉樹的鏡像
- 19 順時針打印矩陣
- 20 包含main函數的棧
- 21 棧的壓入、彈出序列
- 22 從下往上打印二叉樹
- 23 二叉搜索樹的后序遍歷序列
- 24 二叉樹中和為某一值的路徑
- 25 復雜鏈表的復制
歡迎關注個人數據結構專欄哈
劍指offer系列:
劍指offer(1-10題)詳解
劍指offer(11-25題)詳解
劍指offer(26-33題)詳解
劍指offer(34-40題)詳解
劍指offer(51-59題)詳解
劍指offer(34-40題)詳解
微信公眾號:bigsai
聲明:大部分題基本未參考題解,基本為個人想法,如果由效率太低的或者錯誤還請指正!,如果有誤導,還請指正!
11 二進制種1的個數★
題目描述
輸入一個整數,輸出該數二進制表示中1的個數。其中負數用補碼表示。。
思路:
這題感覺應該還會有很多思路和解法,單身筆者自身只寫了兩個方法吧,后面討論看題解會進行補充。
法一:直接轉成二進制字符串。然后進行比較里面的1的個數。
法二: 大家知道每個類型的數據它的背后實際都是二進制操作。大家知道int的數據類型的范圍是(-231,231-1)。并且int有32位。但是并非32位全部用來表示數據。真正用來表示數據大小的也是31位。最高位用來表示正負。
首先大家要知道:
1<<0=1 =00000000000000000000000000000001
1<<1=2 =00000000000000000000000000000010
1<<2=4 =00000000000000000000000000000100
1<<3=8 =00000000000000000000000000001000
. . . . . .
1<<30=230 =01000000000000000000000000000000
1<<31=-231 =10000000000000000000000000000000
其次還要知道位運算&與。兩個十進制與運算.每一位同1為1。所以我們用2的正數次冪與知道的數分別進行與。如果結果不為0,那么就說明這位為1.(前面31個都是大于0的最后一個與結果是負數但是如果該位為1那么結果肯定不為0)
代碼:
public int NumberOf1(int n) {int va=0;String num=Integer.toBinaryString(n);for(int i=0;i<num.length();i++){if(num.charAt(i)=='1'){va++;}}return va;}public int NumberOf2(int n) {int va=0;for(int i=0;i<32;i++){if((n&(1<<i))!=0){ va++;}}return va; }public int NumberOf3(int n) {//上面的差不多,可能慢一點int va=0;int num=1;for(int i=0;i<32;i++){ if((n&num)!=0){ va++;}num*=2;}return va; }評論區參考:
看了評論區,發現前面的方法很多人采用,但是也有些有差別的:我那個是用這個數分別和2,4,8,16等進行位運算計算,還有事用位運算右移每次和1進行位運算比較。其實本質一樣的,就是選的靜止參照物不同而已。
而還有一種非常棒的方法,確實沒想到,就是運用n&(n-1)。n如果不為0,那么n-1就是二進制第一個為1的變為0,后面全為1.這樣的n&(n-1)一次運算就相當于把最后一個1變成0.這樣一直到運算的數為0停止計算次數就好了。
實現代碼為:
12 數值的正數次方
題目描述
給定一個double類型的浮點數base和int類型的整數exponent。求base的exponent次方。
保證base和exponent不同時為0
思路:
法1:直接利用數學包求。或者直接進行exponent次相乘。復雜度為O(n);
法2:快速冪求法。首先快速冪思想可以參考以前寫的一個記錄快速冪模板。但是這題有點不同就是不需要求余。也就是說數值不會越界,數值可能較小。
還有一點快速冪適合正數次冪這里可能為負數次冪。假設a,b大于0.但是a-b可以轉化為(1/a)b然后進行快速冪!時間復雜度為O(logn).
代碼:
13 調整數組順序使奇數位于偶數前面
題目描述
輸入一個整數數組,實現一個函數來調整該數組中數字的順序,使得所有的奇數位于數組的前半部分,所有的偶數位于數組的后半部分,并保證奇數和奇數,偶數和偶數之間的相對位置不變。
思路:
題目要求1、奇數位于偶數前面2、奇數之間相對位置不變。
注意事項: void函數,注意對象克隆,深淺拷貝。如果是java最好先了解java函數調用參數(指針)問題。
- 需要新開數組team復制(克隆)array數組。
- 進行兩次遍歷team,一次從左向右找奇數從左向右賦值給array對應位置。
- 一次從右向左遍歷team將數值從右往左賦值給array對應位置。
參考討論區:
我是基于開辟新空間數組跑兩遍實現的,但是有的討論區不開辟新空間的話那就只能考慮一些排序(插入排序)。當然,在vx打卡交流群中有個小姐姐曾經用兩個隊列跑一遍儲存感覺思想也很棒!還沒想到用隊列呢!
14 鏈表中倒數第K個節點
題目描述
輸入一個鏈表,輸出該鏈表中倒數第k個結點。
思路:
感覺這題可以搞得實現方式比較多吧。
比如你可以借助一個Arraylist之類集合將鏈表遍歷加入。根據集合大小和K直接取值(自行ac)。
比如你還可以不借助外部集合,第一次遍歷到尾獲得鏈表長度l。根據長度l和倒數第k個節點獲得正數的序號。再遍歷一次取該節點的值即可!
代碼:
/* public class ListNode {int val;ListNode next = null;ListNode(int val) {this.val = val;} }*/ public class Solution {public ListNode FindKthToTail(ListNode head, int k) {int count = 0;ListNode team = head;while (team != null) {count++;team = team.next;}int value = count - k;int index = 0;while (head!= null) {if (index == value) {return head;}index++;head = head.next;}return head;}}參考討論區
這題參考了討論區發現了一些其他也很不錯的解法:
比如遞歸先到頭,返回的時候對整型數值疊加符合題意返回,判斷處理即可!
還有就是定義快慢指針。這個思想就像兩輛車中間拖個繩子,前車走的如果超過繩子長度就拉著后車保持繩子長度跑。如果繩子沒拉直那就肯定不符合題意噠!
15 反轉鏈表
題目描述
輸入一個鏈表,反轉鏈表后,輸出新鏈表的表頭。
思路:
法一:最簡單的想法是遍歷整個鏈表,head用來遍歷。team用來作為新鏈表返回。每次遍歷時候新建一個listNode指向前面的team。
實現代碼:
法二(參考):參考了評論區一個遞歸的方案,感覺很巧妙,至于理解就像圖所畫。
實現代碼為:
public static ListNode ReverseList(ListNode head) {if (head == null||head.next==null)return head; else { ListNode node=ReverseList(head.next);head.next.next = head;head.next = null; return node; }理解為:
16 合并兩個排序的鏈表
題目描述
輸入兩個單調遞增的鏈表,輸出兩個鏈表合成后的鏈表,當然我們需要合成后的鏈表滿足單調不減規則。
思路:
感覺可行方案還是不止一個的。
比如兩個鏈表你可以用一個list1作為主鏈表返回。返回另一個list2進行遍歷比較插入到主鏈表適當位置中。有興趣可以試一試。
當然你還可以直接建立一個新鏈表頭節點value。list1和list2同時遍歷,取小的節點添加到value為頭節點的鏈表中。同時小的那個鏈表向下進行下一輪比較。直到list1和list2都為null為止。
代碼為:
/* public class ListNode {int val;ListNode next = null;ListNode(int val) {this.val = val;} }*/ public class Solution {public ListNode Merge(ListNode list1,ListNode list2) {if(list1==null)return list2;else if (list2== null) {return list1;}ListNode value=new ListNode(0);ListNode team=value;while(list1!=null&&list2!=null){if(list1.val<list2.val){team.next=new ListNode(list1.val);list1=list1.next;team=team.next;if(list1==null){team.next=list2;break; }}else {team.next=new ListNode(list2.val);list2=list2.next;team=team.next;if(list2==null){team.next=list1;break; } }}value=value.next;return value; } }參考討論區:
有個圖感覺很精妙,省的畫啦,當然我是用用兩個節點一直while(這樣的比較),而討論區有兄弟用遞歸的方法感覺也很不錯的!具體討論可以到牛客討論區看得到哈。
17 樹的子結構
題目描述
輸入兩棵二叉樹A,B,判斷B是不是A的子結構。(ps:我們約定空樹不是任意一個樹的子結構)
思路:
這題當時還卡住了。判斷B是不是A的子結構。我最初想著是不是直接在A樹上某個節點開始結構一直往下就和B相同。但這顯然是錯的。因為B是A的子結構。很可能不到葉子節點就是中間的一小部分。但是這個也不難解決。只需要判斷B到葉子節點停止一下就可以了。
這題需要一個節點一個節點的進行比較。我才用隊列的層序遍歷(bfs)。當有相同的就停止返回。執行到最后不停止就說明這個是沒有相同的 。
代碼為:
/** public class TreeNode {int val = 0;TreeNode left = null;TreeNode right = null;public TreeNode(int val) {this.val = val;}} */ import java.util.ArrayDeque; import java.util.Queue; public class Solution { public boolean HasSubtree(TreeNode root1,TreeNode root2) {Queue<TreeNode>q1=new ArrayDeque<TreeNode>();if (root2==null||root1==null)return false;else {q1.add(root1);while (!q1.isEmpty()) {TreeNode t1=q1.poll();if(jud(t1, root2))return true;else {if(t1.left!=null)q1.add(t1.left);if(t1.right!=null)q1.add(t1.right);}}return false;}} public boolean jud(TreeNode root1,TreeNode root2) {if(root2==null)return true;else if(root1==null){return false;}else if(root1.val==root2.val) {return (jud(root1.left, root2.left)&&jud(root1.right,root2.right)); } else {return false;} } }參考評論區:
主要思想差不多吧,要注重子樹和子結構的區別。當然個別大佬把HasSubtree函數用遞歸寫了感覺效率很差,就不貼了有興趣的可以自己研究哈!
18 二叉樹的鏡像
題目描述
操作給定的二叉樹,將其變換為源二叉樹的鏡像。
輸入描述
二叉樹的鏡像定義:源二叉樹 8/ \6 10/ \ / \5 7 9 11鏡像二叉樹8/ \10 6/ \ / \11 9 7 5思路:
最直觀和好想的肯定是遞歸法。遇到二叉樹這種未知結構大部分都可以采用遞歸求解,只需要交換節點左右子樹即可。而上述的遞歸過程大致如下:
實現代碼為:
/** public class TreeNode {int val = 0;TreeNode left = null;TreeNode right = null;public TreeNode(int val) {this.val = val;} } */ public class Solution {public void Mirror(TreeNode root) {if(root==null) return;TreeNode teamNode=root.right;root.right=root.left;root.left=teamNode;if(root.left!=null) {Mirror(root.left);}if(root.right!=null) {Mirror(root.right);}} }19 順時針打印矩陣
題目描述
輸入一個矩陣,按照從外向里以順時針的順序依次打印出每一個數字,例如,如果輸入如下4 X 4矩陣: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 則依次打印出數字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
思路:
這題其實方法還是蠻重要的。個人主要兩個想法吧:
思路一:模擬
- 大家都知道是順時針進行旋轉式的輸出。那么這本身就是一次運動,那么我們就可以模擬這個光標的運動,一直到結束。
- 想法雖然簡單,但是實現起來很麻煩!比如,你可能需要用參數標記當前的方向,還可能需要用個boolean數組判斷是否走過和是否越界問題。還可能需要多個if else當判定當前結束選擇下一個方向。想想這個代碼一定是很臃腫的。雖然可以實現,有興趣的可以嘗試。
思路二:數學計算(不一定說的很好可以自行理解)
正常情況下:一個順時針是進行四次!但是每次走的位置咱們是可以確定的。咱么假設豎的為X,橫的為Y。
- 先不考慮走完的問題。每次走的四次開始的坐標和結束的坐標是可以算出來的!因為相當于每走一圈橫縱坐標都減二。而開始和結束位置分別加1和減1。所以每次走的結束位置可以準確表示出來。
- 考慮走完的問題。肯定是根據窄的走完。問題還有奇偶的問題。如果偶數的話可以剛好走完,而奇數需要特殊判斷一下省略最后一次循環一些步驟才行!
- 其他注意點。當循環添加一輪的時候,實際上當前的坐標是有變化的。需要手動修正一下
實現代碼為:
import java.util.ArrayList; public class Solution {public ArrayList<Integer> printMatrix(int [][] matrix) {ArrayList<Integer> list=new ArrayList<Integer>();int xlen=matrix.length;int ylen=matrix[0].length;int min=Math.min(xlen, ylen);int x=0;//上下 縱坐標int y=0;//左右 橫坐標for(int i=0;i<(min+1)/2;i++){for(;y<ylen-i;y++) {list.add(matrix[x][y]);}y--;x++;for(;x<xlen-i;x++) {list.add(matrix[x][y]);}x--;y--;if(i==xlen/2)continue;for(;y>=i;y--) {list.add(matrix[x][y]);}y++;x--;if(i==ylen/2)continue;for(;x>i;x--) {list.add(matrix[x][y]);}x++;y++;}return list; } }20 包含main函數的棧
題目描述
定義棧的數據結構,請在該類型中實現一個能夠得到棧中所含最小元素的min函數(時間復雜度應為O(1))。
思路:
這題感覺這樣要求返回棧中最小元素為O(1),那么說明需要有個常數min來維護這個最小值,可以直接返回。而這個min維護的時候只需要注意兩點:
實現代碼為:
import java.util.Stack; public class Solution { private int a[]=new int [1000];private int index=0 ;private int min=Integer.MAX_VALUE;public void push(int node) {a[index]=node;if(a[index]<min)min=a[index];index++;} public void pop() {if(a[--index]==min){min=Integer.MAX_VALUE;for(int i=0;i<index;i++){if(a[i]<min){min=a[i];}}} } public int top() {return a[index-1]; } public int min() {return min;} }21 棧的壓入、彈出序列
題目描述
輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否可能為該棧的彈出順序。假設壓入棧的所有數字均不相等。例如序列1,2,3,4,5是某棧的壓入順序,序列4,5,3,2,1是該壓棧序列對應的一個彈出序列,但4,3,5,1,2就不可能是該壓棧序列的彈出序列。(注意:這兩個序列的長度是相等的)
思路:
這題核心思想是模擬吧。給了壓入序列看是不是彈出序列。我們假設這就是個彈出序列。然后根據這個彈出序列的順序。將第一個數組的數據一個一個壓入棧。當遇到在彈出序列當前位置的值時候就彈出。最終如果棧為空那么說明假設成立,如果不為空說明這個假設失敗,就不是彈出序列。
實現代碼為:
22 從下往上打印二叉樹
題目描述
從上往下打印出二叉樹的每個節點,同層節點從左至右打印。
思路:
用隊列,就是二叉樹的層序遍歷。bfs的思想。
實現代碼:
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 {public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {ArrayList<Integer>list=new ArrayList<Integer>();if(root==null)return list;Queue<TreeNode>q1=new ArrayDeque<>();q1.add(root);while (!q1.isEmpty()) {TreeNode node=q1.poll();list.add(node.val);if(node.left!=null) {q1.add(node.left);}if(node.right!=null) {q1.add(node.right);}}return list;} }23 二叉搜索樹的后序遍歷序列
題目描述
輸入一個整數數組,判斷該數組是不是某二叉搜索樹的后序遍歷的結果。如果是則輸出Yes,否則輸出No。假設輸入的數組的任意兩個數字都互不相同。
思路:
這個題和前面的前中確定構造二叉樹那題思想很想。后序遍歷順序為左區域 右區域 根,其中每個節點的左區域肯定小于根,右區域全部大于根。同樣里面的每個小區域也滿足這個要求,再考慮一下邊界和特殊情況,合理使用遞歸即可!
實現代碼:
24 二叉樹中和為某一值的路徑
題目描述
輸入一顆二叉樹的跟節點和一個整數,打印出二叉樹中結點值的和為輸入整數的所有路徑。路徑定義為從樹的根結點開始往下一直到葉結點所經過的結點形成一條路徑。(注意: 在返回值的list中,數組長度大的數組靠前)
思路:
dfs深度優先遍歷。要注意的是要遍歷到葉子節點的路徑。如果有中途遍歷的時候路徑滿足而非葉子節點是不停止的。
而這樣一個dfs函數target是個參數,每到達一個點會減去對應值。list是當前遍歷的路徑存儲列表。而這個dfs是一個回溯的過程,list根據遞歸過程元素是不斷變化的。所以這個list不能直接加到list2中。當遍歷到根節點的時候滿足條件要用新的list3復制list的值加入到list2中。如果對這個地方不理解請百度深度優先搜索或者回溯法 。 當然更多還需要自己理解的。
另外,要求路徑中較長的路徑在前面。這里我就實現了個Comparator排個序就行了。
實現代碼為:
import java.util.ArrayList;import java.util.Comparator; /** public class TreeNode {int val = 0;TreeNode left = null;TreeNode right = null;public TreeNode(int val) {this.val = val;} } */ public class Solution {public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {ArrayList<Integer> team = new ArrayList<Integer>();ArrayList<ArrayList<Integer>> list = new ArrayList<ArrayList<Integer>>();if (root == null) {return list;}dfs(root, target, team, list);return list;}static Comparator<ArrayList<Integer>> comparator = new Comparator<ArrayList<Integer>>() {public int compare(ArrayList<Integer> o1, ArrayList<Integer> o2) {// TODO Auto-generated method stubreturn o2.size() - o1.size();}};public void dfs(TreeNode root, int target, ArrayList<Integer> list, ArrayList<ArrayList<Integer>> list2) {if (target < 0) {// 停止} else {list.add((Integer) root.val);target -= root.val;if (root.left != null)dfs(root.left, target, list, list2);if (root.right != null)dfs(root.right, target , list, list2);if (root.left==null&&root.right==null&&target == 0) {//葉子節點ArrayList<Integer> list3 = new ArrayList<Integer>();list3.addAll(list);list2.add(list3);}target+=root.val;list.remove((Integer) root.val);}} }參考評論區
評論區看到有個遞歸寫的非常好,比我的要精簡很多,但是沒有保證最短的在前面(需要自己排序一下,為了更好實現可以寫個新函數代替這個只能在返回的函數中只需進行一次排序)。但是過了說明這個數據還是很水的。大家可以參考這個過程。
25 復雜鏈表的復制
題目描述:
輸入一個復雜鏈表(每個節點中有節點值,以及兩個指針,一個指向下一個節點,另一個特殊指針指向任意一個節點),返回結果為復制后復雜鏈表的head。(注意,輸出結果中請不要返回參數中的節點引用,否則判題程序會直接返回空)
思路:
說實話,這題自己還卡了不少時間。剛開始一直沒想到該怎么處理。后面經過自己不斷思考終于克服了!——你雖然是鏈表,但我想怎么搞怎么搞(搞個數組)。
首先明確,他讓我們克隆,也就是不能引用原鏈表的任何一個節點。也就是要100%的創造和克隆。大致分析思路如下:
實現代碼為:
參考討論區:
這題在討論區發現很多人再用的非常巧妙的效率很高的方法(誰是原創也不知道)。
實現代碼為:
歡迎關注公眾號:bigsai
總結
以上是生活随笔為你收集整理的剑指offer(11-25题)详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 剑指offer(1-10题)详解
- 下一篇: 剑指offer(26-33题)详解