题目描述
给定两个整数 n
和 x
,要求构造一个长度为 n
的正整数数组 nums
,其中:
-
数组中的所有元素是严格递增的。
-
数组中的所有元素按位与的结果等于
x
。
返回数组 nums
中最后一个元素的最小值。
题目分析与思路
1. 初始思路:
-
题目要求构造一个严格递增的数组,同时满足数组中的所有元素按位与的结果为
x
。如果所有数组元素的按位与结果要等于
x
,那么每个元素的二进制形式中的那些与x
的二进制表示匹配的位 必须和x
一致。这意味着:-
在
x
的二进制中为**1
** 的位置上,数组中每个元素 的对应位置也必须为1
。 -
对于
x
中为**0
**的位,这些位可能在数组的某些元素中出现不同的值。
-
2. 分析递增的条件:
-
要构造递增的数组,需要确保每一步都使得数组中的元素变大,而不改变最终的按位与结果。
-
我们通过逐位更新数组中的元素来确保递增性。
3. 控制操作步数:
n
表示数组的长度,而n - 1
表示我们有多少步需要操作。我们可以通过检查n - 1
的二进制位来控制具体的操作步骤,确保我们在n-1
步内完成所有必要的递增操作。
4. 位运算的使用:
-
我们通过位运算来控制更新哪些位,以及如何更新。
-
m = n - 1
:m
的二进制表示告诉我们哪些位可以用于更新。 -
while
循环:我们通过逐步检查ans
中的位,找到第一个可以安全更新的0
位。 -
|=
操作:用于将m
中的位安全地添加到ans
中的合适位置。
-
题解步骤
-
初始化:
-
我们从
x
开始,将其作为ans
的初始值,因为我们要求最终的按位与结果必须等于x
。 -
m = n - 1
:表示我们有n-1
次步骤来调整ans
的值。
-
-
逐步更新
ans
:-
使用位运算逐步检查
m
的每一位,并决定是否对ans
进行更新。 -
while
循环用于确保我们找到ans
中第一个可以更新的位。 -
|=
操作用于将m
的当前位合并到ans
中,确保ans
递增。
-
-
返回结果:
- 最终更新后的
ans
即为满足条件的最小值。
- 最终更新后的
代码实现
java
class Solution {
public long minEnd(int n, int x) {
long ans = x; // 初始化 ans 为 x
int m = n - 1; // m 表示剩余的操作步数
for (long i = 1, offset = 0; i <= m; i <<= 1) {
// 找到 ans 中的第一个 0 位
while ((ans & (i << offset)) > 0) {
offset++;
}
// 更新 ans 的值
ans |= (m & i) << offset;
}
return ans; // 返回最终的结果
}
}
题解分析
-
位运算的使用:
- 位运算在这道题中至关重要。我们通过
m & i
来检查是否可以对ans
进行更新,并通过|=
操作来确保ans
的递增性。(有点难懂,后面会详细说明)
- 位运算在这道题中至关重要。我们通过
-
m
的作用:m
控制了我们能够进行的操作步数,m
的每一位决定了当前步骤是否可以更新ans
的某一位。
-
递增的保证:
while
循环确保我们总是找到ans
中(从低位向高位)第一个0
位 ,并将其更新为1
,从而确保ans
是递增的。
-
复杂度分析:
-
由于我们逐位检查
m
和ans
的位,时间复杂度与n
和x
的位数相关,复杂度为 O(log n)。 -
空间复杂度较低,仅使用了几个辅助变量,因此空间复杂度为 O(1)。
-
关键知识点
-
位运算:
-
&
:按位与,用于逐位检查二进制位。 -
|=
:按位或,用于将一个位设置为1
。 -
<<
:左移运算,用于将位掩码i
移动到合适的位置。
-
-
递增性:
- 通过检查
ans
中的0
位并逐步更新,确保生成的ans
是递增的。
- 通过检查
-
控制操作步骤:
- 使用
m = n - 1
的二进制位来控制在n-1
步内完成所有必要的位更新。
- 使用
说白了,要明白的是这道题的主要步骤就是找x的二进制中从低位到高位的0然后改为1.
那,如何找0?如何改值?如何判断已经结束需要返回?
1. 如何找 x
的二进制中从低位到高位的 0
?
在这道题中,我们要从 x
的二进制表示中找到第一个可以更新为 1
的位置(即第一个 0
位),从而确保数组递增。这个过程可以通过以下步骤完成:
-
位运算的使用:我们通过逐位检查x的每一位,利用&和<<来找到第一个0位。具体操作是:
-
ans & (1 << offset)
:我们从offset = 0
开始,检查ans
的第offset
位。如果ans
在该位为1
,则说明这一位已经被占用,我们不能在这一位上进行更新。 -
while
循环 :如果当前位已经为1
,我们递增offset
,继续检查下一位,找到ans
中的第一个为0
的位。
-
2. 如何将 0
改为 1
?
找到 0
位后,我们需要将其更新为 1
。这个操作通过以下步骤完成:
-
按位或
|=
操作:当找到第一个0位时,我们可以使用按位或操作将其更新为1:-
(m & i) << offset
:这里m & i
的结果是1
(即我们要将某一位更新为1
)。通过左移offset
位,我们将这一位放到合适的位置(即0
位)。 -
ans |= (m & i) << offset
:这一步将m
中的某一位合并到ans
中的第offset
位,从而将0
更新为1
。
-
3. 如何判断已经结束,需要返回结果?
结束条件非常简单:当我们完成了所有的 n - 1
步操作,成功找到了所有合适的 0
位并将其更新为 1
后,ans
就是最终的结果。这是因为我们已经确保 ans
是递增的,且满足按位与结果为 x
的条件。
-
循环控制 :
for (long i = 1, offset = 0; i <= m; i <<= 1)
这个循环控制了我们逐步检查和更新ans
的过程,直到完成所有的n - 1
次操作。 -
返回结果 :当循环结束时,
ans
已经更新到正确的值,我们直接返回ans
作为最终结果。
是否检测了 n-1
次操作?
并不是直接检测 n-1
次操作,而是通过 m = n - 1
来间接控制操作次数。m
的二进制位控制了哪些步骤需要执行,而 offset
控制的是在哪些位置进行这些步骤。
更详细的解释:
m = n - 1
:它的二进制位表示我们在执行n-1
个操作。通过检测m
的每一位是否为1
,我们确定是否需要在ans
的某个位置进行更新。while
循环中offset
的作用:offset
的递增和检查帮助我们找到ans
中的某个0
位。这是我们准备将其更新为1
的位置,以确保ans
的递增性。while
循环不断递增offset
,直到找到一个可以安全设置为1
的位置。
示例:如何通过这些步骤找到并更新 0
位
让我们通过一个简单的例子来进一步说明如何找到 0
位并将其更新为 1
。
输入示例:
-
n = 3
-
x = 4
操作步骤:
-
初始化:
-
ans = x = 4
,二进制为100
。 -
m = n - 1 = 2
,二进制为10
。
-
-
第一步操作:
-
i = 1
(二进制为1
),从最低位开始检查。 -
while
循环 :检查ans
的第 0 位,ans & (1 << 0) = 0
。第 0 位为0
,可以更新。 -
更新 :
ans |= 1 << 0
,即将第 0 位从0
更新为1
。 -
更新后的
ans = 5
,二进制为101
。
-
-
第二步操作:
-
i = 2
(二进制为10
),检查m
的下一位。 -
while
循环 :检查ans
的第 1 位,ans & (1 << 1) = 0
。第 1 位为0
,可以更新。 -
更新 :
ans |= 2 << 1
,即将第 1 位从0
更新为1
。 -
更新后的
ans = 6
,二进制为110
。
-
-
结束并返回:
- 操作完
n - 1
次后,ans
最终值为6
,这是符合条件的最小递增值。
- 操作完