LeetCode 雙週賽 98,腦筋急轉彎轉不過來!

語言: CN / TW / HK

theme: jzman

我正在參加「掘金·啟航計劃」

大家好,我是小彭。

昨晚是 LeetCode 第 98 場雙週賽,你參加了嗎?這場周賽需要腦筋急轉彎,轉不過來 Medium 就會變成 Hard,轉得過來就變成 Easy。


小彭的 Android 交流群 02 群已經建立啦,公眾號回覆 “加群” 加入我們~


2566. 替換一個數字後的最大差值(Easy)

題目地址

http://leetcode.cn/problems/maximum-difference-by-remapping-a-digit/

題目描述

給你一個整數 num 。你知道 Danny Mittal 會偷偷將 0 到 9 中的一個數字 替換 成另一個數字。

請你返回將 num 中 恰好一個 數字進行替換後,得到的最大值和最小值的差位多少。

注意:

  • 當 Danny 將一個數字 d1 替換成另一個數字 d2 時,Danny 需要將 nums 中所有 d1 都替換成 d2 。
  • Danny 可以將一個數字替換成它自己,也就是說 num 可以不變。
  • Danny 可以將數字分別替換成兩個不同的數字分別得到最大值和最小值。
  • 替換後得到的數字可以包含前導 0 。
  • Danny Mittal 獲得周賽 326 前 10 名,讓我們恭喜他。

題解(字串操作)

  • 技巧:將整型轉換為字串能夠更方便地修改具體位置。

簡單模擬題,有 2 個思路:

  • 思路 1 - 暴力列舉:嘗試列舉每類的數字,將其替換為 9 取得最大值,將其替換為 0 取得最小值,最後取所有方案的最大值和最小值取差值;
  • 思路 2 - 貪心思路:替換越靠近 “高位” 的數字能夠使得差值越大,所以我們將從高位開始的首個非 9 數字替換為 9(例如 90 替換為 99)必然得到最大值,將從高位開始的首個數字替換為 0(例如 90 替換為 00)必然得到最小值。

kotlin // 思路 1 class Solution { fun minMaxDifference(num: Int): Int { val numStr = "$num" var max = num var min = num for (element in numStr) { max = Math.max(max, numStr.replace(element, '9').toInt()) min = Math.min(min, numStr.replace(element, '0').toInt()) } return max - min } }

複雜度分析:

  • 時間複雜度:$O(log^2\,{num})$ 數字最多有 log num 位,外層迴圈與記憶體迴圈的字串替換操作都是 $O(log\,{num})$ 時間級別複雜度;
  • 空間複雜度:$O(log\,{num})$ 字串佔用空間。

kotlin // 思路 2 class Solution { fun minMaxDifference(num: Int): Int { val numStr = "$num" val min = numStr.replace(numStr[0], '0').toInt() var max = num for (element in numStr) { if ('9' != element) { max = numStr.replace(element, '9').toInt() break } } return max - min } }

複雜度分析:

  • 時間複雜度:$O(log\,{num})$ 記憶體迴圈的字串替換操作最多隻會執行一次,均攤下來整體只有 $O(log\,{num})$ 級別的時間複雜度;
  • 空間複雜度:$O(log\,{num})$ 字串佔用空間。

2567. 修改兩個元素的最小分數(Medium)

題目地址

http://leetcode.cn/problems/minimum-score-by-changing-two-elements/

題目描述

給你一個下標從 0 開始的整數陣列 nums 。

  • nums 的 最小 得分是滿足 0 <= i < j < nums.length 的 |nums[i] - nums[j]| 的最小值。
  • nums的 最大 得分是滿足 0 <= i < j < nums.length 的 |nums[i] - nums[j]| 的最大值。
  • nums 的分數是 最大 得分與 最小 得分的和。

我們的目標是最小化 nums 的分數。你 最多 可以修改 nums 中 2 個元素的值。

請你返回修改 nums 中 至多兩個 元素的值後,可以得到的 最小分數 。

|x| 表示 x 的絕對值。

題解(排序 + 列舉)

這道題也有腦筋急轉彎的成分,同時我們可以擴充套件思考下 “最多修改 k 個元素的最小得分” 問題,最後再說。

這道題的關鍵在於得分的定義:

  • “最小得分” 表示任意陣列中兩個數字之間的最小絕對差;
  • “最大得分” 表示任意陣列中兩個數字之間的最大絕對差。

