[LeetCode解题报告] LCP 49. 环形闯关游戏

一、 题目

1. 题目描述

在这里插入图片描述

2. 原题链接

链接: LCP 49. 环形闯关游戏

二、 解题报告

1. 思路分析

 预处理按位贪心。
  • 假设我们存在一个方法check(val),可以查询当前数字是否存在方案成功闯关。
  • 那么,我们可以从高位到低位遍历每个位1是否是必须要:
  • 即,如果要判断二进制s=10000,s的最右的这个1是否需要,那只需要判断s-1=01111是否可以通关,如果可以,那这位就不需要;
  • 如果不可以通关,那这位就是必要的。
  • 因此可以初始化ans=0,然后从高到低逐位判断ans|(s-1)是否可以通关,如果可以就不要这位s;否则必须要,ans|=s

  • 那么check(val)方法怎么实现呢。
  • 只能尝试每一位作为起点,然后向左右扩展,显然一次时间复杂度是O(n^2)。
  • 那么需要剪枝。两个方案。
    1. 每次check内设置visited,如果前边没能成功的起点已经途径过这个点,那么不需要它作为起点了。因为它作为途经,当时的状态不可能小于自己,都转不完,那它作为起点更转不完了。
    1. 预处理一下challenge数组,计算每个点作为起点且起始分正好是它自己的话,左右最远能延伸到哪,那就可以直接跳到左右两端了。
  • 最坏时间复杂度没变,但是能过了。

2. 复杂度分析

最坏时间复杂度O(n × 61)

3. 代码实现

class Solution:
    def ringGame(self, challenge: List[int]) -> int:
        n = len(challenge)
        vis = [-1]*n
        nxt=lambda i:(i+1) if i < n-1 else 0
        pre = lambda i :i-1 if i > 0 else n-1
        
        # d = challenge+challenge
        # n2 = n*2
        # left = list(range(n2))
        # left_s = d[:]
        # for i in range(1,n2):
        #     if d[i] >= d[i-1]:
        #         left[i] = left[i-1]
        #         left_s[i-1] |= left_s[i-1]
        # left = [left[i]%n for i in range(n,n2)]
        # left_s = left_s[:n]

        # right = list(range(n2))
        # right_s = d[:]
        # for i in range(n2-2,-1,-1):
        #     if d[i]>= d[i+1]:
        #         right[i] = right[i+1]
        #         right_s[i] != right_s[i+1]
        # right = [right[i]%n for i in range(n)]
        # right_s = right_s[:n]
        d = challenge+challenge
        n2 = n*2
        left = list(range(n2))
        left_s = d[:]
        for i in range(1,n2):
            j = i - 1
            while j >= 0 and left_s[i] >= d[j]:
                left_s[i] |= left_s[j]
                left[i] = left[j]
                j =left[j] - 1
            
        left = [left[i]%n for i in range(n,n2)]
        left_s = left_s[:n]

        right = list(range(n2))
        right_s = d[:]
        for i in range(n2-2,-1,-1):
            j = i + 1
            while j < n2 and right_s[i] >= d[j]:
                right_s[i] |= right_s[j]
                right[i] = right[j]
                j = right[j] + 1

        right = [right[i]%n for i in range(n)]
        right_s = right_s[:n]
        
        # print(left_s,right_s)
        # print(left_s,right_s)

        def check(val):
            for i in range(n):
                if val < challenge[i]:
                    continue
                # if vis[i] == val:
                #     continue
                
                # s = val|challenge[i]
                # # vis[i] = val
                # l = r = i
                s = val | left_s[i] | right_s[i]
                l,r = left[i],right[i]
                while True:
                    # print(s,l,r,i)
                    if l == nxt(r):return True  # 成环
                    if s >= challenge[pre(l)]:   # 左右尝试吃
                        l = pre(l)
                        # vis[l] = val
                        s |= challenge[l]
                    elif s >= challenge[nxt(r)]: 
                        r = nxt(r)
                        # vis[r] = val
                        s |= challenge[r]
                    else:  # 吃不到就完了
                        break                 
            return False
        s = 1<<62
        ans = 0
        while s:
            if check((ans|s)-1) == False:
                ans |= s 
            s >>= 1
        # print(check(3,4))
        return ans

三、 本题小结

  1. 按位贪心,第一次写

四、 参考链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值