Android複習資料——常見面試演算法題彙總(一)

JasonWuuu發表於2019-07-13

接觸 Android 開發也有一段時間了,前段時間便開始想抽空整理一些知識點,通過筆記整理的方式減少自己重複學習的時間成本和提高自身的效率。

本文總結的部分是常見面試演算法題,演算法題解均有 java 實現。目錄可以在右邊側邊欄檢視跳轉。

之後會整理的知識點還會有 java、Android SDK、Android 原始碼、其他的一些計算機基礎以及常見的面試題等幾個部分,往後的一個月時間裡會陸續補充更新,在 Github 上建立了專案,想關注的歡迎 star

另外,可檢視下一篇::

排序

比較排序

氣泡排序

重複地走訪過要排序的數列,每次比較相鄰兩個元素,如果它們的順序錯誤就把它們交換過來,越大的元素會經由交換慢慢“浮”到數列的尾端。

public void bubbleSort(int[] arr) {
    int temp = 0;
    boolean swap;
    for (int i = arr.length - 1; i > 0; i--) { // 每次需要排序的長度
        // 增加一個swap的標誌,當前一輪沒有進行交換時,說明陣列已經有序
        swap = false;
        for (int j = 0; j < i; j++) { // 從第一個元素到第i個元素
            if (arr[j] > arr[j + 1]) {
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                swap = true;
            }
        }
        if (!swap){
            break;
        }
    }
}
複製程式碼

歸併排序

分解待排序的陣列成兩個各具 n/2 個元素的子陣列,遞迴呼叫歸併排序兩個子陣列,合併兩個已排序的子陣列成一個已排序的陣列。

public void mergeSort(int[] arr) {
    int[] temp = new int[arr.length];
    mergeSort(arr, temp, 0, arr.length - 1);
}

private void mergeSort(int[] arr, int[] temp, int left, int right) {
    // 當left == right時,不需要再劃分
    if (left < right) {
        int mid = (left + right) / 2;
        internalMergeSort(arr, temp, left, mid);
        internalMergeSort(arr, temp, mid + 1, right);
        mergeSortedArray(arr, temp, left, mid, right);
    }
}

// 合併兩個有序子序列
public void mergeSortedArray(int[] arr, int[] temp, int left, int mid, int right) {
    int i = left;
    int j = mid + 1;
    int k = 0;
    while (i <= mid && j <= right) {
        temp[k++] = arr[i] < arr[j] ? arr[i++] : arr[j++];
    }
    while (i <= mid) {
        temp[k++] = arr[i++];
    }
    while (j <= right) {
        temp[k++] = arr[j++];
    }
    // 把temp資料複製回原陣列
    for (i = 0; i < k; i++) {
        arr[left + i] = temp[i];
    }
}
複製程式碼

快速排序

在待排序的陣列選取一個元素作為基準,將待排序的元素進行分割槽,比基準元素大的元素放在一邊,比其小的放另一邊,遞迴呼叫快速排序對兩邊的元素排序。選取基準元素並分割槽的過程採用雙指標左右交換。

public void quickSort(int[] arr){
    quickSort(arr, 0, arr.length-1);
}

private void quickSort(int[] arr, int low, int high){
    if (low >= high)
        return;
    int pivot = partition(arr, low, high);        //將陣列分為兩部分
    quickSort(arr, low, pivot - 1);                   //遞迴排序左子陣列
    quickSort(arr, pivot + 1, high);                  //遞迴排序右子陣列
}

private int partition(int[] arr, int low, int high){
    int pivot = arr[low];     //基準
    while (low < high){
        while (low < high && arr[high] >= pivot) {
            high--;
        }
        arr[low] = arr[high];             //交換比基準大的記錄到左端
        while (low < high && arr[low] <= pivot) {
            low++;
        }
        arr[high] = arr[low];           //交換比基準小的記錄到右端
    }
    //掃描完成,基準到位
    arr[low] = pivot;
    //返回的是基準的位置
    return low;
}
複製程式碼

線性排序

計數排序

根據待排序的陣列中最大和最小的元素,統計陣列中每個值為i的元素出現的次數,存入陣列C的第i項,對所有的計數累加,然後反向填充目標陣列。

