3171. 找到按位或最接近 K 的子数组

1. 题目描述

给你一个数组nums和一个整数k 。你需要找到nums的一个子数组满足子数组中所有元素按位或运算 OR的值与k绝对差尽可能。换言之,你需要选择一个子数组nums[l..r]满足|k-(nums[l] OR nums[l + 1] ... OR nums[r])| 最小
请你返回最小的绝对差值。
子数组是数组中连续的非空元素序列。

  1. 示例 1:
    输入:nums = [1,2,4,5], k = 3
    输出:0
    解释:子数组 nums[0…1] 的按位 OR 运算值为 3 ,得到最小差值 |3 - 3| = 0 。

  2. 示例 2:
    输入:nums = [1,3,1,3], k = 2
    输出:1
    解释:子数组 nums[1…1] 的按位 OR 运算值为 3 ,得到最小差值 |3 - 2| = 1 。

  3. 示例 3:
    输入:nums = [1], k = 10
    输出:9
    解释:只有一个子数组,按位 OR 运算值为 1 ,得到最小差值 |10 - 1| = 9 。

  4. 提示:
    1 <= nums.length <= 105
    1 <= nums[i] <= 109
    1 <= k <= 109

题目链接:https://leetcode.cn/problems/find-subarray-with-bitwise-or-closest-to-k/description/?envType=daily-question&envId=2024-10-07

2. 解题思路

  1. 返回最小的差值,即寻找一个子数组,首先想到滑动窗口,思考下是否具有滑动窗口的条件;
  2. 或运算,运算的结果不变或增加,不会减小,所以随着元素的增加,结果具有单调性;
  3. 求与k的最小绝对差值,即距离k最近,当小于k时,越大越接近k,当大于k时,越大距离k越远;即刚大于k时,如果在这个范围内没有最小的绝对差值,那值继续增大,也没有最小的绝对差值;
  4. 思考下,即得到滑入和滑出窗口的条件,即当或的值小于k时,继续向窗口内添加元素,当或值不小于k时停止,然后滑出最左边的一个元素,继续判断或值是否小于k;

3. 实现

3.1 code

class Solution {

    public int minimumDifference(int[] nums, int k) {
        int res = Integer.MAX_VALUE;
        // 31位数组,记录子数组内元素每个位置1的个数
        int[] dp = new int[31];
        // 右窗口边界,包含r
        int r = 0;
        // 窗口内所有元素或的值
        int tmp = 0;
        for (int l = 0; l < nums.length; l++) {
        	// 终止条件,tmp 大于等于 k,或者r出界
            while (tmp < k && r < nums.length) {
            	// 加上nump[r]每个位置上1的个数
                sumBit(nums[r++], dp);
                // 计算或的值
                tmp = countNum(dp);
                // 计算最小值
                res = Math.min(res, Math.abs(k - tmp));
            }
            // 加速点,当tmp == k时,此时最小,返回0
            if (tmp == k) {
                return 0;
            }
            // 最左边的元素滑出窗口,减去num[l]每个位置上1的个数
            subtractionBit(nums[l], dp);
            tmp = countNum(dp);
            // 注意,存在 l==r的情况,此时窗口内没有元素,不满足非空元素序列条件
            if (tmp > 0) {
                res = Math.min(res, Math.abs(k - tmp));
            }
        }
        return res;
    }
    
    // 把num每个位上的1累加到数组dp中
    private void sumBit(int num, int[] dp) {
        for (int i = 0; i < dp.length; i++) {
            dp[i] += (num >>> i) & 1;
        }
    }
	
	// 在dp中去除num每个位上1的个数
    private void subtractionBit(int num, int[] dp) {
        for (int i = 0; i < dp.length; i++) {
            dp[i] -= (num >>> i) & 1;
        }
    }

	// 统计dp中或的值 
    private int countNum(int[] dp) {
        int res = 0;
        for (int i = 0; i < dp.length; i++) {
            if (dp[i] > 0) {
                res |= 1 << i;
            }
        }
        return res;
    }
}

3.2 说明

  1. 使用一个长度位31的int数组,记录每个位1的个数,这样可以快速得到窗口内或的值,不需要遍历窗口内的所有元素求或的值;
  2. 窗口内没进入一个元素和出去一个元素,就要比较一次当前值是否是答案;
  3. 无回退的情况,时间复杂度为: O ( n ) O(n) O(n),但是常数时间比较大

3.3 运行结果

在这里插入图片描述

4. 优秀答案

阅读一下优秀的示例代码,揣摩一下大神的思路、增长一下见识、学习一下技巧;

4.1 code

    public int minimumDifference(int[] nums, int k) {
        int l = 0, r = 1;
        int ans = Math.abs(nums[0] - k);
        // 以右边界为基础,遍历数组
        for (r = 1; r < nums.length; r++) {
        	// 如果和前一个元素相同,前一种已经统计过了,跳过、不再重复统计
            if (nums[r] == nums[r - 1]) continue;
            // 如果等于k,此时最小,返回结果
            if (nums[r] == k) return 0;
            // 计算只有num[r]一个元素时,是否是最小值
            ans = Math.min(ans, Math.abs(nums[r] - k));
            // 如果nums[r] 大于 k,继续往前追加元素,或值只会不变或变大,最小值不会变小,所以跳过
            if (nums[r] > k) continue;
            // 往左追加元素,当或值 等于 nums[l]时,终止循环(加速项),nums[l]之前已经计算过了,如果或值没有变化,说明不会增加了,退出循环
            for (l = r - 1; l >= 0 && ((nums[l] | nums[r]) != nums[l]); l--) {
            	// 加速项,前缀或值,把后面的值或到前一个值上
                nums[l] |= nums[r];
                // 计算当前值是否是最小值
                ans = Math.min(ans, Math.abs(nums[l] - k));
                // 或值大于k时,退出循环,
                if (nums[l] > k) break;
                // 或值大于k时,获得最优解
                if (nums[l] == k) return 0;
            }
        }
        return ans;
    }

4.2 说明

  1. 时间复杂度是 O ( n 2 ) O(n^2) O(n2),最坏情况下无法减枝,l一直遍历到l=0时结束向前的循环;
  2. 通过巧妙的减枝技巧,使得时间非常的块,非常值得学习;

5. 总结

  1. 笔记记录

6. 参考资料

  1. https://leetcode.cn/problems/find-subarray-with-bitwise-or-closest-to-k/description/?envType=daily-question&envId=2024-10-07
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值