12.位运算的性质(异或的性质)


& ; 或 | ; 异或 ^

异或的性质

如果 a ^ b = c 成立,那么a ^ c = bb ^ 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 。如果一对整数 xy 满足以下条件,则称其为 强数对

  • |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/

注意,使用两数之和方法最后会求字串的长度和个数

  • 求长度:mapvaluesum[i]第一次出现的位置
  • 求个数:mapvaluesum[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;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值