public void countSort(int[] arr) {
    int max = Integer.MIN_VALUE;
    int min = Integer.MAX_VALUE;
    for(int i = 0; i < arr.length; i++){
        max = Math.max(max, arr[i]);
        min = Math.min(min, arr[i]);
    }

    int[] b = new int[arr.length]; // 儲存陣列
    int[] count = new int[max - min + 1]; // 計數陣列

    for (int num = min; num <= max; num++) {
        // 初始化各元素值為0,陣列下標從0開始因此減min
        count[num - min] = 0;
    }

    for (int i = 0; i < arr.length; i++) {
        int num = arr[i];
        count[num - min]++; // 每出現一個值,計數陣列對應元素的值+1
        // 此時count[i]表示數值等於i的元素的個數
    }

    for (int i = min + 1; i <= max; i++) {
        count[i - min] += count[i - min - 1];
        // 此時count[i]表示數值<=i的元素的個數
    }

    for (int i = 0; i < arr.length; i++) {
            int num = arr[i]; // 原陣列第i位的值
            int index = count[num - min] - 1; //加總陣列中對應元素的下標
            b[index] = num; // 將該值存入儲存陣列對應下標中
            count[num - min]--; // 加總陣列中,該值的總和減少1。
    }

    // 將儲存陣列的值替換給原陣列
    for(int i=0; i < arr.length;i++){
        arr[i] = b[i];
    }
}
複製程式碼

桶排序

找出待排序陣列中的最大值max、最小值min,陣列ArrayList作為桶,桶裡放的元素用ArrayList儲存。計算每個元素 arr[i] 放的桶,每個桶各自排序,遍歷桶陣列,把排序好的元素放進輸出陣列。

public static void bucketSort(int[] arr){
    int max = Integer.MIN_VALUE;
    int min = Integer.MAX_VALUE;
    for(int i = 0; i < arr.length; i++){
        max = Math.max(max, arr[i]);
        min = Math.min(min, arr[i]);
    }
    // 桶數
    int bucketNum = (max - min) / arr.length + 1;
    ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum);
    for(int i = 0; i < bucketNum; i++){
        bucketArr.add(new ArrayList<Integer>());
    }
    // 將每個元素放入桶
    for(int i = 0; i < arr.length; i++){
        int num = (arr[i] - min) / (arr.length);
        bucketArr.get(num).add(arr[i]);
    }
    // 對每個桶進行排序
    for(int i = 0; i < bucketArr.size(); i++){
        Collections.sort(bucketArr.get(i));
        for (int j = 0; j < bucketArr.get(i).size(); j++) {
            arr[j] = bucketArr.get(i).get(j);
        }
    }
}
複製程式碼

二叉樹

class TreeNode {
    public TreeNode left, right;
    public int val;

    public TreeNode(int val) {
        this.val = val;
    }
}
複製程式碼

順序遍歷

先序遍歷: 根->左->右
中序遍歷: 左->根->右
後序遍歷: 左->右->根

// 先序遍歷
public void preTraverse(TreeNode root) {
    if (root != null) {
        System.out.println(root.val);
        preTraverse(root.left);
        preTraverse(root.right);
    }
}

// 中序遍歷
public void inTraverse(TreeNode root) {
    if (root != null) {
        inTraverse(root.left);
        System.out.println(root.val);
        inTraverse(root.right);
    }
}

// 後序遍歷
public void postTraverse(TreeNode root) {
    if (root != null) {
        postTraverse(root.left);
        postTraverse(root.right);
        System.out.println(root.val);
    }
}
複製程式碼

層次遍歷

// 層次遍歷(DFS)
public static List<List<Integer>> levelOrder(TreeNode root) {
    List<List<Integer>> res = new ArrayList<>();
    if (root == null) {
        return res;
    }
    
    dfs(root, res, 0);
    return res;
}

private void dfs(TreeNode root, List<List<Integer>> res, int level) {
    if (root == null) {
        return;
    }
    if (level == res.size()) {
        res.add(new ArrayList<>());
    }
    res.get(level).add(root.val);
    
    dfs(root.left, res, level + 1);
    dfs(root.right, res, level + 1);
}

