赞
踩
一个数组的 异或总和 定义为数组中所有元素按位 XOR 的结果;如果数组为 空 ,则异或总和为 0 。
例如,数组 [2,5,6] 的 异或总和 为 2 XOR 5 XOR 6 = 1 。
给你一个数组 nums ,请你求出 nums 中每个 子集 的 异或总和 ,计算并返回这些值相加之 和 。
注意:在本题中,元素 相同 的不同子集应 多次 计数。
数组 a 是数组 b 的一个 子集 的前提条件是:从 b 删除几个(也可能不删除)元素能够得到 a 。
1 <= nums.length <= 12
1 <= nums[i] <= 20
法一:递归求子集的和,递归每个元素时有两个分支,选择此元素或不选择此元素:
class Solution { public: void dfs(int index, int curSum, vector<int> &nums) { if (index == depth) { res += curSum; return; } dfs(index + 1, curSum, nums); dfs(index + 1, curSum ^ nums[index], nums); } int subsetXORSum(vector<int>& nums) { depth = nums.size(); dfs(0, 0, nums); return res; } private: int res = 0; int depth = 0; };
时间复杂度O(2n),对于n过大时,效率很低;空间复杂度O(n),主要是栈空间开销,n为输入数组长度。
法二:迭代求,子集数量为2n,将子集从0到2n-1编号,对于编号为x(0<=x<2n)的子集,我们可以将编号x转换为n位的二进制形式,之后将这n位二进制形式与整个集合对比,如果是1就将该元素放入第n个子集,即可计算出编号为x的子集的异或和:
class Solution { public: int subsetXORSum(vector<int>& nums) { int sz = nums.size(); int res = 0; for (int i = 0; i < (1 << sz); ++i) { // 对于每个子集 int aSubsetXorRes = 0; for (int j = 0; j < sz; ++j) { // 对于该子集中每个元素 if (i & (1 << j)) { // 如果该元素在这个子集中 aSubsetXorRes ^= nums[j]; } } res += aSubsetXorRes; } return res; } };
时间复杂度O(n 2 ^2 2),空间复杂度O(1),n为输入数组长度。
法三:对于数组中所有元素的某一位,假设数组中有m个元素的此位为1,设该数组的子集中包含该位为1的元素的数量为k,则包含k个该位为1的元素的子集数量为(0<=k<=m):
即m个该位为1的元素中选出k个的选法数量乘剩下的n-m个该位不是1的元素能选出的子集数。
包含奇数个该位为1的元素的子集数量为:
包含偶数个该位为1的元素的子集数量为:
将(x+1)^m展开并将x取-1得:
即包含奇数个该位为1的元素的子集和包含偶数个该位为1的元素的子集数量相等,都等于元素子集数量的一半,即2n-1。
我们可以先遍历一遍数组,用变量res保存信息,信息内容是如果数组中有该位为1的元素,则res的该位为1。对于res中某位的1,它存在于2n-1个子集的异或结果中(只有含奇数个此位为1的子集的异或结果才是1,而这样的子集有2n-1个),这位1对于结果相当于加了2n-1次,因此将该位乘2n-1即可;如果res中某位为0,说明原数组中所有元素的这一位都为0,每个子集该位异或的结果都是0,该位对于结果的贡献也为0,则将该位乘2n-1结果还是0不变。因此结果就是res左移n-1位:
class Solution {
public:
int subsetXORSum(vector<int>& nums) {
int sz = nums.size();
int res = 0;
for (int i : nums) {
res |= i;
}
return res << (sz - 1);
}
};
时间复杂度O(n),空间复杂度O(1),n为输入数组长度。
由于1 <= nums[i] <= 20,1 <= nums.length <= 12,因此每个数字最多5位,且左移11次,返回结果最多是16位数,能存在int中。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。