劍指offer第二版面試題-Java描述-持續更新

最近在用Java刷劍指offer(第二版)的面試題。書中原題的代碼採用C++編寫,有些題的初衷是爲了考察C++的指針、模板等特性,這些題使用Java編寫有些不合適。但多數題仍是考察通用的算法、數據結構以及編程思想等,與語言自己無太大關係。所以在選擇編程語言時,我仍是選擇了Java。好吧,主要是我C++忘得差很少了,僅僅是曾經學過倆月,使用Java順手一些。後續可能再用Python刷一遍。java

github見:https://github.com/ikheu/sword_offernode

第2章 面試須要的基礎知識

面試題3  數組中重複的數字

  題目一:找出數組中重複的數字git

  • 描述:在長度爲n的數組裏全部數字都在0~n-1範圍內。數組中某些數字是重複的,請找出任意一個重複的數字。如數組{2, 3, 1, 0, 2, 5, 3},輸出的重複的數字爲2或3。
  • 思路:利用數組的索引在0~n-1這個範圍的性質,將數字i移至索引i的位置。
  • 考點:對數組的理解以及對問題的分析能力。

  題目二:不修改數組找出重複的數字github

  • 描述:在長度爲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 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(getDuplication(arr2));
    }
}

 

面試題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 void printOut() {
        System.out.println(value);
        ListNode tmp = next;
        while (tmp != null) {
            System.out.println(tmp.value);
            tmp = tmp.next;
        }
    }
}

public class Solution06 {
        
    //方法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()); } }

 

面試題10 斐波那契數列

