1. 程式人生 > 其它 >1787. 使所有區間的異或結果為零

1787. 使所有區間的異或結果為零

1787. 使所有區間的異或結果為零

難度困難72

給你一個整數陣列nums​​​ 和一個整數k​​​​​ 。區間[left, right]left <= right)的異或結果是對下標位於leftright(包括leftright)之間所有元素進行XOR運算的結果:nums[left] XOR nums[left+1] XOR ... XOR nums[right]

返回陣列中要更改的最小元素數,以使所有長度為k的區間異或結果等於零。

示例 1:

輸入:nums = [1,2,0,3,0], k = 1
輸出:3
解釋:將陣列 [1,2,0,3,0] 修改為 [0,0,0,0,0]

示例 2:

輸入:nums = [3,4,5,2,1,7,3,4,7], k = 3
輸出:3
解釋:將陣列 [3,4,5,2,1,7,3,4,7] 修改為 [3,4,7,3,4,7,3,4,7]

示例 3:

輸入:nums = [1,2,4,1,2,5,1,2,6], k = 3
輸出:3
解釋:將陣列[1,2,4,1,2,5,1,2,6] 修改為 [1,2,3,1,2,3,1,2,3]

提示:

  • 1 <= k <= nums.length <= 2000
  • ​​​​​​0 <= nums[i] < 210

1/30下一題解

使所有區間的異或結果為零

力扣官方題解L6

釋出於15 小時前11.2k官方

位運算陣列雜湊表動態規劃CC++C#GoJavaJavaScriptPython

方法一:動態規劃

思路與演算法

設陣列\textit{nums}nums的長度為nn。首先我們需要分析出陣列\textit{nums}nums在修改之後應當具有哪些性質。

由於任意長度為kk的區間異或結果等於00,那麼對於任意的ii,有:

\textit{nums}[i] \oplus \textit{nums}[i+1] \oplus \cdots \oplus \textit{nums}[i+k-1] = 0 \tag{1}nums[i]⊕nums[i+1]⊕⋯⊕nums[i+k−1]=0(1)

以及:

\textit{nums}[i+1] \oplus \textit{nums}[i+2] \oplus \cdots \oplus \textit{nums}[i+k] = 0 \tag{2}nums[i+1]⊕nums[i+2]⊕⋯⊕nums[i+k]=0(2)

其中\oplus⊕表示異或運算。根據異或運算的性質a \oplus b \oplus b = aa⊕b⊕b=a,將(1)(2)(1)(2)兩式聯立,即可得到:

\textit{nums}[i] \oplus \textit{nums}[i+k] = 0nums[i]⊕nums[i+k]=0

其等價於\textit{nums}[i] = \textit{nums}[i+k]nums[i]=nums[i+k]。因此我們可以得出一個重要的結論:

陣列\textit{nums}nums在修改之後是一個以kk為週期的週期性陣列,即\forall i \in [0, n-k), \textit{nums}[i] = \textit{nums}[i+k]∀i∈[0,n−k),nums[i]=nums[i+k]。

因此,我們可以將陣列\textit{nums}nums按照下標對kk取模的結果0, 1, \cdots, k-10,1,⋯,k−1分成kk個組,每個組內的元素必須要相等,並且這kk個組對應的元素的異或和為00,即:

\textit{nums}[0] \oplus \textit{nums}[1] \oplus \cdots \oplus \textit{nums}[k-1] = 0nums[0]⊕nums[1]⊕⋯⊕nums[k−1]=0

只要滿足上述的要求,修改後的陣列的所有長度為kk的區間異或結果一定等於00。

對於第ii個組,我們可以使用雜湊對映來儲存該組內的元素以及每個元素出現的次數,這樣一來,我們就可以嘗試使用動態規劃來解決本題了。

設f(i, \textit{mask})f(i,mask)表示我們已經處理了第0, 1, \cdots, i0,1,⋯,i個組,並且這些組對應元素的異或和:

\textit{nums}[0] \oplus \textit{nums}[1] \oplus \cdots \oplus \textit{nums}[i]nums[0]⊕nums[1]⊕⋯⊕nums[i]