理解題意後容易發現:

  • 影響 “最小得分” 的是陣列中最接近的兩個數字。當陣列中存在兩個相同元素時,“最小得分” 可以取到最小值 0;
  • 影響 “最大得分” 的是陣列中最不接近的兩個數,即最大值和最小值。當我們將最大值和最小值修改為陣列中間的某個元素時,能使得差值變小的同時,保持 “最小得分” 取最小值 0。

因此得知: 這道題的關鍵點在於修改陣列的最大值或最小值成為陣列中間的某個元素。 要麼讓最大值變小,要麼讓最小值變大。由於題目最多隻能修改 2 次,因此最多隻能以下 3 種情況:

  • 情況 1:修改陣列中最大的兩個數為 nums[n - 3]
  • 情況 2:修改陣列中最小的兩個數為 nums[2]
  • 情況 3:修改陣列的最大值為 nums[n - 1],修改陣列的最小值為 nums[1]

簡單枚舉出 3 種情況的解後再進行一輪比較即可。

最後再觀察邊界條件,陣列的最小長度為 3,所以不需要特判。

kotlin class Solution { fun minimizeSum(nums: IntArray): Int { nums.sort() val n = nums.size val choice1 = nums[n - 3] - nums[0] val choice2 = nums[n - 1] - nums[2] val choice3 = nums[n - 2] - nums[1] return Math.min(choice1, Math.min(choice2, choice3)) } }

複雜度分析:

  • 時間複雜度:$O(nlgn)$ 快速排序佔用的時間,如果手動維護最小的 3 個元素和最大的 3 個元素可以降低到 $O(n)$ 時間複雜度;
  • 空間複雜度:$O(lgn)$ 排序佔用的遞迴棧空間。

再擴充套件思考一下,如果題目說明最多可以修改 $k (0 ≤ k ≤ nums.length)$次的話,應該解決問題呢? —— 即 “求最多修改 k 個元素的最小得分”,原題就是 k = 2 的情況。

那麼這道題就是考察 “滑動視窗” 技巧了,我們可以將修改的範圍視為一個跨越陣列首尾且長度為 k 的滑動視窗,那麼而問題的答案就取決於 “不被” 滑動視窗包圍的另一部分。再逆向思考一下,我們可以用長度為 length - k 的滑動視窗在陣列上移動,並記錄視窗首尾元素的差值,列舉所有情況後記錄最小值即為最小得分:

舉個例子,在輸入陣列為 [1, 4, 5, 7, 8] ,k = 2 時,前文提到的 3 種方案分別對應以下 3 個視窗狀態:

  • 情況 1:修改陣列中最大的兩個數:1,4 | 5,7,8 |
  • 情況 2:修改陣列中最小的兩個數:| 1,4,5 | 7,8
  • 情況 3:修改陣列的最大值和最小值:1 | 4,5,7 | 8