// 層次遍歷(BFS)
public List<List<Integer>> levelOrder(TreeNode root) {
    List result = new ArrayList();

    if (root == null) {
        return result;
    }

    Queue<TreeNode> queue = new LinkedList<TreeNode>();
    queue.offer(root);

    while (!queue.isEmpty()) {
        ArrayList<Integer> level = new ArrayList<Integer>();
        int size = queue.size();
        for (int i = 0; i < size; i++) {
            TreeNode head = queue.poll();
            level.add(head.val);
            if (head.left != null) {
                queue.offer(head.left);
            }
            if (head.right != null) {
                queue.offer(head.right);
            }
        }
        result.add(level);
    }

    return result;
}

// "Z"字遍歷
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
    List<List<Integer>> result = new ArrayList<>();
    
    if (root == null){
        return result;
    }
    
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    boolean isFromLeft = false;
    while(!queue.isEmpty()){
        int size = queue.size();
        isFromLeft = !isFromLeft;
        List<Integer> list = new ArrayList<>();
        for(int i = 0; i < size; i++){
            TreeNode node;
            if (isFromLeft){
                node = queue.pollFirst();
            }else{
                node = queue.pollLast();
            }
            list.add(node.val);
            
            if (isFromLeft){
                if (node.left != null){
                    queue.offerLast(node.left);
                }
                if (node.right != null){
                    queue.offerLast(node.right);
                }
            }else{
                if (node.right != null){
                    queue.offerFirst(node.right);
                }
                if (node.left != null){
                    queue.offerFirst(node.left);
                }
            }
        }
        result.add(list);
    }
    
    return result;
}
複製程式碼

左右翻轉

public void invert(TreeNode root) {
    if (root == null) {
        return;
    }
    TreeNode temp = root.left;
    root.left = root.right;
    root.right = temp;
    
    invert(root.left);
    invert(root.right);
}
複製程式碼

最大值

public int getMax(TreeNode root) {
    if (root == null) {
        return Integer.MIN_VALUE;
    } else {
        int left = getMax(root.left);
        int right = getMax(root.right);
        return Math.max(Math.max(left, rigth), root.val);
    }
}
複製程式碼

最大深度

public int maxDepth(TreeNode root) {
    if (root == null) {
        return 0;
    }

    int left = maxDepth(root.left);
    int right = maxDepth(root.right);
    return Math.max(left, right) + 1;
}
複製程式碼

最小深度

public int minDepth(TreeNode root) {
    if (root == null) {
        return 0;
    }
    
    int left = minDepth(root.left);
    int right = minDepth(root.right);
    
    if (left == 0) {
        return right + 1;
    } else if (right == 0) {
        return left + 1;
    } else {
        return Math.min(left, right) + 1;
    }
}
複製程式碼

平衡二叉樹

平衡二叉樹每一個節點的左右兩個子樹的高度差不超過1

public boolean isBalanced(TreeNode root) {
    return maxDepth(root) != -1;
}

private int maxDepth(TreeNode root) {
    if (root == null) {
        return 0;
    }

    int left = maxDepth(root.left);
    int right = maxDepth(root.right);
    if (left == -1 || right == -1 || Math.abs(left - right) > 1) {
        return -1;
    }
    return Math.max(left, right) + 1;
}
複製程式碼

連結串列

public class ListNode {
     int val;
     ListNode next;
     ListNode(int x) {
         val = x;
         next = null;
    }
}
複製程式碼

刪除節點

public void deleteNode(ListNode node) {
    if (node.next == null){
        node = null;
        return;
    }
    // 取締下一節點
    node.val = node.next.val
    node.next = node.next.next
}
複製程式碼

翻轉連結串列

public ListNode reverse(ListNode head) {
    //prev表示前繼節點
    ListNode prev = null;
    while (head != null) {
        //temp記錄下一個節點,head是當前節點
        ListNode temp = head.next;
        head.next = prev;
        prev = head;
        head = temp;
    }
    return prev;
}
複製程式碼

中間元素

public ListNode findMiddle(ListNode head){
    if(head == null){
        return null;
    }
    
    ListNode slow = head;
    ListNode fast = head;
    
    // fast.next = null 表示 fast 是連結串列的尾節點
    while(fast != null && fast.next != null){
        fast = fast.next.next;
        slow = slow.next;
    }
    return slow;
}
複製程式碼

判斷是否為迴圈連結串列

