找出数组中的 K-or 值
给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。
nums 中的 K-or 是一个满足以下条件的非负整数:
只有在 nums 中,至少存在 k 个元素的第 i 位值为 1 ,那么 K-or 中的第 i 位的值才是 1 。
返回 nums 的 K-or 值。
注意 :对于整数 x ,如果 (2i AND x) == 2i ,则 x 中的第 i 位值为 1 ,其中 AND 为按位与运算符。
示例 1:
输入:nums = [7,12,9,8,9,15], k = 4
输出:9
解释:nums[0]、nums[2]、nums[4] 和nums[5] 的第 0 位的值为 1 。nums[0] 和 nums[5] 的第 1 位的值为 1 。 nums[0]、nums[1]和 nums[5] 的第 2 位的值为 1 。 nums[1]、nums[2]、nums[3]、nums[4] 和 nums[5] 的第 3位的值为 1 。 只有第 0 位和第 3 位满足数组中至少存在 k 个元素在对应位上的值为 1 。因此,答案为 2^0 + 2^3 = 9。
示例 2:
输入:nums = [2,12,1,11,4,5], k = 6
输出:0
解释:因为 k == 6 == nums.length ,所以数组的 6-or 等于其中所有元素按位与运算的结果。因此,答案为 2 AND 12 AND 1 AND 11 AND 4 AND 5= 0 。
示例 3:
输入:nums = [10,8,5,9,11,6,8], k = 1
输出:15
解释:因为 k == 1 ,数组的 1-or等于其中所有元素按位或运算的结果。因此,答案为 10 OR 8 OR 5 OR 9 OR 11 OR 6 OR 8 = 15 。
提示:
1
<
=
n
u
m
s
.
l
e
n
g
t
h
<
=
50
1 <= nums.length <= 50
1<=nums.length<=50
0
<
=
n
u
m
s
[
i
]
<
2
31
0 <= nums[i] < 2^{31}
0<=nums[i]<231
1
<
=
k
<
=
n
u
m
s
.
l
e
n
g
t
h
1 <= k <= nums.length
1<=k<=nums.length
分析:
按照题目意思模拟即可,将nums数组中的每一个数字的各个位数中,值为1的位置记录下来,用一个数组维护即可。
代码:
class Solution {
public:
int findKOr(vector<int>& nums, int k) {
int res = 0;
int n = nums.size();
vector<int>cnt(35);
for(int i = 0;i < n; i++){
int x = nums[i],j = 0;
while(x){
if(x & 1)cnt[j]++;
x >>= 1;
j++;
}
}
for(int i = 0; i <=31 ;i++){
if(cnt[i] >= k)res += pow(2, i);
}
return res;
}
};
数组的最小相等和
给你两个由正整数和 0 组成的数组 nums1 和 nums2 。
你必须将两个数组中的 所有 0 替换为 严格 正整数,并且满足两个数组中所有元素的和 相等 。
返回 最小 相等和 ,如果无法使两数组相等,则返回 -1 。
示例 1:
输入:nums1 = [3,2,0,1,0], nums2 = [6,5,0]
输出:12
解释:可以按下述方式替换数组中的 0 :
- 用 2 和 4 替换 nums1 中的两个 0 。得到 nums1 = [3,2,2,1,4] 。
- 用 1 替换 nums2 中的一个 0 。得到 nums2 = [6,5,1] 。 两个数组的元素和相等,都等于 12 。可以证明这是可以获得的最小相等和。
示例 2:
输入:nums1 = [2,0,2,0], nums2 = [1,4]
输出:-1
解释:无法使两个数组的和相等。
提示:
1
<
=
n
u
m
s
1.
l
e
n
g
t
h
,
n
u
m
s
2.
l
e
n
g
t
h
<
=
1
0
5
1 <= nums1.length, nums2.length <= 10^5
1<=nums1.length,nums2.length<=105
0
<
=
n
u
m
s
1
[
i
]
,
n
u
m
s
2
[
i
]
<
=
1
0
6
0 <= nums1[i], nums2[i] <= 10^6
0<=nums1[i],nums2[i]<=106
分析:
这个题目按照题目意思去分类讨论即可,但在赛中的时候,我分类讨论的方法很复杂。其实因为要使得两个数组各自的和最小且相等,不难想到的是,可以先把所有的数字0用1替代,分别求和先得到
s
u
m
1
sum1
sum1和
s
u
m
2
sum2
sum2。
那么如何保证两个数组的和
s
u
m
1
sum1
sum1和
s
u
m
2
sum2
sum2相等呢?
- 首先,如果两边都有数字0,那么最后的答案肯定存在,因为将小的那一边(这里假设sum2小)中,取出一个0数字,将其改成sum1和sum2的差值,让sum2和sum1相等即可,所以最后答案是 m a x ( s u m 1 , s u m 2 ) max(sum1,sum2) max(sum1,sum2)。
- 但是,如果有一个数组有0,而另一个数组没有0,答案是什么呢?假如现在 s u m 1 > s u m 2 sum1>sum2 sum1>sum2,且数组1中没有0,数组2中有0,那么很简单,将数组2中的0补上,使得 s u m 1 = s u m 2 sum1=sum2 sum1=sum2即可。但是反过来,如果数组1中有0,数组2中没有0,则答案肯定不存在。 s u m 2 > s u m 1 sum2>sum1 sum2>sum1同理。
- 此外,还有一种情况,如果两边数组 s u m 1 ! = s u m 2 sum1!=sum2 sum1!=sum2,且两边都没有0,那么也是不存在答案。
代码:
赛时的代码,考虑了很多,仅供参考
class Solution {
public:
long long minSum(vector<int>& nums1, vector<int>& nums2) {
int zero1 = 0, zero2 = 0;
long long sum1 = 0, sum2 = 0;
for(auto x:nums1){
if(x == 0)zero1++;
sum1 += x;
}
for(auto x:nums2){
if(x == 0)zero2++;
sum2 += x;
}
long long res = 0;
if(sum1 > sum2){
long long esp = sum1 - sum2;
if(zero2 == 0)res = 0;
else if((long long)(esp + zero1) < zero2){
if(zero1 == 0)res = 0;
else res = sum2 + zero2;
}
else res = (long long)zero1 + esp + sum2;
}
else if(sum1 < sum2){
long long esp = sum2 - sum1;
if(zero1 == 0)res = 0;
else if((long long)(esp + zero2) < zero1 ){
if(zero2 == 0)res = 0;
else res = sum1 + zero1;
}
else res = (long long)zero2 + esp + sum1;
}
else{
if( (zero1 && (!zero2)) || (zero2 && (!zero1)) )res = 0;
else res = sum1 + max(zero1, zero2);
}
if(res == 0)res = -1;
return res;
}
};
赛后修改
class Solution {
public:
long long minSum(vector<int>& nums1, vector<int>& nums2) {
bool zero1 = 0, zero2 = 0;
long long sum1 = 0, sum2 = 0;
for(auto x:nums1){
if(x == 0)zero1 = true, ++sum1;
sum1 += x;
}
for(auto x:nums2){
if(x == 0)zero2 = true, ++sum2;
sum2 += x;
}
if( (sum1 > sum2 && zero1 && (!zero2)) || (sum2 > sum1 && zero2 && (!zero1)) || (sum1 != sum2 && (!zero1) && (!zero2)) )return -1;
return max(sum1, sum2);
}
};
使数组变美的最小增量运算数
给你一个下标从 0 开始、长度为 n 的整数数组 nums ,和一个整数 k 。
你可以执行下述 递增 运算 任意 次(可以是 0 次):
从范围 [0, n - 1] 中选择一个下标 i ,并将 nums[i] 的值加 1 。
如果数组中任何长度 大于或等于 3 的子数组,其 最大 元素都大于或等于 k ,则认为数组是一个 美丽数组 。
以整数形式返回使数组变为 美丽数组 需要执行的 最小 递增运算数。
子数组是数组中的一个连续 非空 元素序列。
示例 1:
输入:nums = [2,3,0,0,2], k = 4
输出:3
解释:可以执行下述递增运算,使 nums 变为美丽数组: 选择下标 i= 1 ,并且将 nums[1] 的值加 1 -> [2,4,0,0,2] 。 选择下标 i = 4 ,并且将 nums[4] 的值加 1 -> [2,4,0,0,3] 。 选择下标 i = 4 ,并且将 nums[4] 的值加 1 -> [2,4,0,0,4] 。 长度大于或等于 3 的子数组为 [2,4,0], [4,0,0], [0,0,4], [2,4,0,0], [4,0,0,4], [2,4,0,0,4] 。 在所有子数组中,最大元素都等于 k = 4 ,所以 nums 现在是美丽数组。 可以证明无法用少于 3次递增运算使 nums 变为美丽数组。 因此,答案为 3 。
示例 2:
输入:nums = [0,1,3,3], k = 5
输出:2
解释:可以执行下述递增运算,使 nums 变为美丽数组: 选择下标 i =2 ,并且将 nums[2] 的值加 1 -> [0,1,4,3] 。 选择下标 i = 2 ,并且将 nums[2] 的值加 1 ->[0,1,5,3] 。 长度大于或等于 3 的子数组为 [0,1,5]、[1,5,3]、[0,1,5,3] 。在所有子数组中,最大元素都等于 k = 5 ,所以 nums 现在是美丽数组。 可以证明无法用少于 2 次递增运算使 nums变为美丽数组。 因此,答案为 2 。
示例 3:
输入:nums = [1,1,2], k = 1
输出:0
解释:在这个示例中,只有一个长度大于或等于 3 的子数组 [1,1,2] 。其最大元素 2 已经大于 k = 1 ,所以无需执行任何增量运算。 因此,答案为 0 。
提示:
3
<
=
n
=
=
n
u
m
s
.
l
e
n
g
t
h
<
=
1
0
5
3 <= n == nums.length <= 10^5
3<=n==nums.length<=105
0
<
=
n
u
m
s
[
i
]
<
=
1
0
9
0 <= nums[i] <= 10^9
0<=nums[i]<=109
0
<
=
k
<
=
1
0
9
0 <= k <= 10^9
0<=k<=109
分析:
如果数组中任何长度 大于或等于 3 的子数组,其 最大 元素都大于或等于 k ,则认为数组是一个 美丽数组 。其实意思就是只要任何一个长度为3的子数组,其中都有一个元素的值大于等于k即可。 因为任何一个长度大于3的子数组都会包含一个长度等于3的子数组。
那么我们如何去解答呢?
我们考虑第
i
i
i个元素是否要变成大于等于k的数,如果第
i
i
i个元素要增大到k,那么对其右边
i
+
1
i+1
i+1的数字来说,其左边就有一个k了。
如果不增大,那么对其右边的数字来说,其左边就有一个没有达到k。
那么如果对
i
+
2
i+2
i+2位置的数字来分析,如果
i
+
1
i+1
i+1位置的数字也不去增大到k,那么其左边就有两个没有增大到k的数,那么它一定要增大(不去考虑右边的数,因为在后续讨论中,右边的数也会被涉及到的)。
那么这道题目可以使用dp的方法或者记忆化搜索来做。
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示到第i个位置,离其最近的增大到k的数字的距离。
状态转移方程为:
d
p
[
i
]
[
0
]
=
m
i
n
(
d
p
[
i
−
1
]
[
0
]
,
d
p
[
i
−
1
]
[
1
]
,
d
p
[
i
−
1
]
[
2
]
)
+
m
a
x
(
0
,
k
−
n
u
m
s
[
i
]
)
;
dp[i][0] = min({dp[i - 1][0], dp[i - 1][1], dp[i - 1][2]}) + max(0, k - nums[i]);
dp[i][0]=min(dp[i−1][0],dp[i−1][1],dp[i−1][2])+max(0,k−nums[i]);
d
p
[
i
]
[
1
]
=
d
p
[
i
−
1
]
[
0
]
dp[i][1] = dp[i - 1][0]
dp[i][1]=dp[i−1][0]
d
p
[
i
]
[
2
]
=
d
p
[
i
−
1
]
[
1
]
dp[i][2] = dp[i - 1][1]
dp[i][2]=dp[i−1][1]
d
f
s
(
x
,
c
n
t
)
dfs(x,cnt)
dfs(x,cnt)表示到第x个位置时,前面有几个没有增到的k的数字。
记忆化搜索的方式为:
如果当前位置增大到k,则
d
f
s
(
x
+
1
,
0
)
+
m
a
x
(
0
,
k
−
n
u
m
s
[
i
]
)
dfs(x + 1,0) + max(0, k - nums[i])
dfs(x+1,0)+max(0,k−nums[i])
如果
c
n
t
<
2
cnt < 2
cnt<2,即前面有不超过2个没有增到k的数字,则当前位置可以不增加到k,
d
f
s
(
x
+
1
,
c
n
t
+
1
)
dfs(x + 1,cnt + 1)
dfs(x+1,cnt+1)
代码:
记忆化搜索
const int N = 1e5+5;
long long dp[N][3];
class Solution {
public:
//当前位置x是否要增大到k,且当前位置的后面有cnt个数字没有增大到了k
long long dfs(int x, int cnt, int k, vector<int>& nums){
if(x >= nums.size())return 0;
long long &res = dp[x][cnt];//引用类型 后续会更改
if(res != -1)return res;
res = dfs(x + 1, 0, k, nums) + max(0, k - nums[x]);//当前位置增大到k
if(cnt < 2)res = min(res, dfs(x + 1, cnt + 1, k ,nums));//如果前面有2个数字都没增大,则当前位置必须增大到k
return res;
}
long long minIncrementOperations(vector<int>& nums, int k) {
int n = nums.size();
memset(dp, -1, sizeof(dp));
return dfs(0, 0, k, nums);
}
};
class Solution:
def minIncrementOperations(self, nums: List[int], k: int) -> int:
n = len(nums)
dp = [[-1] * 3 for _ in range(n)]
def dfs(x, cnt):# 当前位置为x,cnt表示前面最近达到k的值离当前位置的距离
if x >= n:
return 0
if dp[x][cnt] != -1:
return dp[x][cnt]
dp[x][cnt] = dfs(x + 1, 0) + max(0, k - nums[x])
if cnt < 2:
dp[x][cnt] = min(dp[x][cnt], dfs(x + 1,cnt + 1))
return dp[x][cnt]
return dfs(0, 0)
dp
class Solution {
public:
long long minIncrementOperations(vector<int>& nums, int k) {
int n = nums.size();
vector<vector<long long>>dp(n, vector<long long>(3));
dp[0][0] = max(0, k - nums[0]);
for(int i = 1; i < n; i++){
dp[i][0] = min({dp[i - 1][0], dp[i - 1][1], dp[i - 1][2]}) + max(0, k - nums[i]);
dp[i][1] = dp[i - 1][0];
dp[i][2] = dp[i - 1][1];
}
return min({dp[n - 1][0], dp[n - 1][1], dp[n - 1][2]});
}
};
class Solution:
def minIncrementOperations(self, nums: List[int], k: int) -> int:
n = len(nums)
dp = [[0] * 3 for _ in range(n)]
# dp[i][j] 表示当前数字i,没有到达k的数字离当前位置的距离为j
dp[0][0] = max(0, k - nums[0])
for i in range(1, n):
dp[i][0] = min(dp[i - 1][0], dp[i - 1][1], dp[i - 1][2]) + max(0, k - nums[i])
dp[i][1] = dp[i - 1][0]
dp[i][2] = dp[i - 1][1]
return min(dp[n - 1][0], dp[n - 1][1], dp[n - 1][2])
收集所有金币可获得的最大积分
节点 0 处现有一棵由 n 个节点组成的无向树,节点编号从 0 到 n - 1 。给你一个长度为 n - 1 的二维 整数 数组 edges ,其中 edges[i] = [ai, bi] 表示在树上的节点 ai 和 bi 之间存在一条边。另给你一个下标从 0 开始、长度为 n 的数组 coins 和一个整数 k ,其中 coins[i] 表示节点 i 处的金币数量。
从根节点开始,你必须收集所有金币。要想收集节点上的金币,必须先收集该节点的祖先节点上的金币。
节点 i 上的金币可以用下述方法之一进行收集:
收集所有金币,得到共计 coins[i] - k 点积分。如果 coins[i] - k 是负数,你将会失去 abs(coins[i] - k) 点积分。
收集所有金币,得到共计 floor(coins[i] / 2) 点积分。如果采用这种方法,节点 i 子树中所有节点 j 的金币数 coins[j] 将会减少至 floor(coins[j] / 2) 。
返回收集 所有 树节点的金币之后可以获得的最大积分。
示例 1:
输入:edges = [[0,1],[1,2],[2,3]], coins = [10,10,3,3], k = 5
输出:11
解释: 使用第一种方法收集节点 0 上的所有金币。总积分 = 10 - 5 = 5 。 使用第一种方法收集节点 1 上的所有金币。总积分 =5 + (10 - 5) = 10 。 使用第二种方法收集节点 2 上的所有金币。所以节点 3 上的金币将会变为 floor(3 / 2) = 1 ,总积分 = 10 + floor(3 / 2) = 11 。 使用第二种方法收集节点 3 上的所有金币。总积分 = 11 + floor(1 / 2) = 11. 可以证明收集所有节点上的金币能获得的最大积分是 11 。
示例 2:
输入:edges = [[0,1],[0,2]], coins = [8,4,4], k = 0
输出:16
解释:使用第一种方法收集所有节点上的金币,因此,总积分 = (8 - 0) + (4 - 0) + (4 - 0) = 16 。
提示:
n
=
=
c
o
i
n
s
.
l
e
n
g
t
h
n == coins.length
n==coins.length
2
<
=
n
<
=
1
0
5
2 <= n <= 10^5
2<=n<=105
0
<
=
c
o
i
n
s
[
i
]
<
=
1
0
4
0 <= coins[i] <= 10^4
0<=coins[i]<=104
e
d
g
e
s
.
l
e
n
g
t
h
=
=
n
−
1
edges.length == n - 1
edges.length==n−1
0
<
=
e
d
g
e
s
[
i
]
[
0
]
,
e
d
g
e
s
[
i
]
[
1
]
<
n
0 <= edges[i][0], edges[i][1] < n
0<=edges[i][0],edges[i][1]<n
0
<
=
k
<
=
1
0
4
0 <= k <= 10^4
0<=k<=104
分析:
树形DP的思路,可以使用dfs来实现,我们先定义
d
f
s
(
u
,
c
n
t
)
dfs(u, cnt)
dfs(u,cnt)表示到
u
u
u结点时,已经除以了cnt次的2,即右移了cnt次可以获得多少的积分。
r
e
s
1
=
(
c
o
i
n
s
[
u
]
>
>
c
n
t
)
−
k
res1 = (coins[u] >> cnt) - k
res1=(coins[u]>>cnt)−k表示使用第一种方法当前结点可以获得的积分;
r
e
s
2
=
c
o
i
n
s
[
u
]
>
>
(
c
n
t
+
1
)
res2 = coins[u] >> (cnt + 1)
res2=coins[u]>>(cnt+1)表示使用第二种方法当前结点可以获得的积分;
如果当前不移动,即使用第一种方法获得的积分,则
r
e
s
1
+
=
d
f
s
(
v
,
c
n
t
)
res1 += dfs(v, cnt)
res1+=dfs(v,cnt)
如果当前移动,即使用第二种方法可以获得的积分,则
r
e
s
2
+
=
d
f
s
(
v
,
c
n
t
+
1
)
res2 += dfs(v, cnt+1)
res2+=dfs(v,cnt+1),注意,如果
c
n
t
+
1
>
=
14
cnt + 1 >= 14
cnt+1>=14了,也没必要往下面的结点搜索了,因为按照题目所给数据范围,以下的点获得的积分都是0。
m
a
x
(
r
e
s
1
,
r
e
s
2
)
max(res1,res2)
max(res1,res2)就是要求的值。
代码:
class Solution {
public:
int maximumPoints(vector<vector<int>>& edges, vector<int>& coins, int k) {
int n = coins.size();
vector<vector<int>>g(n);
for(auto edge:edges){
g[edge[0]].push_back(edge[1]);
g[edge[1]].push_back(edge[0]);
}
vector<vector<int>>dp(n, vector<int>(15, -1));
function<int(int, int, int)> dfs = [&](int u,int cnt,int pre){//当前位置的数字被/2了几次
int &res = dp[u][cnt];
if(res != -1)return res;//已经计算过
int res1 = (coins[u] >> cnt) - k;
int res2 = coins[u] >> (cnt + 1);
for(int v: g[u]){
if(v == pre)continue;
res1 += dfs(v, cnt, u);//当前不移动
if(cnt < 13)res2 += dfs(v, cnt + 1, u);//超过13就没必要往下了,coin值肯定是0了
}
res = max(res1, res2);
return res;
};
return dfs(0, 0, -1);
}
};