【每日算法Day 103】老題新做,幾乎不會有人想到的解法,它來了

前兩天 Day 99 的時候,做過一道順子的題目,當時有一個網友的妙解有點沒看懂,今天我來給大家詳細講解一下。

題目鏈接

LeetCode 846. 一手順子[1]

往期回顧:
【每日算法Day 99】你們可能不知道只用20萬贏到578萬是什麼概念[2]

題目描述

盧本偉有一手(hand)由整數數組給定的牌。

現在她想把牌重新排列成組,使得每個組的大小都是 W,且由 W 張連續的牌組成。

如果她可以完成分組就返回 true,否則返回 false

說明:

  • 1 <= hand.length <= 10000
  • 0 <= hand[i] <= 10^9
  • 1 <= W <= hand.length

示例1

        輸入:
hand = [1,2,3,6,2,3,4,7,8], W = 3
輸出:
true
解釋:
盧本偉的手牌可以被重新排列爲 [1,2,3],[2,3,4],[6,7,8]。
      

示例2

        輸入:
hand = [1,2,3,4,5], W = 4
輸出:
false
解釋:
盧本偉的手牌無法被重新排列成幾個大小爲 4 的組。
      

題解

這題的妙解來自於題解區網友 zhanzq,當時沒怎麼看懂,現在我來給大家講解一下。

網友題解地址[3]

我們用一個例子來講解:

v2-062818c4fb586eb6eceae5edba0c6ede_b.jpg

假設 W = 3,給定的手牌正好是三個順子:[1,2,3], [2,3,4], [6,7,8]

那麼我們統計出每張牌的數量,並且從小到大排序,記爲 count ,這裏就是 [1,2,2,1,0,1,1,1,0] ,並且在數字不連續處和末尾補 0 (作用後面會詳細說)。

  • 然後從小到大遍歷每一張牌,首先 1 只有一張,那麼如果它和後面牌能構成順子,那麼 2, 3 至少要有一張才行,於是 total 數組後面兩個位置都加上 1
  • 然後遍歷到 2 ,因爲 2 的數量是大於該位置處的 total 值的,所以 2 的數量足夠滿足前面的牌順子要求。此外 2 還會多出一張,那麼後面兩個位置至少要有一張牌才行,於是 total 後面兩個位置再加上 1
  • 然後遍歷 3, 4 ,發現數量正好都等於 total ,那說明它倆正好和前面的牌構成順子,一點都不會多餘。
  • 然後遍歷到 0 了,這就說明和前面的牌斷開了。如果這時候 total 不爲 0 ,就說明中間缺失了一些牌,前面存在順子沒法補足結尾。而如果最開始沒有填充 0 的話,就沒有辦法判斷這裏的牌是否和前面連續的,你就有可能把 6 這張牌直接接到 4 後面組成順子了。
  • 然後遍歷 6, 7, 8 同理,在對應位置處更新 total 就行了。
  • 最後遍歷 0 ,發現 total 也是 0 ,那就說明整副牌可以構成順子,完美!

時間複雜度是 O(n \log n + nW) ,這題數據不強也可以過的。

有沒有辦法優化呢?其實更新 total 這一步可以優化掉 O(W) 這個複雜度,直接 O(1) 更新 total

  • 首先遍歷 1 ,因爲 1 只有一張,那麼如果它和後面牌能構成順子,那麼 2, 3 至少要有一張才行。但是這裏我們不對這幾張牌的 total 加上一,而是在這個順子結尾的下一張牌處的 deltas 減去 1
  • 然後遍歷 2 ,那麼這時候沒有 total 了,怎麼計算應該扣除多少前面順子需要的 2 呢?其實只需要用前一張牌的牌數加上當前的 deltas 值就行了。爲什麼呢?前面一張牌有多少張,你當前這張就得至少有那麼多去構成順子,但是如果前面一張牌是某些順子的結尾,你還得扣掉一些,而扣掉的數值正好就是當前的 deltas ,這在前面順子的開頭處已經記錄過了。
  • 後面操作類似,就不詳細闡述了。

這種方法精髓就在於,不需要直接更新所有的 total 值,只需要在順子結尾下一個元素處更新一下 deltas 就行了,每次的 total 可以通過上一張牌的 count 和當前的 deltas 推算出來。

這樣總的時間複雜度就降到了 O(n \log n + n),近似 O(n \log n)

不得不說,這個方法還是非常妙的,反正我是一下子想不到的,看了代碼都想了很久纔想通。

代碼

暴力更新(c++)

        class Solution {
public:
    bool valid(vector<int> &count, int W) {
        int n = count.size();
        vector<int> total(n, 0);
        for (int i = 0; i < n; ++i) {
            if (count[i] > total[i]) {
                int delta = count[i] - total[i];
                for (int j = i; j < i+W && j < n; ++j) total[j] += delta;
            } else if (count[i] < total[i]) {
                return false;
            }
        }
        return true;
    }

    bool isNStraightHand(vector<int>& hand, int W) {
        int n = hand.size();
        if (W == 1) return true;
        if (n%W) return false;
        sort(hand.begin(), hand.end());
        vector<int> count;
        int i = 0, j = 0;
        while (i < n) {
            while (j < n && hand[i] == hand[j]) j++;
            count.push_back(j-i);
            if (j >= n) break;
            else if (hand[j] != hand[j-1]+1) count.push_back(0);
            i = j;
        }
        count.push_back(0);
        return valid(count, W);
    }
};

      

優化(c++)

        class Solution {
public:
    bool valid(vector<int> &count, int W) {
        int n = count.size(), pre = 0;
        vector<int> deltas(n, 0);
        for (int i = 0; i < n; ++i) {
            pre += deltas[i];
            if (pre < count[i]) {
                int delta = count[i] - pre;
                pre = count[i];
                if (i + W < n) deltas[i+W] -= delta;
            } else if (pre > count[i]) {
                return false;
            }
        }
        return true;
    }

    bool isNStraightHand(vector<int>& hand, int W) {
        int n = hand.size();
        if (W == 1) return true;
        if (n%W) return false;
        sort(hand.begin(), hand.end());
        vector<int> count;
        int i = 0, j = 0;
        while (i < n) {
            while (j < n && hand[i] == hand[j]) j++;
            count.push_back(j-i);
            if (j >= n) break;
            else if (hand[j] != hand[j-1]+1) count.push_back(0);
            i = j;
        }
        count.push_back(0);
        return valid(count, W);
    }
};

      

參考資料

[1]

LeetCode 846. 一手順子: leetcode-cn.com/problem

[2]

【每日算法Day 99】你們可能不知道只用20萬贏到578萬是什麼概念: godweiyang.com/2020/04/

[3]

網友題解地址: leetcode-cn.com/problem

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