public Boolean hasCycle(ListNode head) {
    if (head == null || head.next == null) {
        return false;
    }

    ListNode slow = head;
    ListNode fast = head.next;
    
    while (fast != slow) {
        if(fast == null || fast.next == null) {
            return false;
        }
        fast = fast.next.next;
        slow = slow.next;
    } 
    return true;
}
複製程式碼

合併兩個已排序連結串列

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    ListNode dummy = new ListNode(0);
    ListNode lastNode = dummy;
    
    while (l1 != null && l2 != null) {
        if (l1.val < l2.val) {
            lastNode.next = l1;
            l1 = l1.next;
        } else {
            lastNode.next = l2;
            l2 = l2.next;
        }
        lastNode = lastNode.next;
    }
    
    if (l1 != null) {
        lastNode.next = l1;
    } else {
        lastNode.next = l2;
    }
    
    return dummy.next;
}
複製程式碼

連結串列排序

可利用歸併、快排等演算法實現

// 歸併排序
public ListNode sortList(ListNode head) {
    if (head == null || head.next == null) {
        return head;
    }

    ListNode mid = findMiddle(head);

    ListNode right = sortList(mid.next);
    mid.next = null;
    ListNode left = sortList(head);

    return mergeTwoLists(left, right);
}

// 快速排序
public ListNode sortList(ListNode head) {
    quickSort(head, null);
    return head;
}

private void quickSort(ListNode start, ListNode end) {
    if (start == end) {
        return;
    }
    
    ListNode pt = partition(start, end);
    quickSort(start, pt);
    quickSort(pt.next, end);
}

private ListNode partition(ListNode start, ListNode end) {
    int pivotKey = start.val;
    ListNode p1 = start, p2 = start.next;
    while (p2 != end) {
        if (p2.val < pivotKey) {
            p1 = p1.next;
            swapValue(p1, p2);
        }
        p2 = p2.next;
    }
    
    swapValue(start, p1);
    return p1;
}

private void swapValue(ListNode node1, ListNode node2) {
    int tmp = node1.val;
    node1.val = node2.val;
    node2.val = tmp;
}
複製程式碼

刪除倒數第N個節點

public ListNode removeNthFromEnd(ListNode head, int n) {
    if (n <= 0) {
        return null;
    }
    
    ListNode dummy = new ListNode(0);
    dummy.next = head;
    
    ListNode preDelete = dummy;
    for (int i = 0; i < n; i++) {
        if (head == null) {
            return null;
        }
        head = head.next;
    }
    // 此時head為正數第N個節點
    while (head != null) {
        head = head.next;
        preDelete = preDelete.next;
    }
    preDelete.next = preDelete.next.next;
    return dummy.next;
}
複製程式碼

兩個連結串列是否相交

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    if (headA == null || headB == null) {
        return null;
    }

    ListNode currA = headA;
    ListNode currB = headB;
    int lengthA = 0;
    int lengthB = 0;

    // 讓長的先走到剩餘長度和短的一樣
    while (currA != null) {
        currA = currA.next;
        lengthA++;
    }
    while (currB != null) {
        currB = currB.next;
        lengthB++;
    }

    currA = headA;
    currB = headB;
    while (lengthA > lengthB) {
        currA = currA.next;
        lengthA--;
    }
    while (lengthB > lengthA) {
        currB = currB.next;
        lengthB--;
    }
    
    // 然後同時走到第一個相同的地方
    while (currA != currB) {
        currA = currA.next;
        currB = currB.next;
    }
    // 返回交叉開始的節點
    return currA;
}
複製程式碼

棧 / 佇列

帶最小值操作的棧

實現一個棧, 額外支援一個操作:min() 返回棧中元素的最小值

public class MinStack {
    private Stack<Integer> stack;
    private Stack<Integer> minStack; // 維護一個輔助棧,傳入當前棧的最小值
    
    public MinStack() {
        stack = new Stack<Integer>();
        minStack = new Stack<Integer>();
    }

    public void push(int number) {
        stack.push(number);
        if (minStack.isEmpty()) {
            minStack.push(number);
        } else {
            minStack.push(Math.min(number, minStack.peek()));
        }
    }

    public int pop() {
        minStack.pop();
        return stack.pop();
    }

