周賽 400。更新答案少寫一行,虧一個 WA,好慘。
LC 官方最近宣布使用先進的作弊檢查計數,嚴格禁止任何作弊行為,不知道效果如何。
唯一確定的是這場伺服器有點問題,希望別又在我上分的時候 unrate。

題目

輸入整數陣列 nums 和整數 k。
你必須找到一個 nums 的子陣列,其做 AND 運算後的結果與 k 的絕對差最小化
也就是找到子陣列 nums[l..r],滿足最小的 | k - (nums[l] AND nums[l + 1] … AND nums[r]) |。

求子陣列與 k 的最小絕對差

解法

複習 AND 的特性:只減不增。

一個子陣列擴展邊界、增加元素時,其 AND 結果只可能變小,不可能增加;
反之,縮減邊界、減少元素時,其 AND 結果只可能變大,不可能變小。


子陣列問題通常可以透過滑動窗口解決。

維護 left 作為窗口左端點,並枚舉右端點 right。
如果窗口內子陣列的 AND 結果大於 k,需要等待加入新元素以降低結果;若小於 k,則要刪除左方元素,以提高結果。

至於如何有效率維護窗口內的 AND 結果?
回想到 AND 是只大不小,對於每個 bit 來說,只有所有元素對應的該位元都是 1,結果才會是 1;否則肯定是 0。
因此只要分別維護各位元中出現 1 的次數,若出現次數等於窗口大小,則該位元在結果中會是 1。

時間複雜度 O(N log MX),其中 MX = max(nums)。
空間複雜度 O(log MX)。

class Solution:
    def minimumDifference(self, nums: List[int], k: int) -> int:
        M = max(nums).bit_length()
        cnt = [0] * M
        
        def calc(sz):
            res = 0 
            for i in range(M):
                if cnt[i] == sz:
                    res |= (1 << i)
            return res
        
        def add(x, inc):
            for i in range(M):
                if x & (1 << i):
                    cnt[i] += inc
        
        ans = inf
        left = 0
        for right, x in enumerate(nums):
            add(x, 1)
            ans = min(ans, abs(calc(right-left+1) - k))
            while left < right and calc(right-left+1) < k:
                add(nums[left], -1)
                left += 1
                ans = min(ans, abs(calc(right-left+1) - k))
        
        return ans

我們在枚舉右端點的時候,會將原有的所有子陣列結果都 AND 上新的右端點 x。
例如:

nums = [a,b,c,x]
原本以 c 結尾的子陣列結果有 a&b&c, b&c, c 三種
加上 x 之後,會變成 a&b&c&x, b&c&x, c&x, x 四種

如果 c 和 x 是相同的元素,那麼這些子陣列的結果根本不會改變,所以結果的數量也不會變多;否則必定使原有的結果都變小
每次變小,都會失去一個 1 位元,最多只能失去 log(MX) = 30 次。
也就是說,每個右端點所擁有的子陣列 AND 結果,去除重複後最多只會有 30 個。

時間複雜度 O(N log MX)。
空間複雜度 O(log MX)。

class Solution:
    def minimumDifference(self, nums: List[int], k: int) -> int:
        ans = inf
        s = set()
        for x in nums:
            s = set(x & y for y in s) # AND x for all results
            s.add(x)
            for y in s:
                ans = min(ans, abs(k - y))
        
        return ans