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

JasonWuuu發表於2019-07-20

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

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

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

另外,可檢視上一篇:

字串處理

生成括號

給定 n,表示有 n 對括號, 請寫一個函式以將其生成所有的括號組合,並返回組合結果。

public List<String> generateParenthesis(int n) {
    List<String> res = new ArrayList<>();
    helper(n, n, "", res);
    return res;
}

// DFS
private void helper(int nL, int nR, String parenthesis, List<String> res) {
    // nL 和 nR 分別代表左右括號剩餘的數量
    if (nL < 0 || nR < 0) {
        return;
    }
    
    if (nL == 0 && nR == 0) {
        res.add(parenthesis);
        return;
    }
    helper(nL - 1, nR, parenthesis + "(", res);
    if (nL >= nR) {
        return;
    }
    helper(nL, nR - 1, parenthesis + ")", res);
}
複製程式碼

Excel表列標題

給定一個正整數,返回相應的列標題,如Excel表中所示。如1 -> A,2 -> B...26 -> Z,27 -> AA

public String convertToTitle (int n) {
    StringBuilder str = new StringBuilder();

    while (n > 0) {
        n--;
        str.append ( (char) ( (n % 26) + 'A'));
        n /= 26;
    }
    return str.reverse().toString();
}
複製程式碼

翻轉游戲

翻轉游戲:給定一個只包含兩種字元的字串:+和-,你和你的小夥伴輪流翻轉"++"變成"--"。當一個人無法採取行動時遊戲結束,另一個人將是贏家。編寫一個函式,計算字串在一次有效移動後的所有可能狀態。

public List<String> generatePossibleNextMoves (String s) {
    List list = new ArrayList();
    for (int i = -1; (i = s.indexOf ("++", i + 1)) >= 0;) {
        list.add (s.substring (0, i) + "--" + s.substring (i + 2));
    }
    return list;
}
複製程式碼

翻轉字串中的單詞

給定一個字串,逐個翻轉字串中的每個單詞。

public String reverseWords(String s) {
    if(s.length() == 0 || s == null){
        return " ";
    }
    //按照空格將s切分
    String[] array = s.split(" ");
    StringBuilder sb = new StringBuilder();
    //從後往前遍歷array,在sb中插入單詞
    for(int i = array.length - 1; i >= 0; i--){
        if(!array[i].equals("")) {
            if (sb.length() > 0) {
                sb.append(" ");
            }
            
            sb.append(array[i]);
        }
    }
    return sb.toString();
}
複製程式碼

轉換字串到整數

實現atoi這個函式,將一個字串轉換為整數。如果沒有合法的整數,返回0。如果整數超出了32位整數的範圍,返回INT_MAX(2147483647)如果是正整數,或者INT_MIN(-2147483648)如果是負整數。

public int myAtoi(String str) {
    if(str == null) {
        return 0;
    }
    str = str.trim();
    if (str.length() == 0) {
        return 0;
    }
        
    int sign = 1;
    int index = 0;

    if (str.charAt(index) == '+') {
        index++;
    } else if (str.charAt(index) == '-') {
        sign = -1;
        index++;
    }
    long num = 0;
    for (; index < str.length(); index++) {
        if (str.charAt(index) < '0' || str.charAt(index) > '9') {
            break;
        }
        num = num * 10 + (str.charAt(index) - '0');
        if (num > Integer.MAX_VALUE ) {
            break;
        }
    }   
    if (num * sign >= Integer.MAX_VALUE) {
        return Integer.MAX_VALUE;
    }
    if (num * sign <= Integer.MIN_VALUE) {
        return Integer.MIN_VALUE;
    }
    return (int)num * sign;
}
複製程式碼

最長公共字首

public String longestCommonPrefix(String[] strs) {
    if (strs == null || strs.length == 0) {
        return "";
    }
    String prefix = strs[0];
    for(int i = 1; i < strs.length; i++) {
        int j = 0;
        while (j < strs[i].length() && j < prefix.length() && strs[i].charAt(j) == prefix.charAt(j)) {
            j++;
        }
        if( j == 0) {
            return "";
        }
        prefix = prefix.substring(0, j);
    }
    return prefix;
}
複製程式碼

迴文數

判斷一個正整數是不是迴文數。迴文數的定義是,將這個數反轉之後,得到的數仍然是同一個數。

public boolean palindromeNumber(int num) {
    // Write your code here
    if(num < 0){
        return false;
    }
    int div = 1;
    while(num / div >= 10){
        div *= 10;
    }
    while(num > 0){
        if(num / div != num % 10){
            return false;
        }
        num = (num % div) / 10;
        div /= 100;
    }
    return true;
}
複製程式碼

動態規劃

單詞拆分

給定字串 s 和單詞字典 dict,確定 s 是否可以分成一個或多個以空格分隔的子串,並且這些子串都在字典中存在。

