Android面經| 算法題解

整理了校招面試算法題,部分《劍指offer》算法題,以及LeetCode算法題,本博文中算法題均使用Java實現

題目描述:

瞭解哪些排序算法,依次描述並說下時間、空間複雜度


技術點:

排序

參考

十大經典排序算法最強總結(含JAVA代碼實現)

思路:

名稱 描述 時間複雜度 空間複雜度
冒泡排序 重複走訪要排序的數列,一次比較兩個元素,若較小元素在後則交換,能看到越小的元素會經由交換慢慢浮到數列的頂端 O(n2) O(1)
簡單選擇 每次都在未排序序列中找最小元素 O(n2) O(1)
直接插入 對於未排序數據,在已排序序列中從後向前掃描,找到相應位置並插入 最好O(n),平均O(n2) O(1)
希爾排序 將整個待排序的記錄序列分割成爲若干子序列分別進行直接插入排序 O(nlogn)~ O(n2) O(1)
歸併排序 先使每個子序列有序,再使子序列段間有序 O(nlogn) O(n)
堆排序 近似完全二叉樹的結構,子結點的鍵值或索引總是小於(或大於)其父節點 O(nlogn) O(1)
快速排序 取一個記錄作爲樞軸,經過一趟排序將整段序列分爲兩個部分,使得數軸左側都小於樞軸、右側都大於樞軸;再對這兩部分繼續進行排序使整個序列達到有序 最壞 O(n2),平均O(nlogn) O(logn)~O(n)

題目描述:

在海量數據中找出出現頻率最高的前k個數,從海量數據中找出最大的前k個數


技術點:

鏈表 數學

思路:

  • 方法一:分治+Trie樹/hash|+小根堆
  1. 先將數據按照hash方法分解爲多個小數據集
  2. 使用Trie樹或者hash統計每個數據集中的詞頻
  3. 使用小根堆求每個數據中出現頻率最高的k個數
  4. 在所有top k中最終的top k
  • 方法二:MapReduce解決
    一個map 兩個reduce
  1. map切分數據
  2. reduce1統計詞頻
  3. reduce2求top k

題目描述:

給出兩個 非空 的鏈表用來表示兩個非負的整數。其中,它們各自的位數是按照 逆序 的方式存儲的,並且它們的每個節點只能存儲 一位 數字。
如果,我們將這兩個數相加起來,則會返回一個新的鏈表來表示它們的和。


技術點:

鏈表 數學

思路:

  • 將兩個鏈表看成是相同長度的進行遍歷,如果一個鏈表較短則在前面補 0
  • 每一位計算的同時需要考慮上一位的進位問題,而當前位計算結束後同樣需要更新進位值
  • 如果兩個鏈表全部遍歷完畢後,進位值爲 1,則在新鏈表最前方添加節點 1

參考代碼:

    int temp = 0;
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode ln = new ListNode(calcu(l1.val,l2.val));
        ListNode lnL = ln;
        while(l1.next != null || l2.next != null || temp != 0) {
            int v1,v2;
            if (l1.next != null) {
                l1 = l1.next;
                v1 = l1.val;
            } else {
                v1 = 0;
            }
            
            if (l2.next != null) {
                l2 = l2.next;
                v2 = l2.val;
            } else {
                v2 = 0;
            }
            
            ListNode newLn = new ListNode(calcu(v1,v2));
            lnL.next = newLn;
            lnL = lnL.next;
        }
        return ln;
    }
    
    public int calcu(int a, int b) {
        if(a+b+temp>=10){
            if(temp==0){
                temp = 1;
                return a+b-10;
            }else{
                temp = 1;
                return a+b-9;
            }
        }else {
            if(temp==1){
                temp = 0;
                return a+b+1;
            }else{
                temp = 0;
                return a+b;
            }
        }
    }

題目描述:

給定 n 個非負整數 a1,a2,…,an,每個數代表座標中的一個點 (i, ai) 。在座標內畫 n 條垂直線,垂直線 i 的兩個端點分別爲 (i, ai) 和 (i, 0)。找出其中的兩條線,使得它們與 x 軸共同構成的容器可以容納最多的水。
盛最多水的容器
圖中垂直線代表輸入數組 [1,8,6,2,5,4,8,3,7]。在此情況下,容器能夠容納水(表示爲藍色部分)的最大值爲 49。


技術點:

鏈表 雙指針

思路:

爲了使面積最大化,我們需要考慮更長的兩條線段之間的區域。如果我們試圖將指向較長線段的指針向內側移動,矩形區域的面積將受限於較短的線段而不會獲得任何增加。但是,在同樣的條件下,移動指向較短線段的指針儘管造成了矩形寬度的減小,但卻可能會有助於面積的增大。因爲移動較短線段的指針會得到一條相對較長的線段,這可以克服由寬度減小而引起的面積減小。

參考代碼:

public int maxArea(int[] height) {
    int max = 0;
    for(int i=0,j=height.length-1;i<j;) {
        int minHeight = height[i] < height[j] ? height[i++] : height[j--];
         max = Math.max(max, (j-i+1)*minHeight);
    }
    return max;
}
    
public int calcu(int x1,int y1,int x2,int y2) {
        return Math.abs(x1-x2) * Math.abs(y1-y2);
}

題目描述:

給定一個包含 n 個整數的數組 nums,判斷 nums中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?找出所有滿足條件且不重複的三元組。


技術點:

數組 雙指針

思路:

固定一個值,找另外二個值它們和等於 0,
使用雙指針找另外兩個值。

參考代碼:

public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> ls = new ArrayList<>();
 
        for (int i = 0; i < nums.length - 2; i++) {
            if (i == 0 || (i > 0 && nums[i] != nums[i - 1])) {  // 跳過可能重複的答案
 
                int l = i + 1, r = nums.length - 1, sum = 0 - nums[i];
                while (l < r) {
                    if (nums[l] + nums[r] == sum) {
                        ls.add(Arrays.asList(nums[i], nums[l], nums[r]));
                        while (l < r && nums[l] == nums[l + 1]) l++;
                        while (l < r && nums[r] == nums[r - 1]) r--;
                        l++;
                        r--;
                    } else if (nums[l] + nums[r] < sum) {
                        while (l < r && nums[l] == nums[l + 1]) l++;   // 跳過重複值
                        l++;
                    } else {
                        while (l < r && nums[r] == nums[r - 1]) r--;
                        r--;
                    }
                }
            }
        }
        return ls;
    }

題目描述:

假設按照升序排序的數組在預先未知的某個點上進行了旋轉。
( 例如,數組 [0,1,2,4,5,6,7] 可能變爲 [4,5,6,7,0,1,2] )。
搜索一個給定的目標值,如果數組中存在這個目標值,則返回它的索引,否則返回 -1 。


技術點:

數組 二分查找

思路:

  • 找到旋轉的下標 rotation_index ,也就是數組中最小的元素。二分查找在這裏可以派上用場。
  • 在選中的數組區域中再次使用二分查找。

參考代碼:

  int [] nums;
  int target;

  public int find_rotate_index(int left, int right) {
    if (nums[left] < nums[right])
      return 0;

    while (left <= right) {
      int pivot = (left + right) / 2;
      if (nums[pivot] > nums[pivot + 1])
        return pivot + 1;
      else {
        if (nums[pivot] < nums[left])
          right = pivot - 1;
        else
          left = pivot + 1;
      }
    }
    return 0;
  }

  public int search(int left, int right) {
    /*
    Binary search
    */
    while (left <= right) {
      int pivot = (left + right) / 2;
      if (nums[pivot] == target)
        return pivot;
      else {
        if (target < nums[pivot])
          right = pivot - 1;
        else
          left = pivot + 1;
      }
    }
    return -1;
  }

  public int search(int[] nums, int target) {
    this.nums = nums;
    this.target = target;

    int n = nums.length;

    if (n == 0)
      return -1;
    if (n == 1)
      return this.nums[0] == target ? 0 : -1;

    int rotate_index = find_rotate_index(0, n - 1);

    // if target is the smallest element
    if (nums[rotate_index] == target)
      return rotate_index;
    // if array is not rotated, search in the entire array
    if (rotate_index == 0)
      return search(0, n - 1);
    if (target < nums[0])
      // search in the right side
      return search(rotate_index, n - 1);
    // search in the left side
    return search(0, rotate_index);
  }

