1787. 使所有区间的异或结果为零 / 剑指Offer56 - I. 数组中数字出现的次数 / 剑指Offer56 - II. 数组中数字出现的次数 II / 剑指Offer57.和为s的两个数字

1787. 使所有区间的异或结果为零

2021.5.25每日一题,连续四天困难了,太难了

题目描述
给你一个整数数组 nums​​​ 和一个整数 k​​​​​ 。区间 [left, right](left <= right)的 异或结果 是对下标位于 left 和 right(包括 left 和 right )之间所有元素进行 XOR 运算的结果:nums[left] XOR nums[left+1] XOR ... XOR nums[right] 。

返回数组中 要更改的最小元素数 ,以使所有长度为 k 的区间异或结果等于零。


示例 1:

输入:nums = [1,2,0,3,0], k = 1
输出:3
解释:将数组 [1,2,0,3,0] 修改为 [0,0,0,0,0]
示例 2:

输入:nums = [3,4,5,2,1,7,3,4,7], k = 3
输出:3
解释:将数组 [3,4,5,2,1,7,3,4,7] 修改为 [3,4,7,3,4,7,3,4,7]
示例 3:

输入:nums = [1,2,4,1,2,5,1,2,6], k = 3
输出:3
解释:将数组[1,2,4,1,2,5,1,2,6] 修改为 [1,2,3,1,2,3,1,2,3]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/make-the-xor-of-all-segments-equal-to-zero
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

我看到区间和,然后又可以修改区间里面的数,第一时间想到的是线段树和树状数组,因为前几天做了几道这种题了。但是不知道该采用一个什么样的策略去修改里面的数,使每个区间异或结果为0
然后看了一下标签,动态规划,直接懵了

这里有个关键点,就是因为所有k长度的区间异或结果为0,所以位置i和位置i+k的元素必定相同,因为区间i到i+k-1异或为0,区间i+1到k异或为0。然后整个数组就变成了一个k个数循环的序列。
但是到底修改哪个呢,还是不懂

看了题解还是不懂,算了,这个题放弃了,弄懂又得花很久,没时间了,等等三叶姐题解来了看一下,看不懂就拜拜了。

三叶姐的看懂了,说一下自己的理解
就是每k个一组循环,所以将数组分成k个部分,将每个部分排成一行,那么就有k列,最终的结果要使每一列中所有的数都是相同的,并且第一行异或结果为0

定义dp[i][xor]数组为前 i 列,并且首行前 i 列异或结果为xor的最小修改次数
因为nums[i] < 1024,而无论多少个数异或并不会使这个范围增大,所以第二维的范围就是1024

然后用一个哈希表记录当前列中,每一个数字出现了多少次;用一个变量count统计当前列数字的个数

考虑如何转移:
对于第一列,直接变成xor,也就是dp[0][xor] = count - map.get(xor);
对于其他列,前面已经修改了i - 1列,修改当前列使得异或结果为xor。而当前列已经有存在的数了,所以要使所有行的结果都变成xor,就是遍历当前存在的数cur,使得前面i-1行的异或结果是xor ^ cur,dp[i][xor] = dp[i - 1][xor ^ cur] + count - map.get(cur);然后取最小值,就是当前结果dp[i][xor]
或者说不管这列中存在的数,将当前列都替换成同一个数,此时最小次数就是前面替换的最小次数加count,dp[i][xor] = g[i - 1] + count(g[i]表示前i列替换的最小次数)
(这里可能会有疑问,如果这列中有不需要替换的那次数不就更小了,其实这种情况已经包含在上面的情况中了)

贴个三叶姐的代码:

class Solution {
    public int minChanges(int[] nums, int k) {
        int n = nums.length;
        int max = 1024; 
        int[][] f = new int[k][max];
        int[] g = new int[k];
        for (int i = 0; i < k; i++) {
            Arrays.fill(f[i], 0x3f3f3f3f);
            g[i] = 0x3f3f3f3f;
        }
        for (int i = 0, cnt = 0; i < k; i++, cnt = 0) {
            // 使用 map 和 cnt 分别统计当前列的「每个数的出现次数」和「有多少个数」
            Map<Integer, Integer> map = new HashMap<>();
            for (int j = i; j < n; j += k) {
                map.put(nums[j], map.getOrDefault(nums[j], 0) + 1);
                cnt++;
            }
            if (i == 0) { // 第 0 列:只需要考虑如何将该列变为 xor 即可
                for (int xor = 0; xor < max; xor++) {
                    f[0][xor] = Math.min(f[0][xor], cnt - map.getOrDefault(xor, 0));
                    g[0] = Math.min(g[0], f[0][xor]);
                }
            } else { // 其他列:考虑与前面列的关系
                for (int xor = 0; xor < max; xor++) {
                    f[i][xor] = g[i - 1] + cnt; // 整列替换
                    for (int cur : map.keySet()) { // 部分替换
                        f[i][xor] = Math.min(f[i][xor], f[i - 1][xor ^ cur] + cnt - map.get(cur));
                    }
                    g[i] = Math.min(g[i], f[i][xor]);
                }
            }
        }
        return f[k - 1][0];
    }
}

