【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, 001, 110, 111] 。
  • 第二次操作中,我们交换第三个和第四个元素中间的数位。
  • 结果数组为 [001, 110, 00 1, 110, 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)。

结果

总结

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

相关推荐
孟大本事要学习28 分钟前
算法第15天:继续二叉树|前序递归+回溯与前序递归的场景总结、最大二叉树、合并二叉树、二叉搜索树中的搜索、验证二叉搜索树
算法
GalaxyPokemon42 分钟前
LeetCode - 76. 最小覆盖子串
运维·服务器·数据结构·算法·leetcode
嵌入式@秋刀鱼1 小时前
《 第三章-招式初成》 C++修炼生涯笔记(基础篇)程序流程结构
linux·开发语言·数据结构·c++·笔记·visual studio code
手握风云-1 小时前
动态规划算法的欢乐密码(二):路径问题
算法·动态规划
小马爱打代码1 小时前
数据结构 - Java 队列
java·数据结构
Raven100862 小时前
L1G2-OpenCompass 评测书生大模型实践
算法
NAGNIP2 小时前
RAG信息检索-如何让模型找到‘对的知识’
算法
大大大大小小2 小时前
26-数据结构-线性表2
java·数据结构·restful
蒟蒻小袁4 小时前
力扣面试150题--实现Trie(前缀树)
leetcode·面试·c#
电院工程师4 小时前
轻量级密码算法CHAM的python实现
python·嵌入式硬件·算法·安全·密码学