public boolean wordBreak(String s, Set<String> dict) {
    // write your code here
    int maxLength = getMaxLength(dict);
    
    // 長度為n的單詞 有n + 1個切割點 比如: _l_i_n_t_
    boolean[] canBreak = new boolean[s.length() + 1];
    // 當s長度為0時
    canBreak[0] = true;
    
    for(int i = 1; i < canBreak.length; i++){
        for(int j = 1; j <= maxLength && j <= i; j++){
            //i - j 表示從 i 點開始往前j個點的位置
            String str = s.substring(i - j,i);
            //如果此str在詞典中 並且 str之前的 字串可以拆分     
            if(dict.contains(str) && canBreak[i - j]){
                canBreak[i] = true;
                break;
            }
        }
    }
    
    return canBreak[canBreak.length - 1];
}

private int getMaxLength(Set<String> dict){
    int max = 0;
    for(String s : dict){
        max = Math.max(max,s.length());
    }
    return max;
}
複製程式碼

爬樓梯

假設你正在爬樓梯,需要n步你才能到達頂部。但每次你只能爬一步或者兩步,你能有多少種不同的方法爬到樓頂部?

public int climbStairs(int n) {
    if (n == 0) return 0;
    int[] array = new int[n + 1];
    array[0] = 1;
    if (array.length > 1) {
        array[1] = 1;
    }
    
    for(int i = 2; i < array.length; i++) {
        array[i] = array[i - 1] + array[i - 2];
    }
    return array[n];
}
複製程式碼

打劫房屋

假設你是一個專業的竊賊,準備沿著一條街打劫房屋。每個房子都存放著特定金額的錢。你面臨的唯一約束條件是:相鄰的房子裝著相互聯絡的防盜系統,且 當相鄰的兩個房子同一天被打劫時,該系統會自動報警。給定一個非負整數列表,表示每個房子中存放的錢, 算一算,如果今晚去打劫,在不觸動報警裝置的情況下, 你最多可以得到多少錢 。

public long houseRobber(int[] A) {
    if (A.length == 0) return 0;
    long[] res = new long[A.length + 1];
    res[0] = 0;
    res[1] = A[0];
    for (int i = 2; i < res.length; i++) {
        res[i] = Math.max(res[i - 2] + A[i - 1], res[i - 1]);
    }
    return res[A.length];
}
複製程式碼

編輯距離

給出兩個單詞word1和word2,計算出將word1 轉換為word2的最少操作次數。你總共三種操作方法:插入一個字元、刪除一個字元、替換一個字元。

public int minDistance(String word1, String word2) {
    // write your code here
    int n = word1.length();
    int m = word2.length();
    int[][] dp = new int[n + 1][m + 1];
    for (int i = 0; i < n + 1; i++){
        dp[i][0] = i;
    }
    for (int j = 0; j < m + 1; j++){
        dp[0][j] = j;
    }
    for (int i = 1; i< n + 1; i++){
        for (int j = 1; j < m + 1; j++){
            if (word1.charAt(i - 1) == word2.charAt(j - 1)){
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                dp[i][j] = 1 + Math.min(dp[i - 1][j - 1], Math.min(dp[i][j - 1], dp[i - 1][j]));
            }
        }
    }
    return  dp[n][m];
}
複製程式碼

乘積最大子序列

public int maxProduct(List<Integer> nums) {
    // 分別記錄正數最大值和負數最小值
    int[] max = new int[nums.size()];
    int[] min = new int[nums.size()];
    
    min[0] = max[0] = nums.get(0);
    int result = nums.get(0);
    for (int i = 1; i < nums.size(); i++) {
        min[i] = max[i] = nums.get(i);
        if (nums.get(i) > 0) {
            max[i] = Math.max(max[i], max[i - 1] * nums.get(i));
            min[i] = Math.min(min[i], min[i - 1] * nums.get(i));
        } else if (nums.get(i) < 0) {
            max[i] = Math.max(max[i], min[i - 1] * nums.get(i));
            min[i] = Math.min(min[i], max[i - 1] * nums.get(i));
        }
        
        result = Math.max(result, max[i]);
    }
    
    return result;
}
複製程式碼

矩陣

螺旋矩陣

給定一個包含 m x n 個要素的矩陣,(m 行, n 列),按照螺旋順序,返回該矩陣中的所有要素。

public List<Integer> spiralOrder(int[][] matrix) {
    ArrayList<Integer> rst = new ArrayList<Integer>();
    if(matrix == null || matrix.length == 0) {
        return rst;
    }
    
    int rows = matrix.length;
    int cols = matrix[0].length;
    int count = 0;
    while(count * 2 < rows && count * 2 < cols){
        for (int i = count; i < cols - count; i++) {
            rst.add(matrix[count][i]);
        }
        
        for (int i = count + 1; i < rows - count; i++) {
            rst.add(matrix[i][cols - count - 1]);
        }
        
        if (rows - 2 * count == 1 || cols - 2 * count == 1) { // 如果只剩1行或1列
            break;
        }
            
        for (int i = cols - count - 2; i >= count; i--) {
            rst.add(matrix[rows - count - 1][i]);
        }
            
        for (int i = rows - count - 2; i >= count + 1; i--) {
            rst.add(matrix[i][count]);
        }
        
        count++;
    }
    return rst;
}
複製程式碼

