Учитывая список чисел, большинство больше половины. Если мы случайным образом выберем число из чисел, какова вероятность того, что хотя бы одно число является числом большинства?

Вероятно, это должно быть 1- (каждое выбранное число не является большинством)

1-(1-P(большинство))**n

Поскольку P(большинство) ≥0,5

1-(1-P(большинство))**n ≤(1–0,5**n)

Мы можем получить результат n и верхнюю границу 1-(1-P(большинство))**n

>>> for n in range(1, 21):
...     print(n, ":", 1-0.5**n)
...
1 : 0.5
2 : 0.75
3 : 0.875
4 : 0.9375
5 : 0.96875
6 : 0.984375
7 : 0.9921875
8 : 0.99609375
9 : 0.998046875
10 : 0.9990234375
11 : 0.99951171875
12 : 0.999755859375
13 : 0.9998779296875
14 : 0.99993896484375
15 : 0.999969482421875
16 : 0.9999847412109375
17 : 0.9999923706054688
18 : 0.9999961853027344
19 : 0.9999980926513672
20 : 0.9999990463256836

Это означает, что если мы случайным образом выберем 20 чисел, хотя бы одно из них является вероятностью большинства, может быть 0,9999990463256836.

Одна проблема с буквенным кодом связана с этой базовой вероятностью.

1157. Элемент онлайн-большинства в подмассиве



У меня есть следующий код, однако он получил TLE.

21 из 27 тестовых случаев пройдено.

class MajorityChecker:
def __init__(self, arr: List[int]):
        self.arr = arr
        self.i_v_cnt = collections.defaultdict(lambda :collections.defaultdict(int))
        self.i_v_cnt[0][arr[0]] +=1
        for i in range(1, len(arr)):
            v = arr[i]
            ###need deep copy this line will generate a shallow copy
            #self.i_v_cnt[i] =self.i_v_cnt[i-1]
            #### this is deep copy
            self.i_v_cnt[i] = copy.deepcopy(self.i_v_cnt[i-1])
            self.i_v_cnt[i][v] += 1             
    
    def query(self, left: int, right: int, threshold: int) -> int:
        from random import randrange
        this_arr = [self.arr[i] for i in (randrange(left, right+1) for _ in range(20))]
        for num in set(this_arr):
            if self.i_v_cnt[right][num] - self.i_v_cnt[left-1][num] >=threshold:return num
        return -1
# Your MajorityChecker object will be instantiated and called as such:
# obj = MajorityChecker(arr)
# param_1 = obj.query(left,right,threshold)

Причина может заключаться в том, что размер self.i_v_cnt может быть очень большим. Поскольку максимальная длина arr будет составлять 20 000, а в худшем случае — 10 000 уникальных номеров. Это сделает self.i_v_cnt очень большим.

Вдохновлен

https://leetcode.com/problems/online-majority-element-in-subarray/discuss/355848/Python-Binary-Search-+-Find-the-Majority-Element

Мы можем сохранить только индекс num в обр. Затем разделите его пополам на индексы, чтобы найти вхождение этого числа в область видимости.

Решение, основанное на идеях выше

Время выполнения: 1548 мс, быстрее, чем 6,95% онлайн-заявок Python3 для онлайн-элемента большинства в подмассиве.

class MajorityChecker:
def __init__(self, arr: List[int]):
        a2i = collections.defaultdict(list)
        for i, x in enumerate(arr):
            a2i[x].append(i)
        self.arr, self.a2i = arr, a2i
    
    def query(self, left: int, right: int, threshold: int) -> int:
        from random import randrange
        this_arr = [self.arr[i] for i in (randrange(left, right+1) for _ in range(40))]
        def get_count(a):
            idxes = self.a2i[a]
            return bisect.bisect(idxes, right) - bisect.bisect_left(idxes, left) 
        
        for num in set(this_arr):
            count = get_count(num)
            if count>=threshold: return num
        return -1
# Your MajorityChecker object will be instantiated and called as such:
# obj = MajorityChecker(arr)
# param_1 = obj.query(left,right,threshold)

Еще один, связанный с использованием вероятности.

837. Новая 21 игра

https://leetcode.com/problems/new-21-game/

Наивная DFS получит TLE.

Пройдено 86 из 146 тестов.

class Solution:
    def new21Game(self, N: int, K: int, W: int) -> float:
        res = []
        total = []
        def dfs(remain, cur_pro):
            if remain==1:
                total.append(cur_pro)
                valid = sum(K-1+w<=N for w in range(1, W+1))
                res.append(cur_pro*valid/W)
            elif remain<=0:
                total.append(cur_pro)
                if K-remain<=N:res.append(cur_pro)
            else:
                for w in range(1, W+1):
                    dfs(remain-w, cur_pro/W)
                  
        dfs (K, 1)
        return sum(res)/sum(total)

Использование кеша также дало TLE, но добавило еще 10 тестов AC

Пройдено 96 из 146 тестов.

