普通视图

发现新文章,点击刷新页面。
今天 — 2025年1月18日LeetCode 每日一题题解

求出数组中最大序列值

2025年1月3日 09:21

方法一:动态规划

思路

长度为 $2k$ 的序列的值的计算方式为,先将其拆成长度相同的前后子串,然后分别计算各子串的所有元素的按位或的结果,再将两个按位或的结果进行按位异或。为了求出 $\textit{nums}$ 的长度为 $2k$ 的子序列的最大值,可以将 $\textit{nums}$ 分成左右两半,分别求出每一半中,长度为 $k$ 的子序列的按位或的所有可能性,再两两求异或的最大值。这样的分法一共有 $n-2k+1$ 种,其中 $n$ 为数组 $\textit{nums}$ 的长度。并且我们可以用动态规划的方法来求出长度为 $k$ 的子序列的按位或的结果的所有可能性。

记 $\textit{dp}[i][j]$ 为哈希集合,表示 $\textit{nums}[0...i]$ 中长度为 $j$ 的自序列按位或的所有可能性。$j \leq k$,且当 $j$ 为 $0$ 时,$\textit{dp}[i][j]$ 为空集。又根据题目限制,$\textit{nums}$ 元素的值为 $0$ 到 $127$,因此 $\textit{dp}[i][j]$ 最多包含 $128$ 个元素。进行转移时,$\textit{dp}[i][j]$ 首先会包含 $\textit{dp}[i-1][j]$ 的所有元素,表示不选取 $\textit{nums}[i]$。还可以将 $\textit{dp}[i-1][j-1]$ 中的所有元素与 $\textit{nums}[i]$ 进行按位或,将结果添加进 $\textit{dp}[i][j]$,表示选取$\textit{nums}[i]$。我们可以将上述步骤抽象成一个函数,并进一步降低空间复杂度,只返回值 $j=k$ 时的 $dp$值,可以算出左一半的长度为 $k$ 的子序列的按位或的所有可能性。再将 $\textit{nums}$ 进行逆序后再次应用这个函数,即可计算出右一半的的长度为 $k$ 的子序列的按位或的所有可能性。再两两求异或的最大值返回。

我们可以将

代码

###Python

class Solution:
    def maxValue(self, nums: List[int], k: int) -> int:
        def findORs(nums: List[int], k: int) -> List[set[int]]:
            dp = []
            prev = [set() for _ in range(k + 1)]
            prev[0].add(0) 
            for i, num in enumerate(nums):
                for j in range(min(k - 1, i + 1), -1, -1):
                    for x in prev[j]:
                        prev[j + 1].add(x | num)
                dp.append(prev[k].copy())
            return dp

        A = findORs(nums, k)
        B = findORs(nums[::-1], k)

        mx = 0
        for i in range(k - 1, len(nums) - k):
                mx = max(mx, *(a ^ b for a in A[i] for b in B[-i - 2]))
        return mx

###Java

class Solution {
    public int maxValue(int[] nums, int k) {
        List<Set<Integer>> A = findORs(nums, k);
        List<Set<Integer>> B = findORs(reverse(nums), k);
        int mx = 0;
        for (int i = k - 1; i < nums.length - k; i++) {
            for (int a : A.get(i)) {
                for (int b : B.get(nums.length - i - 2)) {
                    mx = Math.max(mx, a ^ b);
                }
            }
        }
        return mx;
    }

    private List<Set<Integer>> findORs(int[] nums, int k) {
        List<Set<Integer>> dp = new ArrayList<>();
        List<Set<Integer>> prev = new ArrayList<>();
        for (int i = 0; i <= k; i++) {
            prev.add(new HashSet<>());
        }
        prev.get(0).add(0);
        for (int i = 0; i < nums.length; i++) {
            for (int j = Math.min(k - 1, i + 1); j >= 0; j--) {
                for (int x : prev.get(j)) {
                    prev.get(j + 1).add(x | nums[i]);
                }
            }
            dp.add(new HashSet<>(prev.get(k)));
        }
        return dp;
    }

    private int[] reverse(int[] nums) {
        int[] reversed = new int[nums.length];
        for (int i = 0; i < nums.length; i++) {
            reversed[i] = nums[nums.length - 1 - i];
        }
        return reversed;
    }
}

###C#

public class Solution {
    public int MaxValue(int[] nums, int k) {
        var A = FindORs(nums, k);
        Array.Reverse(nums);
        var B = FindORs(nums, k);
        int mx = 0;
        for (int i = k - 1; i < nums.Length - k; i++) {
            foreach (int a in A[i]) {
                foreach (int b in B[nums.Length - i - 2]) {
                    mx = Math.Max(mx, a ^ b);
                }
            }
        }
        return mx;
    }

    public List<HashSet<int>> FindORs(int[] nums, int k) {
        var dp = new List<HashSet<int>>();
        var prev = new List<HashSet<int>>();
        for (int i = 0; i <= k; i++) {
            prev.Add(new HashSet<int>());
        }
        prev[0].Add(0);
        for (int i = 0; i < nums.Length; i++) {
            for (int j = Math.Min(k - 1, i + 1); j >= 0; j--) {
                foreach (int x in prev[j]) {
                    prev[j + 1].Add(x | nums[i]);
                }
            }
            dp.Add(new HashSet<int>(prev[k]));
        }
        return dp;
    }
}

###C++

class Solution {
public:
    int maxValue(vector<int>& nums, int k) {
        auto findORs = [&](const vector<int>& nums, int k) -> vector<unordered_set<int>> {
            vector<unordered_set<int>> dp;
            vector<unordered_set<int>> prev(k + 1);
            prev[0].insert(0);
            for (int i = 0; i < nums.size(); ++i) {
                for (int j = min(k - 1, i + 1); j >= 0; --j) {
                    for (int x : prev[j]) {
                        prev[j + 1].insert(x | nums[i]);
                    }
                }
                dp.push_back(prev[k]);
            }
            return dp;
        };

        vector<unordered_set<int>> A = findORs(nums, k);
        vector<unordered_set<int>> B = findORs(vector<int>(nums.rbegin(), nums.rend()), k);
        int mx = 0;
        for (size_t i = k - 1; i < nums.size() - k; ++i) {
            for (int a : A[i]) {
                for (int b : B[nums.size() - i - 2]) {
                    mx = max(mx, a ^ b);
                }
            }
        }
        return mx;
    }
};

###Go

func maxValue(nums []int, k int) int {
    findORs := func(nums []int, k int) []map[int]bool {
dp := make([]map[int]bool, 0)
prev := make([]map[int]bool, k + 1)
for i := 0; i <= k; i++ {
prev[i] = make(map[int]bool)
}
prev[0][0] = true

for i := 0; i < len(nums); i++ {
for j := min(k - 1, i + 1); j >= 0; j-- {
for x := range prev[j] {
prev[j + 1][x | nums[i]] = true
}
}
current := make(map[int]bool)
for key := range prev[k] {
current[key] = true
}
dp = append(dp, current)
}
return dp
}

A := findORs(nums, k)
reverse(nums)
B := findORs(nums, k)
mx := 0

for i := k - 1; i < len(nums) - k; i++ {
for a := range A[i] {
for b := range B[len(nums) - i - 2] {
if a ^ b > mx {
mx = a ^ b
}
}
}
}
return mx
}

func reverse(nums []int) {
for i, j := 0, len(nums)-1; i < j; i, j = i+1, j-1 {
nums[i], nums[j] = nums[j], nums[i]
}
}

###C

typedef struct {
    int key;
    UT_hash_handle hh;
} HashItem; 

HashItem *hashFindItem(HashItem **obj, int key) {
    HashItem *pEntry = NULL;
    HASH_FIND_INT(*obj, &key, pEntry);
    return pEntry;
}

bool hashAddItem(HashItem **obj, int key) {
    if (hashFindItem(obj, key)) {
        return false;
    }
    HashItem *pEntry = (HashItem *)malloc(sizeof(HashItem));
    pEntry->key = key;
    HASH_ADD_INT(*obj, key, pEntry);
    return true;
}

void hashFree(HashItem **obj) {
    HashItem *curr = NULL, *tmp = NULL;
    HASH_ITER(hh, *obj, curr, tmp) {
        HASH_DEL(*obj, curr);  
        free(curr);
    }
}

void findORs(int* nums, int numsSize, int k, HashItem** dp) {
    HashItem *prev[k + 1];
    for (int i = 0; i <= k; i++) {
        prev[i] = NULL;
    }
    hashAddItem(&prev[0], 0);
    for (int i = 0; i < numsSize; ++i) {
        for (int j = fmin(k - 1, i + 1); j >= 0; --j) {
            for (HashItem *pEntry = prev[j]; pEntry; pEntry = pEntry->hh.next) {
                int x = pEntry->key;
                hashAddItem(&prev[j + 1], x | nums[i]);
            }
        }
        for (HashItem *pEntry = prev[k]; pEntry; pEntry = pEntry->hh.next) {
            int x = pEntry->key;
            hashAddItem(&dp[i], x);
        }
    }
    for (int i = 0; i <= k; i++) {
        hashFree(&prev[i]);
    }
};

void reverse(int *nums, int numsSize) {
    for (int i = 0, j = numsSize - 1; i < j; i++, j--) {
        int x = nums[i];
        nums[i] = nums[j];
        nums[j] = x;
    }
}

int maxValue(int* nums, int numsSize, int k) {
    HashItem *A[numsSize];
    HashItem *B[numsSize];

    for (int i = 0; i < numsSize; i++) {
        A[i] = B[i] = NULL;
    }
    findORs(nums, numsSize, k, A);
    reverse(nums, numsSize);
    findORs(nums, numsSize, k, B);
    int mx = 0;
    for (size_t i = k - 1; i < numsSize - k; ++i) {
        for (HashItem *pEntry = A[i]; pEntry; pEntry = pEntry->hh.next) {
            int a = pEntry->key;
            for (HashItem *pEntry = B[numsSize - i - 2]; pEntry; pEntry = pEntry->hh.next) {
                int b = pEntry->key;
                mx = fmax(mx, a ^ b);
            }
        }
    }
    for (int i = 0; i < numsSize; i++) {
        hashFree(&A[i]);
        hashFree(&B[i]);
    }
    return mx;
}

###JavaScript

var maxValue = function(nums, k) {
    function findORs(nums, k) {
        const dp = [];
        const prev = Array.from({ length: k + 1 }, () => new Set());
        prev[0].add(0);

        for (let i = 0; i < nums.length; i++) {
            for (let j = Math.min(k - 1, i + 1); j >= 0; j--) {
                for (const x of prev[j]) {
                    prev[j + 1].add(x | nums[i]);
                }
            }
            dp.push(new Set(prev[k]));
        }
        return dp;
    }

    const A = findORs(nums, k);
    nums.reverse();
    const B = findORs(nums, k);
    let mx = 0;

    for (let i = k - 1; i < nums.length - k; i++) {
        for (const a of A[i]) {
            for (const b of B[nums.length - i - 2]) {
                mx = Math.max(mx, a ^ b);
            }
        }
    }
    return mx;
};

###TypeScript

function maxValue(nums: number[], k: number): number {
    function findORs(nums: number[], k: number): Set<number>[] {
        const dp: Set<number>[] = [];
        const prev: Set<number>[] = Array.from({ length: k + 1 }, () => new Set());
        prev[0].add(0);

        for (let i = 0; i < nums.length; i++) {
            for (let j = Math.min(k - 1, i + 1); j >= 0; j--) {
                for (const x of prev[j]) {
                    prev[j + 1].add(x | nums[i]);
                }
            }
            dp.push(new Set(prev[k]));
        }
        return dp;
    }

    const A = findORs(nums, k);
    nums.reverse();
    const B = findORs(nums, k);
    let mx = 0;

    for (let i = k - 1; i < nums.length - k; i++) {
        for (const a of A[i]) {
            for (const b of B[nums.length - i - 2]) {
                mx = Math.max(mx, a ^ b);
            }
        }
    }
    return mx;
};

###Rust

use std::collections::HashSet;
use std::cmp::{max, min};

impl Solution {
    pub fn max_value(nums: Vec<i32>, k: i32) -> i32 {
        fn find_ors(nums: &Vec<i32>, k: i32) -> Vec<HashSet<i32>> {
            let mut dp = Vec::new();
            let mut prev = vec![HashSet::new(); k as usize + 1];
            prev[0].insert(0);

            for i in 0..nums.len() {
                for j in (0..= min(k as usize - 1, i + 1)).rev() {
                    let (before, after) = prev.split_at_mut(j + 1);
                    for &x in &before[j] {
                        after[0].insert(x | nums[i]);
                    }
                }
                dp.push(prev[k as usize].clone());
            }
            dp
        }

        let a = find_ors(&nums, k);
        let reversed_nums: Vec<i32> = nums.iter().rev().cloned().collect();
        let b = find_ors(&reversed_nums, k);
        let mut mx = 0;
        for i in k as usize - 1..nums.len() - k as usize {
            for &a_val in &a[i] {
                for &b_val in &b[nums.len() - i - 2] {
                    mx = mx.max(a_val ^ b_val);
                }
            }
        }
        mx
    }
}

复杂度分析

  • 时间复杂度:$O(n \times k \times U + n \times U^2)$,其中 $n$ 是数组 $\textit{nums}$ 的长度,$U$ 是数组元素的可能性集合大小。$\textit{dp}$ 的状态有 $O(n\times k)$ 种,每个状态消耗 $O(U)$ 计算。最后两两求异或消耗 $O(n\times U^2)$ 的时间。

  • 空间复杂度:$O(n \times U)$。

每日一题-求出数组中最大序列值🔴

2025年1月18日 00:00

给你一个整数数组 nums 和一个  整数 k 。

定义长度为 2 * x 的序列 seq 的  为:

  • (seq[0] OR seq[1] OR ... OR seq[x - 1]) XOR (seq[x] OR seq[x + 1] OR ... OR seq[2 * x - 1]).

请你求出 nums 中所有长度为 2 * k 的 子序列最大值 。

 

示例 1:

输入:nums = [2,6,7], k = 1

输出:5

解释:

子序列 [2, 7] 的值最大,为 2 XOR 7 = 5 。

示例 2:

输入:nums = [4,2,5,6,7], k = 2

输出:2

解释:

子序列 [4, 5, 6, 7] 的值最大,为 (4 OR 5) XOR (6 OR 7) = 2 。

 

提示:

  • 2 <= nums.length <= 400
  • 1 <= nums[i] < 27
  • 1 <= k <= nums.length / 2

DP前后缀 + 枚举 ( 无合并 step by step)

2024年9月16日 20:25

Problem: 3287. 求出数组中最大序列值

思路

  1. 计算所有 下标 $j$ 前 $k$ 个数可能 $or$ 出的结果,记为$prefix[k-1][j+1][x]$
    因为$1 <= nums[j] < 2^7$,所以 $x < 2^7$
  2. 同理,计算所有 下标 $j$ 后 $k$ 个数可能 $or$ 出的结果,记为$suffix[k-1][j][x]$
  3. 枚举可能的分割位置 $i$ ,计算此位置前后的 $xor$ 值 = $prefix[i+1]$ ^ $suffix[i]$