為\textit{mask}mask的前提下,這些組總計最少需要修改的元素個數。在進行狀態轉移時,我們可以列舉第ii組被修改成了哪個數。假設其被修改成了xx,那麼第0, 1, \cdots, i-10,1,⋯,i−1個組對應的元素的異或和即為\textit{mask} \oplus xmask⊕x,因此我們可以得到狀態轉移方程:

f(i, \textit{mask}) = \min_x \big\{ f(i-1, \textit{mask} \oplus x) + \text{size}(i) - \text{count}(i, x) \big\}f(i,mask)=xmin​{f(i−1,mask⊕x)+size(i)−count(i,x)}

其中\text{size}(i)size(i)表示第ii個組裡的元素個數,\text{count}(i, x)count(i,x)表示第ii個組裡元素xx的次數,它們的值都可以通過雜湊對映得到。上述狀態轉移方程的意義即為:如果我們選擇將第ii組全部修改為xx,那麼有\text{count}(i, x)count(i,x)個數是無需修改的,這樣就需要修改\text{size}(i) - \text{count}(i, x)size(i)−count(i,x)次。

動態規劃的時間複雜度是多少呢?我們發現這並不好估計,這是因為xx可以很小,也可以很大,它並沒有一個固定的範圍。然而我們可以發現,題目中規定了\textit{nums}[i] < 2^{10}nums[i]<210,也就是\textit{nums}[i]nums[i]的二進位制表示的位數不超過1010。因此我們可以斷定,xx的上限就是2^{10}210。

如果x \geq 2^{10}x≥210,那麼xx的二進位制表示的最高位11是在陣列\textit{nums}nums的其它元素中沒有出現過的,因此根據異或的性質,要想將最終的異或結果變為00,我們需要將另一個組的所有元素改為yy,且yy有與xx相同的高位11。這樣一來,我們不如將xx和yy的高位11移除,讓它們都在[0, 2^{10})[0,210)的範圍內,這樣更容易增大\text{count}(i, x)count(i,x),減少最終的修改次數。

當我們確定了xx的上限,就可以計算出動態規劃的時間複雜度了。狀態的數量有k \times 2^{10}k×210個,對於每一個狀態,我們需要列舉xx來進行狀態轉移,而xx的數量同樣有2^{10}210個,因此時間複雜度為O(2^{20} k) = O(2^{20} n)O(220k)=O(220n),數量級約為2 \times 10^92×109,超出了時間限制,因此我們必須要進行優化。

優化

對於未優化的狀態轉移方程:

f(i, \textit{mask}) = \min_x \big\{ f(i-1, \textit{mask} \oplus x) + \text{size}(i) - \text{count}(i, x) \big\}f(i,mask)=xmin​{f(i−1,mask⊕x)+size(i)−count(i,x)}

首先\text{size}(i)size(i)是與xx無關的常量,我們可以將其移出最小值的限制,即:

f(i, \textit{mask}) = \text{size}(i) + \min_x \big\{ f(i-1, \textit{mask} \oplus x) - \text{count}(i, x) \big\}f(i,mask)=size(i)+xmin​{f(i−1,mask⊕x)−count(i,x)}

由於我們需要求的是「最小值」,因此在狀態轉移方程中新增若干大於等於「最小值」的項,對最終的答案不會產生影響。

考慮\text{count}(i, x)count(i,x)這一項,如果xx沒有在雜湊表中出現,那麼這一項的值為00,否則這一項的值大於00。即:

  • 如果xx沒有在雜湊表中出現,那麼轉移的狀態為:

    f(i-1, \textit{mask} \oplus x)f(i−1,mask⊕x)

  • 如果xx在雜湊表中出現,那麼轉移的狀態為:

    f(i-1, \textit{mask} \oplus x) - \text{count}(i, x)f(i−1,mask⊕x)−count(i,x)

    它嚴格小於f(i-1, \textit{mask} \oplus x)f(i−1,mask⊕x)。如果我們在狀態轉移方程中新增f(i-1, \textit{mask} \oplus x)f(i−1,mask⊕x),最終的答案不會變化。

因此我們可以將狀態轉移方程變化為:

f(i, \textit{mask}) = \text{size}(i) + \min\{ t_1, t_2 \}f(i,mask)=size(i)+min{t1​,t2​}

其中t_1t1​對應xx在雜湊表中出現的狀態,即:

