【周赛总结】石子游戏IV,仅含1的子串数,概率最大的路径,服务中心的最佳位置——dijstra和梯度下降算法

第三十场双周赛 2020/07/11 rank 432 / 2545 ac 4/4
第197场周赛 2020/07/12 rank 360 / 5274 ac 3/4

题目1,石子游戏

【周赛总结】石子游戏IV,仅含1的子串数,概率最大的路径,服务中心的最佳位置——dijstra和梯度下降算法_第1张图片

是一道比较经典的dp问题,但是还是因为缺少经验,卡了一些时间。
思路还是比较明确的,dp[i]表示剩余 i 个石子的情况下,先手能否获胜。初始条件是dp = [False]*(n+1),对于所有的平方数,都是True。

每次进行转移时,枚举先手方的策略拿j个石子,此时要求后手dp[i-j] == False则,dp[i] == True。其实是进行了先后手的交换,进而枚举。

class Solution:
    def winnerSquareGame(self, n: int) -> bool:
        can = []
        dp = [False]*(n+1)
        for i in range(1,n+1):
            cur = i*i
            if cur<=n:
                can.append(cur)
                dp[cur] = True
            else:
                break
        for i in range(1,n+1):
            if dp[i] == True:
                continue
            for j in can:
                if i-j>0 and dp[i-j] == False:
                    dp[i] = True
                    break
        return dp[n]

记住这个思路,因为双方都会选择最优策略,因此先后手转换,先手拿完以后,后手方就变成了一个先手问题。

题目2,仅含1的子串数

【周赛总结】石子游戏IV,仅含1的子串数,概率最大的路径,服务中心的最佳位置——dijstra和梯度下降算法_第2张图片

核心问题是统计连续的1的个数。这个子问题已经频繁出现在中等难度的题目中了。这里给出一个模板。

class Solution:
    def numSub(self, s: str) -> int:
        n = len(s)
        ans = []
        cur = 0
        s = '0'+s
        mod = 10**9 + 7 
        ##----------------------模板,统计连续的个数----------------------------
        for i in range(1,n+1):
            if s[i] == '1' and s[i-1] == '1':
                cur += 1
            elif s[i] == '1':
                cur = 1
            elif s[i] == '0':
                if cur > 0:
                    ans.append(cur)
                cur = 0
        ## 注意处理最后一个
        if cur > 0:
            ans.append(cur)
		# ---------------------------------------------------------------------
        res = 0
        for num in ans:
            for i in range(1,num+1):
                res += num-(i-1)
        return res%mod

题目3,概率最大的路径

【周赛总结】石子游戏IV,仅含1的子串数,概率最大的路径,服务中心的最佳位置——dijstra和梯度下降算法_第3张图片

首先是没有采用堆优化的方法,维护了一个最佳数组,每次比较是否最好。

class Solution:
    def maxProbability(self, n: int, edges: List[List[int]], succ: List[float], start: int, end: int) -> float:
        dic = collections.defaultdict(list)
        End = end
        for i in range(len(edges)):
            star, end = edges[i]
            dic[star].append([end, succ[i]])
            dic[end].append([star, succ[i]])
        queue = collections.deque()
        queue.append([start,1])
        ## best 数组维护了最佳的答案,思路来源于dijstra
        best = [0]*n
        ## bfs进行搜索

        while queue:
            cur, wight = queue.popleft() # cur是当前的节点, wight是到达当前节点的权重
            ## 这一步不能省略,因为是不停的迭代的,尽管存的时候是最优的,但是可能后浪刷新了最优值
            if best[cur] > wight: ## 如果当前节点的权重不如最优解,跳过
                continue
            for next, w in dic[cur]:
                cur_next = wight*w ## 当前点是最优的前提下,转移到别的点
                if cur_next > best[next]: ## 别的点也是优于最优点的,更新最优点
                    best[next] = cur_next
                    queue.append([next, cur_next])
        return best[End]

考虑本质上还是dijstra算法,因此可以用堆优化,原本问题是求解最短路径,因此用的小顶堆,这里我们求解最大的概率采用大顶堆。并且考虑概率约乘越小,只有第一次考虑就可以。

class Solution(object):
    def maxProbability(self, n, edges, succProb, start, end):

        # 用优先队列,大顶堆进行优化
        dic = collections.defaultdict(list)
        for i in range(len(edges)):
            begin, last = edges[i]
            dic[begin].append([last, succProb[i]])
            dic[last].append([begin, succProb[i]])
        queue = []
        heapq.heappush(queue, [-1, start])
        # 两种堆化的方法,这里需要大顶堆
        #queue.append([-1, start])
        #heapq.heapify(queue)

        ## bfs进行搜索
        visit = set()
        while queue:
        # 每次分析当前最大概率的点,看看能否到达终点
            wight, cur = heapq.heappop(queue)
            visit.add(cur)
            # 第一次出现end,因为是大顶堆一定是最大概率
            if cur == end:
                return -wight
            for next, w in dic[cur]:
                if next in visit:
                    continue
                heapq.heappush(queue, [wight*w, next])
        return 0

题目4,服务中心的最佳位置

【周赛总结】石子游戏IV,仅含1的子串数,概率最大的路径,服务中心的最佳位置——dijstra和梯度下降算法_第4张图片

一道很有意思的题目,第一次出现了梯度下降的方法。给定了若干个点,求解一个点(x, y)使得这个点到这n个点的欧式距离的和最短。

我们可以很容易的得到,优化目标 min ⁡ Σ n ( x i − x ) 2 + ( y i − y ) 2 \min \Sigma_n\sqrt{(x_i-x)^2+(y_i-y)^2} minΣn(xix)2+(yiy)2 。求导可以得到
在这里插入图片描述

因此我们进行合理的迭代就可以。第一次出现这一类题目,值得记录模板

from math import sqrt
class Solution(object):
    def getMinDistSum(self, positions):
        def cost(x, y):
            ans = 0
            for x1, y1 in positions:
                ans += sqrt((x-x1)**2+(y-y1)**2)
            return ans
        n = len(positions)
        x = 0.0
        y = 0.0
        lr = 0.1 # 学习率,初始值可以设置稍大,反正后面可以减小
        epsilon = 1e-8 # 容忍误差
        maxloop = 30000 # 最大迭代次数
        for x_1,y_1 in positions:
            x += x_1
            y += y_1
        # 在重心位置,设置为初始值
        x_0 = x/n 
        y_0 = y/n
        cost0 = cost(x_0, y_0)
        for _ in range(maxloop) :
            dy = 0.0
            dx = 0.0
            # 计算每次的梯度
            for i in range(n):
                cur_x, cur_y = positions[i]
                dx += (x_0 - cur_x) / max(10**(-9), sqrt((x_0 - cur_x)**2 + (y_0 - cur_y)**2))
                dy += (y_0 - cur_y) / max(10**(-9), sqrt((x_0 - cur_x)**2 + (y_0 - cur_y)**2))
            # 得到伪更新数值
            x_i = x_0 - lr*dx
            y_i = y_0 - lr*dy
            costi = cost(x_i, y_i)
            # 判断伪更新数值的性质
            if cost0 - costi > epsilon: # 一次有效的更新
                x_0 = x_i
                y_0 = y_i
                cost0 = costi
            elif cost0 - costi < 0: # 走的步子太大了,需要减小学习率
                lr *= 0.3
            elif cost0 - costi <= epsilon: # 完成目标
                break
                
        return cost0

你可能感兴趣的:(周赛总结,算法,面试,数据结构,python)