【Leetcode】1969. 数组元素的最小非零乘积

文章目录

题目

题目链接🔗

给你一个正整数 p 。你有一个下标从 1 1 1 开始的数组 n u m s nums nums ,这个数组包含范围 1 , 2 p − 1 1, 2\^p - 1 1,2p−1 内所有整数的二进制形式(两端都 包含 )。你可以进行以下操作 任意 次:

从 n u m s nums nums 中选择两个元素 x x x 和 y y y 。

选择 x x x 中的一位与 y y y 对应位置的位交换。对应位置指的是两个整数 相同位置 的二进制位。

比方说,如果 x = 1101 x = 1101 x=1101 且 y = 0011 y = 0011 y=0011 ,交换右边数起第 2 2 2 位后,我们得到 x = 1111 x = 1111 x=1111 和 y = 0001 y = 0001 y=0001 。

请你算出进行以上操作 任意次 以后, n u m s nums nums 能得到的 最小非零 乘积。将乘积对 1 0 9 + 7 10^9 + 7 109+7 取余 后返回。

注意 :答案应为取余 之前 的最小值。

示例 1:

输入:p = 1

输出:1

解释:nums = 1

只有一个元素,所以乘积为该元素。
示例 2:

输入:p = 2

输出:6

解释:nums = 01, 10, 11

所有交换要么使乘积变为 0 ,要么乘积与初始乘积相同。

所以,数组乘积 1 * 2 * 3 = 6 已经是最小值。
示例 3:

输入:p = 3

输出:1512

解释:nums = 001, 010, 011, 100, 101, 110, 111

  • 第一次操作中,我们交换第二个和第五个元素最左边的数位。
  • 结果数组为 001, **1** 10, 011, 100, **0**01, 110, 111
  • 第二次操作中,我们交换第三个和第四个元素中间的数位。
  • 结果数组为 001, 110, 0**0** 1, 1**1**0, 001, 110, 111

数组乘积 1 * 6 * 1 * 6 * 1 * 6 * 7 = 1512 是最小乘积。
提示:
1 ≤ p ≤ 60 1 \leq p \leq 60 1≤p≤60

思路

首先我们要明确一点:通过交换位操作,我们希望得到的是一个最小非零乘积。接下来我们来分析一下贪心策略:

对于两个数 x x x和 y y y,如果我们将 y y y中除了最低位以外的所有位都赋值为1,那么交换后的结果将会使得乘积变小且非零。

为什么会这样呢?假设 x x x的参与交换的位是 000 000 000, y y y的参与交换的位是 111 111 111,交换后的结果中, x x x将变成 x + 2 k x+2^k x+2k, y y y将变成 y ′ y' y′,此时, ( x + 2 k ) × y ′ = x × y ′ + y ′ × 2 k (x+2^k) \times y' = x \times y' + y' \times 2^k (x+2k)×y′=x×y′+y′×2k。对比之前的乘积,我们可以发现只要 y ′ < x y'<x y′<x,交换后的乘积就会变小。因此,我们可以不断地将 y y y中的 111 111 111与 x x x中的 000 000 000交换,使得 y y y变成 111 111 111,从而使得乘积变得最小。

基于这个思路,我们可以构造数组 n u m s nums nums,将 1 , 2 p − 1 1, 2\^p - 1 1,2p−1中的所有整数分为两组:小于 2 p − 1 2^p - 1 2p−1的为一组 A A A,其余的为另一组 B B B。对于 B B B中的元素,除了 2 p − 1 2^p - 1 2p−1以外,其余的数都可以和 A A A中的数一一配对,使得每对的和为 2 p − 1 2^p - 1 2p−1。然后,我们对每一对进行交换操作,使得 B B B中的数变为 111 111 111。这样一来,我们得到了一个数组 n u m s nums nums,其中每一对的乘积都是 2 p − 2 2^p - 2 2p−2,最终的乘积就是 ( 2 p − 1 ) × ( 2 p − 2 ) 2 p − 1 − 1 (2^p - 1) \times (2^p - 2)^{2^{p-1} - 1} (2p−1)×(2p−2)2p−1−1。

代码

c++ 复制代码
class Solution {
    int mod = 1000000007;
    public: int minNonZeroProduct(int p) {
        long x = (1L << p) - 1;
        return (int) (myPow((x - 1) % mod, x >> 1) * (x % mod) % mod);
    }
    long myPow(long a, long b) {
        long res = 1;
        while (b > 0) {
            if ((b & 1) == 1) res = res * a % mod;
            a = a * a % mod;
            b >>= 1;
        }
        return res;
    }
};

复杂度分析

时间复杂度

构造数组 n u m s nums nums的时间复杂度为 O ( 2 p ) O(2^p) O(2p),其中 2 p 2^p 2p为数组中元素的个数。

快速幂算法在计算 ( 2 p − 2 ) 2 p − 1 − 1 (2^p - 2)^{2^{p-1} - 1} (2p−2)2p−1−1时的时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn),其中 n = 2 p − 2 n = 2^p - 2 n=2p−2。

因此,总的时间复杂度为 O ( 2 p + log ⁡ n ) O(2^p + \log n) O(2p+logn)。

空间复杂度

代码中只使用了常数额外的空间,因此空间复杂度为 O ( 1 ) O(1) O(1)。

结果

总结

通过贪心策略和数学推导,我们得到了一个简洁高效的解决方案。贪心策略是通过寻找最小非零乘积的最优交换方式,从而使得乘积变得最小。数学推导则是基于贪心策略得出了最终的乘积公式,通过快速幂算法高效地计算出结果。整体来说,这个解决方案在时间和空间上都表现出了较高的效率。

相关推荐
JieE21211 小时前
LeetCode 56. 合并区间|超清晰 JS 图解思路,面试高频区间题
javascript·算法·面试
Jack2019 小时前
HarmonyOS开发中错误处理策略:网络异常统一处理
算法
小小杨树21 小时前
读懂色彩:拍照调色不再难
算法·计算机视觉·配色
JieE2122 天前
LeetCode 226. 翻转二叉树|JS 递归超详细拆解,二叉树入门经典题
javascript·算法
JieE2122 天前
LeetCode 104. 二叉树的最大深度|递归思路超详细拆解
javascript·算法
vivo互联网技术2 天前
CVPR 2026 | 全新强化学习框架 BeautyGRPO:重塑真实人像
算法·大模型·cvpr·影像
Darling噜啦啦2 天前
列表转树算法深度解析:从 Map 到 Reduce 的两种实现,面试高频考点
数据结构·算法·面试
用户497863050732 天前
(一)小红的数组操作
算法·编程语言
怕浪猫2 天前
Electron 系列文章封面图
算法·架构·前端框架