題目描述:

給定兩個以字符串形式表示的非負整數 num1num2,返回 num1num2 的乘積,它們的乘積也表示爲字符串形式。


技術點:

字符串 數學

思路:

模擬豎式乘法計算過程

參考代碼:

    public String multiply(String num1, String num2) {
        int l = num1.length();
        int r = num2.length();
        int[] result = new int[l + r];
        for (int i = 0; i < l; i++) {
            int n1 = num1.charAt(l - i - 1) - '0';
            int temp = 0;
            for (int t = 0; t < r; t++) {
                int n2 = num2.charAt(r - t - 1) - '0';
                temp = temp + result[i + t] + n1 * n2;
                result[i + t] = temp % 10;
                temp /= 10;
            }
            result[i + r] = temp;
        }

        int len = 0;
        for (int i = l + r - 1; i >= 0; i--)
            if (result[i] > 0) {
                len = i;
                break;
            }
        StringBuilder builder = new StringBuilder();
            
        for (int i = len; i >= 0; i--) {
            builder.append(result[i]);
        }
        return builder.toString();
    }

題目描述:

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


技術點:

數組

思路:

  • 方法一:模擬
    假設數組有 RC 列,seen[r][c]表示第 r 行第 c 列的單元格之前已經被訪問過了。當前所在位置爲 (r, c),前進方向是 di。我們希望訪問所有 R x C 個單元格。
    當我們遍歷整個矩陣,下一步候選移動位置是 (cr, cc)。如果這個候選位置在矩陣範圍內並且沒有被訪問過,那麼它將會變成下一步移動的位置;否則,我們將前進方向順時針旋轉之後再計算下一步的移動位置。
  • 方法二:按層模擬
    我們定義矩陣的第 k 層是到最近邊界距離爲 k 的所有頂點。
    對於每層,我們從左上方開始以順時針的順序遍歷所有元素,假設當前層左上角座標是 (r1, c1),右下角座標是 (r2, c2)
    首先,遍歷上方的所有元素 (r1, c),按照 c = c1,...,c2 的順序。然後遍歷右側的所有元素 (r, c2),按照 r = r1+1,...,r2 的順序。如果這一層有四條邊(也就是 r1 < r2 並且 c1 < c2

參考代碼:

	public List<Integer> spiralOrder(int[][] matrix) {
        if (matrix == null)
            return new ArrayList<>();
        int m = matrix.length;
        if (m == 0)
            return new ArrayList<>();
        int n = matrix[0].length;
        if (n == 0)
            return new ArrayList<>();
        List<Integer> result = new ArrayList<>();
        if (m >= 2 && n >= 2) {
            int round = (Math.min(m,n) - 2)/2;
            for (int i = 0; i <= round; i++) {
                addFloor(i, result, matrix);
            }
            if (result.size() < m*n)
            if (m >= n) {
                for (int i = round + 1;i < m - round - 1;i++) {
                    result.add(matrix[i][round+1]);
                }
            } else {
                for (int i = round + 1;i < n - round - 1;i++) {
                    result.add(matrix[round+1][i]);
                }
            }
        }
    else if(Math.max(m,n) >= 2) {
        if (m >= n) {
                for (int i = 0;i < m;i++) {
                    result.add(matrix[i][0]);
                }
            } else {
                for (int i = 0;i < n;i++) {
                    result.add(matrix[0][i]);
                }
            }
    }
    if (m == 1 && n == 1) {
            result.add(matrix[0][0]);
        }
        return result;
    }

    private void addFloor(int floor, List<Integer> result, int[][] matrix) {
        for (int x = floor; x < matrix[floor].length - floor; x++) {
            result.add(matrix[floor][x]);
        }
        for (int y = floor + 1; y < matrix.length - floor; y++) {
            result.add(matrix[y][matrix[0].length - floor-1]);
        }
        for (int x = matrix[floor].length - floor-2; x >= floor; x--) {
            result.add(matrix[matrix.length - floor-1][x]);
        }
        for (int y = matrix.length - floor-2;y > floor;y--) {
            result.add(matrix[y][floor]);
        }
    }

題目描述:

給定一個鏈表,旋轉鏈表,將鏈表每個節點向右移動 k 個位置,其中 k 是非負數。


技術點:

鏈表 雙指針

思路:

右移k個位置,本質就是將前 len - k % len 個節點放到最後去 分爲幾個核心步驟:

  1. 求出鏈表的長度,以及最後的一個節點
  2. 截取前 len - k % len 個節點
  3. 將兩段鏈表進行拼接

參考代碼:

    public ListNode rotateRight(ListNode head, int k) {
        int len = 0;
        ListNode index = head,last = null,temphead = head;
        while(index != null) {
            if (index.next == null)
                last = index;
            index = index.next;
            ++len;
        }
        if(len == 0)
            return head;
        k %= len;
        if (k != 0) {
            k = len - k;
            index = head;
            for (int i = 0;i < k-1;i++) {
                index = index.next;
            }
            head = index.next;
            index.next = null;
            last.next = temphead;
        }
        return head;
    }

題目描述:

一個機器人位於一個 m x n 網格的左上角 ,每次只能向下或者向右移動一步,機器人試圖達到網格的右下角,問總共有多少條不同的路徑?


技術點:

數組 動態規劃

思路:

我們令 dp[i][j] 是到達 i , j 最少步數
動態方程: dp[i][j] = dp[i-1][j] + dp[i][j-1]
注意,對於第一行 dp[0][j] ,或者第一列 dp[i][0] ,由於都是在邊界,所以只能爲1

參考代碼:

    public int uniquePaths(int m, int n) {
        int[][] dp = new int[m][n];        
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (i == 0 || j == 0)
                    dp[i][j] = 1;
                else {
                    dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
                }
            }
        }
        return dp[m - 1][n - 1];        
    }