    public int min() {
        return minStack.peek();
    }
}
複製程式碼

有效括號

給定一個字串所表示的括號序列,包含以下字元: '(', ')', '{', '}', '[' and ']', 判定是否是有效的括號序列。括號必須依照 "()" 順序表示, "()[]{}" 是有效的括號,但 "([)]" 則是無效的括號。

public boolean isValidParentheses(String s) {
    Stack<Character> stack = new Stack<Character>();
    for (Character c : s.toCharArray()) {
        if ("({[".contains(String.valueOf(c))) {
            stack.push(c);
        } else {
            if (!stack.isEmpty() && isValid(stack.peek(), c)) {
                stack.pop();
            } else {
                return false;
            }
        }
    }
    return stack.isEmpty();
}

private boolean isValid(char c1, char c2) {
    return (c1 == '(' && c2 == ')') || (c1 == '{' && c2 == '}')
        || (c1 == '[' && c2 == ']');
}
複製程式碼

用棧實現佇列

public class MyQueue {
    private Stack<Integer> outStack;
    private Stack<Integer> inStack;

    public MyQueue() {
       outStack = new Stack<Integer>();
       inStack = new Stack<Integer>();
    }
    
    private void in2OutStack(){
        while(!inStack.isEmpty()){
            outStack.push(inStack.pop());
        }
    }
    
    public void push(int element) {
        inStack.push(element);
    }

    public int pop() {
        if(outStack.isEmpty()){
            this.in2OutStack();
        }
        return outStack.pop();
    }

    public int top() {
        if(outStack.isEmpty()){
            this.in2OutStack();
        }
        return outStack.peek();
    }
}
複製程式碼

逆波蘭表示式求值

在反向波蘭表示法中計算算術表示式的值, ["2", "1", "+", "3", "*"] -> (2 + 1) * 3 -> 9

public int evalRPN(String[] tokens) {
    Stack<Integer> s = new Stack<Integer>();
    String operators = "+-*/";
    for (String token : tokens) {
        if (!operators.contains(token)) {
            s.push(Integer.valueOf(token));
            continue;
        }

        int a = s.pop();
        int b = s.pop();
        if (token.equals("+")) {
            s.push(b + a);
        } else if(token.equals("-")) {
            s.push(b - a);
        } else if(token.equals("*")) {
            s.push(b * a);
        } else {
            s.push(b / a);
        }
    }
    return s.pop();
}
複製程式碼

二分

二分搜尋

public int binarySearch(int[] arr, int start, int end, int hkey){
    if (start > end) {
        return -1;
    }

    int mid = start + (end - start) / 2;    //防止溢位
    if (arr[mid] > hkey) {
        return binarySearch(arr, start, mid - 1, hkey);
    }
    if (arr[mid] < hkey) {
        return binarySearch(arr, mid + 1, end, hkey);
    } 
    return mid;  
}
複製程式碼

X的平方根

public int sqrt(int x) {
    if (x < 0)  {
        throw new IllegalArgumentException();
    } else if (x <= 1) {
        return x;
    }

    int start = 1, end = x;
    // 直接對答案可能存在的區間進行二分 => 二分答案
    while (start + 1 < end) {
        int mid = start + (end - start) / 2;
        if (mid == x / mid) {
            return mid;
        }  else if (mid < x / mid) {
            start = mid;
        } else {
            end = mid;
        }  
    }
    if (end > x / end) {
        return start;
    }
    return end;
}
複製程式碼

雜湊表

兩數之和

給一個整數陣列,找到兩個數使得他們的和等於一個給定的數 target。需要實現的函式twoSum需要返回這兩個數的下標。

用一個hashmap來記錄,key記錄target-numbers[i]的值,value記錄numbers[i]的i的值,如果碰到一個 numbers[j]在hashmap中存在,那麼說明前面的某個numbers[i]和numbers[j]的和為target,i和j即為答案

public int[] twoSum(int[] numbers, int target) {

    HashMap<Integer,Integer> map = new HashMap<>();

    for (int i = 0; i < numbers.length; i++) {
        if (map.containsKey(numbers[i])) {
            return new int[]{map.get(numbers[i]), i};
        }
        map.put(target - numbers[i], i);
    }

    return new int[]{};
}
複製程式碼

