小K的区间与值和
题目背景
小 K 有一个数组,她定义数组的权值为数组中任选两个数的按位与的值之和。具体来说,对于数组中的每个连续子数组,我们可以计算所有可能的两个元素的按位与值之和,并将这些值相加。小 K 想知道该数组中所有可能的连续子数组的权值和是多少,最后结果对 取模。
示例
示例 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
,右端点 right
从 left
开始,一直到数组末尾 n - 1
,如此就能确定每一个连续子数组。
2.在确定了每一个连续子数组后,需要对其内部任意两个元素进行按位与操作,并将这些按位与的结果相加。这里使用了两层嵌套的内层循环来实现。
3.将每个子数组的按位与值之和累加到总的结果 result
还有一点需要记住,题目要求对结果取模,这个常见的设定不能忽略。变量中并对 取模,防止结果数值过大导致溢出。
代码实现
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);
}
}
复杂度分析
原始方法的时间复杂度为,四层嵌套循环。非常低效。
实践记录及工具使用
使用这 MarsCode AI 刷题工具,在学习和解题过程中可以方便很多,让 MarsCode AI 来评价我们的代码,提供进一步优化的可能性的拓展。
刷题实践: 每次提交代码后,AI 工具会立即给出反馈,指出代码中的错误和优化建议。
参考了 AI 工具推荐的类似题目和解题思路,逐渐掌握了位运算和前缀和的应用方法。
通过不断的 AI 交流,可以最终成功地将时间复杂度从 降低到
代码实现
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);
}
}
复杂度分析
遍历每一位(从第0位到第31位),总共有32位。线性时间
计算前缀和数组的时间复杂度是
计算每一位的贡献枚举子数组的时间复杂度是
综上,时间复杂度