第三十场双周赛 2020/07/11 rank 432 / 2545 ac 4/4
第197场周赛 2020/07/12 rank 360 / 5274 ac 3/4
是一道比较经典的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]
记住这个思路,因为双方都会选择最优策略,因此先后手转换,先手拿完以后,后手方就变成了一个先手问题。
核心问题是统计连续的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
首先是没有采用堆优化的方法,维护了一个最佳数组,每次比较是否最好。
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
一道很有意思的题目,第一次出现了梯度下降的方法。给定了若干个点,求解一个点(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(xi−x)2+(yi−y)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