  • 描述:計算斐波那契數列的第n項。
  • 思路:使用循環從下往上計算數列。
  • 考點:考察對遞歸和循環的選擇。使用遞歸的代碼一般比循環簡潔,但使用遞歸時要注意一下幾點:一、函數調用的時間和空間消耗;二、遞歸中的重複計算;三、最嚴重的棧溢出。根據斐波那契數列遞歸形式定義很容易直接將代碼寫成遞歸,而這種方式有大量重複計算,效率很低。

$$f\left( n \right) = \left\{ \begin{array}{l}
0\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;n = 0\\
1\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;n = 1\\
f\left( {n - 1} \right) + f\left( {n - 2} \right)\;\;\;\;n > 1
\end{array} \right.$$緩存

package sword_offer;
// page 74 斐波那契數列

public class Solution10 {
    // 得到fibonacci(n)
    public static long fibonacci(int n) {
        if (n <= 1) return n;
        long fibMinusOne = 0;
        long fibMinusTwo = 1;
        long fib = 0;
        for (int i = 2; i <= n; i++) {
            fib = fibMinusOne + fibMinusTwo;
            fibMinusOne = fibMinusTwo;
            fibMinusTwo = fib;
        }
        return fib;
    }
// 測試函數 public static void main(String[] args) { System.out.println(fibonacci(6)); } }

 

面試題11 旋轉數組的最小值

  • 描述:把數組開頭的一部分移動到數組的末尾獲得的數組稱爲旋轉數組。輸入一個遞增排序的數組的旋轉數組,輸出數組的最小值。
  • 思路:考慮旋轉數組的特色,使用二分查找方法逐漸縮小目標值的範圍。須要考慮的特殊狀況有:數組僅有一個元素的情形;數組未旋轉的情形;形如{1,0,1,1,1}的數組須要順序查找,由於二分查找會跳過最小值。
  • 考點:對於新概念的理解;對二分查找的熟練掌握;對不一樣輸入的分析和全面考慮。
package sword_offer;
// page 82 旋轉數組的最小數字

public class Solution11 {

    public static int min(int[] arr) {
        int start = 0;
        int end = arr.length - 1;
        if (arr[start] < arr[end]) // 旋轉數組是其自己的情形
            return arr[start];
        while (start <= end) {
            int mid = start + ((end - start) >> 1);
            if (arr[mid] == arr[start] && arr[mid] == arr[end]) // 考慮順序查找情形
                return min(arr, start, end);
            if (arr[mid] >= arr[start])
                start = mid;
            else
                end = mid;
            if (start == end - 1)
                break;
        }
        return arr[end];
    }
// 特殊情形下的順序查找 private static int min(int[] arr, int start, int end) { int result = arr[start]; for (int i = start + 1; i <= end; i++) if (arr[i] < result) result = arr[i]; return result; }
// 測試 public static void main(String[] args) { int[] arr = { 4, 5, 1, 2, 3 }; int[] arr2 = { 1, 0 , 1, 1, 1 }; System.out.println(min(arr)); System.out.println(min(arr2)); } }

 

面試題12 矩陣中的路徑

  • 描述:判斷字符矩陣中是否存在一條包含字符串字符的路徑。
  • 思路:使用回溯法,上下左右嘗試。以爲想寫出正確的程序還挺難的。
  • 考點:對回溯法的掌握。

路徑搜索示例

package sword_offer;

public class Solution12 {
    public static boolean hasPath(char[][] matrix, String str) {
        int rows = matrix.length;
        int cols = matrix[0].length;
        boolean[][] visited = new boolean[rows][cols];
        int pathLength = 0;
        for (int row = 0; row < rows; row++) {
            for (int col = 0; col < cols; col++) {
                if (hasPathCore(matrix, rows, cols, row, col, str, pathLength, visited))
                    return true;
            }
        }
        return false;
    }

    // 上下左右遞歸搜索
    private static boolean hasPathCore(char[][] matrix, int rows, int cols, int row, int col, String str,
            int pathLength, boolean[][] visited) {
        boolean hasPath = false;
        if (pathLength > str.length() - 1)
            return true;
        if (row >= 0 && row < rows && col >= 0 && col < cols && matrix[row][col] == str.charAt(pathLength)
                && !visited[row][col]) {
            ++pathLength;
            visited[row][col] = true;
            hasPath = hasPathCore(matrix, rows, cols, row + 1, col, str, pathLength, visited)
                    || hasPathCore(matrix, rows, cols, row, col + 1, str, pathLength, visited)
                    || hasPathCore(matrix, rows, cols, row - 1, col, str, pathLength, visited)
                    || hasPathCore(matrix, rows, cols, row, col - 1, str, pathLength, visited);
            if (!hasPath) {
                --pathLength;
                visited[row][col] = false;
            }
        }
        return hasPath;
    }

    // 測試
    public static void main(String[] args) {
        char[][] matrix = { { 'a', 'b', 't', 'g' }, { 'c', 'f', 'c', 's' }, { 'j', 'd', 'e', 'h' } };
        String str = "bfc";
        System.out.println(hasPath(matrix, str));
    }
}

 

面試題14 剪繩子

  • 描述:給一段長度位整數n的繩子,剪成若干段,每段長度也是整數,計算這些長度的乘積的最大值。
  • 思路:比較容易想到的動態規劃以及乍看起來讓人蒙圈的貪婪法。不管是那種方法,能作出來就不容易。
  • 考點:抽象建模能力;對動態規劃和貪婪發的理解。
package sword_offer;
// page 96 剪繩子
import java.lang.Math;

public class Solution14 {
    // 動態規劃算法,比較容易想到
    public static int maxProduct_s1(int len) {
        if (len < 2) return 0;
        if (len == 2) return 1;
        if (len == 3) return 2;
        int[] products = new int[len + 1];
        products[0] = 0;
        products[1] = 1;
        products[2] = 2;
        products[3] = 3;
        int max = 0;
        for (int i = 4; i <= len; i++) {
            for (int j = 1; j <= i / 2; j++) {
                int product = products[j] * products[i - j];
                if (max < product)
                    max = product;
                products[i] = max;
            }
        }
        max = products[len];
        return max;
    }

    // 貪婪算法,很不容易想到
    public static int maxProduct_s2(int len) {
        if (len < 2) return 0;
        if (len == 2) return 1;
        if (len == 3) return 2;
        int times3 = len / 3;
        if (len - times3 * 3 == 1)
            times3--;
        int times2 = (len - times3 * 3) / 2;
        return (int) (Math.pow(3, times3)) * (int) (Math.pow(2, times2));

    }

    // 測試
    public static void main(String[] args) {
        System.out.println(maxProduct_s1(20));
        System.out.println(maxProduct_s2(20));
    }
}

 

面試題15 二進制中1的個數

  • 描述:輸入一個整數,計算該數二進制中1的個數。
  • 思路:方法一逐位上移,將1和整數進行與計算;方法二有點奇技淫巧的感受了,須要想到整數-1後的二進制數特色。在Java裏,能夠經過Integer.toBinaryString函數查看整數的二進制形式。
  • 考點:對二進制的理解,和對位運算的掌握。
package sword_offer;
//page 100 二進制中1的個數

public class Solution15 {
    // 方法1:逐位進行與運算
    public static int numOfOne_a(int n) {
        int flag = 1;
        int count = 0;
        while (flag != 0) {
            if ((n & flag) != 0)
                count++;
            flag = flag << 1;
        }
        return count;
    }

    // 方法二:進行n = (n - 1) & n運算,n含有1的位數減1
    public static int numOfOne_b(int n) {
        int count = 0;
        while (n != 0) {
            n = (n - 1) & n;
            count++;
        }
        return count;
    }

    // 測試
    public static void main(String[] args) {
        System.out.println(Integer.toBinaryString(10000));
        System.out.println(numOfOne_a(10000));
        System.out.println(numOfOne_b(10000));
    }
}

 

第3章 高質量的代碼

面試題16 數值的整數次方

  • 描述:構造求數值的整數冪函數。
  • 思路:根據整數冪求解的特色,構造高效求解的函數;考慮冪爲負整數的情形,以及避免溢出的處理;使用位運算進行除2和判斷奇偶。像這種基本的數學函數,竭力提升運算性能老是有必要的。
  • 考點:對冪運算的掌握。
package sword_offer;
// page 16 數值的整數次方

public class Solution16 {
    // 冪運算函數,f=x^n,不用考慮大數問題,n可能爲負整數
    public static double myPow(double x, int n) {
        if (n == 0)
            return 1.0;
        if (n < 0)
            return 1.0 / (x * myPow(x, -n - 1));// 避免溢出
        double result = myPow(x, n >> 1);// 位運算除2
        result *= result;
        if ((n & 1) == 1)// 位運算判斷奇偶
            result *= x;
        return result;
    }
// 測試 public static void main(String[] args) { int a = -2147483648; System.out.println(myPow(1.0, a)); System.out.println(myPow(2.0, 4)); System.out.println(myPow(2.0, 3)); } }

 

面試題17 打印從1到最大n位數

  • 描述:輸入數字n,按順序打印出1到n的十進制數。
  • 思路:要意識到是大數問題,可移動符號運算以及數字排列輸出兩種方式解決。
  • 考點:對大數問題的處理;打印數字時要考慮到閱讀的習慣。
package sword_offer;
// page 114 打印從1到最大的n位數

public class Solution17 {
    // 數字排列方法
    public static void maxOfND(int n) {
        if (n < 0) {
            return;
        }
        char[] num = new char[n];
        for (int i = 0; i < 10; i++) {
            num[0] = (char) (i + '0');
            printNumRec(num, n, 0);
        }
    }

    // 遞歸部分
    public static void printNumRec(char[] num, int n, int index) {
        if (index == n - 1) {
            printNum(num);
            return;
        }
        for (int i = 0; i < 10; i++) {
            num[index + 1] = (char) (i + '0');
            printNumRec(num, n, index + 1);
        }
    }

    // 以閱讀習慣輸出
    public static void printNum(char[] num) {
        boolean isBegin0 = true;
        for (int i = 0; i < num.length; i++) {
            if (isBegin0 && num[i] != '0') {
                isBegin0 = false;
            }
            if (!isBegin0) {
                System.out.print(num[i]);
            }
        }
        System.out.println();
    }

    // 測試
    public static void main(String[] args) {
        maxOfND(5);
    }
}

 

面試題18 刪除鏈表的節點

  • 描述:在O(1)時間內刪除鏈表節點
  • 思路:不直接刪除,經過賦值完成。在刪除的節點無下一個節點時,則須要從前向後遍歷。在刪除的節點是頭節點時,直接寫root = null是無效的,能夠考慮寫個鏈表類,包含刪除節點的函數。
  • 考點:鏈表具備單方向遍歷的性質,但在操做時也要注意技巧。同時要全面考慮。
package sword_offer;
// page 119 刪除鏈表中的節點 

public class Solution18 {
    public static void deleteNode(ListNode root, ListNode toBeDeleted) {
        // 要刪的節點是根節點(這種狀況下的操做是無效的,沒真正刪除)
        if (root.equals(toBeDeleted)) {
            root = root.next;
            return;
        }
        ListNode tmp = toBeDeleted.next;
        // 是不是最後一個節點?不是則經過賦值完成刪除,是則經過從前向後遍歷,完成刪除
        if (tmp != null) {
            toBeDeleted.value = tmp.value;
            if (tmp.next != null)
                toBeDeleted.next = tmp.next;
            tmp.next = null;
        } else {
            tmp = root;
            while (!tmp.next.equals(toBeDeleted)) {
                tmp = tmp.next;
            }
            tmp.next = null;
        }
    }

    // 測試
    public static void main(String[] args) {
        ListNode[] ln = new ListNode[6]; // 1 -> 2 -> 3 -> 4 -> 5 -> 6
        for (int i = 0; i < ln.length; i++) {
            ln[i] = new ListNode();
            ln[i].value = i;
            if (i > 0)
                ln[i - 1].next = ln[i];
        }
        ln[0].printOut();
        deleteNode(ln[0], ln[2]);
        System.out.println("==== after delete ====");
        ln[0].printOut();
    }
}

 

面試題21 調整數組順序使奇數位於偶數前面

  • 描述:操做輸入的整數數組,實現全部奇數位於數組的前部分,偶數位於數組的後部分。
  • 思路:比較容易想到使用兩個變量分別指向數組的首段、尾端,向中間聚攏,完成數組順序調整。
  • 考點:對數組的操做;對程序擴展性的考慮。
package sword_offer;
// page 21 調整數組順序使奇數位於偶數前面
import java.util.Arrays;

public class Solution21 {
    // 按必定規則調整數組的程序
    public static void reorderOddEven(int[] arr) {
        int start = 0;
        int end = arr.length - 1;
        while (start < end) {
            while (start < end && isOdd(arr[start]))
                start++;
            while (start < end && !isOdd(arr[end]))
                end--;
            if (start < end) {
                int tmp = arr[start];
                arr[start] = arr[end];
                arr[end] = tmp;
            }
        }
    }

    // 單獨定義一個函數,提升擴展性
    private static boolean isOdd(int x) {
        return (x & 1) == 1;
    }

    // 測試
    public static void main(String[] args) {
        int[] arr = { 0, 1, 3, 5, 4, 9 };
        reorderOddEven(arr);
        System.out.println(Arrays.toString(arr));
    }
}

 

面試題22 鏈表中的倒數第k個節點

  • 描述:輸入一個鏈表,輸出鏈表中倒數第k個節點。
  • 思路:沒法判斷鏈表大小是主要難點,所以額外定義一個變量,當鏈表大小大於等於k時進行跟蹤。
  • 考點:對魯棒性的考慮;當心處理程序,可避免原文中提到的程序崩潰問題。
package sword_offer;
// page 134 鏈表中倒數第k個節點

/*class ListNode{           Solution6定義過了
    ListNode next = null;
    int value;
}*/
public class Solution22 {
    public static ListNode findKthTail(ListNode head, int k) {
        ListNode res = head;
        int i = 1;
        while (head.next != null) {
            i++;
            head = head.next;
            if (i > k)
                res = res.next;
        }
        if (i < k || k < 1)
            throw new IllegalArgumentException("Not exist");
        return res;
    }

    public static void main(String[] args) {
        ListNode[] ln = new ListNode[6]; // 1 -> 2 -> 3 -> 4 -> 5 -> 6
        for (int i = 0; i < ln.length; i++) {
            ln[i] = new ListNode();
            ln[i].value = i + 1;
            if (i > 0)
                ln[i - 1].next = ln[i];
        }
        System.out.println(findKthTail(ln[0], 1).value);
    }
}

 

面試題24 反轉鏈表

  • 描述:輸入鏈表的頭結點,反轉鏈表並輸出反轉後鏈表的頭節點。
  • 思路:要定義pre和next變量存儲斷開先後的節點。
  • 考點:對鏈表的理解。
package sword_offer;
// page 142 反轉鏈表

public class Solution24 {
    public static ListNode reverseList(ListNode head) {
        if (head == null)
            return null;
        ListNode pre = null;
        ListNode next = null;
        while (head != null) {
            next = head.next;
            head.next = pre;
            pre = head;
            head = next;
        }
        return pre;
    }

    // 測試
    public static void main(String[] args) {
        ListNode[] ln = new ListNode[6]; // 1 -> 2 -> 3 -> 4 -> 5 -> 6
        for (int i = 0; i < ln.length; i++) {
            ln[i] = new ListNode();
            ln[i].value = i + 1;
            if (i > 0)
                ln[i - 1].next = ln[i];
        }
        ln[0].printOut();
        reverseList(ln[0]);
        System.out.println("=========反轉先後========");
        ln[5].printOut();
    }
}

 

面試題25 合併兩個排序的鏈表

  • 描述:輸入兩個遞增排序的鏈表,合併這兩個鏈表,保證合併後的鏈表還是遞增的。
  • 思路:採用遞歸的方法,逐次得到下一節點。
  • 考點:對問題的分析能力,不使用遞歸會致使代碼不清晰;對鏈表的理解。

 

package sword_offer;
// page 合併兩個排序的鏈表

public class Solution25 {
    public static ListNode merge(ListNode head1, ListNode head2) {
        if (head1 == null)
            return head2;
        if (head2 == null)
            return head1;
        ListNode mergeHead = null;
        if (head1.value < head2.value) {
            mergeHead = head1;
            mergeHead.next = merge(head1.next, head2);
        } else {
            mergeHead = head2;
            mergeHead.next = merge(head1, head2.next);
        }
        return mergeHead;
    }

    public static void main(String[] args) {
        int[] arr1 = { 1, 3, 5, 7 };
        int[] arr2 = { 2, 4, 6, 10 };

        // 定義鏈表1
        ListNode[] ln1 = new ListNode[arr1.length];
        for (int i = 0; i < ln1.length; i++) {
            ln1[i] = new ListNode();
            ln1[i].value = arr1[i];
            if (i > 0)
                ln1[i - 1].next = ln1[i];
        }

        // 定義鏈表2
        ListNode[] ln2 = new ListNode[arr2.length];
        for (int i = 0; i < ln1.length; i++) {
            ln2[i] = new ListNode();
            ln2[i].value = arr2[i];
            if (i > 0)
                ln2[i - 1].next = ln2[i];
        }
        merge(ln1[0], ln2[0]).printOut();
    }
}

 

面試題27 二叉樹的鏡像

  • 描述:輸入一個二叉樹,做鏡像變換👈👉。
  • 思路:即遞歸交換左右子樹。
  • 考點:樹的遍歷算法的熟練掌握,這裏用到的是前序遍歷。
package sword_offer;
// page 157 二叉樹的鏡像

public class Solution26 {
    public static void mirrorTree(BinaryTreeNode root) {
        if (root == null || (root.leftNode == null && root.rightNode == null))
            return;
        BinaryTreeNode tmp = root.leftNode;
        root.leftNode = root.rightNode;
        root.rightNode = tmp;
        if (root.leftNode != null)
            mirrorTree(root.leftNode);
        if (root.rightNode != null)
            mirrorTree(root.rightNode);
    }

    // 測試
    public static void main(String[] args) {
        BinaryTreeNode a = new BinaryTreeNode();
        BinaryTreeNode b = new BinaryTreeNode();
        BinaryTreeNode c = new BinaryTreeNode();
        BinaryTreeNode d = new BinaryTreeNode();
        BinaryTreeNode e = new BinaryTreeNode();
        BinaryTreeNode f = new BinaryTreeNode();
        BinaryTreeNode g = new BinaryTreeNode();
        a.leftNode = b;
        a.rightNode = c;
        b.leftNode = d;
        b.rightNode = e;
        c.leftNode = f;
        c.rightNode = g;
        a.value = 8;
        b.value = 6;
        c.value = 10;
        d.value = 5;
        e.value = 7;
        f.value = 9;
        g.value = 11;
        a.printList();
        mirrorTree(a);
        System.out.println("=========="); // 中序輸出也是對稱的
        a.printList();
    }
}

 

面試題28 對稱的二叉樹

  • 描述:判斷樹是否是對稱的。
  • 思路:書上的分析過程能理解,而要準確無誤地寫出代碼確實另外一回事了。這裏仍是樹的遍歷。程序能夠當作是兩個樹的比較,更有利於組織清晰的代碼。
  • 考點:樹的遍歷。
package sword_offer;
// page 159 對稱的二叉樹

public class Solution28 {
    public static boolean isSymmetrical(BinaryTreeNode root) {
        return isSymmetrical(root, root);
    }

    private static boolean isSymmetrical(BinaryTreeNode root1, BinaryTreeNode root2) {
        if (root1 == null && root2 == null)
            return true;
        if (root1 == null || root2 == null)
            return false;
        if (root1.value != root2.value)
            return false;
        return isSymmetrical(root1.leftNode, root2.rightNode) && isSymmetrical(root1.rightNode, root2.leftNode);
    }

    // 測試
    public static void main(String[] args) {
        BinaryTreeNode a = new BinaryTreeNode();
        BinaryTreeNode b = new BinaryTreeNode();
        BinaryTreeNode c = new BinaryTreeNode();
        BinaryTreeNode d = new BinaryTreeNode();
        BinaryTreeNode e = new BinaryTreeNode();
        BinaryTreeNode f = new BinaryTreeNode();
        BinaryTreeNode g = new BinaryTreeNode();
        a.leftNode = b;
        a.rightNode = c;
        b.leftNode = d;
        b.rightNode = e;
        c.leftNode = f;
        c.rightNode = g;
        a.value = 8;
        b.value = 6;
        c.value = 6;
        d.value = 5;
        e.value = 7;
        f.value = 7;
        g.value = 5;
        System.out.println(isSymmetrical(a));
    }
}

 

面試題32 從上到下打印二叉樹

  • 描述:從上到下打印二叉樹,同一層從左到右打印。
  • 思路:須要一個隊列。這時應該想到雙向鏈表LinkedList實現了隊列、棧。要熟練記住經常使用的操做。
  • 考點:二叉樹的前中後遍歷輸出比較容易處理,而這裏要對二叉樹和隊列有充分的理解才能想到解決方法。
package sword_offer;
// page 171 從上到下打印二叉樹
import java.util.LinkedList;
import java.util.Queue;

public class Solution32 {
    public static void printTreeTopToBottom(BinaryTreeNode root) {
        if (root == null)
            return;
        // 建立個隊列queue
        Queue<BinaryTreeNode> queue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty()) {
            // 彈出後打印,再插入該節點的左節點、右節點
            BinaryTreeNode node = queue.poll();
            System.out.println(node.value);
            if (node.leftNode != null)
                queue.add(node.leftNode);
            if (node.rightNode != null)
                queue.add(node.rightNode);
        }
    }

    // 測試
    public static void main(String[] args) {
        BinaryTreeNode a = new BinaryTreeNode();
        BinaryTreeNode b = new BinaryTreeNode();
        BinaryTreeNode c = new BinaryTreeNode();
        BinaryTreeNode d = new BinaryTreeNode();
        BinaryTreeNode e = new BinaryTreeNode();
        BinaryTreeNode f = new BinaryTreeNode();
        BinaryTreeNode g = new BinaryTreeNode();
        a.leftNode = b;
        a.rightNode = c;
        b.leftNode = d;
        b.rightNode = e;
        c.leftNode = f;
        c.rightNode = g;
        a.value = 8;
        b.value = 6;
        c.value = 10;
        d.value = 5;
        e.value = 7;
        f.value = 9;
        g.value = 11;
        printTreeTopToBottom(a);
    }
}

 

第5章 優化時間和效率

面試題39 數組中出現次數超過一半的數字

  • 描述:數組中有個數字出現的次數超過的數組長度的一半,找出這個數字。
  • 思路:一、基於快排的特色(每次肯定一個元素的位置),以及排序後的數組中位數位於數組中間位置的特色;二、遍歷數組,統計次數。不容易想到。
  • 考點:對同類問題的聯想能力;對問題的發散思考能力。
package sword_offer;
//page 205 數組中出現次數超過一半的數字

public class Solution39 {
    // 方法1:利用數組中位數的特性以及快速排序的思想
    public static int moreThanHalfNum_s1(int[] arr) {
        int mid = arr.length >> 1;
        int start = 0;
        int end = arr.length - 1;
        int index = partition(arr, start, end);
        while (index != mid) {
            if (index > mid) {
                end = index - 1;
                index = partition(arr, start, end);
            } else {
                start = index + 1;
                index = partition(arr, start, end);
            }
        }
        int result = arr[mid];
        return result;
    }

    // 快排肯定一個索引
    public static int partition(int[] arr, int start, int end) {
        int mid = end;
        end = end - 1;
        while (start < end) {
            while (start < end && arr[start] < arr[mid])
                start++;
            while (start < end && arr[end] >= arr[mid])
                end--;
            swap(arr, start, end);
        }
        if (arr[start] >= arr[mid]) {
            swap(arr, start, mid);
        } else
            start++;

        return start;
    }

    // 交換
    private static void swap(int[] arr, int x, int y) {
        int temp = arr[x];
        arr[x] = arr[y];
        arr[y] = temp;
    }

    // 方法2:基於數組特色
    public static int moreThanHalfNum_s2(int[] arr) {
        int result = arr[0];
        int times = 1;
        for (int i = 1; i < arr.length; i++) {
            if (times == 0) {
                result = arr[i];
                times = 1;
            } else if (arr[i] == result)
                times++;
            else
                times--;
        }
        return result;
    }

    // 測試
    public static void main(String[] args) {
        int[] arr = { 3, 3, 1, 1, 1, 4, 4, 1 };
        System.out.println(moreThanHalfNum_s1(arr));
        System.out.println(moreThanHalfNum_s2(arr));
    }
}

 

面試題40 最小的k個數

  • 描述:輸入n個整數,找出其中最小的k個數。
  • 思路:基於快排的特色,容易實現數組元素大小的查找。同理也很容易找到第k大的數,別忘了容易犯的差1錯誤。
  • 考點:對同類問題的聯想能力。
package sword_offer;
// page 209 最小的k個數
import java.util.Arrays;

public class Solution40 {

    public static void getLeastNums(int[] arr, int k) {
        int start = 0;
        int end = arr.length - 1;
        int index = partition(arr, start, end);
        while (index != k - 1) { // 注意索引與個數的關係,注意差1錯誤
            if (index < k - 1) {
                start = index + 1;
                index = partition(arr, start, end);
            } else {
                end = index - 1;
                index = partition(arr, start, end);
            }
        }
    }

    // 快排肯定一個索引
    public static int partition(int[] arr, int start, int end) {
        int mid = end;
        end = end - 1;
        while (start < end) {
            while (start < end && arr[start] < arr[mid])
                start++;
            while (start < end && arr[end] >= arr[mid])
                end--;
            swap(arr, start, end);
        }
        if (arr[start] >= arr[mid]) {
            swap(arr, start, mid);
        } else
            start++;
        return start;
    }

    // 交換
    private static void swap(int[] arr, int x, int y) {
        int temp = arr[x];
        arr[x] = arr[y];
        arr[y] = temp;
    }

    // 測試
    public static void main(String[] args) {
        int[] arr = { 3, 3, 1, 1, 1, 4, 4, 1 };
        getLeastNums(arr, 1);
        System.out.println(Arrays.toString(arr));
    }
}

 

面試題42 連續子數組的最大和

  • 描述:輸入一個整形數組,元素有正有負。求子數組的和的最大值。要求時間複雜度爲O(n)。
  • 思路:累加數組,存儲臨時變量,一旦該量爲負則置0,一旦該量大於累加值,則賦爲累加值。
  • 考點:這道題挺有名的,編程珠璣這本書又講到,也算是考察了歷史了。沒見過這道題卻能想出來O(n)算法的確定很厲害。
package sword_offer;
// page 218 連續子數組的最大和

public class Solution42 {
    public static int maxSub(int[] arr) {
        int maxSum = 0, tempSum = 0;
        for (int i = 0; i < arr.length; i++) {
            tempSum += arr[i];
            if (tempSum > maxSum)
                maxSum = tempSum;
            else if (tempSum < 0)
                tempSum = 0;
        }
        return maxSum;
    }

    // 測試
    public static void main(String[] args) {
        int[] arr = { 1, -2, 3, 10, -4, 7, 2, -5 };
        System.out.println(maxSub(arr));
    }
}

 

面試題43 1~n整數中出現1的個數

  • 描述:輸入一個整數n,求1~n這n個整數的十進制表示中出現1的次數。
  • 思路:很容易想到nlog(n)的算法,即對10求餘。log(n)的方法不易想到,後面再更。
  • 考點:通常的方法大部分都能想到,但更好的方法則須要優化的激情和必定的思惟能力。
package sword_offer;
// page 221 1~n整數中出現1的個數

public class Solution43 {
    // 計算整數n中含有1的個數
    private static int countOne(int n) {
        int count = 0;
        while (n != 0) {
            if (n % 10 == 1)
                count++;
            n = n / 10;
        }
        return count;
    }

    // 計算整數1~n中含有1的個數
    public static int countTotalOne(int n) {
        int count = 0;
        for (int i = 1; i <= n; i++) {
            count += countOne(i);
        }
        return count;
    }

    // 測試
    public static void main(String[] args) {
        System.out.println(countTotalOne(100));
    }
}

 

面試題44 數字序列中的某一位的數

  • 描述:數字以012345678910111213141516...的格式化序列排列。從0開始計數:第0位是0,第5位是5,第10位是1...。寫個函數,求任意n位對應的數字。
  • 思路:找規律。發現找規律的題目並不容易寫對,老是在臨界處出現差1錯誤,並且差1問題很容易讓人卡殼;或者規律找到了,但思路很亂的,寫的代碼調了半天也能運行,但結構不清晰。
  • 考點:對數字規律的總結能力。
package sword_offer;
// page 225 數組序列中的某一位數字
import java.lang.Math;
public class Solution44 {
    // 這裏沒采用原文中將程序拆分爲幾個小程序的形式,考慮到個位數的特殊性,直接在前面處理
    public static int digitAtIndex(int index) {
        if (index < 10) return index;
        int n = 2;    //位數
        index = index - 10;//初始化位位數爲1的個數
        while(true) {
            int numsOfN = 9 * (int) Math.pow(10, n - 1);               //n位的個數
            if (index < numsOfN * n) {                                 //是否在這個區間
                int number = (int) Math.pow(10, n - 1) + index / n;    //對應的整數
                int indexFromRight = n - index % n;                    //從右邊數第幾位
                for (int i = 1; i < indexFromRight; i++) {             //找到那一位
                    number /= 10;
                }
                return number % 10;
            }
            index -= numsOfN * n;
            n++;
        }
    }
    
    // 測試
    public static void main(String[] args) {
        System.out.println(digitAtIndex(1001));
    }
}

 

面試題45 把數組排列成最小的數

  • 描述:輸入一個正整數數組,把數組中的全部數字拼接排成一個數,打印出最小的那個數。
  • 思路:字符串表示大數+快速排序+字符串大小比較。
  • 考點:算是對綜合能力的考察。拼接的數很容易溢出,所以能夠用字符串表示大數;考查了對於字符串的處理能力,在比較字符串mn大仍是nm大時,能夠逐位轉化比較或者直接用Sting對象的compareTo方法。要能快速、正確地寫出快排算法,好幾道題都用到了。
package sword_offer;
// page 227 把數組排成最小的數

public class Solution45 {
    public static void printMin(int[] arr) {
        printMinNumber(arr, 0, arr.length - 1);
        for (int i : arr)
            System.out.print(i);
        System.out.println();
    }

    // 注意快排的不一樣形式
    public static void printMinNumber(int[] arr, int left, int right) {
        if (left < right) {
            int main_number = arr[right];
            int small_cur = left;
            for (int j = left; j < right; j++) {
                if (isSmall(String.valueOf(arr[j]), String.valueOf(main_number))) { // 小於時交換
                    int temp = arr[j];
                    arr[j] = arr[small_cur];
                    arr[small_cur] = temp;
                    small_cur++;
                }
            }
            arr[right] = arr[small_cur];
            arr[small_cur] = main_number;
            printMinNumber(arr, 0, small_cur - 1);
            printMinNumber(arr, small_cur + 1, right);
        }
    }

    // 判斷字符串是否知足mn < nm
    public static boolean isSmall(String m, String n) {
        String left = m + n;
        String right = n + m;
        return left.compareTo(right) < 0; // 也能夠重寫函數
    }

    // 測試
    public static void main(String[] args) {
        int arr[] = { 3, 1, 2, 11 };
        printMin(arr);
    }
}

 

面試題49 醜數

  • 描述:將只包含二、三、5的數稱爲醜數。求從小到大的第1500個醜數。第一個醜數定爲1。
  • 思路:第1種方法按個判斷是不是醜數;第二種算法使用了額外空間換得了效率。1500個整形數約佔內存6KB,但換來了千倍的加速。在面試時能夠和麪試官交流可否使用輔助空間。
  • 考點:對新概念的理解能力;對編寫高效程序的能力。

package sword_offer;
// page 240 醜數

public class Solution49 {
    // 方法1:一個一個判斷是不是醜數,直到找到目標值
    public static int getUglyNum_s1(int index) {
        int count = 0;
        int num = 0;
        while (count < index) {
            num++;    // 把num++放在循環的開頭而非末尾
            if (isUglyNum(num))
                count++;

        }
        return num;
    }

    // 判斷是不是醜數
    private static boolean isUglyNum(int n) {
        while (n % 2 == 0)
            n /= 2;
        while (n % 3 == 0)
            n /= 3;
        while (n % 5 == 0)
            n /= 5;
        return n == 1 ? true : false;
    }

    // 方法2:額外定義個緩存數組,將醜數存下來,注意裏面使用3個變量跟蹤的技巧
    public static int getUglyNum_s2(int index) {
        int[] uglyNums = new int[index];
        uglyNums[0] = 1;
        int pm2 = 0;
        int pm3 = 0;
        int pm5 = 0;
        int nextIndex = 1;
        while (nextIndex < index) {
            int min = minNum(uglyNums[pm2] * 2, uglyNums[pm3] * 3, uglyNums[pm5] * 5);
            uglyNums[nextIndex] = min;
            while (uglyNums[pm2] * 2 <= min)
                pm2++;
            while (uglyNums[pm3] * 3 <= min)
                pm3++;
            while (uglyNums[pm5] * 5 <= min)
                pm5++;
            nextIndex++; // 這裏將nextIndex++放到末尾,由於是索引
        }
        return uglyNums[nextIndex - 1];

    }

    // 三個數中的最小值
    public static int minNum(int x, int y, int z) {
        int min = x < y ? x : y;
        min = z < min ? z : min;
        return min;
    }
    
    // 測試
    public static void main(String[] args) {
        long t0 = System.currentTimeMillis();
        System.out.println("方法1計算的第1500個醜數:" + getUglyNum_s1(1500));
        long t1 = System.currentTimeMillis();
        System.out.println("方法1耗時:" + (t1 - t0) + "ms");
        System.out.println("方法2計算的第1500個醜數:" + getUglyNum_s2(1500));
        long t2 = System.currentTimeMillis();
        System.out.println("方法2耗時:" +(t2 - t1) + "ms");
    }
}

 

面試題50 第一個只出現一次的字符

  • 描述:字符串中第一個只出現一次的字符。
  • 思路:利用字符的ASCII做爲數組的索引,實現一個簡單的哈希表。先遍歷字符串計數,再遍歷數組。
  • 考點:對字符串以及哈希表的理解。
package sword_offer;
// 第一個只出現一次的字符

public class Solution50 {
    // 將ASCII做爲數組下標,存儲出現的個數,至關於實現了一個簡單的哈希表,也能夠直接用map
    public static char firstRepeatchar(String str) {
        int maxNum = 256;
        char target = str.charAt(0);
        int[] arr = new int[maxNum];
        for (int i = 0; i < str.length(); i++)
            arr[(int)(str.charAt(i))]++;
        for (int i = 0; i < str.length(); i++) {
            if (arr[(int)(str.charAt(i))] == 1){
                target = str.charAt(i);
                break;
            }
        }
        return target;
    }
    
    // 相關題目:刪除重複的字符
    public static String delRepeatchar(String str) {
        int maxNum = 256;
        StringBuilder strb = new StringBuilder();
        int[] arr = new int[maxNum];
        for (int i = 0; i < str.length(); i++) {
            if (arr[(int)(str.charAt(i))] == 0) {
                arr[(int)(str.charAt(i))]++;
                strb.append(str.charAt(i));
            }
        }
        return strb.toString();
    }
    
    // 相關題目:從第一個字符串中刪除在第二個字符串中出現過的全部字符
    public static String delCharInStr2(String str1, String str2) {
        int maxNum = 256;
        StringBuilder strb = new StringBuilder();
        int[] arr = new int[maxNum];
        for (int i = 0; i < str2.length(); i++) {
            if (arr[(int)(str2.charAt(i))] == 0) {
                arr[(int)(str2.charAt(i))]++;
            }
        }
        for (int i = 0; i < str1.length(); i++) {
            if (arr[(int)(str1.charAt(i))] == 0) {  // str1中的字符對應的哈希表在arr中不存在
                strb.append(str1.charAt(i));
            }
        }
        return strb.toString();
    }
    
    // 相關題目:判斷是不是變位詞
    public static boolean isAnagram(String str1, String str2) {
        int maxNum = 256;
        int[] arr = new int[maxNum];
        if (str1.length() != str2.length()) return false;
        for (int i = 0; i < str1.length(); i++) {
                arr[(int)(str1.charAt(i))]++;
                arr[(int)(str2.charAt(i))]--;
        }
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] != 0) return false;
        }
        return true;
    }
    
    // 測試
    public static void main(String[] args) {
        System.out.println(firstRepeatchar("abaccdeff"));
        System.out.println(delRepeatchar("abaccdeff"));
        System.out.println(delCharInStr2("We are student.", "aeiou"));
        System.out.println(isAnagram("silent", "listen"));
    }
}

 

面試題55 二叉樹的深度