題目描述:

給定一個非空字符串 s 和一個包含非空單詞列表的字典 wordDict,判定 s 是否可以被空格拆分爲一個或多個在字典中出現的單詞。


技術點:

動態規劃

思路:

方法 1:暴力
最簡單的實現方法是用遞歸和回溯。爲了找到解,我們可以檢查字典單詞中每一個單詞的可能前綴,如果在字典中出現過,那麼去掉這個前綴後剩餘部分迴歸調用。同時,如果某次函數調用中發現整個字符串都已經被拆分且在字典中出現過了,函數就返回 true 。
時間複雜度:O(n^n) | 空間複雜度:O(n)
方法 2:記憶化回溯
我們可以使用記憶化的方法,其中一個 memo 數組會被用來保存子問題的結果。每當訪問到已經訪問過的後綴串,直接用 memo 數組中的值返回而不需要繼續調用函數。
通過記憶化,許多冗餘的子問題可以極大被優化,回溯樹得到了剪枝,因此極大減小了時間複雜度。
時間複雜度:O(n^2) | 空間複雜度:O(n)
方法 3:BFS
將字符串可視化成一棵樹,每一個節點是用 endend 爲結尾的前綴字符串。當兩個節點之間的所有節點都對應了字典中一個有效字符串時,兩個節點可以被連接。
時間複雜度:O(n^2) | 空間複雜度:O(n)
方法 4:動態規劃
這個方法的想法是對於給定的字符串(ss)可以被拆分成子問題 s1 和 s2 。如果這些子問題都可以獨立地被拆分成符合要求的子問題,那麼整個問題 ss 也可以滿足。也就是,如果 “catsanddog” 可以拆分成兩個子字符串 “catsand” 和 “dog” 。子問題 “catsand” 可以進一步拆分成 “cats” 和 “and” ,這兩個獨立的部分都是字典的一部分,所以 “catsand” 滿足題意條件,再往前, “catsand” 和 “dog” 也分別滿足條件,所以整個字符串 “catsanddog” 也滿足條件。

參考代碼:

    public boolean wordBreak(String s, List<String> wordDict) {
        if (wordDict == null || wordDict.size() == 0 || s == null || s.length() == 0)
            return false;
        int[] flag = new int[s.length()+1];
        for (int i=0;i < flag.length;i++)
            flag[i]=-1;
        flag[0] = 0;
        for (int i = 1;i <= s.length();i++) {
            for (int j = 0;j < i;j++) {
                if (flag[j]!=-1 && wordDict.contains(s.substring(j,i))) {
                    flag[i] = j;
                    break;
                }
            }
        }
        return flag[s.length()] != -1;
    }

題目描述:

給定一個鏈表,返回鏈表開始入環的第一個節點。 如果鏈表無環,則返回 null
爲了表示給定鏈表中的環,我們使用整數 pos 來表示鏈表尾連接到鏈表中的位置(索引從 0 開始)。 如果 pos 是 -1,則在該鏈表中沒有環。


技術點:

鏈表 雙指針

思路:

  • 方法一:哈希表
    如果我們用一個 Set 保存已經訪問過的節點,我們可以遍歷整個列表並返回第一個出現重複的節點。
  • 方法二:快慢指針+數學
    設快慢指針第一次相遇時,fast 走了 f 步,slow 走了 s 步,設環的長度爲 c ,從表頭到入環點需走 t 步,那麼:當fast 指針追上 slow 指針時, fastslow 多走了 n 個環的長度,即 f = s + n * c;同時,因爲 fast 一次走2步,slow 一次走1步,因此有 f = 2s;由此可得:s = n * cf = 2 * n * c。將 slow 退到起始點,fast 不動,然後每人一次各走一步:當 slow 走到入環點時,它走了t( = 0 * c + t ) 步;而此時 fast 走了 2 * n * c + t 步,也到達了入環點,說明他們正好相遇在了入環點。

參考代碼:

//方法一:
    public ListNode detectCycle(ListNode head) {
        Set<ListNode> visited = new HashSet<ListNode>();

        ListNode node = head;
        while (node != null) {
            if (visited.contains(node)) {
                return node;
            }
            visited.add(node);
            node = node.next;
        }

        return null;
    }
    
//方法二:
	public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        boolean hasCycle = false;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                hasCycle = true;
                break;
            }
        }
        if (!hasCycle) {
            return null;
        }
        slow = head;
        while (fast != slow) {
            fast = fast.next;
            slow = slow.next;
        }
        return fast;
    }

題目描述:

在 O(n log n) 時間複雜度和常數級空間複雜度下,對鏈表進行排序。


技術點:

鏈表 排序

思路:

參考:Sort List——經典(鏈表中的歸併排序)
https://www.cnblogs.com/qiaozhoulin/p/4585401.html
歸併排序法:在動手之前一直覺得空間複雜度爲常量不太可能,因爲原來使用歸併時,都是 O(N)的,需要複製出相等的空間來進行賦值歸併。對於鏈表,實際上是可以實現常數空間佔用的(鏈表的歸併排序不需要額外的空間)。利用歸併的思想,遞歸地將當前鏈表分爲兩段,然後merge,分兩段的方法是使用 fast-slow 法,用兩個指針,一個每次走兩步,一個走一步,知道快的走到了末尾,然後慢的所在位置就是中間位置,這樣就分成了兩段。merge時,把兩段頭部節點值比較,用一個 p 指向較小的,且記錄第一個節點,然後 兩段的頭一步一步向後走,p也一直向後走,總是指向較小節點,直至其中一個頭爲NULL,處理剩下的元素。最後返回記錄的頭即可。
主要考察3個知識點
知識點1:歸併排序的整體思想
知識點2:找到一個鏈表的中間節點的方法
知識點3:合併兩個已排好序的鏈表爲一個新的有序鏈表

