Leetcode第 217 場周賽(思維量比較大)

Frnas發表於2020-11-30

Leetcode第 217 場周賽

比賽連結:點這裡

做完前兩題我就知道今天的競賽我已經結束了

這場比賽思維量還是比較大的。

1673. 找出最具競爭力的子序列

題目

給你一個整數陣列 nums 和一個正整數 k ,返回長度為 k 且最具 競爭力nums 子序列。

陣列的子序列是從陣列中刪除一些元素(可能不刪除元素)得到的序列。

在子序列 a 和子序列 b 第一個不相同的位置上,如果 a 中的數字小於 b 中對應的數字,那麼我們稱子序列 a 比子序列 b(相同長度下)更具 競爭力 。 例如,[1,3,4][1,3,5] 更具競爭力,在第一個不相同的位置,也就是最後一個位置上, 4 小於 5

示例 1:

輸入:nums = [3,5,2,6], k = 2
輸出:[2,6]
解釋:在所有可能的子序列集合 {[3,5], [3,2], [3,6], [5,2], [5,6], [2,6]} 中,[2,6] 最具競爭力。

示例 2:

輸入:nums = [2,4,3,3,5,4,9,6], k = 4
輸出:[2,3,3,4]

提示:

  • \(1 <= nums.length <= 10^5\)
  • \(0 <= nums[i] <= 10^9\)
  • \(1 <= k <= nums.length\)

思路

