劍指offer面試題-Java版-持續更新
最近在用Java刷劍指offer(第二版)的面試題。書中原題的代碼采用C++編寫,有些題的初衷是為了考察C++的指針、模板等特性,這些題使用Java編寫有些不合適。但多數題還是考察通用的算法、數據結構以及編程思想等,與語言本身無太大關系。因此在選擇編程語言時,我還是選擇了Java。好吧,主要是我C++不怎麽會,僅僅是曾經學過倆月,使用Java順手一些。後續可能再用Python刷一遍。
面試題3 數組中重復的數字
題目一:找出數組中重復的數字
- 描述:在長度為n的數組裏所有數字都在0~n-1範圍內。數組中某些數字是重復的,請找出任意一個重復的數字。如數組{2, 3, 1, 0, 2, 5, 3},輸出的重復的數字為2或3。
- 思路:利用數組的索引在0~n-1這個範圍的性質,將數字i移至索引i的位置。
- 考點:對數組的理解以及對問題的分析能力。
題目二:不修改數組找出重復的數字
- 描述:在長度為n+1的數組裏所有數字都在1~n範圍內。找出重復的數字,但不修改數組。
- 思路:當然可完全利用題目一的方法,只是需要輔助空間。不需要輔助空間是最好的了。這裏使用二分法,對數組進行計數,逐漸縮小重復的數字所處的範圍。
- 考點:對二分查找的靈活應用,畢竟寫出正確無誤的二分法時有些難度的。同時要重視與面試官的溝通,明確需求,如是否能更改數組,是否可以使用輔助空間等。
package sword_offer; //page 39 數組中重復的數字public class Solution3 { //題目1 //輸出數組中重復的數字,空間復雜度O(1),時間復雜度O(n) //數組長度為n,數字在0~n-1範圍內 public static int duplicate(int[] arr) { for (int i = 0; i < arr.length; i++) { //System.out.println(i); while (arr[i] != i) { if (arr[i] == arr[arr[i]])return arr[i]; else { int temp = arr[i]; arr[i] = arr[temp]; arr[temp] = temp; //System.out.println(Arrays.toString(arr)); } } } return -1; } //題目2 //輸出數組中重復的數字,空間復雜度O(1),時間復雜度O(nlog(n)) //數組長度為n+1,數字在1~n範圍內,要求不修改數組,並不使用輔助空間 public static int myGetDuplication(int[] arr) { int start = 1; int middle = arr.length / 2; int end = middle; while(end >= start) { //System.out.println("" + start + end); int count = 0; for (int i = 0; i < arr.length; i++) { if (arr[i] >= start && arr[i] <= end) count++; } if (end == start) { if (count > 1) return start; else break; } if (count > end - start + 1) { middle = (start + end) / 2; end = middle; } else { start = middle + 1; end = arr.length - 1; } } return -1; } //輸出數組中重復的數字,空間復雜度O(1),時間復雜度O(nlog(n)) //數組長度為n+1,數字在1~n範圍內,要求不修改數組,並不使用輔助空間 //比上一個函數邏輯清晰一點 public static int getDuplication(int[] arr) { int start = 1; int end = arr.length - 1; while(end >= start) { int middle = (end - start) / 2 + start; int count = getCount(arr, start, middle); if (end == start) { if (count > 1) return start; else break; } if (count > middle - start + 1) { end = middle; } else start = middle + 1; } return -1; } //計算數組arr元素處在[start, end]範圍內元素個數 private static int getCount(int[] arr, int start, int end) { int count = 0; for (int i = 0; i < arr.length; i++) { if (arr[i] >= start && arr[i] <= end) count++; } return count; } //測試 public static void main(String[] args) { int[] arr = {1, 2, 3, 1}; System.out.println(duplicate(arr)); int[] arr2 = {2, 3, 5, 4, 3, 2, 6, 7}; System.out.println(myGetDuplication(arr2)); int[] arr3 = {2, 3, 5, 4, 3, 2, 6, 7}; System.out.println(getDuplication(arr3)); } }
面試題4 二維數組中的查找
- 描述:二維數組中,數字按從左到右、從上到下的順序遞增。給定一個整數,判斷該數組中是否含有該整數。
- 思路:從數組的右上角或左下角開始進行查找數據,縮小可能包含該數的範圍。
- 考點:畫圖分析問題,尋求問題的突破口。並能正確編寫程序,避免死循環等問題。
例如,從二維數組$\left[ {\begin{array}{*{20}{c}}
{1}&{2}&{8}&9 \\
{1}&{4}&{9}&{12} \\
{4}&{7}&{10}&{13} \\
{6}&{8}&{11}&{15}
\end{array}} \right]$中尋找是否包含數字7。
從右上角查找時,逐漸向左下方縮小範圍。紅色的代表包含目標值7的區域,過程如下:
$$\left[ {\begin{array}{*{20}{c}}
{\color{red}1}&{\color{red}2}&{\color{red}8}&9 \\
{\color{red}1}&{\color{red}4}&{\color{red}9}&{12} \\
{\color{red}4}&{\color{red}7}&{\color{red}{10}}&{13} \\
{\color{red}6}&{\color{red}8}&{\color{red}{11}}&{15}
\end{array}} \right]\to\left[ {\begin{array}{*{20}{c}}
{\color{red}1}&{\color{red}2}&{8}&9 \\
{\color{red}1}&{\color{red}4}&{9}&{12} \\
{\color{red}4}&{\color{red}7}&{10}&{13} \\
{\color{red}6}&{\color{red}8}&{11}&{15}
\end{array}} \right]\to\left[ {\begin{array}{*{20}{c}}
{1}&{2}&{8}&9 \\
{\color{red}1}&{\color{red}4}&{9}&{12} \\
{\color{red}4}&{\color{red}7}&{10}&{13} \\
{\color{red}6}&{\color{red}8}&{11}&{15}
\end{array}} \right]\to\left[ {\begin{array}{*{20}{c}}
{1}&{2}&{8}&9 \\
{1}&{4}&{9}&{12} \\
{\color{red}4}&{\color{red}7}&{10}&{13} \\
{\color{red}6}&{\color{red}8}&{11}&{15}
\end{array}} \right]$$
從左下角查找時,逐漸向右上方縮小範圍。過程如下:
$$\left[ {\begin{array}{*{20}{c}}
{1}&{\color{red}2}&{\color{red}8}&{\color{red}9} \\
{1}&{\color{red}4}&{\color{red}9}&{\color{red}{12}} \\
{4}&{\color{red}7}&{\color{red}{10}}&{\color{red}{13}} \\
{6}&{\color{red}8}&{\color{red}{11}}&{\color{red}{15}}
\end{array}} \right]\to\left[ {\begin{array}{*{20}{c}}
{1}&{\color{red}2}&{\color{red}8}&{\color{red}9} \\
{1}&{\color{red}4}&{\color{red}9}&{\color{red}{12}} \\
{4}&{\color{red}7}&{\color{red}{10}}&{\color{red}{13}} \\
{6}&{8}&{11}&{15}
\end{array}} \right]$$
package sword_offer; //page 44 二維數組中的查找 public class Solution4 { //從右上角的元素開始查找,逐漸縮小範圍 public static boolean findNum(int[][] arr, int target) { boolean found = false; int row = 0; int col = arr[0].length - 1; while (col > 0 && row <= arr.length) { int diff = arr[row][col] - target; if (diff == 0) { found = true; break; } else if (diff > 0) col--; else row++; } return found; } //測試 public static void main(String[] args) { int[][] arr = {{1,2,8,9},{2,4,9,12},{4,7,10,13},{6,8,11,15}}; System.out.println(findNum(arr, 9)); } }
面試題5 替換空格
- 描述:將字符串中的每個空格替換成%20。如輸入"we are fine",輸出"we%20are%20fine"。
- 思路:原題考察了C++中指針的操作。Java裏數組不可變,因此本題變得沒有難度了。利用String對象的.charAt函數遍歷每個字符,並使用StringBuilder構建新的字符串。
- 考點:對字符串的處理。
package sword_offer; //page 51 替換空格 public class Solution5 { //在Java中字符串時不可變的,因而只能構造一個新的字符串。原文中該題的難點也無法體現出來了。 public static String replaceBlank(String str) { StringBuilder strb = new StringBuilder(); for (int i = 0; i < str.length(); i++) { if (str.charAt(i) == ‘ ‘) { strb.append("%20"); } else strb.append(str.charAt(i)); } return strb.toString(); } //測試 public static void main(String[] args) { String str = "We are happr."; System.out.println(replaceBlank(str)); } }
面試題6 從尾到頭打印鏈表
- 描述:輸入一個鏈表的頭節點,從尾到頭打印每個節點的值。
- 思路:從尾到頭打印,即為“先進後出”,則可以使用棧來處理;考慮遞歸的本質也是一個棧結構,可遞歸輸出。
- 考點:對鏈表、棧、遞歸的理解。
package sword_offer; //page 58 從尾到頭打印鏈表 import java.util.Stack; //鏈表類 class ListNode{ ListNode next = null; int value; } public class Solution6 { //方法1:使用Stack棧的先push後pop public static void printListReverse(ListNode listNode) { Stack<ListNode> stack = new Stack<ListNode>(); while(listNode != null) { stack.push(listNode); listNode = listNode.next; } while(!stack.isEmpty()) { System.out.println(stack.pop().value); } } //方法2:使用遞歸的方式,相當於從內部往外部推 public static void printListReverse_rec(ListNode listNode) { if(listNode != null) { if (listNode.next != null) printListReverse_rec(listNode.next); System.out.println(listNode.value); } } //測試 public static void main(String[] args) { ListNode ln1 = new ListNode(); ListNode ln2 = new ListNode(); ListNode ln3 = new ListNode(); ln1.next = ln2; ln2.next = ln3; ln1.value = 1; ln2.value = 2; ln3.value = 3; printListReverse_rec(ln1); printListReverse(ln1); } }
面試題7 重建二叉樹
- 描述:輸入某二叉樹的前序遍歷和中序遍歷結果,重建該二叉樹。假設前序遍歷或中序遍歷的結果中無重復的數字。
- 思路:前序遍歷的第一個元素為根節點的值,據此將中序遍歷數組拆分為左子樹+root+右子樹,前序遍歷數組拆分為root+左子樹+右子樹。再對左右子樹進行同樣的操作。
- 考點:對二叉樹不同遍歷方法的掌握。
package sword_offer; //page 62 重建二叉樹 //二叉樹類,包含左右子樹,以及用於查看的方法 class BinaryTreeNode { int value; BinaryTreeNode leftNode; BinaryTreeNode rightNode; //中序遍歷輸出查看 public void printList() { if (leftNode != null) leftNode.printList(); System.out.println(value); if (rightNode != null) rightNode.printList(); } } public class Solution7 { //重建二叉樹函數 public static BinaryTreeNode rebultTree(int[] preorder, int[] inorder) { BinaryTreeNode root = rebultTree(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1); return root; } //重寫函數 private static BinaryTreeNode rebultTree(int[] preorder, int startPre, int endPre, int[] inorder, int startIn, int endIn) { if (startPre > endPre || startIn > endIn) return null; BinaryTreeNode root = new BinaryTreeNode(); root.value = preorder[startPre]; for (int i = startIn; i <= endIn; i++) { if (inorder[i] == preorder[startPre]) { root.leftNode = rebultTree(preorder, startPre + 1, startPre + i - startIn, inorder, startIn, i - 1); root.rightNode = rebultTree(preorder, startPre + i - startIn + 1, endPre, inorder, i + 1, endIn); } } return root; } //測試 public static void main(String[] args) { int[] preorder = { 1, 2, 4, 7, 3, 5, 6, 8 }; int[] inorder = { 4, 7, 2, 1, 5, 3, 8, 6 }; BinaryTreeNode root = rebultTree(preorder, inorder); //System.out.println(root.leftNode.rightNode.value); root.printList(); } }
面試題8 二叉樹的下一個節點
- 描述:給定一棵二叉樹和其中的一個節點,找出中序遍歷序列的下一個節點。樹中應定義指向左節點、右節點、父節點的三個變量。
- 思路:該節點若存在右節點,右子樹的最左節點則為下一節點;若不存在右節點,則向上遍歷,直至找到是父節點的左節點的那個節點,該節點的父節點則為下一節點。
- 考點:對中序遍歷的理解。
package sword_offer; //page 65 二叉樹的下一個節點 //定義二叉樹類,包含左右子樹、父節點,以及用於查看的函數 class TreeNode { char value; TreeNode left; TreeNode right; TreeNode father; //中序遍歷輸出查看 public void printList() { if (left != null) left.printList(); System.out.println(value); if (right != null) right.printList(); } } public class Solution8 { public static TreeNode findNext(TreeNode node) { //有右節點,找到右子樹的最左節點 if (node.right!= null) { node = node.right; while(node.left != null) node = node.left; return node; } //無右節點,則向上遍歷,直至找到節點為父節點的左子節點 while(node.father != null) { if (node.father.left == node) return node.father; node = node.father; } return null; } public static void main(String[] args) { //建立一個二叉樹節點的數組,並tree[i].value賦值 TreeNode[] tree = new TreeNode[9]; char[] chars = {‘a‘, ‘b‘, ‘c‘, ‘d‘, ‘e‘, ‘f‘, ‘g‘, ‘h‘, ‘i‘}; for (int i = 0; i < tree.length; i++) { tree[i] = new TreeNode(); tree[i].value = chars[i]; } /* * a * / * b c * / \ / * d e f g * / * h i */ //左右節點關系 tree[0].left = tree[1]; tree[0].right = tree[2]; tree[1].left = tree[3]; tree[1].right = tree[4]; tree[2].left = tree[5]; tree[2].right = tree[6]; tree[4].left = tree[7]; tree[4].right = tree[8]; //父節點關系 tree[1].father = tree[0]; tree[2].father = tree[0]; tree[3].father = tree[1]; tree[4].father = tree[1]; tree[5].father = tree[2]; tree[6].father = tree[2]; tree[7].father = tree[4]; tree[8].father = tree[4]; tree[0].printList(); } }
面試題9 兩個棧實現隊列
- 描述:使用兩個棧實現一個隊列。隊列中實現尾部插入和頭部刪除函數。
- 思路:棧結構“先進後出”,插入數據時進入第一個棧;刪除數據時,將第一個棧的所有數據都彈出到第二個棧,這時原先先插入的數據位於棧的頂端。即可滿足隊列的“先進先出”。
- 考點:對棧和隊列的理解;對泛型的使用等。
package sword_offer; //page 68 兩個棧實現隊列 import java.util.Stack; //隊列類,包含兩個棧、兩個操作隊列的方法 class Queue <T>{ Stack<T> stack1 = new Stack<>(); Stack<T> stack2 = new Stack<>(); //插入節點 public void appendTail(T element) { stack1.push(element); } //刪除節點 public T deleteHead(){ if (stack2.isEmpty()) { while(!stack1.isEmpty()) { T data = stack1.pop(); stack2.push(data); } } //為空時,輸出異常 if (stack2.isEmpty()) throw new IllegalArgumentException("queue is empty"); return stack2.pop(); } } public class Solution9 { //測試 public static void main(String[] args) { Queue<Integer> queue = new Queue<>(); queue.appendTail(1); queue.appendTail(2); queue.appendTail(3); System.out.println(queue.deleteHead()); System.out.println(queue.deleteHead()); queue.appendTail(4); System.out.println(queue.deleteHead()); System.out.println(queue.deleteHead()); System.out.println(queue.deleteHead()); } }
劍指offer面試題-Java版-持續更新