連續陣列

給一個二進位制陣列,找到 0 和 1 數量相等的子陣列的最大長度

使用一個數字sum維護到i為止1的數量與0的數量的差值。在loop i的同時維護sum並將其插入hashmap中。對於某一個sum值,若hashmap中已有這個值,則當前的i與sum上一次出現的位置之間的序列0的數量與1的數量相同。

public int findMaxLength(int[] nums) {
    Map<Integer, Integer> prefix = new HashMap<>();
    int sum = 0;
    int max = 0;
    prefix.put(0, -1); // 當第一個0 1數量相等的情況出現時,陣列下標減去-1得到正確的長度
    for (int i = 0; i < nums.length; i++) {
        int num = nums[i];
        if (num == 0) {
            sum--;
        } else {
            sum++;
        }
        
        if (prefix.containsKey(sum)) {
            max = Math.max(max, i - prefix.get(sum));
        } else {
            prefix.put(sum, i);
        }
    }
    
    return max;
}
複製程式碼

最長無重複字元的子串

用HashMap記錄每一個字母出現的位置。設定一個左邊界, 到當前列舉到的位置之間的字串為不含重複字元的子串。若新碰到的字元的上一次的位置在左邊界右邊, 則需要向右移動左邊界

public int lengthOfLongestSubstring(String s) {
    if (s == null || s.length() == 0) {
        return 0;
    }
    HashMap<Character, Integer> map = new HashMap<>();
    int max = Integer.MIN_VALUE;
    int start = -1; // 計算無重複字元子串開始的位置
    int current = 0;
    for (int i = 0; i < s.length(); i++) {
        if (map.containsKey(s.charAt(i))) {
            int tmp = map.get(s.charAt(i));
            if (tmp >= start) { // 上一次的位置在左邊界右邊, 則需要向右移動左邊界
                start = tmp;
            }
        } 
        
        map.put(s.charAt(i), i);
        max = Math.max(max, i - start);
    }
    return max;
}
複製程式碼

最多點在一條直線上

給出二維平面上的n個點,求最多有多少點在同一條直線上

class Point {
    int x;
    int y;
    Point() { 
        x = 0; y = 0; 
    }
    Point(int a, int b) { 
        x = a; y = b; 
    }
}
複製程式碼

通過HashMap記錄下兩個點之間的斜率相同出現的次數,注意考慮點重合的情況

public int maxPoints(Point[] points) {
    if (points == null) {
        return 0;
    }
    
    int max = 0;
    for (int i = 0; i < points.length; i++) {
        Map<Double, Integer> map = new HashMap<>();
        int maxPoints = 0;
        int overlap = 0;
        for (int j = i + 1; j < points.length; j++) {
            if (points[i].x == points[j].x && points[i].y == points[j].y) {
                overlap++; // 兩個點重合的情況記錄下來
                continue;
            }
            double rate = (double)(points[i].y - points[j].y) / (points[i].x - points[j].x);
            if (map.containsKey(rate)) {
                map.put(rate, map.get(rate) + 1);
            } else {
                map.put(rate, 2);
            }
            maxPoints = Math.max(maxPoints, map.get(rate));
        }
        if (maxPoints == 0) maxPoints = 1;
        max = Math.max(max, maxPoints + overlap);
    }
    return max;
}
複製程式碼

堆 / 優先佇列

前K大的數

// 維護一個 PriorityQueue,以返回前K的數
public int[] topk(int[] nums, int k) {
    int[] result = new int[k];
    if (nums == null || nums.length < k) {
        return result;
    }
    
    Queue<Integer> pq = new PriorityQueue<>();
    for (int num : nums) {
        pq.add(num);
        if (pq.size() > k) {
            pq.poll();
        }
    }
    
    for (int i = k - 1; i >= 0; i--) {
        result[i] = pq.poll(); 
    }
    
    return result;
}
複製程式碼

前K大的數II

實現一個資料結構,提供下面兩個介面:1.add(number) 新增一個元素 2.topk() 返回前K大的數

public class Solution {
    private int maxSize;
    private Queue<Integer> minheap;
    public Solution(int k) {
        minheap = new PriorityQueue<>();
        maxSize = k;
    }

