1. 程式人生 > >[LeetCode] Minimum Moves to Equal Array Elements II 最少移動次數使陣列元素相等之二

[LeetCode] Minimum Moves to Equal Array Elements II 最少移動次數使陣列元素相等之二

Given a non-empty integer array, find the minimum number of moves required to make all array elements equal, where a move is incrementing a selected element by 1 or decrementing a selected element by 1.

You may assume the array's length is at most 10,000.

Example:

Input:
[1,2,3]

Output:
2

Explanation:
Only two moves are needed (remember each move increments or decrements one element):

[1,2,3]  =>  [2,2,3]  =>  [2,2,2]

這道題是之前那道Minimum Moves to Equal Array Elements的拓展,現在我們可以每次對任意一個數字加1或者減1,讓我們用最少的次數讓陣列所有值相等。一般來說這種題目是不能用暴力方法算出所有情況,因為OJ一般是不會答應的。那麼這道題是否像上面一道題一樣,有巧妙的方法呢?答案是肯定的。下面這種解法實際上利用了之前一道題Best Meeting Point的思想,是不感覺很amazing,看似完全不相干的兩道題,居然有著某種內部聯絡。我們首先給陣列排序,那麼我們最終需要變成的相等的數字就是中間的數,如果陣列有奇數個,那麼就是最中間的那個數字;如果是偶數個,那麼就是中間兩個數的區間中的任意一個數字。而兩端的數字變成中間的一個數字需要的步數實際上就是兩端數字的距離,講到這裡發現是不是就和這道題

Best Meeting Point的思路是一樣了。那麼我們就兩對兩對的累加它們的差值就可以了,參見程式碼如下:

解法一:

class Solution {
public:
    int minMoves2(vector<int>& nums) {
        int res = 0, i = 0, j = (int)nums.size() - 1;
        sort(nums.begin(), nums.end());
        while (i < j) {
            res += nums[j--] - nums[i++];
        }
        
return res; } };

既然有了上面的分析,我們知道實際上最後相等的數字就是陣列的最中間的那個數字,那麼我們在給陣列排序後,直接利用座標定位到中間的數字,然後算陣列中每個陣列與其的差的絕對值累加即可,參見程式碼如下:

解法二:

class Solution {
public:
    int minMoves2(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int res = 0, mid = nums[nums.size() / 2];
        for (int num : nums) {
            res += abs(num - mid);
        }
        return res;
    }
};

上面的兩種方法都給整個陣列排序了,時間複雜度是O(nlgn),其實我們並不需要給所有的數字排序,我們只關係最中間的數字,那麼這個stl中自帶的函式nth_element就可以完美的發揮其作用了,我們只要給出我們想要數字的位置,它就能在O(n)的時間內返回正確的數字,然後算陣列中每個陣列與其的差的絕對值累加即可,參見程式碼如下:

解法三:

class Solution {
public:
    int minMoves2(vector<int>& nums) {
        int res = 0, n = nums.size(), mid = n / 2;
        nth_element(nums.begin(), nums.begin() + mid, nums.end());
        for (int i = 0; i < n; ++i) {
            res += abs(nums[i] - nums[mid]);
        }
        return res;
    }
};

下面這種方法是改進版的暴力破解法,它遍歷了所有的數字,讓每個數字都當作最後相等的值,然後演算法出來總步數,每次和res比較,留下較小的。而這種方法叼就叼在它在O(1)的時間內完成了步數統計,那麼這樣整個遍歷下來也只是O(n)的時間,不過由於還是要給陣列排序,所以整體的時間複雜度是O(nlgn),這已經能保證可以通過OJ啦。那麼我們來看看如何快速計算總步數,首先我們給陣列排序,我們假設中間某個位置有個數字k,那麼此時陣列就是:nums[0], nums[1], ..., k, ..., nums[n - 1], 如果i為數字k在陣列中的座標,那麼有k = nums[i],那麼總步數為:

Y = k - nums[0] + k - nums[1] + ... + k - nums[i - 1] + nums[i] - k + nums[i + 1] - k + ... + nums[n - 1] - k

   = i * k - (nums[0] + nums[1] + ... + nums[i - 1]) + (nums[i] + nums[i + 1] + ... + nums[n - 1]) - (n - i) * k

   = 2 * i * k - n * k + sum - 2 * curSum

那麼我們只要算出sum和curSum就可以快速得到總步數了,陣列之和可以通過遍歷陣列計算出來,curSum可以在遍歷的過程中累加,那麼我們就可以算出總步數,然後每次更新結果res了,參見程式碼如下:

解法四:

class Solution {
public:
    int minMoves2(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        long long sum = accumulate(nums.begin(), nums.end(), 0);
        long long res = LONG_MAX, curSum = 0;
        int n = nums.size();
        for (int i = 0; i < n; ++i) {
            long long k = nums[i];
            curSum += k;
            res = min(res, 2 * k * (i + 1) - n * k + sum - 2 * curSum);
        }
        return res;
    }
};

類似題目:

參考資料: