Leetcode 3145. Find Products of Elements of Big Array

1. 解题思路

这道题思路上还是比较直接的,就是实现上非常的繁琐,着实花了不少力气。

显然,这道题本质上就是要实现一个快速的query(i, j, k)函数,可以求出big array当中i到j位置当中所有元素的积对于k的模。

而这个,我们就可以拆分为以下一个问题:

  • 定义函数count(i)来对于1到i个位置所有的二的指数出现的次数来进行计数。

此时,我们就可以快速得到query(i, j, k)函数的伪代码实现如下:

def query(i, j, k):
    cnt1 = count(i-1)
    cnt2 = count(j)
    ans = 1
    for p, v in cnt2.items():
        # p为2的指数
        ans = ans * pow(p, v) % k
    return ans

因此,我们的问题就有转换为了如何实现这个count函数。

对于这个问题,我们将其进一步拆分为以下两个问题:

  • 坐标位置i对应的整数为多少?
  • 对于任意整数n,将1到n拆分为big array之后其中包含的所有2的指数出现的次数是多少?

对于后者,我们将n写作二进制数,然后看从 2 p 2^p 2p一共出现了多少次就只需要找到不大于n的第一个p位置上为1的数,然后将第p个位置抽走,此时剩下的的位上表示的数字k加1就是一共出现了多少次(0-k)。比如5,写作二进制就是101,此时对于1到5,出现的1的个数就是3(2(10)+1),出现的2的个数就是2(1(1)+1),出现的4的个数就是2(1(01)+1)。

这样,我们就可以很快地求得对任意整数n,从1到n当中出现过的2的指数的个数,此时,我们将其相加即可得到从1到n组成的big array的元素个数,因此,通过二分法,我们又可以快速回答1中的问题,即对于任意坐标i,它对应的整数是多少。

综上,我们就对上述问题进行了完整的回答。思路上还是比较简单清晰的,不过实现上……

诸君,自求多福吧,反正我是被折磨得够呛……

2. 代码实现

给出python代码实现如下:

class Solution:
    def findProductsOfElements(self, queries: List[List[int]]) -> List[int]:
        
        @lru_cache(None)
        def power(i, k, mod):
            if i==1 or k == 0:
                return 1
            elif k == 1:
                return i % mod
            return (power(i, k//2, mod) * power(i, k-k//2, mod)) % mod
        
        @lru_cache(None)
        def num2digits(n):
            return tuple([int(x) for x in bin(n)[2:][::-1]])
        
        def digits2num(digits):
            num = 0
            for d in digits[::-1]:
                num = num * 2 + d
            return num

        @lru_cache(None)
        def count_power(n, k):
            digits = list(num2digits(n))
            if k >= len(digits):
                return 0
            if digits[k] == 0:
                i = 0
                while i < k:
                    digits[i] = 1
                    i += 1
                while digits[i] == 0:
                    digits[i] = 1
                    i += 1
                digits[i] = 0
            digits.pop(k)
            return digits2num(digits) + 1
        
        @lru_cache(None)
        def get_big_num_end_index(num):
            ans = 0
            k = 0
            while True:
                cnt = count_power(num, k)
                if cnt == 0:
                    break
                ans += cnt
                k += 1
            return ans
        
        @lru_cache(None)
        def _count(idx, k):
            i, j = 0, idx+1
            while j-i > 1:
                m = (i+j) // 2
                cnt = get_big_num_end_index(m)
                if cnt <= idx:
                    i = m
                else:
                    j = m
            loc = get_big_num_end_index(i)
            r = idx-loc
            digits_j = num2digits(j)
            extra = 1 if len(digits_j) > k and digits_j[k] == 1 and sum(digits_j[:k+1]) <= r else 0
            return count_power(i, k) + extra
        
        @lru_cache(None)
        def count(idx):
            if idx == 0:
                return defaultdict(int)
            cnt = defaultdict(int)
            k = 0
            while True:
                c = _count(idx, k)
                if c == 0:
                    break
                cnt[k] = c
                k += 1
            return cnt
        
        @lru_cache(None)
        def query(i, j, mod):
            if i == 0:
                cnt1 = defaultdict(int)
            else:
                cnt1 = count(i-1)
            cnt2 = count(j)
            ans = 1
            p, k = 1, 0
            while cnt2[k] > 0:
                c = cnt2[k] - cnt1[k]
                ans = (ans * power(p, c, mod)) % mod
                p = (p * 2) % mod
                k += 1
            return ans
            
        return [query(i+1, j+1, mod) for i, j, mod in queries]

提交代码评测得到:耗时7624ms,占用内存662.3MB。

  • 11
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值