參考代碼:

    public ListNode sortList(ListNode head) {
        return head == null ? null : mergeSort(head);
    }

    private ListNode mergeSort(ListNode head) {
        if (head.next == null) {
            return head;
        }
        ListNode p = head, q = head, pre = null;
        while (q != null && q.next != null) {
            pre = p;
            p = p.next;
            q = q.next.next;
        }
        pre.next = null;
        ListNode l = mergeSort(head);
        ListNode r = mergeSort(p);
        return merge(l, r);
    }

    ListNode merge(ListNode l, ListNode r) {
        ListNode dummyHead = new ListNode(0);
        ListNode cur = dummyHead;
        while (l != null && r != null) {
            if (l.val <= r.val) {
                cur.next = l;
                cur = cur.next;
                l = l.next;
            } else {
                cur.next = r;
                cur = cur.next;
                r = r.next;
            }
        }
        if (l != null) {
            cur.next = l;
        }
        if (r != null) {
            cur.next = r;
        }
        return dummyHead.next;
    }

題目描述:

給定一個整數數組 nums ,找出一個序列中乘積最大的連續子序列(該序列至少包含一個數)。


技術點:

鏈表 數學 動態規劃

思路:

遍歷數組時計算當前最大值,不斷更新
令imax爲當前最大值,則當前最大值爲 imax = max(imax * nums[i], nums[i])
由於存在負數,那麼會導致最大的變最小的,最小的變最大的。因此還需要維護當前最小值iminimin = min(imin * nums[i], nums[i])
當負數出現時則imax與imin進行交換再進行下一步計算

參考代碼:

public int maxProduct(int[] nums) {
        int max = Integer.MIN_VALUE, imax = 1, imin = 1; //一個保存最大的,一個保存最小的。
        for(int i=0; i<nums.length; i++){
            if(nums[i] < 0){ int tmp = imax; imax = imin; imin = tmp;} //如果數組的數是負數,那麼會導致最大的變最小的,最小的變最大的。因此交換兩個的值。
            imax = Math.max(imax*nums[i], nums[i]);
            imin = Math.min(imin*nums[i], nums[i]);
            
            max = Math.max(max, imax);
        }
        return max;
    }

題目描述:

實現一個 Trie (前綴樹),包含 insert, search, 和 startsWith 這三個操作。


技術點:

設計 字典樹

思路:

據 字節跳動 的同學說面試面到過這道題
關鍵點在於每棵樹最多隻有26棵子樹,並且對每棵樹標記是否爲終點。最後遍歷該前綴樹即可得出結果。

參考代碼:

  public static class TrieNode {
        boolean isEnd = false;
        char val;
        TrieNode[] list = new TrieNode[26];

        TrieNode(char x) {
            val = x;
        }

        boolean hasNode(char node) {
            return list[node - 'a'] != null;
        }

        TrieNode findNode(char node) {
            return list[node - 'a'];
        }


        TrieNode createNode(char node) {
            list[node - 'a'] = new TrieNode(node);
            return list[node - 'a'];
        }
    }

    TrieNode head = new TrieNode('-');

    /**
     * Initialize your data structure here.
     */
    public Trie() {

    }

    /**
     * Inserts a word into the trie.
     */
    public void insert(String word) {
        TrieNode index = head;
        for (char c : word.toCharArray()) {
            if (index.hasNode(c))
                index = index.findNode(c);
            else
                index = index.createNode(c);
        }
        index.isEnd = true;
    }

    /**
     * Returns if the word is in the trie.
     */
    public boolean search(String word) {
        TrieNode index = head;
        for (char c : word.toCharArray())
            if (index.hasNode(c))
                index = index.findNode(c);
            else
                return false;
        return index.isEnd;
    }

    /**
     * Returns if there is any word in the trie that starts with the given prefix.
     */
    public boolean startsWith(String prefix) {
        TrieNode index = head;
        for (char c : prefix.toCharArray())
            if (index.hasNode(c))
                index = index.findNode(c);
            else
                return false;
        return true;
    }


