劍指offer中幾道算法題的思考

一、在一個二維數組中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。

  • 假如二維數組爲 {{1, 2, 8, 9}, {2, 4, 9, 12}, {4, 7, 10, 13}, {6, 8, 11, 15}};同時查找的值爲15html

    • 常規的思路爲咱們取到每一個二維數組的值,而後遍歷每一個值,判斷是否相等!同時咱們知道會循環16次。代碼以下
      private boolean lookUpInTwoDimensionalArrays(int[][] a, int num) {
        // 至少保證 長度至少爲1,且還有元素,
        if (a == null || a.length < 1 || a[0].length < 1) {
            return false;
        }
        int b = 0;
        for (int i = 0; i < a.length; i++) {
            for (int j = 0; j < a[i].length; j++) {
                b++;
                if (num == a[i][j]) {
                    // 一共循環多少次:12
                    System.out.println(TAG + "一共循環多少次:" + b);
                    return true;
                }
            }
        }
        return false;
       }
      複製代碼
    • 解題的思路以下
      • 一、選取二維數組的中的第一個元素,比較最右邊的值,若是相等,就查找結束
      • 二、選取二維數組的中的第一個元素,比較最右邊的值,若是大於,查找的就是這個元素,同時角標減去1,而後繼續查找
      • 三、選取二維數組的中的第一個元素,比較最右邊的值,若是大於,查找的不是這個元素,就去查找下一個數組,而後繼續查找
        二維數組的查找過程.png
  • 最終的代碼以下node

/**
     * 數組是遞增的 ,從左到右  從上到下,那麼數組必定是個正方形的樣子
     *
     * @param arr 原始的數組
     * @param num 須要查找的數字
     * @return 是否找到了
     */
    private boolean newLookUpInTwoDimensionalArrays(int[][] arr, int num) {
        if (arr == null || arr.length < 1 || arr[0].length < 1) {
            return false;
        }
        int rowTotal = arr.length;// 數組的行數
        int colTolal = arr[0].length;// 數組的列數
        //開始的角標
        int row = 0;
        int col = colTolal - 1;
        int i = 0;
        while (row >= 0 && row < rowTotal && col >= 0 && col < colTolal) {
            // 是二維數組的 arr[0][arr[0].length-1] 的值,就是最右邊的值
            i++;
            if (arr[row][col] == num) {
                System.out.println(TAG + "newLookUpInTwoDimensionalArrays 執行了多少次" + i);
                return true;
            } else if (arr[row][col] > num) {// 若是找到的值 比目標的值大的話,就把查找的列數減去1
                col--;//列數減去1 ,表明向左移動
            } else {// 比目標的num大的,就把行數加上1,而後往下移動
                row++;
            }
        }
        return false;
    }
複製代碼
  • 若是查找的值爲15,那麼舊的代碼會執行15次,新的代碼只會執行3次。

二、請實現一個函數,把字符串中的每一個空格替換成"%20",例如「We are happy.」,則輸出「We%20are%20happy.」.

  • 第一種方法:先判斷字符串中空格的數量。根據數量判斷該字符串有沒有足夠的空間替換成"%20"。若是有足夠空間,計算出須要的空間。根據最終須要的總空間,維護一個指針在最後。從後到前,遇到非空的就把該值挪到指針指向的位置,而後指針向前一位,遇到「 」,則指針前移,依次替換爲「02%」。
public char[]  replaceBlank(char[] string, int usedLength) {
        // 判斷輸入是否合法
        System.out.println(TAG+"string.length="+string.length);
        System.out.println(TAG+"usedLength="+usedLength);
        if (string == null || string.length < usedLength) {
            return null;
        }

        // 統計字符數組中的空白字符數
        int whiteCount = 0;
        for (int i = 0; i < usedLength; i++) {
            if (string[i] == ' ') {
                whiteCount++;
            }
        }
        // 若是沒有空白字符就不用處理
        if (whiteCount == 0) {
            return string;
        }
        // 計算轉換後的字符長度是多少
        int targetLength = whiteCount * 2 + usedLength;
         //新的保存的字符串的數組
        char[] newChars = new char[targetLength];
        int tmp = targetLength; // 保存長度結果用於返回
//        if (targetLength > string.length) { // 若是轉換後的長度大於數組的最大長度,直接返回失敗
//            return -1;
//        }
        // todo  必須先作 這個,注意體會  --i  和 i-- 的區別
        usedLength--; // 從後向前,第一個開始處理的字符
        targetLength--; // 處理後的字符放置的位置
        // 字符中有空白字符,一直處理到全部的空白字符處理完
        while (usedLength >= 0 && usedLength < targetLength) {
            // 如是當前字符是空白字符,進行"%20"替換
            if (string[usedLength--] == ' ') {
                newChars[targetLength--] = '0';
                newChars[targetLength--] = '2';
                newChars[targetLength--] = '%';
            } else { // 不然移動字符
                newChars[targetLength--] = string[usedLength];
            }
            usedLength--;
        }
        return newChars;
    }
