1787. 使所有区间的异或结果为零 / 剑指Offer56 - I. 数组中数字出现的次数 / 剑指Offer56 - II. 数组中数字出现的次数 II / 剑指Offer57.和为s的两个数字(
Posted Zephyr丶J
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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]};
}
}
以上是关于1787. 使所有区间的异或结果为零 / 剑指Offer56 - I. 数组中数字出现的次数 / 剑指Offer56 - II. 数组中数字出现的次数 II / 剑指Offer57.和为s的两个数字(的主要内容,如果未能解决你的问题,请参考以下文章
LeetCode 1787 使所有区间的异或结果为零[动态规划 哈希表] HERODING的LeetCode之路
树状数组区间出现偶数次数的异或和(区间不同数的异或和)@ codeforce 703 D