力扣每日一题-08.22

题目描述

给定两个整数 nx,要求构造一个长度为 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 - 1m 的二进制表示告诉我们哪些位可以用于更新。

    • while 循环:我们通过逐步检查 ans 中的位,找到第一个可以安全更新的 0 位。

    • |= 操作:用于将 m 中的位安全地添加到 ans 中的合适位置。


题解步骤
  1. 初始化

    • 我们从 x 开始,将其作为 ans 的初始值,因为我们要求最终的按位与结果必须等于 x

    • m = n - 1:表示我们有 n-1 次步骤来调整 ans 的值。

  2. 逐步更新 ans

    • 使用位运算逐步检查 m 的每一位,并决定是否对 ans 进行更新。

    • while 循环用于确保我们找到 ans 中第一个可以更新的位。

    • |= 操作用于将 m 的当前位合并到 ans 中,确保 ans 递增。

  3. 返回结果

    • 最终更新后的 ans 即为满足条件的最小值。


代码实现
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;  // 返回最终的结果
    }
}

题解分析
  1. 位运算的使用

    • 位运算在这道题中至关重要。我们通过 m & i 来检查是否可以对 ans 进行更新,并通过 |= 操作来确保 ans 的递增性。(有点难懂,后面会详细说明)

  2. m 的作用

    • m 控制了我们能够进行的操作步数,m 的每一位决定了当前步骤是否可以更新 ans 的某一位。

  3. 递增的保证

    • while 循环确保我们总是找到 ans 中(从低位向高位)第一个 0,并将其更新为1,从而确保 ans 是递增的。

  4. 复杂度分析

    • 由于我们逐位检查 mans 的位,时间复杂度与 nx 的位数相关,复杂度为 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

操作步骤:
  1. 初始化

    • ans = x = 4,二进制为 100

    • m = n - 1 = 2,二进制为 10

  2. 第一步操作

    • i = 1(二进制为 1),从最低位开始检查。

    • while 循环:检查 ans 的第 0 位,ans & (1 << 0) = 0。第 0 位为 0,可以更新。

    • 更新ans |= 1 << 0,即将第 0 位从 0 更新为 1

    • 更新后的 ans = 5,二进制为 101

  3. 第二步操作

    • i = 2(二进制为 10),检查 m 的下一位。

    • while 循环:检查 ans 的第 1 位,ans & (1 << 1) = 0。第 1 位为 0,可以更新。

    • 更新ans |= 2 << 1,即将第 1 位从 0 更新为 1

    • 更新后的 ans = 6,二进制为 110

  4. 结束并返回

    • 操作完 n - 1 次后,ans 最终值为 6,这是符合条件的最小递增值。

  • 13
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值