複製代碼
  • 第二種方法,很是的消耗時間和內存。
private String idoWorkReplace(String str, String tagStr) {
        if (str == null || str.length() < 1) {
            return "";
        }
        String[] split = str.split(" ");
        StringBuffer stringBuffer = new StringBuffer();
        for (String s : split) {
            stringBuffer.append(s);
            stringBuffer.append(tagStr);
        }
        CharSequence charSequence = stringBuffer.subSequence(0, stringBuffer.length() - tagStr.length());
        return charSequence.toString();
    }
複製代碼
  • 第三種方法,調用replace方法,若是僅僅來說替換字符串的話,是最好的方法,關鍵的方法是indexOf(this, targetStr, lastMatch).
StringBuffer sb = new StringBuffer();
        sb.append(str);
        // todo  最節能的方法
        sb.toString().replace(" ", "%20");
複製代碼
  • 第四種方法和第一種方法殊途同歸:先統計空白的數量,而後計算出有多少空白的長度,而後設置新的長度StringBuffer,經過插入最大角標的地方,不斷的插入,就能夠了
private String replaceSpaces(String string) {
        //判斷是否 輸入合法
        if (string == null || string.length() < 1) {
            return "";
        }
        char[] chars = string.toCharArray();
        // 統計有多少的空白的數組
        int whiteCount = 0;
        for (int i = 0; i < chars.length; i++) {
            if (chars[i] == ' ') {
                whiteCount++;
            }
        }
        if (whiteCount == 0) {
            return string;
        }
        //最本來的長度
        int indexold = string.length() - 1;
        // 轉換完成後的長度
        int newlength = string.length() + whiteCount * 2;
        //新的長度
        int indexnew = newlength - 1;
        StringBuffer stringBuffer = new StringBuffer(string);
        //設置新的buffer的長度
        stringBuffer.setLength(newlength);
        for (; indexold >= 0 && indexold < newlength; indexold--) {
            // 原來的 字符串的最後一位爲空格
            if (string.charAt(indexold) == ' ') {
                stringBuffer.setCharAt(indexnew--, '0');
                stringBuffer.setCharAt(indexnew--, '2');
                stringBuffer.setCharAt(indexnew--, '%');
            } else {//不爲空的話,就直接放進去 就好了
                stringBuffer.setCharAt(indexnew--, string.charAt(indexold));
            }
        }
        return stringBuffer.toString();
    }
複製代碼

三、輸入個鏈表的頭結點,從尾到頭反過來打印出每一個結點的值。

  • 數據結構
public static class ListNode {
        int val;
        ListNode next;
        public ListNode(int  v){
            this.val=v;
        }
    }
複製代碼
  • 初始化
ListNode listNode = new ListNode(10);
     ListNode listNode1 = new ListNode(11);
     ListNode listNode2 = new ListNode(12);
     ListNode listNode3 = new ListNode(13);
      ListNode listNode4 = new ListNode(14);
        listNode.next=listNode1;
        listNode1.next=listNode2;
        listNode2.next=listNode3;
        listNode3.next=listNode4;
複製代碼
  • 第一種方法的詳情,很是像二叉樹的遍歷,也是最簡單的方法。
public static ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        arrayList.clear();
        if (listNode != null) {
            printListFromTailToHead(listNode.next);//指向下一個節點
            arrayList.add(listNode.val);//將當前節點的值存到列表中
        }
        return arrayList;
    }
複製代碼
  • 第二種方法,利用Stack的特色,Stack類:繼承自Vector,實現一個後進先出的棧,不太明白的同窗能夠看這裏經常使用集合的原理分析;
private static void doWhat(ListNode listNode) {
        Stack<ListNode> stack = new Stack<>();
        while (listNode!=null){
            stack.push(listNode);
            listNode=listNode.next;
        }
        System.out.println(" stack "+stack.size());
//        for (int i=0;i<stack.size();i++){
//            System.out.println("每一個該打印的元素 ::"+stack.get(i).val);
//        }
        ListNode tmp;
        while (!stack.empty()){
            // 移除堆棧頂部的對象,並做爲此函數的值返回該對象。
            tmp = stack.pop();
            System.out.println("tmp="+tmp.val);
          //  System.out.println("每一個該打印的元素 :"+tmp.val);
        }
    }