Code

###Python3

class Solution:
    def maxValue(self, nums: List[int], k: int) -> int:
        n = len(nums)
        mx = reduce(or_, nums)
        prefix = [[[False] * (mx + 1) for i in range(n + 1)] for j in range(k + 1)]
        ans = -inf
        # 1. prefix or for k
        for i in range(n):
            prefix[0][i + 1] = prefix[0][i].copy()
            prefix[0][i + 1][nums[i]] = True
        for i in range(1, k):
            for j in range(i, n - k + 1):
                x = nums[j]
                # unselect
                prefix[i][j + 1] = prefix[i][j].copy()
                # select
                for h in range(0, mx + 1):
                    if prefix[i - 1][j][h]:
                        prefix[i][j + 1][h | x] = True
            # print( f[i])
        # 2. suffix or for k
        suffix = [[[False] * (mx + 1) for i in range(n + 1)] for j in range(k + 1)]
        for i in range(n - 1, -1, -1):
            suffix[0][i] = suffix[0][i + 1].copy()
            suffix[0][i][nums[i]] = True

        for i in range(1, k):
            for j in range(n - 1, k - 1, -1):
                x = nums[j]
                # unselect
                suffix[i][j] = suffix[i][j + 1].copy()
                # select
                for h in range(0, mx + 1):
                    if suffix[i - 1][j + 1][h]:
                        suffix[i][j][h | x] = True

        # 3. 枚举 所有 prefix xor suffix
        ans = -inf
        for i in range(k - 1, n - k + 1):  # [0, i] [i+1, i+k]
            pre = prefix[k - 1][i + 1]
            for j in range(1, mx + 1):
                if not pre[j]:
                    continue
                post = suffix[k - 1][i + 1]
                for h in range(1, mx + 1):
                    if post[h]:
                        ans = max(ans, j ^ h)
        return ans

复杂度

  • 时间复杂度: $O(nkU)$,$n$ 是 $nums$ 的长度,$U$ 是 $nums$ 所有元素的 $OR$,最多为 $2 ^ 7 -1$
  • 空间复杂度: $O(nkU)$

前后缀分解 + 二维 0-1 背包 + 优化所选元素个数 + 试填法(Python/Java/C++/Go)

作者 endlesscheng
2024年9月15日 08:45

题意

从 $\textit{nums}$ 中选一个长为 $2k$ 的子序列,计算其前一半的 OR,后一半的 OR,这两个 OR 再计算 XOR。

问:计算出的 XOR 最大能是多少?

核心思路

  • 想象有一根分割线,把 $\textit{nums}$ 分成左右两部分,左和右分别计算所有长为 $k$ 的子序列的 OR 都有哪些值。比如左边计算出的 OR 有 $2,3,5$,右边计算出的 OR 有 $1,3,6$,那么两两组合计算 XOR,其中最大值即为答案。
  • 枚举分割线的位置,把 $\textit{nums}$ 分割成一个前缀和一个后缀,问题变成:从前缀/后缀中选一个长为 $k$ 的子序列,这个子序列 OR 的结果能否等于 $x$?

把 OR 理解成一个类似加法的东西,转换成二维 0-1 背包。如果你不了解 0-1 背包,或者不理解为什么下面代码 $j$ 要倒序枚举,请看【基础算法精讲 18】

二维:指背包有两个约束,一个是所选元素的个数是 $k$,另一个是所选元素的 OR 是 $x$。

具体算法

计算后缀。对于 0-1 背包问题,我们定义 $f[i][j][x]$ 表示从 $\textit{nums}[i]$ 到 $\textit{nums}[n-1]$ 中选 $j$ 个数,这些数的 OR 能否等于 $x$。

设 $v=\textit{nums}[i]$,用刷表法转移:

  • 不选 $v$,那么 $f[i][j][x] = f[i+1][j][x]$。
  • 选 $v$,如果 $f[i+1][j][x]=\texttt{true}$,那么 $f[i][j+1][x\ |\ v]=\texttt{true}$。

刷表法:本题计算 $x = v\ |\ ?$ 中的 $?$ 是困难的,但计算 $x\ |\ v$ 是很简单的。也就是说,对于状态 $f[i][j][x]$ 而言,其转移来源是谁不好计算,但从 $f[i][j][x]$ 转移到的目标状态 $f[i][j+1][x\ |\ v]$ 是好计算的。在动态规划中,根据转移来源计算状态叫查表法,用当前状态更新其他状态叫刷表法。

初始值 $f[n][0][0]=\texttt{true}$。什么也不选,OR 等于 $0$。

对于每个 $i$,由于我们只需要 $f[i][k]$ 中的数据,把 $f[i][k]$ 复制到 $\textit{suf}[i]$ 中。这样做无需创建三维数组,空间复杂度更小。

代码实现时,$f$ 的第一个维度可以优化掉。

对于前缀 $\textit{pre}$ 的计算也同理。

最后,枚举 $i=k-1,k,k+1,\ldots,n-k-1$,两两组合 $\textit{pre}[i]$ 和 $\textit{suf}[i+1]$ 中的数计算 XOR,其中最大值即为答案。

小优化:如果在循环中,发现答案 $\textit{ans}$ 达到了理论最大值 $2^7-1$(或者所有元素的 OR),则立刻返回答案。

也可以用哈希集合代替布尔数组,见下面的 Python 优化代码。

具体请看 视频讲解 第三题,欢迎点赞关注~

优化前

###py

class Solution:
    def maxValue(self, nums: List[int], k: int) -> int:
        mx = reduce(or_, nums)
        n = len(nums)
        suf = [None] * (n - k + 1)
        f = [[False] * (mx + 1) for _ in range(k + 1)]
        f[0][0] = True
        for i in range(n - 1, k - 1, -1):
            v = nums[i]
            # 注意当 i 比较大的时候,循环次数应和 i 有关,因为更大的 j,对应的 f[j] 全为 False
            for j in range(min(k - 1, n - 1 - i), -1, -1):
                for x, has_x in enumerate(f[j]):
                    if has_x:
                        f[j + 1][x | v] = True
            if i <= n - k:
                suf[i] = f[k].copy()

        ans = 0
        pre = [[False] * (mx + 1) for _ in range(k + 1)]
        pre[0][0] = True
        for i, v in enumerate(nums[:-k]):
            for j in range(min(k - 1, i), -1, -1):
                for x, has_x in enumerate(pre[j]):
                    if has_x:
                        pre[j + 1][x | v] = True
            if i < k - 1:
                continue
            for x, has_x in enumerate(pre[k]):
                if has_x:
                    for y, has_y in enumerate(suf[i + 1]):
                        if has_y and x ^ y > ans:  # 手写 if
                            ans = x ^ y
            if ans == mx:
                return ans
        return ans

###py

# 使用 set 代替 bool list
class Solution:
    def maxValue(self, nums: List[int], k: int) -> int:
        n = len(nums)
        suf = [None] * (n - k + 1)
        f = [set() for _ in range(k + 1)]
        f[0].add(0)
        for i in range(n - 1, k - 1, -1):
            v = nums[i]
            for j in range(min(k - 1, n - 1 - i), -1, -1):
                f[j + 1].update(x | v for x in f[j])
            if i <= n - k:
                suf[i] = f[k].copy()

        mx = reduce(or_, nums)
        ans = 0
        pre = [set() for _ in range(k + 1)]
        pre[0].add(0)
        for i, v in enumerate(nums[:-k]):
            for j in range(min(k - 1, i), -1, -1):
                pre[j + 1].update(x | v for x in pre[j])
            if i < k - 1:
                continue
            ans = max(ans, max(x ^ y for x in pre[k] for y in suf[i + 1]))
            if ans == mx:
                return ans
        return ans

###java