class Solution:
    def new21Game(self, N: int, K: int, W: int) -> float:
        from functools import lru_cache
        @lru_cache(None)
        def dfs(remain):
            if remain==1:
                valid = sum(K-1+w<=N for w in range(1, W+1))
                return 1, valid/W
            elif remain<=0:
                return 1, K-remain<=N
            else:
                total, res = 0, 0
                for w in range(1, W+1):
                    this_remain =remain- w
                    this_total, this_res = dfs(this_remain)
                    total += this_total/W
                    res += this_res/W
                return total, res
                  
        total, res = dfs(K)
        return res/total

Этот код, скорректированный из поста ниже, также получил TLE.

Сложность O((K + W) * W)

Пройдено 96 из 146 тестов.

class Solution:
    def new21Game(self, N: int, K: int, W: int) -> float:
        if K == 0 or N >= K + W: return 1
        max_ = K + W - 1
        dp = [0]*(max_+1)
        dp[0] = 1
        for i in range(1, max_+1):
            for j in range(1, W+1):
                if i-j>=0 and i-j<K:
                    dp[i] += dp[i-j]/W
        return sum(dp[i] for i in range(K, N+1))

Затем я понимаю, что уравнение перехода dp[i] = (dp[i - W] + dp[i - W + 1] + ... + dp[i - 1]) / W можно упростить до dp[i] = (sum[i - 1] - sum[i - W - 1]) / W.

Кроме того, если мы используем dp[i] для прямого представления sum[i], мы можем получить dp[i] = dp[i - 1] + (dp[i - 1] - dp[i - W - 1]) / W. Это уравнение приводит нас к окончательному O(K + W) решению. Просто позаботьтесь о начале и конце массива.

dp (200 ms)

class Solution:
    def new21Game(self, N: int, K: int, W: int) -> float:
        if K == 0 or N >= K + W: return 1
        max_ = K + W - 1
        dp = [0]*(max_+1)
        dp[0] = 1
        for i in range(1, max_+1):
            dp[i] = dp[i-1]
            if i<=W:
                dp[i] += dp[i-1]/W
            else:
                dp[i] += (dp[i-1]-dp[i-W-1]) / W
            if i>K:
                dp[i] -= (dp[i-1]-dp[K-1])/W
        return dp[N]-dp[K-1]

Наконец, из поста от lee215 я понял, что это проблема DP. Временная сложность, если O(n) и код по ссылке ниже

https://leetcode.com/problems/new-21-game/discuss/132334/One-Pass-DP-O(N)

Время выполнения (70 мс)

class Solution:
    def new21Game(self, N: int, K: int, W: int) -> float:
        if K == 0 or N >= K + W: return 1
        dp = [1.0] + [0.0] * N
        Wsum = 1.0
        for i in range(1, N + 1):
            dp[i] = Wsum / W
            if i < K: Wsum += dp[i]
            if i - W >= 0: Wsum -= dp[i - W]
        return sum(dp[K:])

Или другой DP (60 мс)

class Solution:
    def new21Game(self, N: int, K: int, W: int) -> float:
        dp = [0.0] * (N + W + 1)
        # dp[x] = the answer when Alice has x points
        for k in range(K, N + 1):
            dp[k] = 1.0
S = min(N - K + 1, W)
        # S = dp[k+1] + dp[k+2] + ... + dp[k+W]
        for k in range(K - 1, -1, -1):
            dp[k] = S / W
            S += dp[k] - dp[k + W]
return dp[0]

Этот пост хорош.

https://leetcode.com/problems/new-21-game/discuss/437832/Python-DP-iterate-on-points-rather-than-draw-count.-Should-be-a- жесткий -проблема

Я скопировал содержимое, как показано ниже:

«dp(n) означает вероятность получения точки «n» при любом количестве розыгрышей.
например. когда случайное число равно 1~3, мы имеем:
dp(n)=dp(n-3)*(1/3)+dp(n-2)*(1/3)+dp(n- 1)*(1/3)
И этот вид суммы можно оптимизировать с помощью суммы скользящего окна.

Мы не можем итерировать DP при подсчете отрисовки, потому что трудно решить, когда прекратить итерацию. Вместо этого мы повторяем полученную точку, потому что каждая точка может быть получена по значению dp предыдущих точек.

Эта задача должна быть «сложной», а не «средней».

def new21Game(self, N: int, K: int, W: int) -> float:
        if K==0 or K-1+W<=N:
            return 1
        dp=[1]+[0]*N
        cursum=1
        for i in range(1,N+1):
            # dp[i]=sum(dp[max(0,i-W):min(i,K)])*(1/W)
            dp[i]=cursum*(1/W)
            if i<K:
                cursum+=dp[i]
            if i>=W:
                cursum-=dp[i-W]
        return sum(dp[K:])

808. Порции супа



from functools import lru_cache
class Solution:
    def soupServings(self, N: int) -> float:
        if N > 4800: return 1
        @lru_cache(None)
        def f(a, b):
            if a <= 0 and b <= 0: return 0.5
            if a <= 0: return 1
            if b <= 0: return 0
            return (f(a - 4, b) + f(a - 3, b - 1) + f(a - 2, b - 2) + f(a - 1, b - 3))/4
        N = math.ceil(N / 25) 
        return f(N, N)

Спасибо за чтение.