複製代碼

四、輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。這個題的官方的答案,我本人持有嚴重的懷疑,也有多是我根本沒有找到正確的答案!

  • 認識下什麼二叉樹:二叉樹是每一個結點最多有兩個子樹的樹結構。一般子樹被稱做「左子樹」(left subtree)和「右子樹」(right subtree)。二叉樹常被用於實現二叉查找樹和二叉堆。樹的數據結構可以同時具有數組查找快的優勢以及鏈表插入和刪除快的優勢
  • 代碼結構
public interface Tree {
    //查找節點
     Node find(int key);
    //插入新節點
     boolean insert(int data);
    //中序遍歷
     void infixOrder(Node current);
    //前序遍歷
     void preOrder(Node current);
    //後序遍歷
     void postOrder(Node current);
    //查找最大值
     Node findMax();
    //查找最小值
     Node findMin();
    //刪除節點
     boolean delete(int key);
}
複製代碼
  • Node
public class Node {
    int data;   //節點數據
    Node leftChild; //左子節點的引用
    Node rightChild; //右子節點的引用

    public Node(int data){
        this.data = data;
    }
    //打印節點內容
    public void display(){
        System.out.println(data);
    }

    @Override
    public String toString() {
        return super.toString();
    }
}
複製代碼
  • 插入數據的過程
BinaryTree bt = new BinaryTree();
       // 第一個插入的結點是 根節點
       bt.insert(50);
       bt.insert(20);
       bt.insert(80);
       bt.insert(10);
       bt.insert(30);
       bt.insert(60);
       bt.insert(90);
       bt.insert(25);
       bt.insert(85);
       bt.insert(100);
複製代碼
  • insert 代碼
//插入節點
    public boolean insert(int data) {
        Node newNode = new Node(data);
        if (root == null) {//當前樹爲空樹,沒有任何節點
            root = newNode;
            return true;
        } else {
            Node current = root;
            Node parentNode = null;
            while (current != null) {
                parentNode = current;
                if (current.data > newNode.data) {//當前值比插入值大,搜索左子節點
                    current = current.leftChild;
                    if (current == null) {//左子節點爲空,直接將新值插入到該節點
                        parentNode.leftChild = newNode;
                        return true;
                    }
                } else {
                    current = current.rightChild;
                    if (current == null) {//右子節點爲空,直接將新值插入到該節點
                        parentNode.rightChild = newNode;
                        return true;
                    }
                }
            }
        }
        return false;
    }
複製代碼

二叉樹插入數據的過程.png

二叉樹最終的結構.png

  • 最終的圖解以下

二叉樹 insert 圖解.jpg

  • 二叉樹的前序遍歷:根節點>>左子樹>>右子樹
public void preOrder(Node current) {
        if (current != null) {
            System.out.print(current.data + " ");
            infixOrder(current.leftChild);
            infixOrder(current.rightChild);
        }
    }
複製代碼
  • 一、根節點50,查找左節點,找到20,而後找到10,輸出10,而後找到25 ,而後30 。到這裏輸出的結果是 50 10 20 25 30
  • 二、查找根節點的50的右節點,而後找出80,找出80的左節點60.接着80,查找80的右節點95,輸出85 而後 90 ,最後輸出100
  • 三、最終的輸出的結果50 10 20 25 30 60 80 85 90 100

前序遍歷.png

  • 二叉樹中序遍歷:首先遍歷左子樹,而後訪問根結點,最後遍歷右子樹。 左子樹 ---》根節點----》 右子樹
public void infixOrder(Node current) {
        if (current != null) {
            infixOrder(current.leftChild);
            System.out.print(current.data + " ");
            infixOrder(current.rightChild);
        }
    }
複製代碼
  • 一、傳入根節點 值爲50 ,查找50的左節點爲20不爲null,再次查找20的左節點,爲10,也不爲null,而後在查找10的左節點,爲null ,輸出10,第一個節點輸出完成爲 10
  • 二、接下來,查找的10的右節點,爲null,不進入輸出語句
  • 三、這下輸出語句20,查找20右節點,查找到值爲30,第一步查找的值爲左節點,查找到的值爲25,查找右節點爲null
  • 四、到這裏輸出的結果爲 10 20 25 30
  • 五、這樣子50左節點就所有查找完了
  • 六、接下來查找50的右節點,查找右節點80,而後查找80的左節點,查找到的值爲60,60沒有右節點,繼續查找80的右節點,到這裏 輸出的結果10 20 25 30 50 60 80
  • 七、查找80右節點,查到到值爲90,接着查找90的左節點,查到到的值85,接着85的左右節點爲null,返回直接查找90的右節點,查找到了100,
  • 八、最終輸出的結果,10 20 25 30 50 60 80 85 90 100