class Solution {
    public int maxValue(int[] nums, int k) {
        final int MX = 1 << 7;
        int n = nums.length;
        boolean[][] suf = new boolean[n - k + 1][];
        boolean[][] f = new boolean[k + 1][MX];
        f[0][0] = true;
        for (int i = n - 1; i >= k; i--) {
            int v = nums[i];
            // 注意当 i 比较大的时候,循环次数应和 i 有关,因为更大的 j,对应的 f[j] 全为 false
            for (int j = Math.min(k - 1, n - 1 - i); j >= 0; j--) {
                for (int x = 0; x < MX; x++) {
                    if (f[j][x]) {
                        f[j + 1][x | v] = true;
                    }
                }
            }
            if (i <= n - k) {
                suf[i] = f[k].clone();
            }
        }

        int ans = 0;
        boolean[][] pre = new boolean[k + 1][MX];
        pre[0][0] = true;
        for (int i = 0; i < n - k; i++) {
            int v = nums[i];
            for (int j = Math.min(k - 1, i); j >= 0; j--) {
                for (int x = 0; x < MX; x++) {
                    if (pre[j][x]) {
                        pre[j + 1][x | v] = true;
                    }
                }
            }
            if (i < k - 1) {
                continue;
            }
            for (int x = 0; x < MX; x++) {
                if (pre[k][x]) {
                    for (int y = 0; y < MX; y++) {
                        if (suf[i + 1][y]) {
                            ans = Math.max(ans, x ^ y);
                        }
                    }
                }
            }
            if (ans == MX - 1) {
                return ans;
            }
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    int maxValue(vector<int>& nums, int k) {
        const int MX = 1 << 7;
        int n = nums.size();
        vector<array<int, MX>> suf(n - k + 1);
        vector<array<int, MX>> f(k + 1);
        f[0][0] = true;
        for (int i = n - 1; i >= k; i--) {
            int v = nums[i];
            // 注意当 i 比较大的时候,循环次数应和 i 有关,因为更大的 j,对应的 f[j] 全为 false
            for (int j = min(k - 1, n - 1 - i); j >= 0; j--) {
                for (int x = 0; x < MX; x++) {
                    if (f[j][x]) {
                        f[j + 1][x | v] = true;
                    }
                }
            }
            if (i <= n - k) {
                suf[i] = f[k];
            }
        }

        int ans = 0;
        vector<array<int, MX>> pre(k + 1);
        pre[0][0] = true;
        for (int i = 0; i < n - k; i++) {
            int v = nums[i];
            for (int j = min(k - 1, i); j >= 0; j--) {
                for (int x = 0; x < MX; x++) {
                    if (pre[j][x]) {
                        pre[j + 1][x | v] = true;
                    }
                }
            }
            if (i < k - 1) {
                continue;
            }
            for (int x = 0; x < MX; x++) {
                if (pre[k][x]) {
                    for (int y = 0; y < MX; y++) {
                        if (suf[i + 1][y]) {
                            ans = max(ans, x ^ y);
                        }
                    }
                }
            }
            if (ans == MX - 1) {
                return ans;
            }
        }
        return ans;
    }
};

###go

func maxValue(nums []int, k int) (ans int) {
const mx = 1 << 7
n := len(nums)
suf := make([][mx]bool, n-k+1)
f := make([][mx]bool, k+1)
f[0][0] = true
for i := n - 1; i >= k; i-- {
v := nums[i]
// 注意当 i 比较大的时候,循环次数应和 i 有关,因为更大的 j,对应的 f[j] 全为 false
for j := min(k-1, n-1-i); j >= 0; j-- {
for x, hasX := range f[j] {
if hasX {
f[j+1][x|v] = true
}
}
}
if i <= n-k {
suf[i] = f[k]
}
}

pre := make([][mx]bool, k+1)
pre[0][0] = true
for i, v := range nums[:n-k] {
for j := min(k-1, i); j >= 0; j-- {
for x, hasX := range pre[j] {
if hasX {
pre[j+1][x|v] = true
}
}
}
if i < k-1 {
continue
}
for x, hasX := range pre[k] {
if hasX {
for y, hasY := range suf[i+1] {
if hasY {
ans = max(ans, x^y)
}
}
}
}
if ans == mx-1 {
return
}
}
return
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(nkU + nU^2)$,其中 $n$ 是 $\textit{nums}$ 的长度,$U$ 是 $\textit{nums}$ 所有元素的 OR,本题至多为 $2^7-1$。DP 是 $\mathcal{O}(nkU)$ 的,计算 XOR 最大值是 $\mathcal{O}(nU^2)$ 的。
  • 空间复杂度:$\mathcal{O}(nU)$。

优化

例如 $x=1101_{(2)}$,我们至多要选几个 $\textit{nums}[i]$,就能 OR 得到 $x$?(前提是可以得到 $x$)

答案是 $3$ 个。考虑 $x$ 中的每个比特 $1$,它来自某个 $\textit{nums}[i]$。

设 $\textit{nums}$ 所有元素 OR 的二进制中的 $1$ 的个数为 $\textit{ones}$(本题数据范围保证 $textit{ones}\le 7$)。一般地,我们至多选 $\textit{ones}$ 个 $\textit{nums}[i]$,就能 OR 得到 $x$。

但是,本题要求(前缀/后缀)恰好选 $k$ 个元素。选的元素越多 OR 越大,那么某些比较小的 $x$ 可能无法 OR 出来。

为了判断(前缀/后缀)恰好选 $k$ 个元素能否 OR 出整数 $x$,定义:

  • $\textit{minI}[x]$,表示从 $0$ 开始遍历,至少要遍历到 $i$ 才有可能找到 $k$ 个数 OR 等于 $x$。如果无法得到 $x$ 那么 $\textit{minI}[x] = \infty$。
  • $\textit{maxI}[x]$,表示从 $n-1$ 开始遍历,至少要遍历到 $i$ 才有可能找到 $k$ 个数 OR 等于 $x$。如果无法得到 $x$ 那么 $\textit{maxI}[x] = 0$。

根据 从集合论到位运算,如果能 OR 得到 $x$,那么参与 OR 运算的元素都是 $x$ 的子集。换句话说,$x$ 是参与 OR 运算的元素的超集(superset)。

对于 $\textit{minI}[x]$ 的计算,我们可以在遍历 $\textit{nums}$ 的同时,用一个数组 $\textit{cnt}$ 维护 $\textit{nums}$ 元素的超集的出现次数。如果发现 $\textit{cnt}[x]=k$,说明至少要遍历到 $i$ 才有可能找到 $k$ 个数 OR 等于 $x$,记录 $\textit{minI}[x]=i$。对于 $\textit{maxI}[x]$ 的计算也同理。

对于两数异或最大值的计算,可以用试填法解决,原理请看【图解】421. 数组中两个数的最大异或值

###py

class Solution:
    def maxValue(self, nums: List[int], k: int) -> int:
        n = len(nums)
        mx = reduce(or_, nums)
        k2 = min(k, mx.bit_count())  # 至多选 k2 个数

        suf = [None] * (n - k + 1)
        f = [set() for _ in range(k2 + 1)]
        f[0].add(0)
        max_i = [0] * (mx + 1)
        cnt = [0] * (mx + 1)
        for i in range(n - 1, k - 1, -1):
            v = nums[i]
            for j in range(min(k2 - 1, n - 1 - i), -1, -1):
                f[j + 1].update(x | v for x in f[j])
            if i <= n - k:
                suf[i] = f[k2].copy()
            # 枚举 v 的超集
            s = v
            while s <= mx:
                cnt[s] += 1
                if cnt[s] == k:
                    # 从 n-1 开始遍历,至少要遍历到 i 才有可能找到 k 个数 OR 等于 s
                    max_i[s] = i
                s = (s + 1) | v

        ans = 0
        pre = [set() for _ in range(k2 + 1)]
        pre[0].add(0)
        min_i = [inf] * (mx + 1)
        cnt = [0] * (mx + 1)
        w = mx.bit_length()  # 用于 findMaximumXOR
        for i, v in enumerate(nums[:-k]):
            for j in range(min(k2 - 1, i), -1, -1):
                pre[j + 1].update(x | v for x in pre[j])
            # 枚举 v 的超集
            s = v
            while s <= mx:
                cnt[s] += 1
                if cnt[s] == k:
                    # 从 0 开始遍历,至少要遍历到 i 才有可能找到 k 个数 OR 等于 s
                    min_i[s] = i
                s = (s + 1) | v
            if i < k - 1:
                continue
            a = [x for x in pre[k2] if min_i[x] <= i]
            b = [x for x in suf[i + 1] if max_i[x] > i]
            ans = max(ans, self.findMaximumXOR(a, b, w))
            if ans == mx:
                return ans
        return ans

    # 421. 数组中两个数的最大异或值
    # 改成两个数组的最大异或值,做法是类似的,仍然可以用【试填法】解决
    def findMaximumXOR(self, a: List[int], b: List[int], w: int) -> int:
        ans = mask = 0
        for i in range(w - 1, -1, -1):  # 从最高位开始枚举
            mask |= 1 << i
            new_ans = ans | (1 << i)  # 这个比特位可以是 1 吗?
            set_a = set(x & mask for x in a)  # 低于 i 的比特位置为 0
            for x in b:
                x &= mask  # 低于 i 的比特位置为 0
                if new_ans ^ x in set_a:
                    ans = new_ans  # 这个比特位可以是 1
                    break
        return ans

###java

class Solution {
    private static final int BIT_WIDTH = 7;

    public int maxValue(int[] nums, int k) {
        final int MX = 1 << BIT_WIDTH;
        int n = nums.length;
        int k2 = Math.min(k, BIT_WIDTH); // 至多选 k2 个数

        boolean[][] suf = new boolean[n - k + 1][];
        boolean[][] f = new boolean[k2 + 1][MX];
        f[0][0] = true;
        int[] maxI = new int[MX];
        int[] cnt = new int[MX];
        for (int i = n - 1; i >= k; i--) {
            int v = nums[i];
            for (int j = Math.min(k2 - 1, n - 1 - i); j >= 0; j--) {
                for (int x = 0; x < MX; x++) {
                    if (f[j][x]) {
                        f[j + 1][x | v] = true;
                    }
                }
            }
            if (i <= n - k) {
                suf[i] = f[k2].clone();
            }
            // 枚举 v 的超集
            for (int s = v; s < MX; s = (s + 1) | v) {
                if (++cnt[s] == k) {
                    // 从 n-1 开始遍历,至少要遍历到 i 才有可能找到 k 个数 OR 等于 s
                    maxI[s] = i;
                }
            }
        }

        int ans = 0;
        boolean[][] pre = new boolean[k2 + 1][MX];
        pre[0][0] = true;
        int[] minI = new int[MX];
        Arrays.fill(minI, Integer.MAX_VALUE);
        Arrays.fill(cnt, 0);
        int[] a = new int[MX];
        int[] b = new int[MX];
        for (int i = 0; i < n - k; i++) {
            int v = nums[i];
            for (int j = Math.min(k2 - 1, i); j >= 0; j--) {
                for (int x = 0; x < MX; x++) {
                    if (pre[j][x]) {
                        pre[j + 1][x | v] = true;
                    }
                }
            }
            // 枚举 v 的超集
            for (int s = v; s < MX; s = (s + 1) | v) {
                if (++cnt[s] == k) {
                    // 从 0 开始遍历,至少要遍历到 i 才有可能找到 k 个数 OR 等于 s
                    minI[s] = i;
                }
            }
            if (i < k - 1) {
                continue;
            }
            int na = 0;
            int nb = 0;
            for (int x = 0; x < MX; x++) {
                if (pre[k2][x] && minI[x] <= i) {
                    a[na++] = x;
                }
                if (suf[i + 1][x] && maxI[x] > i) {
                    b[nb++] = x;
                }
            }
            ans = Math.max(ans, findMaximumXOR(a, na, b, nb));
            if (ans == MX - 1) {
                return ans;
            }
        }
        return ans;
    }

    // 421. 数组中两个数的最大异或值
    // 改成两个数组的最大异或值,做法是类似的,仍然可以用【试填法】解决
    private int findMaximumXOR(int[] a, int n, int[] b, int m) {
        int ans = 0;
        int mask = 0;
        boolean[] seen = new boolean[1 << BIT_WIDTH];
        for (int i = BIT_WIDTH - 1; i >= 0; i--) { // 从最高位开始枚举
            mask |= 1 << i;
            int newAns = ans | (1 << i); // 这个比特位可以是 1 吗?
            Arrays.fill(seen, false);
            for (int j = 0; j < n; j++) {
                seen[a[j] & mask] = true; // 低于 i 的比特位置为 0
            }
            for (int j = 0; j < m; j++) {
                int x = b[j] & mask; // 低于 i 的比特位置为 0
                if (seen[newAns ^ x]) {
                    ans = newAns; // 这个比特位可以是 1
                    break;
                }
            }
        }
        return ans;
    }
}

###cpp

class Solution {
    static constexpr int BIT_WIDTH = 7;

    // 421. 数组中两个数的最大异或值
    // 改成两个数组的最大异或值,做法是类似的,仍然可以用【试填法】解决
    int findMaximumXOR(vector<int>& a, vector<int>& b) {
        int ans = 0, mask = 0;
        vector<int> seen(1 << BIT_WIDTH);
        for (int i = BIT_WIDTH - 1; i >= 0; i--) { // 从最高位开始枚举
            mask |= 1 << i;
            int new_ans = ans | (1 << i); // 这个比特位可以是 1 吗?
            ranges::fill(seen, false);
            for (int x : a) {
                seen[x & mask] = true; // 低于 i 的比特位置为 0
            }
            for (int x : b) {
                x &= mask; // 低于 i 的比特位置为 0
                if (seen[new_ans ^ x]) {
                    ans = new_ans; // 这个比特位可以是 1
                    break;
                }
            }
        }
        return ans;
    }

public:
    int maxValue(vector<int>& nums, int k) {
        const int MX = 1 << BIT_WIDTH;
        int n = nums.size();
        int k2 = min(k, BIT_WIDTH); // 至多选 k2 个数

        vector<array<int, MX>> suf(n - k + 1);
        vector<array<int, MX>> f(k2 + 1);
        f[0][0] = true;
        int max_i[MX]{}, cnt[MX]{};
        for (int i = n - 1; i >= k; i--) {
            int v = nums[i];
            // 注意当 i 比较大的时候,循环次数应和 i 有关,因为更大的 j,对应的 f[j] 全为 false
            for (int j = min(k2 - 1, n - 1 - i); j >= 0; j--) {
                for (int x = 0; x < MX; x++) {
                    if (f[j][x]) {
                        f[j + 1][x | v] = true;
                    }
                }
            }
            if (i <= n - k) {
                suf[i] = f[k2];
            }
            // 枚举 v 的超集
            for (int s = v; s < MX; s = (s + 1) | v) {
                if (++cnt[s] == k) {
                    // 从 n-1 开始遍历,至少要遍历到 i 才有可能找到 k 个数 OR 等于 s
                    max_i[s] = i;
                }
            }
        }

        int ans = 0;
        vector<array<int, MX>> pre(k2 + 1);
        pre[0][0] = true;
        int min_i[MX];
        ranges::fill(min_i, INT_MAX);
        ranges::fill(cnt, 0);
        for (int i = 0; i < n - k; i++) {
            int v = nums[i];
            for (int j = min(k2 - 1, i); j >= 0; j--) {
                for (int x = 0; x < MX; x++) {
                    if (pre[j][x]) {
                        pre[j + 1][x | v] = true;
                    }
                }
            }
            // 枚举 v 的超集
            for (int s = v; s < MX; s = (s + 1) | v) {
                if (++cnt[s] == k) {
                    // 从 0 开始遍历,至少要遍历到 i 才有可能找到 k 个数 OR 等于 s
                    min_i[s] = i;
                }
            }
            if (i < k - 1) {
                continue;
            }
            vector<int> a, b;
            for (int x = 0; x < MX; x++) {
                if (pre[k2][x] && min_i[x] <= i) {
                    a.push_back(x);
                }
                if (suf[i + 1][x] && max_i[x] > i) {
                    b.push_back(x);
                }
            }
            ans = max(ans, findMaximumXOR(a, b));
            if (ans == MX - 1) {
                return ans;
            }
        }
        return ans;
    }
};

###go

const bitWidth = 7
const mx = 1 << bitWidth

func maxValue(nums []int, k int) (ans int) {
n := len(nums)
k2 := min(k, bitWidth) // 至多选 k2 个数
suf := make([][mx]bool, n-k+1)
f := make([][mx]bool, k2+1)
f[0][0] = true
maxI := [mx]int{}
cnt := [mx]int{}
for i := n - 1; i >= k; i-- {
v := nums[i]
for j := min(k2-1, n-1-i); j >= 0; j-- {
for x, hasX := range f[j] {
if hasX {
f[j+1][x|v] = true
}
}
}
if i <= n-k {
suf[i] = f[k2]
}
// 枚举 v 的超集
for s := v; s < mx; s = (s + 1) | v {
cnt[s]++
if cnt[s] == k {
// 从 n-1 开始遍历,至少要遍历到 i 才有可能找到 k 个数 OR 等于 s
maxI[s] = i
}
}
}

pre := make([][mx]bool, k2+1)
pre[0][0] = true
minI := [mx]int{}
for i := range minI {
minI[i] = math.MaxInt
}
cnt = [mx]int{}
for i, v := range nums[:n-k] {
for j := min(k2-1, i); j >= 0; j-- {
for x, hasX := range pre[j] {
if hasX {
pre[j+1][x|v] = true
}
}
}
// 枚举 v 的超集
for s := v; s < mx; s = (s + 1) | v {
cnt[s]++
if cnt[s] == k {
// 从 0 开始遍历,至少要遍历到 i 才有可能找到 k 个数 OR 等于 s
minI[s] = i
}
}
if i < k-1 {
continue
}
a := []int{}
b := []int{}
for x, has := range pre[k2] {
if has && minI[x] <= i {
a = append(a, x)
}
if suf[i+1][x] && maxI[x] > i {
b = append(b, x)
}
}
ans = max(ans, findMaximumXOR(a, b))
if ans == mx-1 {
return
}
}
return
}

// 421. 数组中两个数的最大异或值
// 改成两个数组的最大异或值,做法是类似的,仍然可以用【试填法】解决
func findMaximumXOR(a, b []int) (ans int) {
mask := 0
for i := bitWidth - 1; i >= 0; i-- { // 从最高位开始枚举
mask |= 1 << i
newAns := ans | 1<<i // 这个比特位可以是 1 吗?
seen := [mx]bool{}
for _, x := range a {
seen[x&mask] = true // 低于 i 的比特位置为 0
}
for _, x := range b {
x &= mask // 低于 i 的比特位置为 0
if seen[newAns^x] {
ans = newAns
break
}
}
}
return
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(nU\log U)$,其中 $n$ 是 $\textit{nums}$ 的长度,$U$ 是 $\textit{nums}$ 所有元素的 OR,本题至多为 $2^7-1$。
  • 空间复杂度:$\mathcal{O}(nU)$。

更多相似题目,见下面动态规划题单中的「§3.1 0-1 背包」和「专题:前后缀分解」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
  7. 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

枚举 & DP

作者 tsreaper
2024年9月15日 00:16

解法:枚举 & DP

一般这种一个序列分两半的题目,我们会尝试枚举分界点。

设 $f(i, j, x)$ 表示从前 $i$ 个元素中选出 $j$ 个,OR 起来能否得到 $x$;$g(i, j, x)$ 表示从第 $i$ 个元素到最后一个元素中选出 $j$ 个,OR 起来能否得到 $x$。我们枚举子序列左半边的结束点 $i$,左半边 OR 起来的值 $x$,以及右半边 OR 起来的值 $y$,那么答案就是

$$
\max\limits_{f(i, k, x) = \text{true and } g(i + 1, k, y) = \text{true}} x \oplus y
$$

枚举的复杂度是 $\mathcal{O}(n \times m^2)$,其中 $m = 2^7$ 是题目中提到的取值范围。

剩下的问题就是 $f$ 和 $g$ 怎么求。考虑是否选择第 $i$ 个元素,可以得到转移方程

f(i, j, x) -> f(i + 1, j, x) // 不选 nums[i]
f(i, j, x) -> f(i + 1, j + 1, x | nums[i]) // 选 nums[i]

初值 f(0, 0, 0) = true,其它位置初值为 false。$g$ 的求法类似。这一部分的复杂度为 $\mathcal{O}(nkm)$。

参考代码(c++)

###cpp

bool f[410][210][1 << 7], g[410][210][1 << 7];

class Solution {
public:
    int maxValue(vector<int>& nums, int K) {
        int n = nums.size();
        int m = 1 << 7;
        for (int i = 0; i <= n + 1; i++) for (int j = 0; j <= K; j++) for (int x = 0; x < m; x++)
            f[i][j][x] = g[i][j][x] = false;

        auto update = [&](bool &x, bool y) { x = x || y; };

        // DP 求子序列前半部分的情况
        f[0][0][0] = true;
        for (int i = 0; i < n; i++) for (int j = 0; j <= K; j++) for (int x = 0; x < m; x++) if (f[i][j][x]) {
            update(f[i + 1][j][x], f[i][j][x]);
            if (j < K) update(f[i + 1][j + 1][x | nums[i]], f[i][j][x]);
        }

        // DP 求子序列后半部分的情况
        g[n + 1][0][0] = true;
        for (int i = n + 1; i > 1; i--) for (int j = 0; j <= K; j++) for (int x = 0; x < m; x++) if (g[i][j][x]) {
            update(g[i - 1][j][x], g[i][j][x]);
            if (j < K) update(g[i - 1][j + 1][x | nums[i - 2]], g[i][j][x]);
        }

        int ans = 0;
        // 枚举子序列的分界点,以及前后半的值
        for (int i = K; i + K <= n; i++) for (int x = 0; x < m; x++) for (int y = 0; y < m; y++)
            if (f[i][K][x] && g[i + 1][K][y]) ans = max(ans, x ^ y);
        return ans;
    }
};
昨天 — 2025年1月17日LeetCode 每日一题题解

或值至少为 K 的最短子数组 II

2025年1月6日 15:48

方法一:滑动窗口

思路与算法

根据运算的性质可以知道,给定的元素 $A$ 与任意元素 $B$ 进行运算的结果一定满足 $A|B \ge A$,由此可以知道对于任意子数组的长度增加元素后按位运算的结果一定大于等于增加前的结果,满足单调性。由此可知当每次固定数组的右端点时,我们可以使用二分查找或者滑动窗口来找到最短特别子数组的长度。

我们每次用 $[\text{left}, \textit{right}]$ 表示滑动窗口,每次固定右端点 $\textit{right}$,如果当前窗口内子数组按位的结果大于等于 $k$,此时向右移动左端点 $\textit{left}$ 直到窗口内子数组的值按位的结果刚好满足小于 $k$ 为止,此时以 $\textit{right}$ 为右端点的最短特别子数组长度即为 $\textit{right} - \textit{left} + 1$。

为了实时计算当前窗口中子数组运算的结果,我们需要维护二进制位每一位中 $1$ 出现的次数,如果当前位中 $1$ 的出现的次数为 $0$,则按位运算后该位为 $0$,否则该位则为 $1$。由于给定数组 $\textit{nums}$ 中的元素大小不超过 $10^9$ ,因此最多需要考虑二进制表示的前 $30$ 位。我们需要维护一个长度为 $30$ 的数组 $\textit{bits}$,其中 $\textit{bits}[i]$ 表示滑动窗口中满足二进制表示的从低到高第 $i$ 位的值为 $1$ 的元素个数。

具体计算过程如下:

  • 初始时 $\textit{left} = \textit{right} = 0$。每次将滑动窗口的右端点 $\textit{right}$ 向右移动一位,并更新 $\textit{bits}$ 数组,通过计算 $\textit{bits}$ 数组,如果当前窗口内子数组按位的结果大于等于 $k$ 时,窗口内的子数组一定为特别子数组,则尝试应向右移动左端点 $\textit{right}$,并更新 $\textit{bits}$ 数组。

  • 向右移动左端点直到当窗口内子数组按位的结果小于 $k$ 或者 $\textit{left} > \textit{right}$ 时,此时不再移动左端点 $\textit{left}$。在移动窗口的过程中,我们同时更新最短特别子数组长度的长度。

  • 由于可能存在整个数组中所有元素按位的结果小于 $k$,此时不存在特别子数组,此时应返回 $-1$。

代码

###C++

class Solution {
public:
    int minimumSubarrayLength(vector<int>& nums, int k) {
        int n = nums.size();
        vector<int> bits(30);
        auto calc = [](vector<int> &bits) -> int {
            int ans = 0;
            for (int i = 0; i < bits.size(); i++) {
                if (bits[i] > 0) {
                    ans |= 1 << i;
                }
            }
            return ans;
        };

        int res = INT_MAX;
        for (int left = 0, right = 0; right < n; right++) {
            for (int i = 0; i < 30; i++) {
                bits[i] += (nums[right] >> i) & 1;
            }
            while (left <= right && calc(bits) >= k) {
                res = min(res, right - left + 1);
                for (int i = 0; i < 30; i++) {
                    bits[i] -= (nums[left] >> i) & 1;
                }
                left++;
            }
        }
        
        return res == INT_MAX ? -1: res;
    }
};

###Java

class Solution {
    public int minimumSubarrayLength(int[] nums, int k) {
        int n = nums.length;
        int[] bits = new int[30];
        int res = Integer.MAX_VALUE;
        for (int left = 0, right = 0; right < n; right++) {
            for (int i = 0; i < 30; i++) {
                bits[i] += (nums[right] >> i) & 1;
            }
            while (left <= right && calc(bits) >= k) {
                res = Math.min(res, right - left + 1);
                for (int i = 0; i < 30; i++) {
                    bits[i] -= (nums[left] >> i) & 1;
                }
                left++;
            }
        }

        return res == Integer.MAX_VALUE ? -1 : res;
    }

    private int calc(int[] bits) {
        int ans = 0;
        for (int i = 0; i < bits.length; i++) {
            if (bits[i] > 0) {
                ans |= 1 << i;
            }
        }
        return ans;
    }
}

###C#

public class Solution {
    public int MinimumSubarrayLength(int[] nums, int k) {
        int n = nums.Length;
        int[] bits = new int[30];
        int res = int.MaxValue;

        for (int left = 0, right = 0; right < n; right++) {
            for (int i = 0; i < 30; i++) {
                bits[i] += (nums[right] >> i) & 1;
            }
            while (left <= right && Calc(bits) >= k) {
                res = Math.Min(res, right - left + 1);
                for (int i = 0; i < 30; i++) {
                    bits[i] -= (nums[left] >> i) & 1;
                }
                left++;
            }
        }

        return res == int.MaxValue ? -1 : res;
    }

    private int Calc(int[] bits) {
        int ans = 0;
        for (int i = 0; i < bits.Length; i++) {
            if (bits[i] > 0) {
                ans |= 1 << i;
            }
        }
        return ans;
    }
}

###Go

func minimumSubarrayLength(nums []int, k int) int {
    n := len(nums)
bits := make([]int, 30)
res := math.MaxInt32

for left, right := 0, 0; right < n; right++ {
for i := 0; i < 30; i++ {
bits[i] += (nums[right] >> i) & 1
}
for left <= right && calc(bits) >= k {
res = min(res, right - left + 1)
for i := 0; i < 30; i++ {
bits[i] -= (nums[left] >> i) & 1
}
left++
}
}

if res == math.MaxInt32 {
return -1
}
return res
}

func calc(bits []int) int {
ans := 0
for i := 0; i < len(bits); i++ {
if bits[i] > 0 {
ans |= 1 << i
}
}
return ans
}

###Python

class Solution:
    def minimumSubarrayLength(self, nums: List[int], k: int) -> int:
        n = len(nums)
        bits = [0] * 30
        res = inf
        def calc(bits):
            return sum(1 << i for i in range(30) if bits[i] > 0)

        left = 0
        for right in range(n):
            for i in range(30):
                bits[i] += (nums[right] >> i) & 1
            while left <= right and calc(bits) >= k:
                res = min(res, right - left + 1)
                for i in range(30):
                    bits[i] -= (nums[left] >> i) & 1
                left += 1

        return -1 if res == inf else res

###C

int calc(int* bits) {
    int ans = 0;
    for (int i = 0; i < 30; i++) {
        if (bits[i] > 0) {
            ans |= 1 << i;
        }
    }
    return ans;
}

int minimumSubarrayLength(int* nums, int numsSize, int k) {
    int bits[30] = {0};
    int res = INT_MAX;

    for (int left = 0, right = 0; right < numsSize; right++) {
        for (int i = 0; i < 30; i++) {
            bits[i] += (nums[right] >> i) & 1;
        }
        while (left <= right && calc(bits) >= k) {
            res = fmin(res, right - left + 1);
            for (int i = 0; i < 30; i++) {
                bits[i] -= (nums[left] >> i) & 1;
            }
            left++;
        }
    }

    return res == INT_MAX ? -1 : res;
}

###JavaScript

var minimumSubarrayLength = function(nums, k) {
    const n = nums.length;
    const bits = new Array(30).fill(0);
    let res = Number.MAX_SAFE_INTEGER;;

    const calc = (bits) => {
        let ans = 0;
        for (let i = 0; i < bits.length; i++) {
            if (bits[i] > 0) {
                ans |= 1 << i;
            }
        }
        return ans;
    };

    let left = 0;
    for (let right = 0; right < n; right++) {
        for (let i = 0; i < 30; i++) {
            bits[i] += (nums[right] >> i) & 1;
        }
        while (left <= right && calc(bits) >= k) {
            res = Math.min(res, right - left + 1);
            for (let i = 0; i < 30; i++) {
                bits[i] -= (nums[left] >> i) & 1;
            }
            left++;
        }
    }

    return res === Number.MAX_SAFE_INTEGER ? -1 : res;
};

###TypeScript

function minimumSubarrayLength(nums: number[], k: number): number {
    const n = nums.length;
    const bits = new Array(30).fill(0);
    let res = Infinity;

    const calc = (bits: number[]): number => {
        let ans = 0;
        for (let i = 0; i < bits.length; i++) {
            if (bits[i] > 0) {
                ans |= 1 << i;
            }
        }
        return ans;
    };

    let left = 0;
    for (let right = 0; right < n; right++) {
        for (let i = 0; i < 30; i++) {
            bits[i] += (nums[right] >> i) & 1;
        }
        while (left <= right && calc(bits) >= k) {
            res = Math.min(res, right - left + 1);
            for (let i = 0; i < 30; i++) {
                bits[i] -= (nums[left] >> i) & 1;
            }
            left++;
        }
    }

    return res === Infinity ? -1 : res;
};

###Rust

impl Solution {
    pub fn minimum_subarray_length(nums: Vec<i32>, k: i32) -> i32 {
        let n = nums.len();
        let mut bits = [0; 30];
        let mut res = i32::MAX;

        let calc = |bits: &[i32]| -> i32 {
            let mut ans = 0;
            for i in 0..30 {
                if bits[i] > 0 {
                    ans |= 1 << i;
                }
            }
            ans
        };

        let mut left = 0;
        for right in 0..n {
            for i in 0..30 {
                bits[i] += (nums[right] >> i) & 1;
            }
            while left <= right && calc(&bits) >= k {
                res = res.min((right - left + 1) as i32);
                for i in 0..30 {
                    bits[i] -= (nums[left] >> i) & 1;
                }
                left += 1;
            }
        }

        if res == i32::MAX {
            -1
        } else {
            res
        }
    }
}

复杂度分析

  • 时间复杂度:$O(n \log U)$,其中 $n$ 表示给定数组 $\textit{nums}$ 的长度,$U$ 表示数组中的最大的元素。由于使用滑动窗口遍历需要的时间为 $O(n)$,每次更新窗口元素时需要实时计算当前子数组按位的值需要的时间为 $O(\log U)$,此时需要的总时间即为 $O(n \log U)$。

  • 空间复杂度:$O(\log U)$。计算时需要存储当前子数组中每一个二进制位中的统计情况,最多有 $\log U$ 位需要记录,因此需要的空间为 $\log U$。

每日一题-或值至少为 K 的最短子数组 II🟡

2025年1月17日 00:00

给你一个 非负 整数数组 nums 和一个整数 k 。

如果一个数组中所有元素的按位或运算 OR 的值 至少 为 k ,那么我们称这个数组是 特别的 。

请你返回 nums 中 最短特别非空 子数组的长度,如果特别子数组不存在,那么返回 -1 。

 

示例 1:

输入:nums = [1,2,3], k = 2

输出:1

解释:

子数组 [3] 的按位 OR 值为 3 ,所以我们返回 1 。

示例 2:

输入:nums = [2,1,8], k = 10

输出:3

解释:

子数组 [2,1,8] 的按位 OR 值为 11 ,所以我们返回 3 。

示例 3:

输入:nums = [1,2], k = 0

输出:1

解释:

子数组 [1] 的按位 OR 值为 1 ,所以我们返回 1 。

 

提示:

  • 1 <= nums.length <= 2 * 105
  • 0 <= nums[i] <= 109
  • 0 <= k <= 109

logtrick

2024年7月11日 14:39

logtrick的用法与实战

logtrick是我从灵神视频中学习到的,此文章介绍logtrick用法与实践,以及灵神视频中未提到的,我本人总结出来的小技巧

用法

logtrick通常用于求 子数组(gcd,lcm,&,|)后的max或者min或者计数问题

子数组问题

logtrick主要是解决子数组问题,所以在此我们先引出子数组问题

什么是子数组问题?

就是求 f(l,r)的所有操作的(最优值 或者 计数值) 1<=l<=r<=n

类似这个公式:
$$
\sum_{l=1}^{n} \sum_{r=l}^{n} f(l, r)
$$
但不一定是求和,应是题目要求的求(最优值 或者 计数值)等

**这个f即函数 是求区间[l,r]完成题目特定操作后 得到的特定值 **

比如说:f(l,r),f函数是求min的 那么就是求[l,r]区间中最小的一个值

如何高效计算子数组问题

计算子数组,有一种可行思路是把注意力放在右端点,通过不断移动右端点,在移动的时候利用前面的统计结果算出移动右端点后的结果,从而得出所有子区间的答案。

这种方法经典套路有 前缀和,logtrick

今天我们重点是logtrick

logtrick是基于O(n^2)的计算子数组问题 利用(|,&,lcm,gcd)等性质 优化的一种套路亦或者是算法

先介绍o(n^2)的算法

###c++

for(int i = 0; i < n;i++){
for(int j = i - 1;j >= 0;j--){
a[j] ?= a[i]; //此问号可以为gcd lcm | &
}
}

这个算法挺清晰的我们研究一下过程,然后考虑优化

比如说 求a的子数组按位&等于k的子数组的总和

给出数据与对应二进制:

###c++

 7     3     4     1
111 011    100    001    

操作流程:

第一次:
             7     3     4     1
            111 011    100    001

存储:a[0]
111

第二次:
             7     3     4     1
            111 011    100    001

存储:  a[0]&a[1]            a[1]
011                 011

第三次:
             7     3     4     1
            111 011    100    001

存储: a[0]&a[1]&a[2]      a[1]&a[2]              a[2]
000                 000 100

第四次:
             7     3     4     1
            111 011    100    001

存储: a[0]&a[1]&a[2]&a[3]   a[1]&a[2]&a[3]         a[2]&a[3]             a[3]
000                 000 000 001

可以发现这样枚举右端点更新左端点可以保证求出所有的子数组的运算结果

优化:

这是最最最重点的了我们发现第三次到第四次过程中是没有变化的,为什么呢,因为&越多数&的的总结局是越来越小

而&不变就是说明了我这个数已经不足以让当前位置变小,又因为当前位置的前面都是与当前位置&过的,所以不能让当前位置变小也自然不会让前面位置再变小,这点很难理解用灵神的话来讲就是看做是集和的关系,我后面枚举的这个数设为x & 当前这个数设为y然后 y不变小, 意味这 y 一定是x的子集 而y前面的数都&过y 说明都是与y的交集了,与x交就相当于没有交; 很难理解借助图形理解

图形

我们设当前枚举的是x 然后 x&y=y , 然后y前面的数为z z&y设为 t

前面的数组的记录的数t是这样的

image-20240711135353359.png{:width=400}

y是x的子集所以加入x长这样

image-20240711135534473.png{:width=400}

虚线部分是x,可以看到加入x是不会对y和y前面的t产生影响的 因为 x与y与z的交集还是t

所以当我们遇到 a[j] & a[i] == a[j] 时 直接break ,不必管前面的数

就因为这个小优化我们的时间复杂度可以从

O(n^2) 变为 O(nlogU)

u为数组中最大值

因为我们一个数不断缩小就加强了成为x的子集的可能性,就更可能的触及 a[j] & a[i] == a[j] 这个条件,而最坏也是log级别的缩小,此为logtrick精髓所在

除了& 还有| gcd lcm 殊途同归读者自己思考

模版

###c++

for(int i = 1; i <= n;i++){
    int x = nums[i];
    //这里可以进行一些操作
    for(int j = i - 1;j >= 1;j--){
        if((nums[j] 操作 nums[i]) = ?){
            break;
        }
        nums[j] 操作= nums[i];
    }
    然后进行二分或者一些操作
}

小技巧

logtrick 配合 map使用有出其不意的效果,甚至说更加无脑与方便,下面题目有讲

实战

简单题

源于灵神总结的题单

力扣3097

3097. 或值至少为 K 的最短子数组 II - 力扣(LeetCode)

思路: 因为如果 nums[j] | nums[i] == nums[i] 就代表前面都不会再变大,所以直接结束,很经典的板子题

###c++

class Solution {
public:
    int minimumSubarrayLength(vector<int>& nums, int k) {
        int mi = 0x3f3f3f3f;
        int n = nums.size();
        for(int i = 0; i < n;i++){
            if(nums[i] >= k){
                return 1;
            }
            for(int j= i - 1; j >= 0 && (nums[j] | nums[i]) != nums[j];j--){
                nums[j] |= nums[i];
                if(nums[j]>=k){
                    mi = min(mi,i-j+1);
                }
            }
        }
        return mi==0x3f3f3f3f ? -1 : mi;
    }
};

力扣2411

2411. 按位或最大的最小子数组长度 - 力扣(LeetCode)

思路:和上题类似

###C++

class Solution {
public:
    vector<int> smallestSubarrays(vector<int>& nums) {
        int n = nums.size();
        vector<int> ans(n,1);
        for(int i = 0; i < n;i++){
            int x= nums[i];
            for(int j = i - 1 ;j >= 0 && (nums[j] | nums[i]) != nums[j];j--) {
                nums[j]|=nums[i],ans[j] = i - j + 1;
            }
        }
        return ans;

    }
};

力扣3209

3209. 子数组按位与值为 K 的数目 - 力扣(LeetCode)

思路: 该题更快做法应该是二分,但是这里我们用无脑的map计数直接无视二分,让问题难度直线下降

###c++

class Solution {
public:
    long long countSubarrays(vector<int>& nums, int k) {
        long long ans = 0;
        int n = nums.size();
        map<int,int> h;
        for(int i = 0;i<n;i++){
            int x = nums[i];
            h[x]++;
            for(int j = i - 1; j >= 0 && (nums[j] & nums[i]) != nums[j];j--){
                h[nums[j]]--;
                nums[j]&=nums[i];
                h[nums[j]]++;
            }
            ans+=h[k];

        }
        return ans;
    }
};

力扣1521

1521. 找到最接近目标值的函数值 - 力扣(LeetCode)

思路: 简单板子运用

###c++

class Solution {
public:
    int closestToTarget(vector<int>& nums, int target) {
        int ans = 0x3f3f3f3f;
        for(int i = 0;i < nums.size();i++){
            ans = min(ans,abs(nums[i] - target));
            for(int j = i - 1;j >= 0;j--){
                if((nums[j]&nums[i]) == nums[j]) break;
                nums[j]&=nums[i];
                ans = min(ans,abs(nums[j] - target));
            }
        }
        return ans;
    }
};

力扣898

898. 子数组按位或操作 - 力扣(LeetCode)

思路:计数问题带个map 会好解决很多

###c++

class Solution {
public:
    int subarrayBitwiseORs(vector<int>& a) {
       int n = a.size();
       map<int,int> h;
       for(int i = 0;i<n;i++){
           h[a[i]]++;
           for(int j = i - 1; j >= 0;j--){
                if((a[j]|a[i])==a[j]) break;
                h[a[j]|a[i]]++;
                a[j]|=a[i];
           }
       }
       return h.size();
    }
};

中等题

CF475D

Problem - 475D - Codeforces

思路: 运用map然后累加到res中

###c++

const int N = 200005;
int n,m;
map<int,int> res;
int a[N];
void solve(){

cin >> n;
for(int i = 1; i <= n;i++){
cin >> a[i];
}
map<int,int> h;
for(int i = 1; i <= n;i++){
h[a[i]]++;
for(int j = i - 1; j >=1;j--){
if(__gcd(a[i],a[j]) == a[j]){
break;
}else{
h[a[j]]--;
if(h[a[j]]==0) h.erase(a[j]);
a[j] = __gcd(a[i],a[j]);
h[a[j]]++;
}
}
for(auto p:h) res[p.first]+=p.second;
}
int q;
cin >> q;
while(q--){
int t;
cin >> t;
cout << res[t] <<endl;
}

}

困难题

CF1632D

此题是我做到目前为止 logtrick中最难的题,但是其实也不是很难啦,就是相对别的题目比较难,就是要灵活掌握变形

Problem - 1632D - Codeforces

思路:思维+logtrick,首先我们要清楚要贪心修改数字必定是把数字修改成1,因为这样才不会影响后面的数,然后就是如果修改了前面的子数组的一个元素,前面的子数组就全部没用了,直接map.claer(),然后做logtrick时候j那一维度为不用遍历到前面了

###c++

const int N = 200005;
int n,m;
map<int,int> res;
int a[N];
void solve(){

cin >> n;
for(int i = 1; i <= n;i++){
cin >> a[i];
}
map<int,set<int> > h;
int cnt = 0;
int lt = 1;
for(int i = 1; i <= n;i++){
h[a[i]].insert(i);
for(int j = i - 1;j>=lt;j--){
if(__gcd(a[j],a[i])==a[j]){
break;
}
h[a[j]].erase(j);
if(h[a[j]].size()==0) h.erase(a[j]);
h[__gcd(a[j],a[i])].insert(j);
a[j]=__gcd(a[j],a[i]);
}
//i-j+1 == k
bool f = false;
for(auto &[k,se]:h){
if(se.count(i - k + 1)){
cnt++;
f = true;
break;
}
}
if(f){
map<int,set<int> > h2;
h=h2;
h[1].insert(i);
lt=i+1;
}
cout << cnt << " ";
}

}

最后感谢@灵茶山艾府
基础起源于灵神的教学,题单也是灵神那里找的

两种方法:LogTrick/滑动窗口+栈(Python/Java/C++/C/Go/JS/Rust)

作者 endlesscheng
2024年3月31日 08:39

去年十月的每日一题,出过一道类似的 3171. 找到按位或最接近 K 的子数组

由于思路是一样的,本文只留代码,具体原理请看 我的题解

方法一:LogTrick

###py

class Solution:
    def minimumSubarrayLength(self, nums: List[int], k: int) -> int:
        ans = inf
        for i, x in enumerate(nums):
            if x >= k:
                return 1
            j = i - 1
            while j >= 0 and nums[j] | x != nums[j]:
                nums[j] |= x
                if nums[j] >= k:
                    ans = min(ans, i - j + 1)
                j -= 1
        return ans if ans < inf else -1

###java

class Solution {
    public int minimumSubarrayLength(int[] nums, int k) {
        int ans = Integer.MAX_VALUE;
        for (int i = 0; i < nums.length; i++) {
            int x = nums[i];
            if (x >= k) {
                return 1;
            }
            for (int j = i - 1; j >= 0 && (nums[j] | x) != nums[j]; j--) {
                nums[j] |= x;
                if (nums[j] >= k) {
                    ans = Math.min(ans, i - j + 1);
                }
            }
        }
        return ans == Integer.MAX_VALUE ? -1 : ans;
    }
}

###cpp

class Solution {
public:
    int minimumSubarrayLength(vector<int>& nums, int k) {
        int ans = INT_MAX;
        for (int i = 0; i < nums.size(); i++) {
            int x = nums[i];
            if (x >= k) {
                return 1;
            }
            for (int j = i - 1; j >= 0 && (nums[j] | x) != nums[j]; j--) {
                nums[j] |= x;
                if (nums[j] >= k) {
                    ans = min(ans, i - j + 1);
                }
            }
        }
        return ans == INT_MAX ? -1 : ans;
    }
};

###c

#define MIN(a, b) ((b) < (a) ? (b) : (a))

int minimumSubarrayLength(int* nums, int numsSize, int k) {
    int ans = INT_MAX;
    for (int i = 0; i < numsSize; i++) {
        int x = nums[i];
        if (x >= k) {
            return 1;
        }
        for (int j = i - 1; j >= 0 && (nums[j] | x) != nums[j]; j--) {
            nums[j] |= x;
            if (nums[j] >= k) {
                ans = MIN(ans, i - j + 1);
            }
        }
    }
    return ans == INT_MAX ? -1 : ans;
}

###go

func minimumSubarrayLength(nums []int, k int) int {
ans := math.MaxInt
for i, x := range nums {
if x >= k {
return 1
}
for j := i - 1; j >= 0 && nums[j]|x != nums[j]; j-- {
nums[j] |= x
if nums[j] >= k {
ans = min(ans, i-j+1)
}
}
}
if ans == math.MaxInt {
return -1
}
return ans
}

###js

var minimumSubarrayLength = function(nums, k) {
    let ans = Infinity;
    for (let i = 0; i < nums.length; i++) {
        const x = nums[i];
        if (x >= k) {
            return 1;
        }
        for (let j = i - 1; j >= 0 && (nums[j] | x) !== nums[j]; j--) {
            nums[j] |= x;
            if (nums[j] >= k) {
                ans = Math.min(ans, i - j + 1);
            }
        }
    }
    return ans === Infinity ? -1 : ans;
};

###rust

impl Solution {
    pub fn minimum_subarray_length(mut nums: Vec<i32>, k: i32) -> i32 {
        let mut ans = usize::MAX;
        for i in 0..nums.len() {
            let x = nums[i];
            if x >= k {
                return 1;
            }
            let mut j = i - 1;
            while j < nums.len() && (nums[j] | x) != nums[j] {
                nums[j] |= x;
                if nums[j] >= k {
                    ans = ans.min(i - j + 1);
                }
                j -= 1;
            }
        }
        if ans == usize::MAX { -1 } else { ans as _ }
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n\log U)$,其中 $n$ 是 $\textit{nums}$ 的长度,$U=\max(\textit{nums})$。由于 $2^{29}-1<10^9<2^{30}-1$,二进制数对应集合的大小不会超过 $29$,因此在 OR 运算下,每个数字至多可以增大 $29$ 次。总体上看,二重循环的总循环次数等于每个数字可以增大的次数之和,即 $O(n\log U)$。
  • 空间复杂度:$\mathcal{O}(1)$。

方法二:滑动窗口+栈

###py

class Solution:
    def minimumSubarrayLength(self, nums: List[int], k: int) -> int:
        ans = inf
        left = bottom = right_or = 0
        for right, x in enumerate(nums):
            right_or |= x
            while left <= right and nums[left] | right_or >= k:
                ans = min(ans, right - left + 1)
                left += 1
                if bottom < left:
                    # 重新构建一个栈
                    for i in range(right - 1, left - 1, -1):
                        nums[i] |= nums[i + 1]
                    bottom = right
                    right_or = 0
        return ans if ans < inf else -1

###java

class Solution {
    public int minimumSubarrayLength(int[] nums, int k) {
        int ans = Integer.MAX_VALUE;
        int left = 0;
        int bottom = 0;
        int rightOr = 0;
        for (int right = 0; right < nums.length; right++) {
            rightOr |= nums[right];
            while (left <= right && (nums[left] | rightOr) >= k) {
                ans = Math.min(ans, right - left + 1);
                left++;
                if (bottom < left) {
                    // 重新构建一个栈
                    for (int i = right - 1; i >= left; i--) {
                        nums[i] |= nums[i + 1];
                    }
                    bottom = right;
                    rightOr = 0;
                }
            }
        }
        return ans == Integer.MAX_VALUE ? -1 : ans;
    }
}

###cpp

class Solution {
public:
    int minimumSubarrayLength(vector<int>& nums, int k) {
        int ans = INT_MAX, left = 0, bottom = 0, right_or = 0;
        for (int right = 0; right < nums.size(); right++) {
            right_or |= nums[right];
            while (left <= right && (nums[left] | right_or) >= k) {
                ans = min(ans, right - left + 1);
                left++;
                if (bottom < left) {
                    // 重新构建一个栈
                    for (int i = right - 1; i >= left; i--) {
                        nums[i] |= nums[i + 1];
                    }
                    bottom = right;
                    right_or = 0;
                }
            }
        }
        return ans == INT_MAX ? -1 : ans;
    }
};

###c

#define MIN(a, b) ((b) < (a) ? (b) : (a))

int minimumSubarrayLength(int* nums, int numsSize, int k) {
    int ans = INT_MAX, left = 0, bottom = 0, right_or = 0;
    for (int right = 0; right < numsSize; right++) {
        right_or |= nums[right];
        while (left <= right && (nums[left] | right_or) >= k) {
            ans = MIN(ans, right - left + 1);
            left++;
            if (bottom < left) {
                // 重新构建一个栈
                for (int i = right - 1; i >= left; i--) {
                    nums[i] |= nums[i + 1];
                }
                bottom = right;
                right_or = 0;
            }
        }
    }
    return ans == INT_MAX ? -1 : ans;
}

###go

func minimumSubarrayLength(nums []int, k int) int {
ans := math.MaxInt
var left, bottom, rightOr int
for right, x := range nums {
rightOr |= x
for left <= right && nums[left]|rightOr >= k {
ans = min(ans, right-left+1)
left++
if bottom < left {
// 重新构建一个栈
for i := right - 1; i >= left; i-- {
nums[i] |= nums[i+1]
}
bottom = right
rightOr = 0
}
}
}
if ans == math.MaxInt {
return -1
}
return ans
}

###js

var minimumSubarrayLength = function(nums, k) {
    let ans = Infinity, left = 0, bottom = 0, rightOr = 0;
    for (let right = 0; right < nums.length; right++) {
        rightOr |= nums[right];
        while (left <= right && (nums[left] | rightOr) >= k) {
            ans = Math.min(ans, right - left + 1);
            left++;
            if (bottom < left) {
                // 重新构建一个栈
                for (let i = right - 1; i >= left; i--) {
                    nums[i] |= nums[i + 1];
                }
                bottom = right;
                rightOr = 0;
            }
        }
    }
    return ans === Infinity ? -1 : ans;
};

###rust

impl Solution {
    pub fn minimum_subarray_length(mut nums: Vec<i32>, k: i32) -> i32 {
        let mut ans = usize::MAX;
        let mut left = 0;
        let mut bottom = 0;
        let mut right_or = 0;
        for right in 0..nums.len() {
            right_or |= nums[right];
            while left <= right && (nums[left] | right_or) >= k {
                ans = ans.min(right - left + 1);
                left += 1;
                if bottom < left {
                    // 重新构建一个栈
                    for i in (left..right).rev() {
                        nums[i] |= nums[i + 1];
                    }
                    bottom = right;
                    right_or = 0;
                }
            }
        }
        if ans == usize::MAX { -1 } else { ans as _ }
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是 $\textit{nums}$ 的长度。虽然我们写了个三重循环,但每个元素至多入栈出栈各一次,所以三重循环的循环次数是 $\mathcal{O}(n)$ 的,所以时间复杂度是 $\mathcal{O}(n)$。
  • 空间复杂度:$\mathcal{O}(1)$。

更多相似题目,见位运算题单中的「LogTrick」。

思考题

如果把 OR 改成 XOR,要怎么做?

提示:前缀异或和 + 1803. 统计异或值在范围内的数对有多少

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 【本题相关】位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
  7. 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

滑动窗口+数组模拟位运算

2024年3月31日 01:03

Problem: 3097. 或值至少为 K 的最短子数组 II

[TOC]

思路

本题就是 209. 长度最小的子数组 的升级版,关于滑动窗口的使用可以看我的另一篇题解:滑动窗口零基础入门

言归正传

本题的难点在于滑动窗口缩小左端点时元素由于是或运算,不能像加减一样直接减掉就能抛弃,或运算相当于不可逆运算,因为你不知道其他元素的第i个二进制位是否为1
我们可以创建一个大小为32的int型数组arr来模拟2进制位:
第i个元素的第j个2进制位为1时:arr[j]++;否则不动

这样我们就可以知道当前n个元素进行或运算后每个2进制位上的1总共有多少个,当要删掉某个元素x时我们只需把x二进制位上的1从arr中减掉就行,然后遍历arr算出新的sum,并判断这个新的sum值是否满足条件,不断循环缩小left直至找出满足条件长度最小的子数组即可。

复杂度

时间复杂度:

arr[]长度为32,每次进行遍历依旧是常数故复杂度依旧是: $O(n)$

空间复杂度:

$O(n)$

Code

###Java

class Solution {
    public int minimumSubarrayLength(int[] nums, int k) {
        /*
         * sum:临时和,用来判断当前子数组是否满组条件
         * mask:掩码,从最低位开始
         * arr[]:用来累计当前sum每个二进制位上1的个数
         */
        int sum = 0, left = 0;
        int ans = Integer.MAX_VALUE;
        int mask;
        int[] arr = new int[32];

        if (k == 0)
            return 1; // 把目标值k为0的案例单独判断
        // 开始滑动窗口:外层循环right负责移动右端点,当右端点满足条件了开始缩小左端点left
        for (int right = 0; right < nums.length; right++) {
            sum |= nums[right]; // 异或累加sum
            // 每加上一个元素都要更新arr
            mask = 1;
            for (int i = 31; i >= 0; i--) {
                if ((nums[right] & mask) != 0) {
                    arr[i]++;
                }
                mask <<= 1; // 将掩码左移一位,以检查下一位
            }
            // 判断当前sum是否满足条件
            if (sum >= k) {
                while (left <= right) { // 满足条件开始缩小左端点
                    int x = nums[left];
                    // 从arr上删除当前左端点元素x的二进制位
                    mask = 1;
                    for (int i = 31; i >= 0; i--) {
                        if ((x & mask) != 0) {
                            arr[i]--;
                        }
                        mask <<= 1;
                    }
                    // 将更新后的arr转成新的sum
                    StringBuilder s = new StringBuilder();
                    for (int m : arr) {
                        if (m > 0) {
                            s.append(1);
                        } else {
                            s.append(0);
                        }
                    }
                    sum = Integer.parseInt(s.toString(), 2);

                    if (sum >= k) { // 如果当前sum依旧满足条件则继续缩小左端点
                        left++;
                    } else { // 否则返还左端点元素x,继续外循环right增加右端点
                        sum |= nums[left];
                        mask = 1;
                        for (int i = 31; i >= 0; i--) {
                            if ((nums[left] & mask) != 0) {
                                arr[i]++;
                            }
                            mask <<= 1;
                        }
                        /*
                         * 这里对right - left + 1进行证明:
                         * 当左端点与右端点在统一下标时
                         * 即:left==right
                         * 此时子数组内只有一个元素,长度自然为1
                         * 故相减后需要+1
                         */
                        ans = Math.min(ans, right - left + 1);
                        break;
                    }
                }
            }
        }
        return ans == Integer.MAX_VALUE ? -1 : ans;
    }
}
昨天以前LeetCode 每日一题题解

[Python3/Java/C++/Go/TypeScript] 一题一解:双指针 + 计数(清晰题解)

作者 lcbin
2025年1月16日 08:39

方法一:双指针 + 计数

我们可以发现,如果我们固定子数组的左端点,随着右端点向右移动,子数组的按位或值只会增大,不会减小。因此我们可以使用双指针的方法,维护一个满足条件的子数组。

具体地,我们使用两个指针 $i$ 和 $j$ 分别表示子数组的左右端点,初始时两个指针都位于数组的第一个元素。用一个变量 $s$ 表示子数组的按位或值,初始时 $s$ 的值为 $0$。我们还需要维护一个长度为 $32$ 的数组 $cnt$,表示子数组中每个元素的二进制表示中每一位的出现次数。

在每一步操作中,我们将 $j$ 向右移动一位,更新 $s$ 和 $cnt$。如果 $s$ 的值大于等于 $k$,我们不断更新子数组的最小长度,并将 $i$ 向右移动一位,直到 $s$ 的值小于 $k$。在这个过程中,我们也需要更新 $s$ 和 $cnt$。

最后,我们返回最小长度,如果不存在满足条件的子数组,则返回 $-1$。

###python

class Solution:
    def minimumSubarrayLength(self, nums: List[int], k: int) -> int:
        n = len(nums)
        cnt = [0] * 32
        ans = n + 1
        s = i = 0
        for j, x in enumerate(nums):
            s |= x
            for h in range(32):
                if x >> h & 1:
                    cnt[h] += 1
            while s >= k and i <= j:
                ans = min(ans, j - i + 1)
                y = nums[i]
                for h in range(32):
                    if y >> h & 1:
                        cnt[h] -= 1
                        if cnt[h] == 0:
                            s ^= 1 << h
                i += 1
        return -1 if ans > n else ans

###java

class Solution {
    public int minimumSubarrayLength(int[] nums, int k) {
        int n = nums.length;
        int[] cnt = new int[32];
        int ans = n + 1;
        for (int i = 0, j = 0, s = 0; j < n; ++j) {
            s |= nums[j];
            for (int h = 0; h < 32; ++h) {
                if ((nums[j] >> h & 1) == 1) {
                    ++cnt[h];
                }
            }
            for (; s >= k && i <= j; ++i) {
                ans = Math.min(ans, j - i + 1);
                for (int h = 0; h < 32; ++h) {
                    if ((nums[i] >> h & 1) == 1) {
                        if (--cnt[h] == 0) {
                            s ^= 1 << h;
                        }
                    }
                }
            }
        }
        return ans > n ? -1 : ans;
    }
}

###cpp

class Solution {
public:
    int minimumSubarrayLength(vector<int>& nums, int k) {
        int n = nums.size();
        int cnt[32]{};
        int ans = n + 1;
        for (int i = 0, j = 0, s = 0; j < n; ++j) {
            s |= nums[j];
            for (int h = 0; h < 32; ++h) {
                if ((nums[j] >> h & 1) == 1) {
                    ++cnt[h];
                }
            }
            for (; s >= k && i <= j; ++i) {
                ans = min(ans, j - i + 1);
                for (int h = 0; h < 32; ++h) {
                    if ((nums[i] >> h & 1) == 1) {
                        if (--cnt[h] == 0) {
                            s ^= 1 << h;
                        }
                    }
                }
            }
        }
        return ans > n ? -1 : ans;
    }
};

###go

func minimumSubarrayLength(nums []int, k int) int {
n := len(nums)
cnt := [32]int{}
ans := n + 1
s, i := 0, 0
for j, x := range nums {
s |= x
for h := 0; h < 32; h++ {
if x>>h&1 == 1 {
cnt[h]++
}
}
for ; s >= k && i <= j; i++ {
ans = min(ans, j-i+1)
for h := 0; h < 32; h++ {
if nums[i]>>h&1 == 1 {
cnt[h]--
if cnt[h] == 0 {
s ^= 1 << h
}
}
}
}
}
if ans == n+1 {
return -1
}
return ans
}

###ts

function minimumSubarrayLength(nums: number[], k: number): number {
    const n = nums.length;
    let ans = n + 1;
    const cnt: number[] = new Array<number>(32).fill(0);
    for (let i = 0, j = 0, s = 0; j < n; ++j) {
        s |= nums[j];
        for (let h = 0; h < 32; ++h) {
            if (((nums[j] >> h) & 1) === 1) {
                ++cnt[h];
            }
        }
        for (; s >= k && i <= j; ++i) {
            ans = Math.min(ans, j - i + 1);
            for (let h = 0; h < 32; ++h) {
                if (((nums[i] >> h) & 1) === 1 && --cnt[h] === 0) {
                    s ^= 1 << h;
                }
            }
        }
    }
    return ans === n + 1 ? -1 : ans;
}

###rust

impl Solution {
    pub fn minimum_subarray_length(nums: Vec<i32>, k: i32) -> i32 {
        let n = nums.len();
        let mut cnt = vec![0; 32];
        let mut ans = n as i32 + 1;
        let mut s = 0;
        let mut i = 0;

        for (j, &x) in nums.iter().enumerate() {
            s |= x;
            for h in 0..32 {
                if (x >> h) & 1 == 1 {
                    cnt[h] += 1;
                }
            }

            while s >= k && i <= j {
                ans = ans.min((j - i + 1) as i32);
                let y = nums[i];
                for h in 0..32 {
                    if (y >> h) & 1 == 1 {
                        cnt[h] -= 1;
                        if cnt[h] == 0 {
                            s ^= 1 << h;
                        }
                    }
                }
                i += 1;
            }
        }
        if ans > n as i32 { -1 } else { ans }
    }
}

时间复杂度 $O(n \times \log M)$,空间复杂度 $O(\log M)$,其中 $n$ 和 $M$ 分别是数组的长度和数组中元素的最大值。


有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~

或值至少 K 的最短子数组 I

2025年1月6日 15:46

方法一:枚举

思路与算法

题目要求找到数组中最短特别非空子数组的长度,即找到最短的子数组且该子数组满足所有元素的按位或运算 $\text{OR}$ 的值大于等于 $k$。我们依次枚举数组中以每个下标 $i$,并找到以 $i$ 为起始位置的最短特别子数组,并记录最短长度即可,如果数组中不存在特别子数组,则返回 $-1$。

代码

###Java

class Solution {
    public int minimumSubarrayLength(int[] nums, int k) {
        int n = nums.length;
        int res = Integer.MAX_VALUE;
        for (int i = 0; i < n; i++) {
            int value = 0;
            for (int j = i; j < n; j++) {
                value |= nums[j];
                if (value >= k) {
                    res = Math.min(res, j - i + 1);
                    break;
                }
            }
        }
        return res == Integer.MAX_VALUE ? -1 : res;
    }
}

###C#

public class Solution {
    public int MinimumSubarrayLength(int[] nums, int k) {
        int n = nums.Length;
        int res = int.MaxValue;
        for (int i = 0; i < n; i++) {
            int value = 0;
            for (int j = i; j < n; j++) {
                value |= nums[j];
                if (value >= k) {
                    res = Math.Min(res, j - i + 1);
                    break;
                }
            }
        }
        return res == int.MaxValue ? -1 : res;
    }
}

###C++

class Solution {
public:
    int minimumSubarrayLength(vector<int>& nums, int k) {
        int n = nums.size();
        int res = INT_MAX;
        for (int i = 0; i < n; i++) {
            int value = 0;
            for (int j = i; j < n; j++) {
                value |= nums[j];
                if (value >= k) {
                    res = min(res, j - i + 1);
                    break;
                }
            }
        }
        return res == INT_MAX ? -1 : res;
    }
};

###Go

func minimumSubarrayLength(nums []int, k int) int {
    n := len(nums)
    res := n + 1
    for i := 0; i < n; i++ {
        value := 0
        for j := i; j < n; j++ {
            value |= nums[j]
            if value >= k {
                res = min(res, j-i+1)
                break
            }
        }
    }
    if res == n + 1 {
        return -1
    }
    return res
}

###Python

class Solution:
    def minimumSubarrayLength(self, nums: List[int], k: int) -> int:
        n = len(nums)
        res = inf
        for i in range(n):
            value = 0
            for j in range(i, n):
                value |= nums[j]
                if value >= k:
                    res = min(res, j - i + 1)
                    break
        return res if res != inf else -1

###C

int minimumSubarrayLength(int* nums, int numsSize, int k) {
    int res = INT_MAX;
    for (int i = 0; i < numsSize; i++) {
        int value = 0;
        for (int j = i; j < numsSize; j++) {
            value |= nums[j];
            if (value >= k) {
                res = (res < j - i + 1) ? res : j - i + 1;
                break;
            }
        }
    }
    return res == INT_MAX ? -1 : res;
}

###JavaScript

var minimumSubarrayLength = function(nums, k) {
    let n = nums.length;
    let res = Number.MAX_VALUE;
    for (let i = 0; i < n; i++) {
        let value = 0;
        for (let j = i; j < n; j++) {
            value |= nums[j];
            if (value >= k) {
                res = Math.min(res, j - i + 1);
                break;
            }
        }
    }
    return res === Number.MAX_VALUE ? -1 : res;
};

###TypeScript

function minimumSubarrayLength(nums: number[], k: number): number {
    const n = nums.length;
    let res = Number.MAX_VALUE;
    for (let i = 0; i < n; i++) {
        let value = 0;
        for (let j = i; j < n; j++) {
            value |= nums[j];
            if (value >= k) {
                res = Math.min(res, j - i + 1);
                break;
            }
        }
    }
    return res === Number.MAX_VALUE ? -1 : res;
};

###Rust

impl Solution {
    pub fn minimum_subarray_length(nums: Vec<i32>, k: i32) -> i32 {
        let n = nums.len();
        let mut res = i32::MAX;
        for i in 0..n {
            let mut value = 0;
            for j in i..n {
                value |= nums[j];
                if value >= k {
                    res = res.min((j - i + 1) as i32);
                    break;
                }
            }
        }
        if res == i32::MAX { -1 } else { res }
    }
}

复杂度分析

  • 时间复杂度:$O(n^2)$,其中 $n$ 表示给定数组 $\textit{nums}$ 的长度。以当前索引 $i$ 为起始位置的子数组元素进行或运算,需要的时间为 $O(n)$,需要枚举每个索引 $i$,一共需要枚举 $n$ 次,因此总的时间为 $O(n^2)$。

  • 空间复杂度:$O(1)$。

方法二:滑动窗口

思路与算法

根据运算的性质可以知道,给定的元素 $A$ 与任意元素 $B$ 进行运算的结果一定满足 $A|B \ge A$,由此可以知道对于任意子数组的长度增加元素后按位运算的结果一定大于等于增加前的结果,满足单调性。由此可知当每次固定数组的右端点时,我们可以使用二分查找或者滑动窗口来找到最短特别子数组的长度。

我们每次用 $[\text{left}, \textit{right}]$ 表示滑动窗口,每次固定右端点 $\textit{right}$,如果当前窗口内子数组按位的结果大于等于 $k$,此时向右移动左端点 $\textit{left}$ 直到窗口内子数组的值按位的结果刚好满足小于 $k$ 为止,此时以 $\textit{right}$ 为右端点的最短特别子数组长度即为 $\textit{right} - \textit{left} + 1$。

为了实时计算当前窗口中子数组运算的结果,我们需要维护二进制位每一位中 $1$ 出现的次数,如果当前位中 $1$ 的出现的次数为 $0$,则按位运算后该位为 $0$,否则该位则为 $1$。由于给定数组 $\textit{nums}$ 中的元素大小不超过 $10^9$ ,因此最多需要考虑二进制表示的前 $30$ 位。我们需要维护一个长度为 $30$ 的数组 $\textit{bits}$,其中 $\textit{bits}[i]$ 表示滑动窗口中满足二进制表示的从低到高第 $i$ 位的值为 $1$ 的元素个数。

具体计算过程如下:

  • 初始时 $\textit{left} = \textit{right} = 0$。每次将滑动窗口的右端点 $\textit{right}$ 向右移动一位,并更新 $\textit{bits}$ 数组,通过计算 $\textit{bits}$ 数组,如果当前窗口内子数组按位的结果大于等于 $k$ 时,窗口内的子数组一定为特别子数组,则尝试应向右移动左端点 $\textit{right}$,并更新 $\textit{bits}$ 数组。

  • 向右移动左端点直到当窗口内子数组按位的结果小于 $k$ 或者 $\textit{left} > \textit{right}$ 时,此时不再移动左端点 $\textit{left}$。在移动窗口的过程中,我们同时更新最短特别子数组长度的长度。

  • 由于可能存在整个数组中所有元素按位的结果小于 $k$,此时不存在特别子数组,此时应返回 $-1$。

代码

###C++

class Solution {
public:
    int minimumSubarrayLength(vector<int>& nums, int k) {
        int n = nums.size();
        vector<int> bits(30);
        auto calc = [](vector<int> &bits) -> int {
            int ans = 0;
            for (int i = 0; i < bits.size(); i++) {
                if (bits[i] > 0) {
                    ans |= 1 << i;
                }
            }
            return ans;
        };

        int res = INT_MAX;
        for (int left = 0, right = 0; right < n; right++) {
            for (int i = 0; i < 30; i++) {
                bits[i] += (nums[right] >> i) & 1;
            }
            while (left <= right && calc(bits) >= k) {
                res = min(res, right - left + 1);
                for (int i = 0; i < 30; i++) {
                    bits[i] -= (nums[left] >> i) & 1;
                }
                left++;
            }
        }
        
        return res == INT_MAX ? -1: res;
    }
};

###Java

class Solution {
    public int minimumSubarrayLength(int[] nums, int k) {
        int n = nums.length;
        int[] bits = new int[30];
        int res = Integer.MAX_VALUE;
        for (int left = 0, right = 0; right < n; right++) {
            for (int i = 0; i < 30; i++) {
                bits[i] += (nums[right] >> i) & 1;
            }
            while (left <= right && calc(bits) >= k) {
                res = Math.min(res, right - left + 1);
                for (int i = 0; i < 30; i++) {
                    bits[i] -= (nums[left] >> i) & 1;
                }
                left++;
            }
        }

        return res == Integer.MAX_VALUE ? -1 : res;
    }

    private int calc(int[] bits) {
        int ans = 0;
        for (int i = 0; i < bits.length; i++) {
            if (bits[i] > 0) {
                ans |= 1 << i;
            }
        }
        return ans;
    }
}

###C#

public class Solution {
    public int MinimumSubarrayLength(int[] nums, int k) {
        int n = nums.Length;
        int[] bits = new int[30];
        int res = int.MaxValue;

        for (int left = 0, right = 0; right < n; right++) {
            for (int i = 0; i < 30; i++) {
                bits[i] += (nums[right] >> i) & 1;
            }
            while (left <= right && Calc(bits) >= k) {
                res = Math.Min(res, right - left + 1);
                for (int i = 0; i < 30; i++) {
                    bits[i] -= (nums[left] >> i) & 1;
                }
                left++;
            }
        }

        return res == int.MaxValue ? -1 : res;
    }

    private int Calc(int[] bits) {
        int ans = 0;
        for (int i = 0; i < bits.Length; i++) {
            if (bits[i] > 0) {
                ans |= 1 << i;
            }
        }
        return ans;
    }
}

###Go

func minimumSubarrayLength(nums []int, k int) int {
    n := len(nums)
bits := make([]int, 30)
res := math.MaxInt32

for left, right := 0, 0; right < n; right++ {
for i := 0; i < 30; i++ {
bits[i] += (nums[right] >> i) & 1
}
for left <= right && calc(bits) >= k {
res = min(res, right - left + 1)
for i := 0; i < 30; i++ {
bits[i] -= (nums[left] >> i) & 1
}
left++
}
}

if res == math.MaxInt32 {
return -1
}
return res
}

func calc(bits []int) int {
ans := 0
for i := 0; i < len(bits); i++ {
if bits[i] > 0 {
ans |= 1 << i
}
}
return ans
}

###Python

class Solution:
    def minimumSubarrayLength(self, nums: List[int], k: int) -> int:
        n = len(nums)
        bits = [0] * 30
        res = inf
        def calc(bits):
            return sum(1 << i for i in range(30) if bits[i] > 0)

        left = 0
        for right in range(n):
            for i in range(30):
                bits[i] += (nums[right] >> i) & 1
            while left <= right and calc(bits) >= k:
                res = min(res, right - left + 1)
                for i in range(30):
                    bits[i] -= (nums[left] >> i) & 1
                left += 1

        return -1 if res == inf else res

###C

int calc(int* bits) {
    int ans = 0;
    for (int i = 0; i < 30; i++) {
        if (bits[i] > 0) {
            ans |= 1 << i;
        }
    }
    return ans;
}

int minimumSubarrayLength(int* nums, int numsSize, int k) {
    int bits[30] = {0};
    int res = INT_MAX;

    for (int left = 0, right = 0; right < numsSize; right++) {
        for (int i = 0; i < 30; i++) {
            bits[i] += (nums[right] >> i) & 1;
        }
        while (left <= right && calc(bits) >= k) {
            res = fmin(res, right - left + 1);
            for (int i = 0; i < 30; i++) {
                bits[i] -= (nums[left] >> i) & 1;
            }
            left++;
        }
    }

    return res == INT_MAX ? -1 : res;
}

###JavaScript

var minimumSubarrayLength = function(nums, k) {
    const n = nums.length;
    const bits = new Array(30).fill(0);
    let res = Number.MAX_SAFE_INTEGER;;

    const calc = (bits) => {
        let ans = 0;
        for (let i = 0; i < bits.length; i++) {
            if (bits[i] > 0) {
                ans |= 1 << i;
            }
        }
        return ans;
    };

    let left = 0;
    for (let right = 0; right < n; right++) {
        for (let i = 0; i < 30; i++) {
            bits[i] += (nums[right] >> i) & 1;
        }
        while (left <= right && calc(bits) >= k) {
            res = Math.min(res, right - left + 1);
            for (let i = 0; i < 30; i++) {
                bits[i] -= (nums[left] >> i) & 1;
            }
            left++;
        }
    }

    return res === Number.MAX_SAFE_INTEGER ? -1 : res;
};

###TypeScript

function minimumSubarrayLength(nums: number[], k: number): number {
    const n = nums.length;
    const bits = new Array(30).fill(0);
    let res = Infinity;

    const calc = (bits: number[]): number => {
        let ans = 0;
        for (let i = 0; i < bits.length; i++) {
            if (bits[i] > 0) {
                ans |= 1 << i;
            }
        }
        return ans;
    };

    let left = 0;
    for (let right = 0; right < n; right++) {
        for (let i = 0; i < 30; i++) {
            bits[i] += (nums[right] >> i) & 1;
        }
        while (left <= right && calc(bits) >= k) {
            res = Math.min(res, right - left + 1);
            for (let i = 0; i < 30; i++) {
                bits[i] -= (nums[left] >> i) & 1;
            }
            left++;
        }
    }

    return res === Infinity ? -1 : res;
};

###Rust

impl Solution {
    pub fn minimum_subarray_length(nums: Vec<i32>, k: i32) -> i32 {
        let n = nums.len();
        let mut bits = [0; 30];
        let mut res = i32::MAX;

        let calc = |bits: &[i32]| -> i32 {
            let mut ans = 0;
            for i in 0..30 {
                if bits[i] > 0 {
                    ans |= 1 << i;
                }
            }
            ans
        };

        let mut left = 0;
        for right in 0..n {
            for i in 0..30 {
                bits[i] += (nums[right] >> i) & 1;
            }
            while left <= right && calc(&bits) >= k {
                res = res.min((right - left + 1) as i32);
                for i in 0..30 {
                    bits[i] -= (nums[left] >> i) & 1;
                }
                left += 1;
            }
        }

        if res == i32::MAX {
            -1
        } else {
            res
        }
    }
}

复杂度分析

  • 时间复杂度:$O(n \log U)$,其中 $n$ 表示给定数组 $\textit{nums}$ 的长度,$U$ 表示数组中的最大的元素。由于使用滑动窗口遍历需要的时间为 $O(n)$,每次更新窗口元素时需要实时计算当前子数组按位的值需要的时间为 $O(\log U)$,此时需要的总时间即为 $O(n \log U)$。

  • 空间复杂度:$O(\log U)$。计算时需要存储当前子数组中每一个二进制位中的统计情况,最多有 $\log U$ 位需要记录,因此需要的空间为 $\log U$。

每日一题-或值至少 K 的最短子数组 I🟢

2025年1月16日 00:00

给你一个 非负 整数数组 nums 和一个整数 k 。

如果一个数组中所有元素的按位或运算 OR 的值 至少 为 k ,那么我们称这个数组是 特别的 。

请你返回 nums 中 最短特别非空 子数组的长度,如果特别子数组不存在,那么返回 -1 。

 

示例 1:

输入:nums = [1,2,3], k = 2

输出:1

解释:

子数组 [3] 的按位 OR 值为 3 ,所以我们返回 1 。

注意,[2] 也是一个特别子数组。

示例 2:

输入:nums = [2,1,8], k = 10

输出:3

解释:

子数组 [2,1,8] 的按位 OR 值为 11 ,所以我们返回 3 。

示例 3:

输入:nums = [1,2], k = 0

输出:1

解释:

子数组 [1] 的按位 OR 值为 1 ,所以我们返回 1 。

 

提示:

  • 1 <= nums.length <= 50
  • 0 <= nums[i] <= 50
  • 0 <= k < 64

或值至少k的最短子数组

作者 away-ks
2024年4月6日 15:34

Problem: 3095. 或值至少 K 的最短子数组 I

[TOC]

思路

由于给定的数据量很小,优先考虑暴力解法。解决了再考虑优化问题。

解题方法

1. 解法1: 暴力

  • 首先遍历一遍数组,判断有没有单个数就已经>=k的,如果有直接返回1,因为自己和自己求或运算还是等于自身(A or A = A)
  • 接着就是三层循环遍历每一个可能的子数组
    • for(int i=0;i<n;i++) 第一层循环枚举每一个可能的起始下标
    • for(int j=i+1;j<n;j++) 第二层循环枚举每一个可能的结束下标
    • for(int index=i;index<=j;index++)第三层循环遍历开始到结尾的子数组
  • 最后将子数组内的值求或运算,进行判断,符合条件就把子数组长度j-i+1更新为最优解
  • 细节:
  1. 在第三层循环之前要将总或值or重置为0,为了防止前面的结果与下一次循环计算到一起
  2. 退出循环后,如果没有找到可行的解,即没有满足条件or>=k的,要返回-1
    if(minLen == Integer.MAX_VALUE) return -1;
    即最小子数组长度没有改变

复杂度

时间复杂度:

添加时间复杂度, 示例: $$$O(n^3)$$$

空间复杂度:

添加空间复杂度, 示例: $O(n)$

Code

###Java

class Solution {
    public int minimumSubarrayLength(int[] nums, int k) {
        int n = nums.length;
        int minLen = Integer.MAX_VALUE;


        for(int i=0;i<n;i++){
            if(nums[i] >= k) return 1;//自己与自己或,简写
        }

        for(int i=0;i<n;i++){//枚举起始下标
            for(int j=i+1;j<n;j++)//枚举结束下标
            {
                int or = 0;
                for(int index=i;index<=j;index++){//遍历开始到结尾的数
                    or |= nums[index];
                    if(or >= k) minLen = Math.min(minLen,j-i+1);
                }
            }
        }
        if(minLen == Integer.MAX_VALUE) return -1;
        return minLen;
    }
}

1. 解法2: 或的性质

对于数据量较少的情况,暴力解法是没有问题的,但数据量太大时,这种方法就行不通了。这就是为什么这道题是Easy,而同样的另一题3097是Medium

所有就有了第二种优化解法:滑动窗口(可以参考灵神的解法,思路也是相当牛逼 子数组 OR/AND/GCD 通用模板

另外,如果对位运算不够了解的,可以看看这篇文章 位运算有什么奇技淫巧

  • 首先我们需要用到或运算的一个很大的特点,就是越或越大,什么意思呢
    就是一个数与另一个数或,只有0变1或1变1,没有1变0,所以或运算要么变大,要么不变。
    举个例子:1001 or 1100 -> 1101变大了,1001 or 0001 -> 1001,即使是或上一个比自己小的数,最多就是不变。
  • 利用这个特性,我们就可以省去很多计算的步骤
    例如:数组为[1,2,3],我们先计算1 or 1,1 or 2 = 2,当要计算1 or 2 or 3时,不用再一个个计算,而是直接2 or 3,我们只需要在每一次或运算之后,保留最大的。
    于是,可以用一个map,key为以该下标的数和前面数的总or值,value为对应的子数组左端点的最大值。为了方便和时间优化起见,我们用一个列表list代替。list存放每一个or值和对应下标的集合。
    例如:[1,2,3]
  1. 初始时:把[0,0]加入集合
  2. 集合中的key与每一个nums[i]或运算,得到[1,0]
  3. [1,0]与2或,得到[3,0],然后把[2,1]加入集合
  4. [3,0]与3或,[2,1]与3或,得到[3,0],[3,1],然后把[3,2]加入,全部更新为[3,2]

复杂度

时间复杂度:

添加时间复杂度, 示例: $O(n)$

空间复杂度:

添加空间复杂度, 示例: $O(n)$

Code

###Java

class Solution {
    public int minimumSubarrayLength(int[] nums, int k) {
        int n = nums.length;
        int minLen = Integer.MAX_VALUE;
        List<int[]> ors = new ArrayList<>();//存放数组
        for(int i=0;i<n;i++){
            ors.add(new int[]{0,i});//初始化,或值为0和对应下标
            int j = 0;
            for(int[] or:ors){//遍历每一个列表中的数组
                or[0] |= nums[i];
                if(or[0] >= k) minLen = Math.min(minLen,i-or[1]+1);
                if(ors.get(j)[0] == or[0]){
                    ors.get(j)[1] = or[1];//更新下标最大值
                }else {
                    j++;
                    ors.set(j,or);//把当前数组替换为计算后的or数组
                }
            }
            ors.subList(j+1,ors.size()).clear();//清空j+1到结尾的一些数组
        }
        return minLen == Integer.MAX_VALUE?-1:minLen;
    }
}

暴力模拟

Problem: 3095. 或值至少 K 的最短子数组 I

[TOC]

思路

暴力模拟

Code

执行用时分布50ms击败47.20%;消耗内存分布16.50MB击败20.98%

###Python3

class Solution:
    def minimumSubarrayLength(self, nums: List[int], k: int) -> int:
        n = len(nums)
        for l in range(1, n + 1):
            for i in range(n - l + 1):
                if reduce(or_, nums[i: i + l]) >= k: return l
        return -1

您若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^

↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者! ^_^

超过阈值的最少操作数 II

2025年1月6日 15:59

方法一: 模拟

思路与算法

本题使用「最小堆」实现的「优先队列」来进行模拟。

首先把所有数加入堆中。当堆顶元素小于 $k$ 时,就把取出堆中最小的两个数,把更新后的数加入堆中。

最后返回操作的数目。

代码

###C++

class Solution {
public:
    int minOperations(vector<int> &nums, int k) {
        int res = 0;
        priority_queue<long long, vector<long long>, greater<long long>> pq(nums.begin(), nums.end());
        while (pq.top() < k) {
            long long x = pq.top(); pq.pop();
            long long y = pq.top(); pq.pop();
            pq.push(x + x + y);
            res++;
        }
        return res;
    }
};

###Java

class Solution {
    public int minOperations(int[] nums, int k) {
        int res = 0;
        PriorityQueue<Long> pq = new PriorityQueue<>();
        for (long num : nums) {
            pq.offer(num);
        }
        while (pq.peek() < k) {
            long x = pq.poll(), y = pq.poll();
            pq.offer(x + x + y);
            res++;
        }
        return res;
    }
}

###Python

class Solution:
    def minOperations(self, nums: List[int], k: int) -> int:
        res = 0
        h = nums[:]
        heapify(h)
        while h[0] < k:
            x = heappop(h)
            y = heappop(h)
            heappush(h, x + x + y)
            res += 1
        return res

###JavaScript

var minOperations = function(nums, k) {
    let res = 0;
    const pq = new MinHeap();

    for (const num of nums) {
        pq.push(num);
    }
    while (pq.peek() < k) {
        const x = pq.pop();
        const y = pq.pop();
        pq.push(x + x + y);
        res++;
    }

    return res;
};

class MinHeap {
    constructor() {
        this.heap = [];
    }

    size() {
        return this.heap.length;
    }

    isEmpty() {
        return this.size() === 0;
    }

    peek() {
        return this.heap[0];
    }

    push(val) {
        this.heap.push(val);
        this._heapifyUp();
    }

    pop() {
        if (this.size() === 1) return this.heap.pop();

        const root = this.heap[0];
        this.heap[0] = this.heap.pop();
        this._heapifyDown();
        return root;
    }

    _heapifyUp() {
        let index = this.size() - 1;
        const element = this.heap[index];

        while (index > 0) {
            const parentIndex = Math.floor((index - 1) / 2);
            const parent = this.heap[parentIndex];

            if (element >= parent) break;

            this.heap[index] = parent;
            this.heap[parentIndex] = element;
            index = parentIndex;
        }
    }

    _heapifyDown() {
        let index = 0;
        const length = this.size();
        const element = this.heap[0];

        while (true) {
            let leftChildIndex = 2 * index + 1;
            let rightChildIndex = 2 * index + 2;
            let smallest = index;

            if (
                leftChildIndex < length &&
                this.heap[leftChildIndex] < this.heap[smallest]
            ) {
                smallest = leftChildIndex;
            }

            if (
                rightChildIndex < length &&
                this.heap[rightChildIndex] < this.heap[smallest]
            ) {
                smallest = rightChildIndex;
            }

            if (smallest === index) break;

            this.heap[index] = this.heap[smallest];
            this.heap[smallest] = element;
            index = smallest;
        }
    }
}

###TypeScript

function minOperations(nums: number[], k: number): number {
    let res = 0;
    const pq = new MinHeap();

    for (const num of nums) {
        pq.push(num);
    }

    while (pq.peek() < k) {
        const x = pq.pop();
        const y = pq.pop();
        pq.push(x + x + y);
        res++;
    }

    return res;
};

class MinHeap {
    private heap: number[];

    constructor() {
        this.heap = [];
    }

    size(): number {
        return this.heap.length;
    }

    isEmpty(): boolean {
        return this.size() === 0;
    }

    peek(): number {
        if (this.isEmpty()) {
            throw new Error("Heap is empty");
        }
        return this.heap[0];
    }

    push(val: number): void {
        this.heap.push(val);
        this._heapifyUp();
    }

    pop(): number {
        if (this.isEmpty()) {
            throw new Error("Heap is empty");
        }
        if (this.size() === 1) {
            return this.heap.pop() as number;
        }

        const root = this.heap[0];
        this.heap[0] = this.heap.pop() as number;
        this._heapifyDown();
        return root;
    }

    private _heapifyUp(): void {
        let index = this.size() - 1;
        const element = this.heap[index];

        while (index > 0) {
            const parentIndex = Math.floor((index - 1) / 2);
            const parent = this.heap[parentIndex];

            if (element >= parent) break;

            this.heap[index] = parent;
            this.heap[parentIndex] = element;
            index = parentIndex;
        }
    }

    private _heapifyDown(): void {
        let index = 0;
        const length = this.size();
        const element = this.heap[0];

        while (true) {
            let leftChildIndex = 2 * index + 1;
            let rightChildIndex = 2 * index + 2;
            let smallest = index;

            if (
                leftChildIndex < length &&
                this.heap[leftChildIndex] < this.heap[smallest]
            ) {
                smallest = leftChildIndex;
            }

            if (
                rightChildIndex < length &&
                this.heap[rightChildIndex] < this.heap[smallest]
            ) {
                smallest = rightChildIndex;
            }

            if (smallest === index) break;

            this.heap[index] = this.heap[smallest];
            this.heap[smallest] = element;
            index = smallest;
        }
    }
}

###Go

func minOperations(nums []int, k int) int {
res := 0
pq := &MinHeap{}
heap.Init(pq)
for _, num := range nums {
heap.Push(pq, num)
}

for (*pq)[0] < k {
x := heap.Pop(pq).(int)
y := heap.Pop(pq).(int)
heap.Push(pq, x+x+y)
res++
}

return res
}

// MinHeap
type MinHeap []int

func (h MinHeap) Len() int           { return len(h) }
func (h MinHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h MinHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }

func (h *MinHeap) Push(x interface{}) {
*h = append(*h, x.(int))
}

func (h *MinHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}

###C#

public class Solution {
    public int MinOperations(int[] nums, int k) {
        int res = 0;
        PriorityQueue<long, long> pq = new PriorityQueue<long, long>();
        foreach (int num in nums) {
            pq.Enqueue(num, num);
        }
        while (pq.Peek() < k) {
            long x = pq.Dequeue(), y = pq.Dequeue();
            pq.Enqueue(x + x + y, x + x + y);
            res++;
        }
        return res;
    }
}

###C

#define MIN_QUEUE_SIZE 64

typedef struct Element {
    long long data[1];
} Element;

typedef bool (*compare)(const void *, const void *);

typedef struct PriorityQueue {
    Element *arr;
    int capacity;
    int queueSize;
    compare lessFunc;
} PriorityQueue;

Element *createElement(int x, int y) {
    Element *obj = (Element *)malloc(sizeof(Element));
    obj->data[0] = x;
    obj->data[1] = y;
    return obj;
}

static bool less(const void *a, const void *b) {
    Element *e1 = (Element *)a;
    Element *e2 = (Element *)b;
    return e1->data[0] > e2->data[0];
}

static bool greater(const void *a, const void *b) {
    Element *e1 = (Element *)a;
    Element *e2 = (Element *)b;
    return e1->data[0] < e2->data[0];
}

static void memswap(void *m1, void *m2, size_t size){
    unsigned char *a = (unsigned char*)m1;
    unsigned char *b = (unsigned char*)m2;
    while (size--) {
        *b ^= *a ^= *b ^= *a;
        a++;
        b++;
    }
}

static void swap(Element *arr, int i, int j) {
    memswap(&arr[i], &arr[j], sizeof(Element));
}

static void down(Element *arr, int size, int i, compare cmpFunc) {
    for (int k = 2 * i + 1; k < size; k = 2 * k + 1) {
        if (k + 1 < size && cmpFunc(&arr[k], &arr[k + 1])) {
            k++;
        }
        if (cmpFunc(&arr[k], &arr[(k - 1) / 2])) {
            break;
        }
        swap(arr, k, (k - 1) / 2);
    }
}

PriorityQueue *createPriorityQueue(compare cmpFunc) {
    PriorityQueue *obj = (PriorityQueue *)malloc(sizeof(PriorityQueue));
    obj->capacity = MIN_QUEUE_SIZE;
    obj->arr = (Element *)malloc(sizeof(Element) * obj->capacity);
    obj->queueSize = 0;
    obj->lessFunc = cmpFunc;
    return obj;
}

void heapfiy(PriorityQueue *obj) {
    for (int i = obj->queueSize / 2 - 1; i >= 0; i--) {
        down(obj->arr, obj->queueSize, i, obj->lessFunc);
    }
}

void enQueue(PriorityQueue *obj, Element *e) {
    // we need to alloc more space, just twice space size
    if (obj->queueSize == obj->capacity) {
        obj->capacity *= 2;
        obj->arr = realloc(obj->arr, sizeof(Element) * obj->capacity);
    }
    memcpy(&obj->arr[obj->queueSize], e, sizeof(Element));
    for (int i = obj->queueSize; i > 0 && obj->lessFunc(&obj->arr[(i - 1) / 2], &obj->arr[i]); i = (i - 1) / 2) {
        swap(obj->arr, i, (i - 1) / 2);
    }
    obj->queueSize++;
}

Element* deQueue(PriorityQueue *obj) {
    swap(obj->arr, 0, obj->queueSize - 1);
    down(obj->arr, obj->queueSize - 1, 0, obj->lessFunc);
    Element *e =  &obj->arr[obj->queueSize - 1];
    obj->queueSize--;
    return e;
}

bool isEmpty(const PriorityQueue *obj) {
    return obj->queueSize == 0;
}

Element* front(const PriorityQueue *obj) {
    if (obj->queueSize == 0) {
        return NULL;
    } else {
        return &obj->arr[0];
    }
}

void clear(PriorityQueue *obj) {
    obj->queueSize = 0;
}

int size(const PriorityQueue *obj) {
    return obj->queueSize;
}

void freeQueue(PriorityQueue *obj) {
    free(obj->arr);
    free(obj);
}

int minOperations(int* nums, int numsSize, int k) {
    int res = 0;
    PriorityQueue *pq = createPriorityQueue(less);
    struct Element e;
    for (int i = 0; i < numsSize; i++) {
        e.data[0] = nums[i];
        enQueue(pq, &e);
    }
    while (front(pq)->data[0] < k) {
        long long x = front(pq)->data[0]; deQueue(pq);
        long long y = front(pq)->data[0]; deQueue(pq);
        e.data[0] = x + x + y;
        enQueue(pq, &e);
        res++;
    }
    freeQueue(pq);
    return res;
}

###Rust

use std::collections::BinaryHeap;
use core::cmp::Reverse;

impl Solution {
    pub fn min_operations(nums: Vec<i32>, k: i32) -> i32 {
        let mut res = 0;
        let mut pq: BinaryHeap<Reverse<i64>> = BinaryHeap::new();
        for &num in &nums {
            pq.push(Reverse(num as i64));
        }
        while let Some(Reverse(x)) = pq.pop() {
            if x >= k as i64 {
                break;
            }
            if let Some(Reverse(y)) = pq.pop() {
                pq.push(Reverse(x + x + y));
                res += 1;
            }
        }
        res
    }
}

复杂度分析

  • 时间复杂度:$O(n\log n)$,其中 $n$ 是数组的长度。

  • 空间复杂度:$O(n)$,其中 $n$ 是数组的长度。

排序双指针+仅操作小于k/3的数

作者 lMFcHre2sC
2024年3月4日 23:19

Problem: 3066. 超过阈值的最少操作数 II

解题方法

先对原数组arr1排序
依次取最小数x,y,产生的min(x, y) * 2 + max(x, y)是有序的,放入新数组arr2
使用双指针从两数组取最小数

可根据 k/3,k 按大小将数划分三类
大于等于k/3小于k的数(k3k)必可以两两配对产生超阈值数
只需要操作和保存小于k/3的数,将其全部转化

复杂度

时间复杂度:$O(nlogn)$
空间复杂度:$O(n)$

Code

###Java

class Solution {
    public int minOperations(int[] nums, int k) {
        double k3=k/3.0;
        int ans=0,index1=0,index2=0;
        int x=0,y=0;
        int n=nums.length,size1=0,size2=0;
        int k3kCnt=0,mink3k=k;
        int[] arr1=nums;
        for(int num:nums){
            if(num<k3){
                arr1[size1++]=num;
            }else if(num<k){
                mink3k=Math.min(mink3k,num);
                k3kCnt++;
            }    
        }
        int[] arr2=new int[size1];
        Arrays.sort(arr1,0,size1);
        while(size1+size2-index1-index2>1){
            if(index1<size1&&(index2==size2||arr1[index1]<arr2[index2])){
                x=arr1[index1++];
            }else{
                x=arr2[index2++];
            }
            ans++;
            if(index1<size1&&(index2==size2||arr1[index1]<arr2[index2])){
                y=arr1[index1++];
            }else{
                y=arr2[index2++];
            }
            int newnum=x*2+y;
            if(newnum<k3){
                arr2[size2++]=newnum;
            }else if(newnum<k){
                mink3k=Math.min(mink3k,newnum);
                k3kCnt++;
            }
        }
        if(size1+size2-index1-index2==1){
            ans++;
            if(mink3k!=k){
                int newnum=(index1!=size1?arr1[index1++]:arr2[index2++])*2+mink3k;
                if(newnum>=k){
                    k3kCnt--;
                }
            }        
        }
        ans+=Math.ceil(k3kCnt/2.0);
        return ans;
    }
}
❌
❌