  • 描述:輸入二叉樹的根節點,求數的深度。
  • 思路:利用遞歸求解:左子樹、右子樹中深度大者加一。
  • 考點:對樹深度的理解,構建遞歸算法。
package sword_offer;
// page 271 二叉樹的深度

public class Solution55 {
    public static int treeDepth(BinaryTreeNode root) {
        // 基準狀況
        if (root == null) return 0;
        // 左子樹深度
        int nleft = treeDepth(root.leftNode);
        // 右子樹深度
        int nright = treeDepth(root.rightNode);
        return (nleft > nright) ? nleft + 1 : nright + 1;
    }
    
    // 測試
    public static void main(String[] args) {
        BinaryTreeNode a = new BinaryTreeNode();
        BinaryTreeNode b = new BinaryTreeNode();
        BinaryTreeNode c = new BinaryTreeNode();
        a.rightNode = b;
        b.leftNode = c;
        System.out.println(treeDepth(a));
    }
}

 

第6章 面試中的各項能力

面試題64 求1+2+...+n 

  • 描述:求1+2+...+n,要求不能使用乘除、for、while、if、else、switch、case等關鍵字和條件判斷語句。
  • 思路:像這樣的題目自己無太大實際意義,少作這種題。這裏能夠利用與、或邏輯運算的性質,想到才能作出來。貌似不能用遞歸,但仍是用了遞歸,不過不用if來終止罷了。
  • 考點:對怪問題的把握能力。
package sword_offer;
// page 307 求1+2+...+n 

public class Solution64 {

    public static int sum1(int n) {
        // 利用與運算的性質,實現了遞歸的終止
        boolean flag = (n > 0) && (n = n + sum1(n - 1)) > 0;
        //  (n == 0) || (n = n + sum1(n - 1)) > 0;
        return n;
    }
    
    // 測試
    public static void main(String[] args) {
        System.out.println(sum1(2));
    }
}
相關文章
相關標籤/搜索