作者:AC_OIer
链接:https://leetcode-cn.com/problems/make-the-xor-of-all-segments-equal-to-zero/solution/gong-shui-san-xie-chou-xiang-cheng-er-we-ww79/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

剑指 Offer 56 - I. 数组中数字出现的次数

一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

 
示例 1:

输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:

输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

好久之前做过,想了半天,也没想起来怎么做
因为只有一个不同的数字的时候,可以通过异或的性质,两个相同的数异或为0来找到
这里有两个不同的数字了,那么就想办法分成两个只有一个不同数字的数组
所以数字异或结果为sum,sum肯定不为0,找到sum二进制位中是1的位置,然后根据这个位置将整个数组划分成两部分,分别求两个不同的值

class Solution {
    public int[] singleNumbers(int[] nums) {
        //因为知道两个相同的数异或结果为0,而这个数组中有两个不同的数字
        //那么想办法把这个问题转换成只有一个不同数字的题
        //设所有数字异或是t,t就为两个不同的数字的异或结果
        //对于这个异或结果,肯定不是0,所以找到t中第一个为1的位置,他表示这两个数字在该位置肯定不相同
        //根据这个位置,将整个数组分成两个部分,这样在两个部分中分别找结果
        //妙

        int sum = 0;
        int l = nums.length;
        for(int i = 0; i < l; i++){
            sum ^= nums[i];
        }

        int x = sum & (-sum);
        int num1 = 0, num2 = 0;
        for(int i = 0; i < l; i++){
            if((x & nums[i]) == 0){
                num1 ^= nums[i];
            }else{
                num2 ^= nums[i];
            }
        }
        return new int[]{num1, num2};
    }
}

剑指 Offer 56 - II. 数组中数字出现的次数 II

题目描述
在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。


示例 1:

输入:nums = [3,4,3,3]
输出:4
示例 2:

输入:nums = [9,1,7,9,7,9,7]
输出:1

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

也做过了,一看到就想起来有限状态机
因为每个数字都出现3次,所以三个状态00->01->10->00之间相互转换,根据转换关系写出真值表。根据真值表中每一位为1的项,写出转移的表达式
另外这里有个地方也要注意,就是因为是高位和低位两个状态都要转移,所以同时转移和先转移低位再转移高位的表达式是不同的。同时转移的时候要用临时变量存储
这里因为先转移低位,在根据转以后的低位计算高位的表达式,结果形式比较简单,这里写这个式子

class Solution {
    public int singleNumber(int[] nums) {
        //因为每个数字都出现了三次,所以对于每一二进制位来说有三个状态,0 1 2
        //出现1次是1,出现两次是2,出现3次又返回0
        //而对于不同的那个数字,肯定会使这个状态破坏,最后的结果就是这个结果

        //设低位为low,高位为high
        int low = 0;
        int high = 0;
        for(int num : nums){
            low = ~high & (low ^ num);
            high = ~low & (high ^ num); 
        }
        //因为最后结果不会有high
        return low;
    }
}

另外,一个二进制的做法,遍历每一个数的每一位,因为都会出现3次,所以每一位都是3的倍数,不是的就是答案

class Solution {
    public int singleNumber(int[] nums) {
        int res = 0;
        int count = 0;
        for(int i = 31; i >= 0; i--){
            for(int num : nums){
                count += (num >> i) & 1;
            }
            if(count % 3 == 1){
                res += (1 << i);
            }
            count = 0;
        }
        return res;
    }
}

剑指 Offer 57. 和为s的两个数字

题目描述
输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。


示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]
示例 2:

输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/he-wei-sde-liang-ge-shu-zi-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

一看很凭感觉的思路,就是先把两个两个指针指向左右边界,然后计算两个指针位置数字的和,如果大于target,因为做指针已经在当前最小值,那么左指针不管怎么移动,两数之和都不可能减小,也就不可能等于target,所以移动右指针,直到两数之和小于等于target;同理,如果小于target,那么右指针不管怎么移动,两数之和不可能增大,也就不可能等于target,所以移动左指针,直到两数之和大于等于target

class Solution {
    public int[] twoSum(int[] nums, int target) {
        //乍一看,这不就是两数之和吗,用哈希表记录当前数字和需要的数,然后依次遍历
        //过了,但是递增条件没用,肯定慢,
        //双指针
        int l = nums.length;
        if(l == 1)
            return new int[]{};
        int left = 0;
        int right = l - 1;
        //先移动右指针
        while(left < right){
            while(nums[left] + nums[right] > target){
                right--;
            }
            while(nums[left] + nums[right] < target){
                left++;
            }
            if(nums[left] + nums[right] == target)
                break;
        }
        return new int[]{nums[left], nums[right]};
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值