二叉樹的中序遍歷.png

  • 後序遍歷:在二叉樹中,先左後右再根,即首先遍歷左子樹,而後遍歷右子樹,最後訪問根結點。
    • 一、根節點50,查找左節點20,在繼續查找20的左節點10,10沒有左右節點,打印10,而後查找到20的右節點30,30繼續查找到25,第一次輸出爲 10 20 25 30
    • 二、最終輸出10 20 25 30 60 80 85 90 100 50

二叉樹的後序遍歷.png

  • 查找最大值或者是最小值
//找到最大值
    public Node findMax() {
        Node current = root;
        Node maxNode = current;
        while (current != null) {
            maxNode = current;
            current = current.rightChild;
        }
        return maxNode;
    }

    //找到最小值
    public Node findMin() {
        Node current = root;
        Node minNode = current;
        while (current != null) {
            minNode = current;
            current = current.leftChild;
        }
        return minNode;
    }
複製代碼
  • 例如:前序遍歷序列{50 10 20 25 30 60 80 85 90 100}和中序遍歷序列{10 20 25 30 50 60 80 85 90 100}, 重建二叉樹並輸出它的頭結點。
  • 原始節點的遍歷結果,能夠很清楚的看到,有多少左右節點,那個節點是那個的節點等等的信息。
09-02 05:51:53.177 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node50  是那邊啊 根
09-02 05:51:53.177 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node20  是那邊啊 左
09-02 05:51:53.177 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node10  是那邊啊 左
09-02 05:51:53.177 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node30  是那邊啊 右
09-02 05:51:53.177 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node25  是那邊啊 左
09-02 05:51:53.177 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node80  是那邊啊 右
09-02 05:51:53.177 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node60  是那邊啊 左
09-02 05:51:53.177 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node90  是那邊啊 右
09-02 05:51:53.177 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node85  是那邊啊 左
09-02 05:51:53.177 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node100  是那邊啊 右
複製代碼
  • 解決方法一:
public static Node reConstructBinaryTree(int[] pre, int[] in) {
        if (pre == null || in == null || pre.length != in.length)//若是先序或者中序數組有一個爲空的話,就沒法建樹,返回爲空
            return null;
        else {
            return reBulidTree(pre, 0, pre.length - 1, in, 0, in.length - 1);
        }
    }

    /**
     * @param pre
     * @param startPre
     * @param endPre
     * @param in
     * @param startIn
     * @param endIn
     * @return
     */
    private static Node reBulidTree(int[] pre, int startPre, int endPre, int[] in, int startIn, int endIn) {
        if (startPre > endPre || startIn > endIn)//先對傳的參數進行檢查判斷
            return null;
        int root = pre[startPre];//數組的開始位置的元素是跟元素
        int locateRoot = locate(root, in, startIn, endIn);//獲得根節點在中序數組中的位置 左子樹的中序和右子樹的中序以根節點位置爲界

        if (locateRoot == -1) //在中序數組中沒有找到跟節點,則返回空
            return null;
        Node treeRoot = new Node(root);//建立樹根節點
        treeRoot.leftChild = reBulidTree(pre, startPre + 1, startPre + locateRoot - startIn, in, startIn, locateRoot - 1);//遞歸構建左子樹
        treeRoot.rightChild = reBulidTree(pre, startPre + locateRoot - startIn + 1, endPre, in, locateRoot + 1, endIn);//遞歸構建右子樹
        return treeRoot;
    }

    //找到根節點在中序數組中的位置,根節點以前的是左子樹的中序數組,根節點以後的是右子樹的中序數組
    private static int locate(int root, int[] in, int startIn, int endIn) {
        for (int i = startIn; i < endIn; i++) {
            if (root == in[i])
                return i;
        }
        return -1;
    }

複製代碼
  • 可是這個的輸出結果以下,很明顯沒有重建二叉樹成功,它的執行流程如上面的代碼,因爲我的認爲沒有重建,因此就沒有輸出示意圖