    public void add(int num) {
        if (minheap.size() < maxSize) {
            minheap.offer(num);
            return;
        }
        
        if (num > minheap.peek()) {
            minheap.poll();
            minheap.offer(num);
        }
    }

    public List<Integer> topk() {
        Iterator it = minheap.iterator();
        List<Integer> result = new ArrayList<Integer>();
        while (it.hasNext()) {
            result.add((Integer) it.next());
        }
        Collections.sort(result, Collections.reverseOrder());
        return result;
    }
}
複製程式碼

第K大的數

public int kthLargestElement(int k, int[] nums) {
    if (nums == null || nums.length == 0 || k < 1 || k > nums.length){
        return -1;
    }
    return partition(nums, 0, nums.length - 1, nums.length - k);
}

private int partition(int[] nums, int start, int end, int k) {
    if (start >= end) {
        return nums[k];
    }
    
    int left = start, right = end;
    int pivot = nums[(start + end) / 2];
    
    while (left <= right) {
        while (left <= right && nums[left] < pivot) {
            left++;
        }
        while (left <= right && nums[right] > pivot) {
            right--;
        }
        if (left <= right) {
            swap(nums, left, right);
            left++;
            right--;
        }
    }
    
    if (k <= right) {
        return partition(nums, start, right, k);
    }
    if (k >= left) {
        return partition(nums, left, end, k);
    }
    return nums[k];
}    

private void swap(int[] nums, int i, int j) {
    int tmp = nums[i];
    nums[i] = nums[j];
    nums[j] = tmp;
}
複製程式碼

二叉搜尋樹

驗證二叉搜尋樹

public boolean isValidBST(TreeNode root) {
    return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
}

private boolean isValidBST(TreeNode root, long min, long max){
    if (root == null) {
        return true;
    }
    
    if (root.val <= min || root.val >= max){
        return false;
    }
    
    return isValidBST(root.left, min, root.val) && isValidBST(root.right, root.val, max);
}
複製程式碼

第K小的元素

增加getCount方法來獲取傳入節點的子節點數(包括自己),從root節點開始判斷k值和子節點數的大小決定遞迴路徑是往左還是往右。

public int kthSmallest(TreeNode root, int k) {
    if (root == null) {
        return 0;
    }
    
    int leftCount = getCount(root.left);
    if (leftCount >= k) {
        return kthSmallest(root.left, k);
    } else if (leftCount + 1 == k) {
        return root.val;
    } else {
        return kthSmallest(root.right, k - leftCount - 1);
    }
}

private int getCount(TreeNode root) {
    if (root == null) {
        return 0;
    }
    
    return getCount(root.left) + getCount(root.right) + 1;
}
複製程式碼

陣列 / 雙指標

加一

給定一個非負數,表示一個數字陣列,在該數的基礎上+1,返回一個新的陣列。該數字按照數位高低進行排列,最高位的數在列表的最前面。

public int[] plusOne(int[] digits) {
    int carries = 1;
    for(int i = digits.length - 1; i >= 0 && carries > 0; i--){  
        int sum = digits[i] + carries;
        digits[i] = sum % 10;
        carries = sum / 10;
    }
    if(carries == 0) {
        return digits;
    }
        
    int[] rst = new int[digits.length + 1];
    rst[0] = 1;
    for(int i = 1; i < rst.length; i++){
        rst[i] = digits[i - 1];
    }
    return rst;
}
複製程式碼

刪除元素

給定一個陣列和一個值,在原地刪除與值相同的數字,返回新陣列的長度。

public int removeElement(int[] A, int elem) {
    if (A == null || A.length == 0) {
        return 0;
    }
    
    int index = 0;
    for (int i = 0; i < A.length; i++) {
        if (A[i] != elem) {
            A[index++] = A[i];
        } 
    }
    
    return index;
}
複製程式碼

刪除排序陣列中的重複數字

在原陣列中“刪除”重複出現的數字,使得每個元素只出現一次,並且返回“新”陣列的長度。

public int removeDuplicates(int[] A) {
    if (A == null || A.length == 0) {
        return 0;
    }
    
    int size = 0;
    for (int i = 0; i < A.length; i++) {
        if (A[i] != A[size]) {
            A[++size] = A[i];
        }
    }
    return size + 1;
}
複製程式碼