t_1 = \min_{x \in \text{HashTable}(i)} \big\{ f(i-1, \textit{mask} \oplus x) - \text{count}(i, x) \big\}t1​=x∈HashTable(i)min​{f(i−1,mask⊕x)−count(i,x)}

t_2t2​對應xx不在雜湊表中出現的狀態,以及xx在雜湊表中出現並且我們額外新增的狀態,即:

t_2 = \min_x f(i - 1, \textit{mask} \oplus x)t2​=xmin​f(i−1,mask⊕x)

由於\textit{mask}mask的取值範圍是[0, 2^{10})[0,210),xx的取值範圍也是[0, 2^{10})[0,210),因此\textit{mask} \oplus xmask⊕x的取值範圍就是[0, 2^{10})[0,210),與xx無關。也就是說:

t_2t2​就是所有狀態f(i-1, ..)f(i−1,..)中的最小值。

那麼優化後的動態規劃的時間複雜度是多少呢?對於第ii組,我們需要計算出第i-1i−1組對應的狀態f(i-1, ..)f(i−1,..)中的最小值,時間複雜度為O(2^{10})O(210),即為t_2t2​部分的狀態轉移。而對於t_1t1​部分,狀態轉移的次數等於第ii組雜湊對映的大小,小於等於\text{size}(i)size(i)。因此總的時間複雜度為:

O\left( \sum_{i=0}^{k-1} \big( 2^{10} + \text{size}(i) \big) \right) = O(2^{10}k + n)O(i=0∑k−1​(210+size(i)))=O(210k+n)

最終的答案即為f(k-1, 0)f(k−1,0)。

細節

由於f(i, ..)f(i,..)只會從f(i-1, ..)f(i−1,..)轉移而來,因此我們可以使用兩個長度為2^{10}210的一維陣列代替二維陣列,交替地進行狀態轉移,減少空間複雜度。

此外,當i=0i=0時,f(-1, ..)f(−1,..)都是需要特殊考慮的邊界條件。由於f(-1, ..)f(−1,..)表示沒有考慮任何組時的異或和,因此該異或和一定為00,即f(-1, 0) = 0f(−1,0)=0。其它的狀態都是不合法的狀態,我們可以將它們賦予一個極大值\infty∞。

c++

class Solution {
private:
    // x 的範圍為 [0, 2^10)
    static constexpr int MAXX = 1 << 10;
    // 極大值,為了防止整數溢位選擇 INT_MAX / 2
    static constexpr int INFTY = INT_MAX / 2;
    
public:
    int minChanges(vector<int>& nums, int k) {
        int n = nums.size();
        vector<int> f(MAXX, INFTY);
        // 邊界條件 f(-1,0)=0
        f[0] = 0;
        
        for (int i = 0; i < k; ++i) {
            // 第 i 個組的雜湊對映
            unordered_map<int, int> cnt;
            int size = 0;
            for (int j = i; j < n; j += k) {
                ++cnt[nums[j]];
                ++size;
            }

            // 求出 t2
            int t2min = *min_element(f.begin(), f.end());

            vector<int> g(MAXX, t2min);
            for (int mask = 0; mask < MAXX; ++mask) {
                // t1 則需要列舉 x 才能求出
                for (auto [x, countx]: cnt) {
                    g[mask] = min(g[mask], f[mask ^ x] - countx);
                }
            }
            
            // 別忘了加上 size
            for_each(g.begin(), g.end(), [&](int& val) {val += size;});
            f = move(g);
        }

        return f[0];
    }
};

python

class Solution:
    def minChanges(self, nums: List[int], k: int) -> int:
        # x 的範圍為 [0, 2^10)
        MAXX = 2**10
        
        n = len(nums)
        f = [float("inf")] * MAXX
        # 邊界條件 f(-1,0)=0
        f[0] = 0
        
        for i in range(k):
            # 第 i 個組的雜湊對映
            count = Counter()
            size = 0
            for j in range(i, n, k):
                count[nums[j]] += 1
                size += 1

            # 求出 t2
            t2min = min(f)

            g = [t2min] * MAXX
            for mask in range(MAXX):
                # t1 則需要列舉 x 才能求出
                for x, countx in count.items():
                    g[mask] = min(g[mask], f[mask ^ x] - countx)
            
            # 別忘了加上 size
            f = [val + size for val in g]

        return f[0]