题目解析 小K的区间与值和 | 豆包MarsCode AI 刷题

94 阅读4分钟

小K的区间与值和

题目链接

题目背景

小 K 有一个数组,她定义数组的权值为数组中任选两个数的按位与的值之和。具体来说,对于数组中的每个连续子数组,我们可以计算所有可能的两个元素的按位与值之和,并将这些值相加。小 K 想知道该数组中所有可能的连续子数组的权值和是多少,最后结果对 (109+7)(10^9 + 7) 取模。

示例

示例 1

  • 输入:n = 4, a = [2, 3, 1, 2]
  • 输出:16

示例 2

  • 输入:n = 3, a = [5, 6, 7]
  • 输出:25

示例 3

  • 输入:n = 2, a = [1, 10]
  • 输出:0

示例 4

  • 输入:n = 5, a = [1, 2, 4, 8, 16]
  • 输出:0

题目解析

问题类型

本题属于组合枚举+位运算结合的算法问题。

核心在于全面地枚举数组中的所有连续子数组,并针对每个子数组进行特定的位运算组合求值

最后汇总这些值得到最终结果。

原始方法

思路

1.首先通过两层循环来实现子数组的枚举。

外层循环以变量 left 控制子数组的左端点,从数组的起始位置 0 开始,逐次向后移动,直到达到数组末尾索引 n - 1

中层循环以变量 right 控制子数组的右端点,对于每一个确定的左端点 left,右端点 rightleft 开始,一直到数组末尾 n - 1,如此就能确定每一个连续子数组。

2.在确定了每一个连续子数组后,需要对其内部任意两个元素进行按位与操作,并将这些按位与的结果相加。这里使用了两层嵌套的内层循环来实现。

3.将每个子数组的按位与值之和累加到总的结果 result

还有一点需要记住,题目要求对结果取模,这个常见的设定不能忽略。变量中并对 (109+7)(10^9 + 7) 取模,防止结果数值过大导致溢出。

代码实现
public class Main {
    private static final int MOD = 1000000007; 

    public static int solution(int n, int[] a) {
        int result = 0;
        for (int left = 0; left < n; left++) {  // 外层循环枚举子数组的 左端点
            for (int right = left; right < n; right++) {  // 中层循环枚举子数组的 右端点
                int subSum = 0;
                for (int i = 0; i < right - left + 1; i++) {  
                    for (int j = i + 1; j < right - left + 1; j++) {  
                        subSum += (a[left + i] & a[left + j]);  
                        subSum %= MOD;  
                    }
                }
                result += subSum; 
                result %= MOD;  
            }
        }
        return result;
    }

    public static void main(String[] args) {
        System.out.println(solution(4, new int[]{2, 3, 1, 2}) == 16);
        System.out.println(solution(3, new int[]{5, 6, 7}) == 25);
        System.out.println(solution(2, new int[]{1, 10}) == 0);
        System.out.println(solution(5, new int[]{1, 2, 4, 8, 16}) == 0);
    }
}
复杂度分析

原始方法的时间复杂度为O(n4) O(n^4),四层嵌套循环。非常低效。

实践记录及工具使用

使用这 MarsCode AI 刷题工具,在学习和解题过程中可以方便很多,让 MarsCode AI 来评价我们的代码,提供进一步优化的可能性的拓展。

image.png

刷题实践: 每次提交代码后,AI 工具会立即给出反馈,指出代码中的错误和优化建议。

参考了 AI 工具推荐的类似题目和解题思路,逐渐掌握了位运算和前缀和的应用方法。

通过不断的 AI 交流,可以最终成功地将时间复杂度从 O(n4)O(n^4)降低到 O(n3)O(n^3)

image.png

代码实现
public class Main {
    private static final int MOD = 1000000007;

    public static int solution(int n, int[] a) {
        long result = 0;
        
        // 遍历每一位(0到31位)
        for (int bit = 0; bit < 32; bit++) {
            int[] prefixSum = new int[n + 1];
            
            // 计算前缀和数组,prefixSum[i]表示前i个元素中第bit位为1的次数
            for (int i = 0; i < n; i++) {
                // 计算当前元素的第bit位是否为1,并累加到前缀和中
                prefixSum[i + 1] = prefixSum[i] + ((a[i] >> bit) & 1);
            }
            
            // 计算每一位的贡献
            for (int left = 0; left < n; left++) {
                for (int right = left; right < n; right++) {
                    // 计算子数组[left, right]中第bit位为1的元素个数
                    int count = prefixSum[right + 1] - prefixSum[left];
                    
                    // 计算当前子数组中第bit位的贡献
                    // 贡献 = 子数组中第bit位为1的元素个数 * (子数组中第bit位为1的元素个数 - 1) / 2
                    // 这是因为我们需要计算所有可能的两个元素的按位与值之和
                    long contribution = (long) count * (count - 1) / 2;
                    
                    // 将当前位的贡献累加到结果中,并取模
                    result = (result + contribution * (1 << bit)) % MOD;
                }
            }
        }
        
        return (int) result;
    }

    public static void main(String[] args) {
        System.out.println(solution(4, new int[]{2, 3, 1, 2}) == 16);
        System.out.println(solution(3, new int[]{5, 6, 7}) == 25);
        System.out.println(solution(2, new int[]{1, 10}) == 0);
        System.out.println(solution(5, new int[]{1, 2, 4, 8, 16}) == 0);
    }
}

复杂度分析

O(n3)O(n^3)

遍历每一位(从第0位到第31位),总共有32位。线性时间

计算前缀和数组的时间复杂度是 O(n)O(n)

计算每一位的贡献枚举子数组的时间复杂度是 O(n2)O(n^2)

综上,时间复杂度O(n3)O(n^3)