求数组中异或和为0的子数组最多有多少
问题描述:
假设一个数组 {3,2,1,0,1,2,3,0},求异或和为0的子数组最多有多少,也就是最多可以划分多少个?
答案:4
解析:
数组 {3 2 1 0 1 2 3 0} 。可以划分为 {3,2,1} {0} {1,2,3} {0},四个子数组异或和都为0。也可以划分为 {3},{2},{1},{0},{1},{2},{3},{0},这样子,子数组中异或和为0的子数组就只有{0},一共2个。
异或的性质:
- 异或运算满足交换律和结合律 a^b = b^a ; 如果 a ^ b ^ c ^ d = 0,a^b = 0, 那么c^d = 0
- n ^ 0 = n
- n ^ n = 0
算法思想:
0-i的范围能求出来最多的异或和为0的子数组。0-i上此时是最优划分
- i所在的最后一个部分不是异或和为0的子数组
{0 , i-1}上能划分出来的最多子数组就等于 {0 - i}上最多子数组,也就是要不要它无所谓。dp[i] = dp[i-1]; - i所在的最后一部分是异或和为0的子数组
那么接下来,i作为最优划分的部分,也就是
也就是我们要找到最后一个k出现的位置让[k+1,i]的异或和为0。这样子,就能使异或和为0的子数组最多。
k也能在j的位置,所以我们要借助哈希表通过找到最后一个k的位置,来求出答案。也就是dp[i] = dp[k ] + 1.
怎么用哈希表将此功能实现呢?
我们要将所有的,从0到1,从0到2,从0到n-1的异或和记录在map中,并且记录他们出现的最后一次的位置。为什么要这么做呢?如上图 0-j的异或和为0,j-k的异或和为0,k到i的异或和为0。所以0-i的异或和也是0,j到i的异或和也是0。我们偏偏需要k的位置,就是因为我们将异或和为0的位置用k覆盖了,所以我么才能找到k的位置。来进行接下来一系列操作,dp数组的记录。
dp数组的功能:
dp[i]表示从 0-i这个区间最多可以由几个子数组来构成。 因为异或的性质,假如v{0,1,2,3}。变量xor一开始为0, xor^= v[0] = 0, dp[0] = 1记录下来。
xor^=v[1] !=0, dp[1]=max(dp[1],dp[0]) = dp[0]。表明的意思是数组v中的[0,1]这个构成的数组中的子数组最多可以有1个,那就是{0}(符合),{1}。
xor^=v[2] !=0, dp[2]=max(dp[2],dp[1]) = dp[1]。表明的意思是数组v中的[0,1,2]这个构成的数组中的子数组最多可以有1个,那就是{0}(符合),{1,2}
直到xor^= v[3] = 0。所以dp[3] = dp[2]+1 = 2。表明的意思是数组v中的[0,1,2]这个构成的数组中的子数组最多可以有2个,那就是{0}(符合),{1,2,3}(符合)
代码实现:
int mostEOR(vector<int>& arr)
{
int ans = 0;
int xor = 0;
vector<int> dp(arr.size());
map<int, int> mymap;
mymap[0] = -1; // 如果异或和为0的话,prev就为-1,标记异或和为0的子数组是否出现过
for (int i = 0; i < arr.size(); i++)
{
xor ^= arr[i];
if (mymap.find(xor) != mymap.end())
{
int pre = mymap[xor]; // 如果xor为-1,说明此时0-i的异或和为0,dp就需要记录
dp[i] = (pre == -1 ? 1 : dp[pre] + 1);
}
if (i > 0)
{
dp[i] = max(dp[i - 1], dp[i]);
}
mymap[xor] = i;
ans = max(ans, dp[i]);
}
return ans;
}