我的日程安排表 I

實現MyCalendar類來儲存活動。如果新新增的活動沒有重複,則可以新增。類將有方法book(int start,int end)。這代表左閉右開的間隔[start,end)有了預定,範圍內的實數x,都滿足start <= x < end,返回true。 否則,返回false,並且事件不會新增到日曆中。

TreeMap 是一個有序的key-value集合,它通過 紅黑樹 實現,繼承於AbstractMap,所以它是一個Map,即一個key-value集合。TreeMap可以查詢小於等於某個值的最大的key,也可查詢大於等於某個值的最小的key。 元素的順序可以改變,並且對新的陣列不會有影響。

class MyCalendar {
    TreeMap<Integer, Integer> calendar;

    MyCalendar() {
        calendar = new TreeMap();
    }

    public boolean book(int start, int end) {
        Integer previous = calendar.floorKey(start), next = calendar.ceilingKey(start);
        if ((previous == null || calendar.get(previous) <= start) && (next == null || end <= next)) {
            calendar.put(start, end);
            return true;
        }
        return false;
    }
}
複製程式碼

合併排序陣列

合併兩個排序的整數陣列A和B變成一個新的陣列。可以假設A具有足夠的空間去新增B中的元素。

public void mergeSortedArray(int[] A, int m, int[] B, int n) {
    int i = m - 1, j = n - 1, index = m + n - 1;
    while (i >= 0 && j >= 0) {
        if (A[i] > B[j]) {
            A[index--] = A[i--];
        } else {
            A[index--] = B[j--];
        }
    }
    while (i >= 0) {
        A[index--] = A[i--];
    }
    while (j >= 0) {
        A[index--] = B[j--];
    }
}
複製程式碼

貪心

買賣股票的最佳時機

假設有一個陣列,它的第i個元素是一支給定的股票在第i天的價格。如果你最多隻允許完成一次交易(例如,一次買賣股票),設計一個演算法來找出最大利潤。

public int maxProfit(int[] prices) {
    if (prices == null || prices.length == 0) {
        return 0;
    }

    int min = Integer.MAX_VALUE;  //記錄最低的價格
    int profit = 0;
    for (int price : prices) {
        min = Math.min(price, min);
        profit = Math.max(price - min, profit);
    }

    return profit;
}
複製程式碼

買賣股票的最佳時機 II

給定一個陣列 prices 表示一支股票每天的價格。可以完成任意次數的交易, 不過不能同時參與多個交易,設計一個演算法求出最大的利潤。

貪心:只要相鄰的兩天股票的價格是上升的, 我們就進行一次交易, 獲得一定利潤。

public int maxProfit(int[] prices) {
    int profit = 0;
    for (int i = 0; i < prices.length - 1; i++) {
        int diff = prices[i + 1] - prices[i];
        if (diff > 0) {
            profit += diff;
        }
    }
    return profit;
}
複製程式碼

最大子陣列

給定一個整數陣列,找到一個具有最大和的子陣列,返回其最大和。

public int maxSubArray(int[] A) {
    if (A == null || A.length == 0){
        return 0;
    }
    //max記錄全域性最大值,sum記錄區間和,如果當前sum>0,那麼可以繼續和後面的數求和,否則就從0開始
    int max = Integer.MIN_VALUE, sum = 0;
    for (int i = 0; i < A.length; i++) {
        sum += A[i];
        max = Math.max(max, sum);
        sum = Math.max(sum, 0);
    }

    return max;
}
複製程式碼

主元素

給定一個整型陣列,找出主元素,它在陣列中的出現次數嚴格大於陣列元素個數的二分之一(可以假設陣列非空,且陣列中總是存在主元素)。

public int majorityNumber(List<Integer> nums) {
    int currentMajor = 0;
    int count = 0;

    for(Integer num : nums) {
        if(count == 0) {
            currentMajor = num;
        }
        
        if(num == currentMajor) {
            count++;
        } else {
            count--;
        }
    }
    return currentMajor;
}
複製程式碼

本次的分享就到這啦,喜歡的話可以點個贊?或關注。如有錯誤的地方歡迎大家在評論裡指出。

Android複習資料——常見面試演算法題彙總(一)

本文為個人原創,轉載請註明出處。

相關文章