劍指offer——java刷題總結【二】

Note

  • 題解彙總:劍指offer題解彙總
  • 代碼地址:Github 劍指offer Java實現彙總
  • 點擊目錄中的題名鏈接可直接食用題解~
  • 有些解法博文中未實現,不代表一定很難,可能只是因爲博主太懶```(Orz)
  • 如果博文中有明顯錯誤或者某些題目有更加優雅的解法請指出,謝謝~

目錄

題號 題目名稱
11 二進制中1的個數
12 數值的整數次方
13 調整數組順序使奇數位於偶數前面
14 鏈表中倒數第k個結點
15 反轉鏈表
16 合併兩個排序的鏈表
17 樹的子結構
18 二叉樹的鏡像
19 順時針打印矩陣
20 包含min函數的棧

正文

11、二進制中1的個數

題目描述

輸入一個整數,輸出該數二進制表示中1的個數。其中負數用補碼錶示。

題目分析

解法一: 首先需要了解一個前提:將數字n和1做&運算,可以判斷n當前最後一位是否是1。有了這個前提後,我們就可以不斷將數字n進行無符號右移,將n和1做&操作判斷當前數字最後一位是否爲1並計數即可。
使用無符號右移>>>而不是帶符號右移>>的原因:因爲對於帶符號右移,正數右移高位補0,負數右移高位補1;而對於無符號右移,無論是正數還是負數,高位通通補0。如果選擇帶符號右移,當n爲負數時,會一直在n的左邊補1,n永遠不會等於0,程序會進入死循環。

代碼實現

解法一: O(1)

public int NumberOf1(int n) {
    int count = 0;
    while (n != 0) {
        count += n & 1;
        n >>>= 1;
    }
    return count;
}

12、數值的整數次方

題目描述

給定一個double類型的浮點數base和int類型的整數exponent。求base的exponent次方。保證base和exponent不同時爲0。

題目分析

解法一: 使用單層循環對底數base進行累乘即可,但是需要考慮邊界0的情況,以及當指數exponent爲負數時,結果應該爲(base的exponent次方)分之1。
解法二: 使用快速冪的方式進行計算,即x的n次方乘以x的n次方等於x的2n次方,由此二分計算結果。

代碼實現

解法一: O(n)

public double Power(double base, int exponent) {
     if (base == 0) return 0;
     if (exponent == 0) return 1;
     double res = 1;
     for (int i = 1; i <= Math.abs(exponent); i++) {
       res *= base;
     }
     return exponent > 0 ? res : 1 / res;
}

解法二: O(logn)

    public double Power(double base, int exponent) {
        long N = n;
        return n >= 0 ? quickCal(x, N) : 1 / quickCal(x, -N);
    }

    public double quickCal(double x, long n) {
        double res = 1.0;
        double tx = x;
        while (n > 0) {
            if ((n & 1) == 1) {
                res *= tx;
            }
            tx *= tx;
            n /= 2;
        }
        return res;
    }

13、調整數組順序使奇數位於偶數前面

題目描述

輸入一個整數數組,實現一個函數來調整該數組中數字的順序,使得所有的奇數位於數組的前半部分,所有的偶數位於數組的後半部分,並保證奇數和奇數,偶數和偶數之間的相對位置不變。

題目分析

解法一: 開闢一個新數組,對數組進行兩次遍歷,第一次遍歷尋找奇數並存放在新數組中,第二次遍歷尋找偶數並存放在新數組中。該解法的空間複雜度爲O(n)。
解法二: 本解法參考快排中的partition思想,不開闢新數組,可以將奇數放在偶數前面,但由於partition方法的不穩定性,無法保證數字之間的相對次序不變,時間複雜度爲O(n),空間複雜度爲O(1)。如果需要滿足題目中所述的“奇數和奇數,偶數和偶數之間的相對位置不變”,就需要通過數組移位實現swap的穩定性,由於需要循環移位,故最終的時間複雜度會退化成O(n²)。

代碼實現

解法一: O(n)

public void reOrderArray(int [] array) {
   int[] newArr = new int[array.length];
   int index = 0;
   for (int i = 0; i < array.length; i++) {
      if ((array[i] & 1) == 1) {
         newArr[index++] = array[i];
      }
   }
   for (int i = 0; i < array.length; i++) {
      if ((array[i] & 1) == 0) {
         newArr[index++] = array[i];
      }
   }
   for (int i = 0; i < array.length; i++) {
      array[i] = newArr[i];
   }
}

解法二: O(n²)

public void reOrderArray1(int [] array) {
   int pre = -1;
   int suf = array.length;
   int l = 0;
   while (l < suf) {
      if ((array[l] & 1) == 1) {
         swap(array, ++pre, l++);
      } else {
         swap(array, --suf, l);
      }
   }
}

14、鏈表中倒數第k個結點

題目描述

輸入一個鏈表,輸出該鏈表中倒數第k個結點。

題目分析

解法一: 雙指針法。使用快指針和慢指針兩個指針,快指針先走k步,然後快慢指針同時走,當快指針指向null時,慢指針所指結點即爲鏈表中倒數第k個結點。

代碼實現

解法一: O(n)

public ListNode FindKthToTail(ListNode head, int k) {
   if (head == null) return null;
   ListNode fast = head;
   ListNode slow = head;
   while (k-- > 0) {
      if (fast != null) {
         fast = fast.next;  
      } else {
          return null;
        }
   }
    while (fast != null) {
      fast = fast.next;
      slow = slow.next;
   }
   return slow;
}

15、反轉鏈表

題目描述

輸入一個鏈表,反轉鏈表後,輸出新鏈表的表頭。

題目分析

解法一: 使用pre和next兩個指針,分別標識當前節點的前驅和後繼,對指針指向進行改變即可。
解法二: 使用遞歸實現,對當前節點的next進行遞歸,只需要考慮返回得到的是指向一個末尾節點的指針,然後對指針進行調整即可。

代碼實現

解法一: O(n)

public ListNode ReverseList(ListNode head) {
    if (head == null || head.next == null) return head;
    ListNode pre = null;
    ListNode next = null;
    while (head != null) {
        next = head.next;
        head.next = pre;
        pre = head;
        head = next;
    }
    return pre;
}

解法二: O(n)

    public ListNode ReverseList(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode last = ReverseList(head.next);
        head.next.next = head;
        head.next = null;
        return last;
    }

16、合併兩個排序的鏈表

題目描述

輸入兩個單調遞增的鏈表,輸出兩個鏈表合成後的鏈表,當然我們需要合成後的鏈表滿足單調不減規則。

題目分析

解法一: 設置頭結點head和頭結點的引用cur。當兩個鏈表都不爲空時,cur指向兩個鏈表指向節點中較小的那一個,然後指針後移;當有一個鏈表爲空時,將cur直接指向非空鏈表的後續頭節點。該過程其實就是歸併排序中的merge過程。

代碼實現

解法一: O(min(m,n))

public ListNode Merge(ListNode list1, ListNode list2) {
    ListNode head = new ListNode(-1);
    ListNode cur = head;
    while (list1 != null && list2 != null) {
        if (list1.val < list2.val) {
            cur.next = list1;
            list1 = list1.next;
        } else {
            cur.next = list2;
            list2 = list2.next;
        }
        cur = cur.next;
    }
    if (list1 != null) {
        cur.next = list1;
    }
    if (list2 != null) {
        cur.next = list2;
    }
    return head.next;
}

17、樹的子結構

題目描述

輸入兩棵二叉樹A,B,判斷B是不是A的子結構。(ps:我們約定空樹不是任意一個樹的子結構)

題目分析

解法一: 非遞歸實現。
1、實現isSubTree()方法,傳入兩個值相同的節點t1和t2,用於判斷以t2爲根節點的樹是否是以t1爲根節點的樹的子結構;
2、在Main方法中對樹A進行遍歷,尋找與樹B根節點值相同的節點,調用isSubTree()判斷是否存在子結構;
3、本解法未使用遞歸實現,而是實現了兩種非遞歸的遍歷,isSubTree()中採用層序遍歷,主方法中採用中序遍歷。
解法二: 遞歸實現。
1、由於判斷的是tree2是否爲tree1的子結構,所以當tree2爲空時,tree1無論爲什麼值,都應該返回true;
2、當tree1爲空,tree2不爲空時,說明tree2不是tree1的子結構,返回false;
3、當tree1和tree2的當前值不等時,返回false;
4、當tree1和tree2都不爲空且值相等時,說明當前節點判斷完畢,接下來需要對當前節點的左、右子樹進行遞歸判斷。

代碼實現

解法一: O(m*n)

public boolean HasSubtree(TreeNode root1, TreeNode root2) {
    if (root2 == null) return false;
    Stack<TreeNode> stack = new Stack<>();
    while (!stack.empty() || root1 != null) {
        if (root1 != null) {
            stack.push(root1);
            root1 = root1.left;
        } else {
            root1 = stack.pop();
            if (root1.val == root2.val && isSubTree(root1, root2)) {
                return true;
            }
            root1 = root1.right;
        }
    }
    return false;
}

public static boolean isSubTree(TreeNode root1, TreeNode root2) {
    Queue<TreeNode> q1 = new LinkedList<>();
    Queue<TreeNode> q2 = new LinkedList<>();
    q1.add(root1);
    q2.add(root2);
    while (!q2.isEmpty()) {
        TreeNode t1 = q1.poll();
        TreeNode t2 = q2.poll();
        if (t1.val != t2.val) {
            return false;
        } else {
            if (t1.left != null && t2.left != null) {
                q1.add(t1.left);
                q2.add(t2.left);
            } else if (t1.left == null && t2.left != null){
                return false;
            }
            if (t1.right != null && t2.right != null) {
                q1.add(t1.right);
                q2.add(t2.right);
            } else if (t1.right == null && t2.right != null){
                return false;
            }
        }
    }
    return true;
}

解法二: O(m*n)

public boolean HasSubtree(TreeNode root1, TreeNode root2) {
    if (root1 == null || root2 == null) return false;
    return isSubTree(root1, root2) || HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);
}

public boolean isSubTree(TreeNode tree1, TreeNode tree2){
    if (tree2 == null) return true;
    if (tree1 == null) return false;
    if (tree1.val != tree2.val) return false;
    return isSubTree(tree1.left, tree2.left) && isSubTree(tree1.right, tree2.right);
}

18、二叉樹的鏡像

題目描述

操作給定的二叉樹,將其變換爲源二叉樹的鏡像。

題目分析

解法一: 非遞歸實現。對該樹進行層次遍歷,對當前遍歷的節點進行判斷:
1、如果左右孩子都不爲空,則交換兩個孩子,並將兩個節點入隊;
2、如果左右孩子有一個爲空,則交換兩個孩子,也就是將非空孩子賦給空節點,對非空孩子置空,將交換後的非空節點入隊;
3、如果左右子樹都爲空,則無需處理。
解法二: 遞歸實現。將當前遍歷節點的左右孩子進行交換,然後遞歸地對左右孩子進行相同操作,當前節點爲null時結束遞歸。

代碼實現

解法一: O(n)

public void Mirror(TreeNode root) {
    if (root == null) return;
    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(root);
    while (!queue.isEmpty()) {
        root = queue.poll();
        if (root.left != null && root.right != null) {
            TreeNode t = root.left;
            root.left = root.right;
            root.right = t;
            queue.add(root.left);
            queue.add(root.right);
        } else if (root.left != null && root.right == null) {
            root.right = root.left;
            root.left = null;
            queue.add(root.right);
        } else if (root.left == null && root.right != null) {
            root.left = root.right;
            root.right = null;
            queue.add(root.left);
        }
    }
}

解法二: O(n)

public void Mirror(TreeNode root) {
    if(root == null) return;
    TreeNode temp = root.left;
    root.left = root.right;
    root.right = temp;
    Mirror(root.left);
    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.

題目分析

解法一: 該類題目需要從宏觀的角度去考慮。
1、首先定義一個方法printLevel,給定矩陣的左上角和右下角兩個點,使用四個循環遍歷順時針輸出矩陣的外圈。注:需要考慮只包含一行和一列的情況,當只有一行時,從左往右依次打印;當只有一列時,從上往下依次打印。
2、確定矩陣的左上角和右下角,不斷縮小矩陣,遞進到內圈矩陣,調用步驟1中的方法打印當前矩陣的最外圈。

代碼實現

解法一: O(m*n)

public ArrayList<Integer> printMatrix(int [][] matrix) {
    ArrayList<Integer> list = new ArrayList<Integer>();
    int row1 = 0;
    int col1 = 0;
    int row2 = matrix.length - 1;
    int col2 = matrix[0].length - 1;
    while (row1 <= row2 && col1 <= col2) {
        printLevel(list, matrix, row1++, col1++, row2--, col2--);
    }
    return list;
}

public static void printLevel(ArrayList<Integer> list, int[][] matrix,
                              int row1, int col1, int row2, int col2) {
    if (row1 == row2) {
        for (int i = col1; i <= col2; i++) {
            list.add(matrix[row1][i]);
        }
    } else if (col1 == col2){
        for (int i = row1; i <= row2; i++) {
            list.add(matrix[i][col1]);
        }
    } else {
        int curRow = row1;
        int curCol = col1;
        while (curCol < col2) {
            list.add(matrix[curRow][curCol++]);
        }
        while (curRow < row2) {
            list.add(matrix[curRow++][curCol]);
        }
        while (curCol > col1) {
            list.add(matrix[curRow][curCol--]);
        }
        while (curRow > row1) {
            list.add(matrix[curRow--][curCol]);
        }
    }
}

20、包含min函數的棧

題目描述

定義棧的數據結構,請在該類型中實現一個能夠得到棧中所含最小元素的min函數(時間複雜度應爲O(1))。

題目分析

解法一: 雙棧法。使用min棧來存儲當前棧的最小元素,對於最小元素相同的連續序列,在min棧中只存儲一個,可以壓縮部分空間,儘管空間複雜度都爲O(n)。
1、當壓入棧時,如果min棧爲空則直接壓入;否則判斷當前壓入元素是否小於等於min棧的棧頂元素:如果小於等於則說明當前壓入的元素是stack的最小元素,則push到min棧,如果大於則說明min棧的棧頂元素依然是stack中的最小元素,則不做任何操作;
2、當彈出棧時,如果stack和min棧的棧頂元素相同,說明stack中的最小元素此時要出棧了,則min棧也執行pop操作。

代碼實現

解法一: O(n)

Stack<Integer> stack = new Stack<>();
Stack<Integer> minStack = new Stack<>();

public void push(int node) {
    stack.push(node);
    if (minStack.empty()) {
        minStack.push(node);
    } else {
        if (node <= minStack.peek()) {
            minStack.push(node);
        }
    }
}

public void pop() {
    if (stack.peek() == minStack.peek()) {
        minStack.pop();
    }
    stack.pop();
}

public int top() {
    return stack.peek();
}

public int min() {
    return minStack.peek();
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章