題目描述:

在未排序的數組中找到第 k 個最大的元素。請注意,你需要找的是數組排序後的第 k 個最大的元素,而不是第 k 個不同的元素。


技術點:

分治算法

思路:

先任取一個數,把比它大的數移動到它的左邊,比它小的數移動到它的右邊。移動完成一輪後,看該數的下標(從0計數),如果剛好爲k-1則它就是第k大的數,如果小於k-1,說明第k大的數在它右邊,如果大於k-1則說明第k大的數在它左邊,取左邊或者右邊繼續進行移動,直到找到。

參考代碼:

    public int findKthLargest(int[] nums, int k) {
        if(nums == null){
            return Integer.MAX_VALUE;
        }
        if(nums.length < k){
            return Integer.MAX_VALUE;
        }
        return quickSort(nums,0,nums.length-1,k);
    }
    
    public static int quickSort(int[] arr,int low,int high,int k){
        int i,j;
        int temp;
        if(low > high){
            return Integer.MAX_VALUE;
        }
        i = low;
        j = high;
        temp = arr[i];
        while(i < j){
            while(i < j && arr[j] < temp){//把小於temp的數都放到右邊
                j--;
            }
            if(i < j){//如果右邊出現大於temp的數,就跟左邊交換
                arr[i++] = arr[j];
            }
            while(i < j && arr[i] >= temp){//把大於等於temp的數都放到左邊
                i++;
            }
            if(i < j){//如果左邊出現小於temp的數,就跟右邊交換
                arr[j--] = arr[i];
            }
        }
        arr[i] = temp;
        if(i == k - 1){//如果temp的位置爲k-1,那麼他就是第k個最大的數
            return temp;
        }else if(i > k - 1){//如果temp的位置大於k-1,說明第k個最大的數在左邊
            return quickSort(arr,low,i - 1,k);
        }else{//如果temp的位置小於k-1,說明第k個最大的數在右邊
            return quickSort(arr,i + 1,high,k);
        }
    }

題目描述:

編寫一個高效的算法來搜索 m x n 矩陣 matrix 中的一個目標值 target。該矩陣具有以下特性:

  • 每行的元素從左到右升序排列。
  • 每列的元素從上到下升序排列。

技術點:

二分查找 分治算法

思路:

從最右上角的元素開始找,如果這個元素比target大,則說明找更小的,往左走;如果這個元素比target小,則說明應該找更大的,往下走。

參考代碼:

    public boolean searchMatrix(int[][] matrix, int target) {
        int m = matrix.length;
        if (m == 0)
            return false;
        int n = matrix[0].length;
        if (n == 0)
            return false;
        if (m == 1 && n == 1)
            return target == matrix[0][0];
        n--;m=0;
        while(n >= 0 && m < matrix.length) {
            if (matrix[m][n] == target)
                return true;
            else if (matrix[m][n] < target)
                m++;
            else if (matrix[m][n] > target)
                n--;
        }
        return false;
    }

題目描述:

合併 k 個排序鏈表,返回合併後的排序鏈表。


技術點:

鏈表 分治算法

思路:

  • 遍歷所有鏈表,將所有節點的值放到一個數組中。
  • 將這個數組排序,然後遍歷所有元素得到正確順序的值。
  • 用遍歷得到的值,創建一個新的有序鏈表。