09-02 05:51:53.177 2590-2590/com.android.interview I/System.out: 新新----的中序遍歷的開始
09-02 05:51:53.177 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node50  是那邊啊 根
09-02 05:51:53.177 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node10  是那邊啊 左
09-02 05:51:53.177 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node20  是那邊啊 右
09-02 05:51:53.177 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node25  是那邊啊 右
09-02 05:51:53.177 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node60  是那邊啊 右
09-02 05:51:53.177 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node80  是那邊啊 右
09-02 05:51:53.177 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node85  是那邊啊 右
09-02 05:51:53.177 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node90  是那邊啊 右
09-02 05:51:53.185 2590-2590/com.android.interview I/System.out: 新新----的中序遍歷的結束
複製代碼
  • 解決方法二:解題思路也是不斷地遍歷,代碼以下
public static Node reConstructBinaryTreeNew(int[] pre, int[] in) {
        if (pre.length == 0 || in.length == 0)
            return null;
         Node node = new Node(pre[0]);
        for (int i = 0; i < pre.length; i++) {
            if (pre[0] == in[i]) {
                node.leftChild = reConstructBinaryTreeNew(Arrays.copyOfRange(pre, 1, i + 1), Arrays.copyOfRange(in, 0, i));
                node.rightChild = reConstructBinaryTreeNew(Arrays.copyOfRange(pre, i + 1, pre.length), Arrays.copyOfRange(in, i + 1, in.length));
                break;
            }
        }
        return node;
    }
複製代碼
  • 輸出的結果和方法一的結果是同樣的。二叉樹的結果仍是不同。
09-02 05:51:53.185 2590-2590/com.android.interview I/System.out: 新新----的中序遍歷的開始------------
09-02 05:51:53.185 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node50  是那邊啊 根
09-02 05:51:53.185 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node10  是那邊啊 左
09-02 05:51:53.186 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node20  是那邊啊 右
09-02 05:51:53.194 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node25  是那邊啊 右
09-02 05:51:53.211 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node30  是那邊啊 右
09-02 05:51:53.212 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node60  是那邊啊 右
09-02 05:51:53.212 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node80  是那邊啊 右
09-02 05:51:53.212 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node85  是那邊啊 右
09-02 05:51:53.213 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node90  是那邊啊 右
09-02 05:51:53.213 2590-2590/com.android.interview I/System.out: 這個值是什麼啊 Node100  是那邊啊 右
09-02 05:51:53.214 2590-2590/com.android.interview I/System.out: 新新----的中序遍歷的結束------------
複製代碼
  • 解決方法三:由前序遍歷的第一個節點可知根節點。根據根節點,能夠將中序遍歷劃分紅左右子樹。在前序遍歷中找出對應的左右子樹,其第一個節點即是根節點的左右子節點。按照上述方式遞歸即可重建二叉樹。
public class Test {  
    /** 
     * 請實現一個函數,把字符串中的每一個空格替換成"%20",例如「We are happy.「,則輸出」We%20are%20happy.「。 
     * 
     * @param string     要轉換的字符數組 
     * @param usedLength 已經字符數組中已經使用的長度 
     * @return 轉換後使用的字符長度,-1表示處理失敗 
     */  
    public static int replaceBlank(char[] string, int usedLength) {  
        // 判斷輸入是否合法  
        if (string == null || string.length < usedLength) {  
            return -1;  
        }  
  
        // 統計字符數組中的空白字符數  
        int whiteCount = 0;  
        for (int i = 0; i < usedLength; i++) {  
            if (string[i] == ' ') {  
                whiteCount++;  
            }  
        }  
  
        // 計算轉換後的字符長度是多少  
        int targetLength = whiteCount * 2 + usedLength;  
        int tmp = targetLength; // 保存長度結果用於返回  
        if (targetLength > string.length) { // 若是轉換後的長度大於數組的最大長度,直接返回失敗  
            return -1;  
        }  
  
        // 若是沒有空白字符就不用處理  
        if (whiteCount == 0) {  
            return usedLength;  
        }  
  
        usedLength--; // 從後向前,第一個開始處理的字符  
        targetLength--; // 處理後的字符放置的位置  
  
        // 字符中有空白字符,一直處理到全部的空白字符處理完  
        while (usedLength >= 0 && usedLength < targetLength) {  
            // 如是當前字符是空白字符,進行"%20"替換  
            if (string[usedLength] == ' ') {  
                string[targetLength--] = '0';  
                string[targetLength--] = '2';  
                string[targetLength--] = '%';  
            } else { // 不然移動字符  
                string[targetLength--] = string[usedLength];  
            }  
            usedLength--;  
        }  
  
        return tmp;  
    }  
}  
複製代碼
相關文章
相關標籤/搜索