比賽時想到了單調棧,但是不熟不敢寫( 還得練練。。

隨便寫了一通居然過了。

思路是找到陣列中的最小值mi,那麼最後的結果中肯定有這個值,如果mi所在的位置p之後的元素個數不足k個了,那麼p之後的元素肯定都是要的,因為沒有比它們更小的了,但是此時元素個數不夠,那麼就需要在p之前的元素的中找剩下的元素。這不就是個子問題麼,直接遞迴完事兒。

程式碼

class Solution {
public:
    vector<int> vis;
    void helper(vector<int>& nums, int k, int l, int r){
        if(l > r || k <= 0) return;
        
        // 最小值
        int p;
        int minn = 0x3f3f3f3f;
        for(int i = l; i <= r; i++){
            if(minn > nums[i]){
                minn = nums[i];
                p = i;
            }
        }
        vis[p] = 1;
        int t = r-p;
        // 剩下元素個數大於k,直接在k後面找剩下的元素,因為此位置一定是最小的。
        if(t >= k) {
            helper(nums, k-1, p+1, r);
        }
        else {
            //剩下元素沒有k個
            for(int i = p+1; i <= r; i++) vis[i] = 1;
            helper(nums, k-t-1, l, p-1);
        }
    }
    vector<int> mostCompetitive(vector<int>& nums, int k) {
        if(!nums.size() || nums.size() < k) return vector<int>();
        if(k == nums.size()) return nums;
        
        vector<int> t = nums;
        sort(t.begin(), t.end());
        if(t == nums){
            return vector<int> (nums.begin(), nums.begin()+k);
        }
        
        vis = vector<int> (nums.size(), 0); //記錄答案
        
        helper(nums, k, 0, nums.size()-1);
        vector<int> ans;
        for(int i = 0; i < nums.size(); i++){
            if(vis[i]) ans.push_back(nums[i]);
        }
        return ans;
    }
};

正解

單調棧維護棧頂元素比當前元素小。

彈棧當且僅當棧頂元素比當前元素大並且保證棧中元素加上剩餘元素能夠湊夠k個。

巨坑:vector .size()方法一定要加(int), 這個錯誤不好找,也想不到。

以示警醒。

程式碼邏輯還是比較簡單的。

程式碼

class Solution {
public:
    vector<int> mostCompetitive(vector<int>& nums, int k) {
        vector<int> st;
        for(int i = 0; i < nums.size(); i++){
            int c = nums[i];
            while(!st.empty() && st.back() > c &&  k - (int)st.size() + 1 <= (int)nums.size() - i) st.pop_back();
            st.push_back(c);
        }

        while(st.size() > k) st.pop_back();
        return st;
    }
};

PS:這道題跟402. 移掉K位數字這道有異曲同工之妙,不同點在於一個是刪除k個數,一個是留下k個數,但是思路大題一致,可以對比著來學。是的,做過的題記不住。ε=(´ο`*)))

1674. 使陣列互補的最少操作次數

題目

給你一個長度為 偶數 n 的整數陣列 nums 和一個整數 limit 。每一次操作,你可以將 nums 中的任何整數替換為 1limit 之間的另一個整數。

如果對於所有下標 i下標從 0 開始),nums[i] + nums[n - 1 - i] 都等於同一個數,則陣列 nums互補的 。例如,陣列 [1,2,3,4] 是互補的,因為對於所有下標 inums[i] + nums[n - 1 - i] = 5

返回使陣列 互補最少 操作次數。

示例 1:

輸入:nums = [1,2,4,3], limit = 4
輸出:1
解釋:經過 1 次操作,你可以將陣列 nums 變成 [1,2,2,3](加粗元素是變更的數字):
nums[0] + nums[3] = 1 + 3 = 4.
nums[1] + nums[2] = 2 + 2 = 4.
nums[2] + nums[1] = 2 + 2 = 4.
nums[3] + nums[0] = 3 + 1 = 4.
對於每個 i ,nums[i] + nums[n-1-i] = 4 ,所以 nums 是互補的。

示例 2:

輸入:nums = [1,2,2,1], limit = 2
輸出:2
解釋:經過 2 次操作,你可以將陣列 nums 變成 [2,2,2,2] 。你不能將任何數字變更為 3 ,因為 3 > limit 。

示例 3:

輸入:nums = [1,2,1,2], limit = 2
輸出:0
解釋:nums 已經是互補的。

提示:

  • \(n == nums.length\)
  • \(2 <= n <= 10^5\)
  • \(1 <= nums[i] <= limit <= 10^5\)
  • \(n\) 是偶數。

思路

完全沒有思路,題刷得還是太少了啊

差分+字首和

首先我們知道答案最大是\(n\), 這是將所有的數字都改了的情況。

假設陣列中互補的兩元素為\(A\)\(B\)

我們用\(delta[i]\) 表示將\(A+B\)改為\(i\)時需要的操作次數,那麼就可以分為以下五種情況:

  • \(2<=i<=min(A, B)\): 需要兩次操作
  • \(min(A, B)+1<=i<=max(A, B) + 1\): 需要一次操作
  • \(i == A+B\): 不需要操作
  • \(A+B+1<=i<=limit + max(A, B)\): 需要一次操作
  • \(i > limit + max(A, B)\): 需要兩次操作

但是知道了這麼多種情況,怎麼來表示他們之間的變化以及求和呢?

答案是使用差分陣列,只需要改變被改變區間的邊界的兩個值,就可以在\(O(n)\)的時間裡求和。

程式碼

class Solution {
public:
    int minMoves(vector<int>& nums, int limit) {
        vector<int> delta(limit*2+20, 0);
        int n = nums.size();
        delta[0] = n; //開始為n次操作
        unordered_map<int, int> freq;
        for(int i = 0; i < n/2; i++){
            int sums = nums[i] + nums[n-i-1];
            int lo = 1 + min(nums[i], nums[n-i-1]);
            int hi = limit + 1 + max(nums[i], nums[n-i-1]);

            // 差分
            delta[lo] --;
            delta[sums]--;
            delta[sums+1] ++;
            delta[hi]++;
        }

        //求和
        for(int i = 1; i <= limit*2; i++) delta[i] += delta[i-1];
        int ans = 0x3f3f3f3f;
        for(int i = 1; i <= limit*2; i++){
            ans = min(ans, delta[i]);
        }
        return ans;
    }
};

1675. 陣列的最小偏移量

題目

給你一個由 n 個正整陣列成的陣列 nums

你可以對陣列的任意元素執行任意次數的兩類操作:

  • 如果元素是偶數,除以\(2\)
    • 例如,如果陣列是 [1,2,3,4] ,那麼你可以對最後一個元素執行此操作,使其變成 [1,2,3, 2]
  • 如果元素是奇數,乘上\(2\)
    • 例如,如果陣列是 [1,2,3,4] ,那麼你可以對第一個元素執行此操作,使其變成 [2,2,3,4]

陣列的 偏移量 是陣列中任意兩個元素之間的 最大差值

返回陣列在執行某些操作之後可以擁有的 最小偏移量

示例 1:

輸入:nums = [1,2,3,4]
輸出:1
解釋:你可以將陣列轉換為 [1,2,3,2],然後轉換成 [2,2,3,2],偏移量是 3 - 2 = 1

示例 2:

輸入:nums = [4,1,5,20,3]
輸出:3
解釋:兩次操作後,你可以將陣列轉換為 [4,2,5,5,3],偏移量是 5 - 2 = 3

示例 3:

輸入:nums = [2,10,8]
輸出:3

提示:

  • \(n == nums.length\)
  • \(2 <= n <= 105\)
  • \(1 <= nums[i] <= 10^9\)

思路

又是智商被吊打的一題

優先佇列

我們考慮維護大根堆,將所有數先化為最大的形式(也就是奇數\(*2\)),加入大根堆,維護 \(mi\) 表示當前堆的最小值。

之後我們不斷地取出堆頂,也就是當前堆最大的數,除\(2\),重新加入大根堆,此過程中不斷更新答案。

當堆頂為奇數時,就說明不能再除以\(2\),最大值不可能再縮小,答案也就不會再被縮小。

程式碼

class Solution {
public:
    int minimumDeviation(vector<int>& nums) {
        priority_queue<int> q; //大根堆
        int mi = INT_MAX;
        for(auto x : nums){
            if(x&1) x <<= 1; //奇數乘2加入
            q.push(x);
            mi = min(mi, x);
        }
        int ans = INT_MAX;
        while(1){
            auto x = q.top();
            q.pop();
            ans = min(ans, x-mi);
            if(x&1) break; //最大值奇數直接退出
            x >>= 1;
            q.push(x);
            mi = min(x, mi); //維護mi為當前堆中的最小值
        }
        return ans;
    }
};

相關文章