參考代碼:

    public ListNode mergeKLists(ListNode[] lists) {
        if (lists == null)
            return null;
        int[] nums = new int[1000024];

        int index = 0;
        for (int i = 0;i < lists.length;i++) {
            while(lists[i] != null) {
                nums[index++] = lists[i].val;
                lists[i] = lists[i].next;
            }
        }
        if (index == 0)
            return null;
        QuickSort(nums,0,index-1);
        ListNode result = new ListNode(nums[0]);
        ListNode ix = result;
        for (int i = 1;i < index;i++) {
            ix.next = new ListNode(nums[i]);
            ix = ix.next;
        }
        return result;
    }
    
    private static int count;
    private static void QuickSort(int[] num, int left, int right) {
        //如果left等於right,即數組只有一個元素,直接返回
        if(left>=right) {
            return;
        }
        //設置最左邊的元素爲基準值
        int key=num[left];
        //數組中比key小的放在左邊,比key大的放在右邊,key值下標爲i
        int i=left;
        int j=right;
        while(i<j){
            //j向左移,直到遇到比key小的值
            while(num[j]>=key && i<j){
                j--;
            }
            //i向右移,直到遇到比key大的值
            while(num[i]<=key && i<j){
                i++;
            }
            //i和j指向的元素交換
            if(i<j){
                int temp=num[i];
                num[i]=num[j];
                num[j]=temp;
            }
        }
        num[left]=num[i];
        num[i]=key;
        count++;
        QuickSort(num,left,i-1);
        QuickSort(num,i+1,right);
    }

題目描述:

給定一個非空二叉樹,返回其最大路徑和。
本題中,路徑被定義爲一條從樹中任意節點出發,達到任意節點的序列。該路徑至少包含一個節點,且不一定經過根節點。


技術點:

深度優先搜索

思路:

遞歸法,最大和路徑的位置共有三種可能的情況:在左子樹中、在右子樹中和包含當前根結點。

參考代碼:

    private int maxSum = Integer.MIN_VALUE;
    public int maxPathSum(TreeNode root) {
        getMax(root);
        return maxSum;
    }
    
    private int getMax(TreeNode root) {
        if (root == null)
            return 0;
        int left = Math.max(0, getMax(root.left));
        int right = Math.max(0, getMax(root.right));
        maxSum = Math.max(maxSum, left+right+root.val);
        return root.val + Math.max(left, right);
    }

題目描述:

給定一個非空字符串 s 和一個包含非空單詞列表的字典 wordDict,在字符串中增加空格來構建一個句子,使得句子中所有的單詞都在詞典中。返回所有這些可能的句子。


技術點:

動態規劃 回溯算法

思路:

先用動態規劃判斷是否可拆分,後用回溯獲得所有結果。

參考代碼:

    public List<String> wordBreak(String s, List<String> wordDict) {
        List<String> list = new ArrayList<>();
        List<String> wordList = new ArrayList<>();
        if(wordBreak_check(s,wordDict)){
              add(list, wordList, s, wordDict);
        }
        return list;
    }
    private void add(List<String> list,List<String> wordList,String s,List<String> wordDict){
        for(String str : wordDict){
            if(s.startsWith(str)){
                if(s.length() == str.length()){
                    StringBuilder b = new StringBuilder();
                    for(String word : wordList){
                        b.append(word).append(" ");
                    }
                    b.append(str);
                    list.add(b.toString());
                }else{
                    wordList.add(str);
                    add(list, wordList, s.substring(str.length()), wordDict);
                    wordList.remove(wordList.size()-1);
                }
            }
        }
    }
    
    public boolean wordBreak_check(String s, List<String> wordDict) {
        int maxWordLength = 0;//字典中單詞最長長度
        for(int i=0;i<wordDict.size();i++){
            maxWordLength = Math.max(maxWordLength,wordDict.get(i).length());
        }
        boolean[] dp = new boolean [s.length()+1];
        dp[0] = true;
        for(int i=1;i<s.length()+1;i++){
            int x = i-maxWordLength>0?i-maxWordLength:0;
            for(int j=x;j<i;j++){
                if(dp[j]&&wordDict.contains(s.substring(j,i))){//s存在以第j位爲末尾的單詞並且截取第j到i位的單詞存在於字典中
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[s.length()];
    }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章