kotlin class Solution { fun minimizeSum(nums: IntArray): Int { val n = nums.size // 操作次數 val k = 2 // 滑動視窗 val len = n - k nums.sort() var min = Integer.MAX_VALUE for (left in 0..n - len) { val right = left + len - 1 min = Math.min(min, nums[right] - nums[left]) } return min } }

複雜度分析同上。


2568. 最小無法得到的或值(Medium)

題目地址

http://leetcode.cn/problems/minimum-impossible-or/

題目描述

給你一個下標從 0 開始的整數陣列 nums 。

如果存在一些整數滿足 0 <= index1 < index2 < ... < indexk < nums.length ,得到 nums[index1] | nums[index2] | ... | nums[indexk] = x ,那麼我們說 x 是 可表達的 。換言之,如果一個整數能由 nums 的某個子序列的或運算得到,那麼它就是可表達的。

請你返回 nums 不可表達的 最小非零整數 。

題解一(散列表)

相似題目:2154. 將找到的值乘以 2

這道題需要腦筋急轉彎。

首先,我們先觀察輸入資料範圍中小數值的二進位制表示,嘗試發現規律:

  • 0 = 0000 = 0
  • 1 = 0001 = 1
  • 2 = 0010 = 2
  • 3 = 0011 = 2 | 1
  • 4 = 0100 = 4
  • 5 = 0101 = 4 | 1
  • 6 = 0110 = 4 | 2
  • 7 = 0111 = 4 | 2 | 1,或者 5 | 1
  • 8 = 1000 = 8
  • 9 = 1001 = 8 | 1
  • 10 = 1010 = 8 | 2

我們發現以下 2 點資訊:

  • 除了數字 7 = 5 | 1 的特殊方案外,其他數字的表示方案都可以由形如 $x = 2^i | 2^j | 2^ k$ 的格式表達(很容易理解);
  • $2^i$ 格式的數字不可能被其他數用 “或” 的形式表示(也很容易理解)。

由此可以得出結論: 影響陣列最小可表達數的關鍵在於陣列中 “未出現的最小的 $2^i$”,並且這個數就是不可表達的最小非零數。

舉例說明:假設 8 是陣列中未出現的最小 $2^i$(此時 [1, 2, 4] 肯定在陣列中出現$2^i$),那麼數字 1 ~ 7 之間的所有數字都可以由 [1、2、4] 通過或表示,而 8 無法被 [1, 2, 3, 4, 5, 6 ,7] 之間的任何數字表達,同時也無法被大於 8 的其他數表示,因此 8 就是最小的可表達數。

完成問題轉換後編碼就很容易了,我們只要從小到大列舉所有 $2^i$ ,並檢查它是否在陣列中出現即可:

kotlin class Solution { fun minImpossibleOR(nums: IntArray): Int { val numSet = nums.toHashSet() var i = 1 while (numSet.contains(i)) { i = i shl 1 } return i } }

複雜度分析:

  • 時間複雜度:$O(n + logU)$ 其中 n 是陣列長度,U 是陣列的最大值,最多隻需要檢查 logU 位數字;
  • 空間複雜度:$O(n)$ 散列表佔用的空間。

題解二(位運算)

題解一使用散列表來輔助判斷 $2^i$ 是否存在於陣列中,可以進一步優化:我們將直接從陣列元素的二進位制資料中提取特徵值,並還原出 “未出現的最小的 $2^i$”:

  • 1、遍歷陣列中所有元素,如果元素值是 $2^i$ 則將其記錄到 mask 特徵值中;
  • 2、遍歷結束後將得到形如 0011, 1011 格式的特徵值,此時 “未出現的最小的 $2^i$” 正好位於從低位到高位出現的首個 0 的位置,即 0000, 0100;
  • 3、為了還原出目標數,執行以下位運算:

kotlin x = ~x // 按位取反: 0011,1011 => 1100,0100 x & -x // lowbit 公式:1100,0100 => 0000,0100

```kotlin class Solution { fun minImpossibleOR(nums: IntArray): Int { var mask = 0 for (x in nums) { // x & (x - 1) 將消除最低位的 1,如果消除後值為 1 說明 x 本身就是 2 的冪 if (x and (x - 1) == 0) mask = mask or x } // 取反 mask = mask.inv() // 取最低位 1 return mask and -mask } }

```

複雜度分析:

  • 時間複雜度:$O(n)$ 其中 n 是陣列長度;
  • 空間複雜度:$O(1)$ 僅佔用常數級別空間。

2569. 更新陣列後處理求和查詢(Hard)

題目地址

http://leetcode.cn/problems/handling-sum-queries-after-update/

題目描述

給你兩個下標從 0 開始的陣列 nums1 和 nums2 ,和一個二維陣列 queries 表示一些操作。總共有 3 種類型的操作:

  1. 操作型別 1 為 queries[i] = [1, l, r] 。你需要將 nums1 從下標 l 到下標 r 的所有 0 反轉成 1 或將 1 反轉成 0 。l 和 r 下標都從 0 開始。
  2. 操作型別 2 為 queries[i] = [2, p, 0] 。對於 0 <= i < n 中的所有下標,令 nums2[i] = nums2[i] + nums1[i] * p 。
  3. 操作型別 3 為 queries[i] = [3, 0, 0] 。求 nums2 中所有元素的和。

請你返回一個數組,包含所有第三種操作型別的答案。

預備知識

類似的區間求和問題,我們先回顧一下解決方案:

  • 1、靜態陣列求區間和:「字首和陣列」、「樹狀陣列」、「線段樹」
  • 2、頻繁單點更新,求區間和:「樹狀陣列」、「線段樹」
  • 3、頻繁區間更新,求具體位置:「差分陣列」
  • 4、頻繁區間更新,求區間和:「線段樹 + 懶更新」

這道題涉及 “區間更新” 和 “區間求和”,所以屬於線段樹的典型例題。

題解一(樸素線段樹)

我們先理解題目中三種操作的含義:

  • 操作一:對 nums1 陣列中位於 [left, right] 區間的數進行反轉,也就是進行 “區間更新”;
  • 操作二:將 nums1 陣列上的數值 nums1[index] 乘以 p 後累加到 nums2 陣列的相同位置上,即 nums2[index] += nums1[index] * p,同樣也是進行 “區間更新”;
  • 操作三:求 nums2 陣列中所有元素和,即 “求區間和”。

OK,既然操作一和操作二是對不同陣列進行 “區間更新”,那麼我們需要分別為這兩個陣列建立線段樹嗎?並不需要,這是題目丟擲的煙霧彈。

因為題目最終的解是求 nums2 陣列的全體和,所以我們並不需要真正地維護 nums2 陣列,只需要將操作二的增量累加到全體和中。這樣的話就是隻需要維護 nums1 陣列的線段樹。

理解題意後,我們可以寫出題解的主框架:

  • 1、首先計算 nums2 陣列的初始全體和 sum
  • 2、建立 nums1 陣列的線段樹;
  • 3、依次處理每種操作,操作一對線段樹做區間更新,操作二對線段樹做區間求和後乘以 p,並累加到全體和 sum 中,操作三將 sum 推入結果列表。

```kotlin // 程式主框架 class Solution { fun handleQuery(nums1: IntArray, nums2: IntArray, queries: Array): LongArray { val n = nums1.size val resultList = LinkedList() // 全體和 var sum = 0L for (num in nums2) { sum += num } val tree = SegementTree(nums1) for (query in queries) { when (query[0]) { 1 -> { // 區間更新 tree.update(query[1], query[2]) } 2 -> { // 求區間和(nums[index] * p) sum += 1L * query[1] * tree.query(0, n - 1) } 3 -> { // 記錄 resultList.add(sum) } } } return resultList.toLongArray() }

private class SegementTree(private val data: IntArray) {

    // 區間更新(反轉)
    fun update(left: Int, right: Int) {

    }

    // 單點更新(反轉)- 本題不需要
    fun set(pos: Int) {

    }

    // 區間查詢
    fun query(left: Int, right: Int): Int {

    }
}

} ```

接下來就是實現線段樹的內部程式碼了。

  • 技巧 1:這道題的更新操作是對 0/ 1 反轉,我們可以用異或來實現;
  • 技巧 2:相對於在函式中重複傳遞節點所代表的區間範圍(例如 update(i: int, l: int, r: int, L: int, R: int)),使用 Node 節點記錄更為方便。

```kotlin class Solution { fun handleQuery(nums1: IntArray, nums2: IntArray, queries: Array): LongArray { val n = nums1.size val resultList = LinkedList() // 全體和 var sum = 0L for (num in nums2) { sum += num } val tree = SegementTree(nums1) for (query in queries) { when (query[0]) { 1 -> { // 區間更新 tree.update(query[1], query[2]) } 2 -> { // 求區間和(nums[index] * p) sum += 1L * query[1] * tree.query(0, n - 1) } 3 -> { // 記錄 resultList.add(sum) } } } return resultList.toLongArray() }

private class SegementTree(private val data: IntArray) {
    // 線段樹節點(區間範圍與區間值)
    private class Node(val left: Int, val right: Int, var value: Int)

    // 線段樹陣列
    private val tree = Array<Node?>(4 * data.size) { null } as Array<Node>

    // 左子節點的索引
    private val Int.left get() = this * 2 + 1

    // 右子節點的索引
    private val Int.right get() = this * 2 + 2

    init {
        // 建樹
        buildNode(0, 0, data.size - 1)
    }

    // 構建線段樹節點
    private fun buildNode(index: Int, left: Int, right: Int) {
        if (left == right) {
            // 葉子節點
            tree[index] = Node(left, right, data[left])
            return
        }
        val mid = (left + right) ushr 1
        // 構建左子節點
        buildNode(index.left, left, mid)
        // 構建左子節點
        buildNode(index.right, mid + 1, right)
        // 合併左右子節點
        tree[index] = Node(left, right, tree[index.left].value + tree[index.right].value)
    }

    // 區間更新(反轉)
    fun update(left: Int, right: Int) {
        update(0, left, right)
    }

    // 區間更新(反轉)
    private fun update(index: Int, left: Int, right: Int) {
        // 1、當前節點不處於區間範圍內
        if (tree[index].left > right || tree[index].right < left) return
        // 2、葉子節點
        if (tree[index].left == tree[index].right) {
            // 反轉:0->1,1->0
            tree[index].value = tree[index].value xor 1
            return
        }
        // 3、更新左子樹
        update(index.left, left, right)
        // 4、更新右子樹
        update(index.right, left, right)
        // 5、合併子節點的結果
        tree[index].value = tree[index.left].value + tree[index.right].value
    }

    // 單點更新(反轉)- 本題不需要
    fun set(pos: Int) {
        set(0, pos)
    }

    // 單點更新(反轉)- 本題不需要
    private fun set(index: Int, pos: Int) {
        // 1、當前節點不處於區間範圍內
        if (tree[index].left > pos || tree[index].right < pos) return
        // 2、葉子節點
        if (tree[index].left == tree[index].right) {
            // 反轉:0->1,1->0
            tree[index].value = tree[index].value xor 1
            return
        }
        // 3、更新左子樹
        set(index.left, pos)
        // 4、更新右子樹
        set(index.right, pos)
        // 5、合併子節點的結果
        tree[index].value = tree[index.left].value + tree[index.right].value
    }

    // 區間查詢
    fun query(left: Int, right: Int): Int {
        return query(0, left, right)
    }

    // 區間查詢
    private fun query(index: Int, left: Int, right: Int): Int {
        // 1、當前節點不處於區間範圍內
        if (tree[index].left > right || tree[index].right < left) return 0
        // 2、當前節點完全處於區間範圍之內
        if (tree[index].left >= left && tree[index].right <= right) return tree[index].value
        // 3、合併子節點的結果
        return query(index.left, left, right) + query(index.right, left, right)
    }
}

} ```

複雜度分析:

  • 時間複雜度:$O(n + q_1n + q_2)$ 其中 n 是 nums1 陣列長度,$q_1$ 是操作一的個數,$q_2$ 是操作二的個數。我們需要花費 $O(n)$ 時間建樹,操作一線段樹區間更新的時間複雜度是 $O(n)$,操作二線段樹區間查詢的複雜度是 $O(lgn)$,但本題中的查詢正好是線段樹根節點,所以操作二實際上只需要 $O(1)$ 複雜度。
  • 空間複雜度:$O(n)$ 線段樹空間。

樸素線段樹解法在本題中會超時,我們需要優化為 “懶更新” 的線段樹實現。

題解二(線段樹 + 懶更新)

樸素線段樹的效能瓶頸在於:區間更新需要改動從根節點到葉子節點中所有與目標區間有交集的節點,因此單次區間更新操作的時間複雜度是 $O(n)$。

懶更新線段樹的核心思想是:當一個節點代表的區間完全包含於目標區間內時,我們沒有必要繼續向下遞迴更新,而是在當前節點上標記 Lazy Tag 。只有將來更新該節點的某個子區間時,才會將懶更新 pushdown 到子區間。

舉個例子:在長度為 10 的線段樹中執行 [1,10][1,5] 兩次區間更新操作(對區間內的元素加一):

  • [1,10] 區間更新:從根節點出發,此時發現根節點與目標區間 [1,10] 完全相同,那麼只更新根節點並標記 Lazy Tag,更新結束;
  • [1,5] 區間更新:從根節點出發,此時發現根節點有 Lazy Tag,那麼需要先將懶更新 pushdown[1,5][6,10] 兩個子節點,然後再更新 [1,5] 區間。
  • 到目前為止,[1,10][1,5] 節點被修改 2 次,[6,10] 節點被修改 1 次,其它節點沒有被修改。

接下來就是實現線段樹的內部程式碼了。

  • 技巧 1:0 /1 反轉是負負得正的,所以 Lazy Tag 可以用 Boolean 型別表示,true 表示被反轉;
  • 技巧 2:區間反轉可以用區間長度 - 舊值實現,即:value = right - left + 1 - value

提示:相比題解一改動的函式有 【懶更新】 標記 。

```kotlin class Solution { fun handleQuery(nums1: IntArray, nums2: IntArray, queries: Array): LongArray { val n = nums1.size val resultList = LinkedList() // 全體和 var sum = 0L for (num in nums2) { sum += num } val tree = LazySegementTree(nums1) for (query in queries) { when (query[0]) { 1 -> { // 區間更新 tree.update(query[1], query[2]) } 2 -> { // 求區間和(nums[index] * p) sum += 1L * query[1] * tree.query(0, n - 1) } 3 -> { // 記錄 resultList.add(sum) } } } return resultList.toLongArray() }

private class LazySegementTree(private val data: IntArray) {
    // 線段樹節點(區間範圍與區間值)【懶更新】
    private class Node(val left: Int, val right: Int, var value: Int, var lazy: Boolean = false)

    // 線段樹陣列
    private val tree = Array<Node?>(4 * data.size) { null } as Array<Node>

    // 左子節點的索引
    private val Int.left get() = this * 2 + 1

    // 右子節點的索引
    private val Int.right get() = this * 2 + 2

    init {
        // 建樹
        buildNode(0, 0, data.size - 1)
    }

    // 構建線段樹節點
    private fun buildNode(index: Int, left: Int, right: Int) {
        if (left == right) {
            // 葉子節點
            tree[index] = Node(left, right, data[left])
            return
        }
        val mid = (left + right) ushr 1
        // 構建左子節點
        buildNode(index.left, left, mid)
        // 構建左子節點
        buildNode(index.right, mid + 1, right)
        // 合併左右子節點
        tree[index] = Node(left, right, tree[index.left].value + tree[index.right].value)
    }

    // 區間更新(反轉)
    fun update(left: Int, right: Int) {
        update(0, left, right)
    }

    // 區間更新(反轉)【懶更新】
    private fun update(index: Int, left: Int, right: Int) {
        // 1、當前節點不處於區間範圍內
        if (tree[index].left > right || tree[index].right < left) return
        // 2、當前節點完全處於區間範圍之內
        if (tree[index].left >= left && tree[index].right <= right) {
            lazyUpdate(index)
            return
        }
        // 3、pushdown 到子節點
        if (tree[index].lazy) {
            lazyUpdate(index.left)
            lazyUpdate(index.right)
            tree[index].lazy = false
        }
        // 4、更新左子樹
        update(index.left, left, right)
        // 5、更新右子樹
        update(index.right, left, right)
        // 6、合併子節點的結果
        tree[index].value = tree[index.left].value + tree[index.right].value
    }

    // 單點更新(反轉)- 本題不需要
    fun set(pos: Int) {
        set(0, pos)
    }

    // 單點更新(反轉)【懶更新】- 本題不需要
    private fun set(index: Int, pos: Int) {
        // 1、當前節點不處於區間範圍內
        if (tree[index].left > pos || tree[index].right < pos) return
        // 2、葉子節點
        if (tree[index].left == tree[index].right) {
            lazyUpdate(index)
            return
        }
        // 3、pushdown 到子節點
        if (tree[index].lazy) {
            lazyUpdate(index.left)
            lazyUpdate(index.right)
            tree[index].lazy = false
        }
        // 4、更新左子樹
        set(index.left, pos)
        // 5、更新右子樹
        set(index.right, pos)
        // 6、合併子節點的結果
        tree[index].value = tree[index.left].value + tree[index.right].value
    }

    // 區間查詢
    fun query(left: Int, right: Int): Int {
        return query(0, left, right)
    }

    // 區間查詢
    private fun query(index: Int, left: Int, right: Int): Int {
        // 1、當前節點不處於區間範圍內
        if (tree[index].left > right || tree[index].right < left) return 0
        // 2、當前節點完全處於區間範圍之內
        if (tree[index].left >= left && tree[index].right <= right) return tree[index].value
        // 3、pushdown 到子節點
        if (tree[index].lazy) {
            lazyUpdate(index.left)
            lazyUpdate(index.right)
            tree[index].lazy = false
        }
        // 4、合併子節點的結果
        return query(index.left, left, right) + query(index.right, left, right)
    }

    // 懶更新
    private fun lazyUpdate(index: Int) {
        // 反轉
        tree[index].value = tree[index].right - tree[index].left + 1 - tree[index].value
        // 標記(負負得正)
        tree[index].lazy = !tree[index].lazy
    }
}

} ```

複雜度分析:

  • 時間複雜度:$O(n + q_1lgn + q_2)$ 其中 n 是 nums1 陣列長度,$q_1$ 是操作一的個數,$q_2$ 是操作二的個數。
  • 空間複雜度:$O(n)$ 線段樹空間。

我們下週見,有用請點贊關注!

本文已收錄到 AndroidFamily,技術和職場問題,請關注公眾號 [彭旭銳] 提問。