0.試合後のまとめ
今回の大会は結果的にはかなり満足のいくもので、国内で84、世界で214と非常に満足のいく結果でしたが、残念ながら4つすべての質問に答えることができず、3番目の質問にとどまりました。
他の人が何をして学んだかを見て、良い仕事を続け、良い仕事を続けてください!
1.トピック1
質問1に与えられた質問へのリンクは次のとおりです。
1.問題解決のアイデア
最初の質問は、全体としてはまだ些細な質問です。質問の意味に従って処理する必要があります。より複雑な場所は、ループの処理だけです。
しかし、これは実際には難しいことではありません。kがゼロよりも大きい場合、我々は数追加シーケンスの最後にkのを、そしてkは0以上であるとき、私たちはシーケンスの最後にkの数を追加します。
2.コードの実装
最終的なPythonコードは次のとおりです。
class Solution:
def decrypt(self, code: List[int], k: int) -> List[int]:
n = len(code)
if k == 0:
return [0 for _ in range(n)]
if k > 0:
code = code + code[:k]
return [sum(code[i+1:i+k+1]) for i in range(n)]
else:
k = -k
code = code[-k:] + code
return [sum(code[i-k:i]) for i in range(k, k+n)]
評価のためにコードを送信した後、28ミリ秒かかり、14.1MBのメモリを消費しました。現在の最適なコード実装について。
2.トピック2
トピック2のテスト問題へのリンクは次のとおりです。
1.問題解決のアイデア
この質問は、私が以前に行ったことがある場所を常に感じます。。。しかし、本質的に、この質問のアイデアはまだ非常に単純です。それが達成したい最終的な状態は、前a
と後ろのb
文字列を達成することです。次に、私たちがしなければならないことは、それぞれの前の位置を数えることです文字b
。a
文字数と後ろの文字数、そして最小値を見つけます。
2.コードの実装
次のように、Pythonコードの実装を直接提供します。
class Solution:
def minimumDeletions(self, s: str) -> int:
n = len(s)
a = [0 for _ in range(n)]
b = [0 for _ in range(n)]
for i in range(n-1):
if s[i] == 'b':
b[i+1] = b[i]+1
else:
b[i+1] = b[i]
for i in range(n-1, 0, -1):
if s[i] == 'a':
a[i-1] = a[i] + 1
else:
a[i-1] = a[i]
return min(a[i] + b[i] for i in range(n))
評価のためにコードを送信した後、1296ミリ秒かかり、19.8MBのメモリを消費しました。
現在の最適なソリューションは500ミリ秒しかかかりません。彼らのコードを調べたところ、アイデアは同じであるはずですが、実装の詳細は異なります。興味のある読者は自分でチェックできます。ここではこれ以上は行いません。
3.トピック3
トピック3のテスト問題へのリンクは次のとおりです。
1.問題解決のアイデア
1日後にこの質問をもう一度読んだ後、昨日の解決策と最終的な答えは、実際には2行のコードの違いであり、それはそれに他なりません。。。
本質的に、これはまだ再帰的なアルゴリズムであり、エイトクイーンの問題に少し似ています。問題を解決するアイデアは次のとおりです:
- 現在の位置がターゲットxよりも小さく、右に移動できる場合は、最初に右に移動して、履歴パスに記録します。
- 現在の位置がターゲットxより大きく、左に移動できる場合は、最初に左に移動して、履歴パスに記録します。
- 特定の位置が履歴パスにすでに表示されている場合、このパスは最適なパスであってはなりません。
- パスが最後に到達して追跡できない場合、または3の状況が発生した場合は、前のパスが次のパスをたどって次の実行可能なパスを通過できるようになるまで戻ります。
2.コードの実装
Pythonコードは次のとおりです。
class Solution:
def minimumJumps(self, forbidden: List[int], a: int, b: int, x: int) -> int:
forbidden = set(forbidden)
seen = {
0}
@lru_cache(None)
def dfs(loc, can_back):
if loc == x:
return 0
if loc < x:
if loc + a not in forbidden and loc + a not in seen:
seen.add(loc + a)
tmp = dfs(loc+a, True)
if tmp != -1:
return tmp + 1
seen.remove(loc+a)
if can_back and loc - b >= 0 and loc - b not in seen and loc-b not in forbidden:
seen.add(loc-b)
tmp = dfs(loc-b, False)
if tmp != -1:
return tmp + 1
seen.remove(loc-b)
return -1
else:
if b-a <= 0 and loc > x+b:
return -1
if can_back and loc - b >= 0 and loc - b not in seen and loc-b not in forbidden:
seen.add(loc-b)
tmp = dfs(loc-b, False)
if tmp != -1:
return tmp + 1
seen.remove(loc-b)
if loc >= 4000:
return -1
if loc + a not in forbidden and loc + a not in seen:
seen.add(loc + a)
tmp = dfs(loc+a, True)
if tmp != -1:
return tmp + 1
seen.remove(loc+a)
return -1
return dfs(0, True)
評価のためにコードを送信した後、96ミリ秒かかり、21.4MBのメモリを消費しました。
現在の最適なコード実装には68ミリ秒かかります。それを見ると、残念ながら、彼のコード実装は実際に私たちよりもエレガントです。。。
3.アルゴリズムの最適化
基本的に、現在の最適なアルゴリズムは私たちの考えと同じですが、彼は直接キューを使用してステップkで可能なすべてのパスを保存し、ステップkで通過したすべての履歴ルートが直接追加されます禁止では、私たちがseen
構築するのとは異なります追加のストレージ用のコレクション。
一方、彼は最大距離をうまく処理し、最大距離の2倍の4000という方法を激しく採用し、この値を最適化しました。
彼のコード実装は次のとおりです。
class Solution:
def minimumJumps(self, forbidden: List[int], a: int, b: int, x: int) -> int:
from collections import deque
queue = deque([(0,1)])
jumps = 0
forbidden = set(forbidden)
forbidden.add(0)
MAX_DIST = max(x, max(forbidden)) + a + b
while queue:
tmp = deque()
for elem in queue:
(pos, direction) = elem
if pos == x:
return jumps
forward = pos + a
if forward not in forbidden and forward <= MAX_DIST:
tmp.append((forward, 1))
forbidden.add(forward)
for elem in queue:
(pos, direction) = elem
if pos == x:
return jumps
backward = pos - b
if direction != -1 and backward >= 0 and backward not in forbidden:
tmp.append((backward, -1))
forbidden.add(backward)
queue = tmp
jumps += 1
return -1
4.トピック4
質問4のテスト問題へのリンクは次のとおりです。
1.問題解決のアイデア
この質問のより簡単なアイデアは、エイトクイーン問題のような再帰的アルゴリズムを使用し、割り当てが完了するか、マッチングを続行できなくなるまで、最初に割り当てることができるアルゴリズムを割り当てることです。
一致が完了した場合は直接戻るかTrue
、そうでない場合は前の割り当てに戻り、1つが成功するか、すべての候補プランが一致しなくなるまで、次の実行可能な割り当てプランを割り当てに使用してから、この手順を繰り返します。
2.コードの実装
Pythonコードは次のとおりです。
class Solution:
def canDistribute(self, nums: List[int], quantity: List[int]) -> bool:
counter = list(Counter(nums).values())
quantity = sorted(quantity, reverse=True)
n = len(quantity)
def dfs(idx):
if idx >= n:
return True
for i, c in enumerate(counter):
if c < quantity[idx]:
continue
counter[i] -= quantity[idx]
if dfs(idx+1):
return True
counter[i] += quantity[idx]
return False
return dfs(0)
コード評価を送信した後、4628ミリ秒かかり、25.6MBのメモリを消費しました。
現在の最適なソリューションは512msしかかからず、私たちのソリューションと比較してほぼ1桁のパフォーマンスの向上があります。したがって、それらのソリューションを注意深く検討する必要があります。
3.アルゴリズムの最適化
私は基本的にこのアルゴリズムの最適化をあきらめました。原則として、上記のdfsコードで最も時間のかかることは、すべてのデータが毎回トラバースされることですが、この順序付けられたシーケンスを何らかの方法で維持できれば、多くの冗長な操作を減らしました。
しかし、この冗長な操作を排除するためのより良い方法は考えていませんでした。私は彼らのコードを見て、この部分で最適化する必要があると感じましたが、私は本当に彼らのコードを見たくありません。私は少し疲れています。次のようにコードを直接抽出すると、興味のある読者は自分でコードを学習できます。
import heapq
class Solution:
def canDistribute(self, nums: List[int], quantity: List[int]) -> bool:
cnt=collections.Counter(nums)
vs=sorted(cnt.values())
qt=tuple(sorted(quantity))
cusum=[vs[0]]*len(vs)
for i in range(1,len(vs)):
cusum[i]=cusum[i-1]+vs[i]
def helper(qt,v):
cands=set()
seen={
qt}
stack=[[qt,v]]
while stack:
cqt,cv=stack.pop()
if cqt[0]>cv:
cands.add((sum(cqt),cqt))
else:
for i in range(len(cqt)):
if cqt[i]<=cv:
nv=cv-cqt[i]
nqt=cqt[:i]+cqt[i+1:]
if nqt not in seen:
seen.add(nqt)
stack.append([nqt,nv])
return cands
state=[[sum(qt),qt,len(vs)-1]]
while state:
csum,cqt,j=heappop(state)
rem=cusum[j]
if j==0:
return rem>=csum
rem-=cusum[j-1]
if rem>=csum:
return True
if cusum[j]>=csum and cqt[-1]<=vs[j]:
cands=helper(cqt,vs[j])
for nsum,nqt in cands:
if nsum<=cusum[j-1] and nqt[-1]<=vs[j-1]:
heapq.heappush(state,[nsum,nqt,j-1])
return False