3133.数组最后一个元素的最小值

目标

给你两个整数 n 和 x 。你需要构造一个长度为 n 的 正整数 数组 nums ,对于所有 0 <= i < n - 1 ,满足 nums[i + 1] 大于 nums[i] ,并且数组 nums 中所有元素的按位 AND 运算结果为 x 。

返回 nums[n - 1] 可能的 最小 值。

示例 1

输入:n = 3, x = 4
输出:6
解释:
数组 nums 可以是 [4,5,6] ,最后一个元素为 6 。

示例 2

输入:n = 2, x = 7
输出:15
解释:
数组 nums 可以是 [7,15] ,最后一个元素为 15 。

说明

  • 1 <= n, x <= 10^8

思路

构造一个长度为 n 的单调递增数组 nums,要求数组所有元素按位与的结果为正整数 x,返回 nums[n - 1] 的最小值。

要使元素按位与的结果为 x,那么 x 中bit位为1的位置在元素中也必须为1。我们可以找到 x 中bit位为0的位置,从最低位开始置1,如果还不凑不够数组个数就将最高位置1,重新从最低位开始置1。

刚开始是按照上面的思路模拟着写的,非常容易出错。调试了半天发现,其实就是将 n - 1 的二进制表示填入 x 的二进制表示中的bit位为0的位置,如果不够就借用高位的0。之所以是 n -1 是因为 x 本身也算一个元素,0 ~ n - 1n 个元素。

官网题解也正是这个思路,不过实现简洁多了。主要是刚开始没有想清楚,其实根本不用讨论低位/高位,直接填就行了。

代码


/**
 * @date 2024-08-22 10:12
 */
public class MinEnd3133 {

    public long minEnd(int n, int x) {
        List<Integer> zeroIndex = new ArrayList<>(63);
        int i = 0;
        int tmp = x;
        // 记录0的位置
        while (tmp > 0) {
            if ((tmp & 1) == 0) {
                zeroIndex.add(i);
            }
            tmp >>= 1;
            i++;
        }
        long res = x;
        int zeroCnt = zeroIndex.size();
        long highBitsCnt;
        // 如果不含0,那么只能填高位
        if (zeroCnt == 0) {
            // 这个不应该全填1,应填n-1的二进制表示
            highBitsCnt = n - 1;
            while (highBitsCnt > 0) {
                if ((highBitsCnt & 1) == 1) {
                    res |= 1L << i;
                }
                i++;
                highBitsCnt >>= 1;
            }
            return res;
        }
        // 如果含0则计算其能表示的数字个数,下面是快速幂
        long oneLoopElementCnt = 1;
        tmp = zeroCnt;
        int base = 2;
        while (tmp > 0) {
            if ((tmp & 1) == 1) {
                oneLoopElementCnt = oneLoopElementCnt * base;
            }
            base *= base;
            tmp >>= 1;
        }
        // 低位需要填的个数
        long lowBitsCnt = n % oneLoopElementCnt - 1;
        if (lowBitsCnt == -1) {
            // 如果为-1,表示能够整除,将低位的0全置为1
            for (Integer index : zeroIndex) {
                res |= 1L << index;
            }
            // 如果低位能够填满数组则无需填高位
            if (n <= oneLoopElementCnt) {
                highBitsCnt = 0;
            } else {
                // 高位的计数需减1,是因为将低位全填1,这时已经完成了一个loop,例如n=4,x=2,oneLoopElementCnt 为2,n / oneLoopElementCnt = 2, (10 11) 110 111,我们只需要填充一个高位
                highBitsCnt = n / oneLoopElementCnt - 1;
            }
        } else {
            // 否则将需要填在低位的个数填入低位的0
            int j = 0;
            while (lowBitsCnt > 0) {
                if ((lowBitsCnt & 1) == 1) {
                    res |= 1L << zeroIndex.get(j);
                }
                j++;
                lowBitsCnt >>= 1;
            }
            // 如果低位能够填满数组则无需填高位
            if (n <= oneLoopElementCnt) {
                highBitsCnt = 0;
            } else {
                // 这里不需要减1,是因为n不能整除oneLoopElementCnt,多余位已经被舍去了,例如n=3,x=2,n / oneLoopElementCnt = 1
                highBitsCnt = n / oneLoopElementCnt;
            }
        }

        // 填充高位个数
        while (highBitsCnt > 0) {
            if ((highBitsCnt & 1) == 1) {
                // 出错点:如果写成1,会溢出,必须加上L
                res |= 1L << i;
            }
            i++;
            highBitsCnt >>= 1;
        }

        return res;
    }

}

性能