与
&
; 或|
; 异或^
异或的性质
如果 a ^ b = c
成立,那么a ^ c = b
与 b ^ c = a
均成立。即 如果有三个数,满足其中两个数的异或值等于另一个值,那么这三个数的顺序可以任意调换。
- 说明:利用这条性质,可以不使用第 3 个变量而交换两个变量的值。
异或运算可以看作 「二进制下不进位的加法」,所以常常用来解决一些和奇偶性相关的问题
求异或和问题
421. 数组中两个数的最大异或值
中等
给你一个整数数组 nums
,返回 nums[i] XOR nums[j]
的最大运算结果,其中 0 ≤ i ≤ j < n
。
示例 1:
输入:nums = [3,10,5,25,2,8]
输出:28
解释:最大运算结果是 5 XOR 25 = 28.
示例 2:
输入:nums = [14,70,53,83,49,91,36,80,92,51,66,70]
输出:127
提示:
1 <= nums.length <= 2 * 105
0 <= nums[i] <= 231 - 1
https://leetcode.cn/problems/maximum-xor-of-two-numbers-in-an-array/solutions/2511644/tu-jie-jian-ji-gao-xiao-yi-tu-miao-dong-1427d/
异或性质、假设修正
class Solution {
// 把 nums数组的数转成二进制来思考,看最高位、次高位..。能不能是1?
// 如果 a ^ b = max 成立,那么一定有 max ^ b = a 成立
// 因此假设 max 的数位i 可以为1,然后
// 问题转化为两数之和,用哈希表记录遍历结果,求max^b=a是否存在
public int findMaximumXOR(int[] nums) {
int max = 0;
for(int x : nums) max = Math.max(max, x);
// 得到为1的最高比特位
int highBit = 31 - Integer.numberOfLeadingZeros(max);
int ans = 0, mask = 0;
Set<Integer> seen = new HashSet();
for(int i = highBit; i >= 0; i--){ // 从最高比特位开始枚举
seen.clear();
mask |= 1 << i; // 将[hightbit,i]位置都变为1
int newAns = ans | (1 << i); // 假定第 i 位可以为1
for(int x : nums){
x &= mask; // 保留前缀,低于 i 的比特位置为 0
if(seen.contains(newAns ^ x)){
ans = newAns; // 这个bit位可以时1
break;
}
seen.add(x);
}
}
return ans;
}
}
2935. 找出强数对的最大异或值 II
困难
给你一个下标从 0 开始的整数数组 nums
。如果一对整数 x
和 y
满足以下条件,则称其为 强数对 :
|x - y| <= min(x, y)
你需要从 nums
中选出两个整数,且满足:这两个整数可以形成一个强数对,并且它们的按位异或(XOR
)值是在该数组所有强数对中的 最大值 。
返回数组 nums
所有可能的强数对中的 最大 异或值。
注意,你可以选择同一个整数两次来形成一个强数对。
示例 1:
输入:nums = [1,2,3,4,5]
输出:7
解释:数组 nums 中有 11 个强数对:(1, 1), (1, 2), (2, 2), (2, 3), (2, 4), (3, 3), (3, 4), (3, 5), (4, 4), (4, 5) 和 (5, 5) 。
这些强数对中的最大异或值是 3 XOR 4 = 7 。
示例 2:
输入:nums = [10,100]
输出:0
解释:数组 nums 中有 2 个强数对:(10, 10) 和 (100, 100) 。
这些强数对中的最大异或值是 10 XOR 10 = 0 ,数对 (100, 100) 的异或值也是 100 XOR 100 = 0 。
示例 3:
输入:nums = [500,520,2500,3000]
输出:1020
解释:数组 nums 中有 6 个强数对:(500, 500), (500, 520), (520, 520), (2500, 2500), (2500, 3000) 和 (3000, 3000) 。
这些强数对中的最大异或值是 500 XOR 520 = 1020 ;另一个异或值非零的数对是 (5, 6) ,其异或值是 2500 XOR 3000 = 636 。
提示:
1 <= nums.length <= 5 * 104
1 <= nums[i] <= 220 - 1
class Solution {
public int maximumStrongPairXor(int[] nums) {
Arrays.sort(nums);
int highBit = 31 - Integer.numberOfLeadingZeros(nums[nums.length - 1]);
int ans = 0, mask = 0;
Map<Integer, Integer> mp = new HashMap<>();
for (int i = highBit; i >= 0; i--) { // 从最高位开始枚举
mp.clear();
mask |= 1 << i;
int newAns = ans | (1 << i); // 这个比特位可以是 1 吗?
for (int y : nums) {
int maskY = y & mask; // 低于 i 的比特位置为 0
if (mp.containsKey(newAns ^ maskY) && mp.get(newAns ^ maskY) * 2 >= y) {
ans = newAns; // 这个比特位可以是 1
break;
}
mp.put(maskY, y);
}
}
return ans;
}
}
异或前缀和问题(最…回文串、串字符出现奇/偶次)
参考:一步步优化!从前缀和到前缀异或和(附题单!)https://leetcode.cn/problems/can-make-palindrome-from-substring/solution/yi-bu-bu-you-hua-cong-qian-zhui-he-dao-q-yh5p/
注意,使用两数之和方法最后会求字串的长度和个数
- 求长度:
map
中value
存sum[i]
第一次出现的位置 - 求个数:
map
中value
存sum[i]
出现的次数
1371. 每个元音包含偶数次的最长子字符串
中等
给你一个字符串 s
,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即 ‘a’,‘e’,‘i’,‘o’,‘u’ ,在子字符串中都恰好出现了偶数次。
示例 1:
输入:s = "eleetminicoworoep"
输出:13
解释:最长子字符串是 "leetminicowor" ,它包含 e,i,o 各 2 个,以及 0 个 a,u 。
示例 2:
输入:s = "leetcodeisgreat"
输出:5
解释:最长子字符串是 "leetc" ,其中包含 2 个 e 。
示例 3:
输入:s = "bcbcbc"
输出:6
解释:这个示例中,字符串 "bcbcbc" 本身就是最长的,因为所有的元音 a,e,i,o,u 都出现了 0 次。
提示:
1 <= s.length <= 5 x 10^5
s
只包含小写英文字母。
class Solution {
public int findTheLongestSubstring(String s) {
int n = s.length();
// 用 bit位 标识对应字母奇偶性
// sum[i] 标识 s[0:i) 的子串中每种字母出现次数的奇偶性
int[] sum = new int[n+1];
for(int i = 0; i < n; i++){
int bit = 0;
// 每个元音字母,即 'a','e','i','o','u' ,在子字符串中都恰好出现了偶数次。
if(check(s.charAt(i)))
bit = 1 << (s.charAt(i) - 'a');
sum[i+1] = sum[i] ^ bit;
}
int res = 0;
// 问题化为两数之和 a ^ b = 0
Map<Integer, Integer> map = new HashMap<>();
for(int i = 0; i <= n; i++){
// 注意求最长串长度,map中只保存sum第一次出现位置
if(map.containsKey(sum[i]))
res = Math.max(res, i - map.get(sum[i]));
else
map.put(sum[i], i);
}
return res;
}
public boolean check(Character c){
return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
}
}
1542. 找出最长的超赞子字符串「求长度」
困难
给你一个字符串 s
。请返回 s
中最长的 超赞子字符串 的长度。
「超赞子字符串」需满足满足下述两个条件:
- 该字符串是
s
的一个非空子字符串 - 进行任意次数的字符交换后,该字符串可以变成一个回文字符串
示例 1:
输入:s = "3242415"
输出:5
解释:"24241" 是最长的超赞子字符串,交换其中的字符后,可以得到回文 "24142"
示例 2:
输入:s = "12345678"
输出:1
示例 3:
输入:s = "213123"
输出:6
解释:"213123" 是最长的超赞子字符串,交换其中的字符后,可以得到回文 "231132"
示例 4:
输入:s = "00"
输出:2
提示:
1 <= s.length <= 10^5
s
仅由数字组成
class Solution {
/**
进行任意次数的字符交换后,该字符串可以变成一个回文字符串
==> 字符串「不同字符出现次数」为奇数的个数 <= 1
*/
public int longestAwesome(String s) {
int n = s.length();
int[] sum = new int[n+1];
for(int i = 0; i < n; i++){
int bit = 1 << (s.charAt(i) - '0');
sum[i+1] = sum[i] ^ bit;
}
int res = 0;
// 问题转化为两数之和
// a ^ b = 1 / 10 / 100 / 1000...
Map<Integer, Integer> map = new HashMap<>();
for(int i = 0; i <= n; i++){
// 字符串「不同字符出现次数」为奇数的个数 = 0
if(map.containsKey(sum[i]))
res = Math.max(res, i - map.get(sum[i]));
// 字符串「不同字符出现次数」为奇数的个数 = 1
for(int j = 0; j < 10; j++){
int target = (1 << j);
// a ^ b == target ==> a = target ^ b
if(map.containsKey(target ^ sum[i]))
res = Math.max(res, i - map.get(target ^ sum[i]));
}
// 注意求最长串长度,map中只保存sum第一次出现位置
if(!map.containsKey(sum[i]))
map.put(sum[i], i);
}
return res;
}
}
1915. 最美子字符串的数目「求个数」
中等
如果某个字符串中 至多一个 字母出现 奇数 次,则称其为 最美 字符串。
- 例如,
"ccjjc"
和"abab"
都是最美字符串,但"ab"
不是。
给你一个字符串 word
,该字符串由前十个小写英文字母组成('a'
到 'j'
)。请你返回 word
中 最美非空子字符串 的数目*。如果同样的子字符串在 word
中出现多次,那么应当对 每次出现 分别计数。*
子字符串 是字符串中的一个连续字符序列。
示例 1:
输入:word = "aba"
输出:4
解释:4 个最美子字符串如下所示:
- "aba" -> "a"
- "aba" -> "b"
- "aba" -> "a"
- "aba" -> "aba"
示例 2:
输入:word = "aabb"
输出:9
解释:9 个最美子字符串如下所示:
- "aabb" -> "a"
- "aabb" -> "aa"
- "aabb" -> "aab"
- "aabb" -> "aabb"
- "aabb" -> "a"
- "aabb" -> "abb"
- "aabb" -> "b"
- "aabb" -> "bb"
- "aabb" -> "b"
示例 3:
输入:word = "he"
输出:2
解释:2 个最美子字符串如下所示:
- "he" -> "h"
- "he" -> "e"
提示:
1 <= word.length <= 105
word
由从'a'
到'j'
的小写英文字母组成
class Solution {
public long wonderfulSubstrings(String word) {
int n = word.length();
int[] sum = new int[n+1];
for(int i = 0; i < n; i++){
int bit = (1 << (word.charAt(i) - 'a'));
sum[i+1] = sum[i] ^ bit;
}
long res = 0;
Map<Integer, Integer> map = new HashMap<>();
// 某个字符串中 至多一个 字母出现 奇数 次
for(int i = 0; i <= n; i++){
if(map.containsKey(sum[i]))
res += map.get(sum[i]);
for(int j = 0; j < 11; j++){
int target = (1 << j);
if(map.containsKey(target ^ sum[i]))
res += map.get(target ^ sum[i]);
}
map.merge(sum[i], 1, Integer::sum);
}
return res;
}
}