判斷數獨是否合法

請判定一個數獨是否有效。該數獨可能只填充了部分數字,其中缺少的數字用 . 表示。

維護一個HashSet用來記同一行、同一列、同一九宮格是否存在相同數字

public boolean isValidSudoku(char[][] board) {
    Set seen = new HashSet();
    for (int i=0; i<9; ++i) {
        for (int j=0; j<9; ++j) {
            char number = board[i][j];
            if (number != '.')
                if (!seen.add(number + " in row " + i) ||
                    !seen.add(number + " in column " + j) ||
                    !seen.add(number + " in block " + i / 3 + "-" + j / 3))
                    return false;
        }
    }
    return true;
}
複製程式碼

旋轉影像

給定一個N×N的二維矩陣表示影像,90度順時針旋轉影像。

public void rotate(int[][] matrix) {
    if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
        return;
    }

    int length = matrix.length;

    for (int i = 0; i < length / 2; i++) {
        for (int j = 0; j < (length + 1) / 2; j++){
            int tmp = matrix[i][j];
            matrix[i][j] = matrix[length - j - 1][i];
            matrix[length -j - 1][i] = matrix[length - i - 1][length - j - 1];
            matrix[length - i - 1][length - j - 1] = matrix[j][length - i - 1];
            matrix[j][length - i - 1] = tmp;
        }
    }   
}
複製程式碼

二進位制 / 位運算

落單的數

給出 2 * n + 1個數字,除其中一個數字之外其他每個數字均出現兩次,找到這個數字。

異或運算具有很好的性質,相同數字異或運算後為0,並且具有交換律和結合律,故將所有數字異或運算後即可得到只出現一次的數字。

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

格雷編碼

格雷編碼是一個二進位制數字系統,在該系統中,兩個連續的數值僅有一個二進位制的差異。給定一個非負整數 n ,表示該程式碼中所有二進位制的總數,請找出其格雷編碼順序。一個格雷編碼順序必須以 0 開始,並覆蓋所有的 2n 個整數。例子——輸入:2;輸出:[0, 1, 3, 2];解釋: 0 - 00,1 - 01,3 - 11,2 - 10

格雷碼生成公式:G(i) = i ^ (i >> 2)

public ArrayList<Integer> grayCode(int n) {
    ArrayList<Integer> result = new ArrayList<Integer>();
    for (int i = 0; i < (1 << n); i++) {
        result.add(i ^ (i >> 1));
    }
    return result;
}
複製程式碼

其他

反轉整數

將一個整數中的數字進行顛倒,當顛倒後的整數溢位時,返回 0 (標記為 32 位整數)。

public int reverseInteger(int n) {
    int reversed_n = 0;
    
    while (n != 0) {
        int temp = reversed_n * 10 + n % 10;
        n = n / 10;
        if (temp / 10 != reversed_n) {
            reversed_n = 0;
            break;
        }
        reversed_n = temp;
    }
    return reversed_n;
}
複製程式碼

LRU快取策略

為最近最少使用(LRU)快取策略設計一個資料結構,它應該支援以下操作:獲取資料(get)和寫入資料(set)。獲取資料get(key):如果快取中存在key,則獲取其資料值(通常是正數),否則返回-1。 寫入資料set(key, value):如果key還沒有在快取中,則寫入其資料值。當快取達到上限,它應該在寫入新資料之前刪除最近最少使用的資料用來騰出空閒位置。

public class LRUCache {
    private class Node{
        Node prev;
        Node next;
        int key;
        int value;

        public Node(int key, int value) {
            this.key = key;
            this.value = value;
            this.prev = null;
            this.next = null;
        }
    }

    private int capacity;
    private HashMap<Integer, Node> hs = new HashMap<Integer, Node>();
    private Node head = new Node(-1, -1);
    private Node tail = new Node(-1, -1);

    public LRUCache(int capacity) {
        this.capacity = capacity;
        tail.prev = head;
        head.next = tail;
    }

    public int get(int key) {
        if( !hs.containsKey(key)) {    		//key找不到
            return -1;
        }

        // remove current
        Node current = hs.get(key);
        current.prev.next = current.next;
        current.next.prev = current.prev;

        // move current to tail
        move_to_tail(current);			//每次get,使用次數+1,最近使用,放於尾部

        return hs.get(key).value;
    }

    public void set(int key, int value) {			//資料放入快取
        // get 這個方法會把key挪到最末端,因此,不需要再呼叫 move_to_tail
        if (get(key) != -1) {
            hs.get(key).value = value;
            return;
        }

        if (hs.size() == capacity) {		//超出快取上限
            hs.remove(head.next.key);		//刪除頭部資料
            head.next = head.next.next;
            head.next.prev = head;
        }

        Node insert = new Node(key, value);		//新建節點
        hs.put(key, insert);
        move_to_tail(insert);					//放於尾部
    }

    private void move_to_tail(Node current) {    //移動資料至尾部
        current.prev = tail.prev;
        tail.prev = current;
        current.prev.next = current;
        current.next = tail;
    }
}
複製程式碼

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

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

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

相關文章