1. 程式人生 > >[Week 2] LeetCode 335. Count of Smaller Numbers After Self

[Week 2] LeetCode 335. Count of Smaller Numbers After Self

LeetCode 335. Count of Smaller Numbers After Self

問題描述

You are given an integer array nums and you have to return a new counts array. The counts array has the property where counts[i] is the number of smaller elements to the right of nums[i].

Example:

Input: [5,2,6,1]
Output: [2,1,1,0] 
Explanation:
To the
right of 5 there are 2 smaller elements (2 and 1). To the right of 2 there is only 1 smaller element (1). To the right of 6 there is 1 smaller element (1). To the right of 1 there is 0 smaller element.

題解:

我們可以採用類似 MergeSort 的演算法來解決這個問題,按照 Example 裡給的資料 [5, 2, 6, 1] 來一步步分析:

  1. [5, 2, 6, 1] = [5, 2] + [6, 1]

  2. [5, 2] = [5] + [2] | [6, 1] = [6] + [1]

  3. 將 [5], [2] 合併成長度為2的陣列:

    • 2 < 5, 則新陣列[2, *], 所有已經被推入新陣列的陣列元素[2]
    • 陣列已空,則新陣列[2, 5], 比該元素小的陣列元素有[2],故有5(1),代表比5小的元素有1個
  4. 將 [6], [1] 合併成長度為2的陣列:

    • 1 < 6, 則新陣列[1, *], 所有已經被推入新陣列的陣列元素[1]

    • 陣列已空,則新陣列[1, 6], 比該元素小的陣列元素有[1],故有6(1),代表比6小的元素有1個

  5. 將 [2, 5], [1, 6] 合併成長度為4的陣列:

    • 1 < 2, 則新陣列[1, *, *, *], 所有已經被推入新陣列的陣列元素[1]

    • 2 < 6, 則新陣列[1, 2, *, *], 比該元素小的陣列元素有[1],故有2(1),代表比2小的元素有1個

    • 5 < 6, 則新陣列[1, 2, 5, *], 比該元素小的陣列元素有[1],故有5(1 + 1),代表比5小的元素有2個

    • 陣列已空,則新陣列[1, 2, 5, 6]

Ok, 目前我們已經算出輸入 [5, 2, 6, 1] 的輸出 [2, 1, 1, 0]。總結一下就是,記錄 MergeSort 每次合併過程中陣列已被推入新陣列的元素(但其實我們只需要維護一個 helpCount 變數來記錄個數即可),當陣列中有元素推入,則其所維護的統計量(比它小的元素個數)加上 helpCount。

Code:

class Solution {
public:
    vector<int> countSmaller(vector<int> &nums)
    {
      vector<int> indices(nums.size(), 0);
      vector<int> result(nums.size(), 0);
      for (int i = 0; i < nums.size(); ++i)
        indices[i] = i;
      helper(nums, indices, result, 0, nums.size() - 1);
      return result;
    }

private:
    void helper(vector<int> &nums, vector<int> &indices,
                vector<int> &result, int low, int high)
    {
      if (low >= high)
        return;

      int mid = (low + high) / 2;
      helper(nums, indices, result, low, mid);
      helper(nums, indices, result, mid + 1, high);

      int i = low, j = mid + 1, helpCount = 0, k = 0;
      vector<int> tmp_nums(high - low + 1), tmp_indices(indices.begin(), indices.end());
      while (i <= mid && j <= high)
      {
        if (nums[i] <= nums[j])
        {
          tmp_nums[k] = nums[i];
          tmp_indices[low + k] = indices[i];
          result[indices[i]] += helpCount;
          ++i;
        }
        else
        {
          tmp_nums[k] = nums[j];
          tmp_indices[low + k] = indices[j];
          ++helpCount;
          ++j;
        }
        ++k;
      }

      while (i <= mid)
      {
        tmp_nums[k] = nums[i];
        tmp_indices[low + k] = indices[i];
        result[indices[i]] += helpCount;
        ++i;
        ++k;
      }

      while (j <= high)
      {
        tmp_nums[k] = nums[j];
        tmp_indices[low + k] = indices[j];
        ++helpCount;
        ++j;
        ++k;
      }

      for (i = low, j = 0; i <= high; ++i, ++j)
        nums[i] = tmp_nums[j];
      indices.assign(tmp_indices.begin(), tmp_indices.end());
    }
};

複雜度分析:

將問題分解成求兩個小陣列(左半陣列和右半陣列,元素個數為 n/2 )的 Count of Smaller Numbers After Self 問題,求完後再在合併過程中求整陣列的 Count of Smaller Numbers After Self 問題,而兩個小問題又能各自分解成兩個小問題,依此遞迴直至陣列元素個數為1。

故我們能寫出表示式:T(n) = 2T(n/2) + O(n)

根據大師定理得:a = 2, b = 2, d = 1 即 d = logab,所以表示式複雜度為 O(nlogn)

Ok,如果對大師定理不瞭解,我們可以這樣分析,將一個元素個數為n的陣列每次折半,需要幾次能將陣列大小折為1呢,答案是 log2n 對吧!每一層陣列元素個數為上一層的一半(第1層除外)則每一層我們需要遍歷陣列幾次呢,答案是一次,例如[5],[2],[6],[1],要將它們變為長度為2的兩個陣列,需要遍歷這四個陣列各一次,所以加起來就是原陣列的長度。

其它解法:

  1. 維護二叉搜尋樹,將陣列的元素從左到右依次插入樹中,如果元素比root小,則root的統計量+1,然後進入左子樹,不斷遞迴到葉子節點…
  2. 使用STL的 upper_bound 和 lower_bound 函式…