import time def timeit(func): def wrapped(*args, **kwargs): start = time.time() ret = func(*args, **kwargs) elapsed = time.time() - start print("elapsed: %s" % elapsed) return ret return wrapped class Solution: @timeit def longestPalindrome(self, S: str) -> str: length = len(S) dp = [[0]*length for _ in range(length)] result = "" for offset in range(length): r = 0 c = 0 + offset while r < length and c < length: if (r+1 < length and c-1 >= 0 and dp[r+1][c-1] == 1) or (c-1 < r+1): if S[r] == S[c]: # This is palindrome dp[r][c] = 1 if c+1 - r > len(result): result = S[r:c+1] r+=1 c+=1 return result
みなさま、目標に近づいてますでしょうか?
おはようございます。
実行時間役O(n**3)の解
#-*- coding: utf-8 -*- from typing import List import math class Solution: def is_parandrome(self, s): half_idx = math.ceil(len(s)/2) j = len(s)-1 i = 0 while i <= j: if s[i] != s[j]: return False i += 1 j -= 1 #print("is_parandrome(%s) == yes" % s) return True def longestPalindrome(self, s: str) -> str: if len(s) <= 1: return s ret = s[0] for i in range(len(s)): for j in range(i+1, len(s)): #print (i, j) y = self.is_parandrome(s[i:j+1]) ret = s[i:j+1] if y and len(s[i:j+1]) > len(ret) else ret return ret
アルゴリズムとしては正解なのですが
実行時間オーバーなので最後の90/100ケース目あたりでパスしません。
どこを削ることができるでしょうか?( backtracking的枝刈りが有効か?)
それても全然違う考え方が必要でしょうか?(DPは有効か?)
経験からですが、substring問題とくればdpと考えてもいいでしょう
ではどうやってdpを組み立てればいいのでしょうか?
これは難しいですね。
とりあえず、string * stringのテーブル書いてみるのがいいんじゃないでしょうか。
そっからまたパターンを探していくのです。個人的にはとりあえず不正解でもいいのでたたき台を書き出して、
そこから正解の落とし込んでいくのが性に合っているようです。
とは行っても難しいでうよねー、毎回dp問題。よくそんなこと思いつくなーって解答を見ていつも思います
めげずに頑張りましょー
ちょっと説明が難しですが、内側がpalindromeだったら外側だけ常にチェックしていけばいい
という戦法です。
#-*- coding: utf-8 -*- from typing import List import math class Solution: def longestPalindrome(self, s: str) -> str: #logging.basicConfig(level=logging.INFO, format="%(message)s") dp = [[-1]*len(s) for i in range(len(s))] # logging.debug("=== dp ===") # for row in dp: # logging.debug(row) # logging.debug("=== main ===") for i in range(len(s)): for r in range(len(s)): for c in range(r, len(s)): try: x = dp[r][c+i] if i == 0: dp[r][c+i] = 1 else: dp[r][c+i] = 0 except: pass break # logging.debug("=== dp ===") # for row in dp: # logging.debug(row) if len(s) == 0: return s lp = s[0] # loop diagonally for i in range(len(s)): for r in range(len(s)): for c in range(r, len(s)): try: x = dp[r][c+i] #logging.debug("(r, c+i) = (%d, %d)" % (r, c+i)) # 1-left-lower try: mid = dp[r+1][c+i-1] except: mid = 0 if s[r] == s[c+i] and (mid == 1 or mid == -1): #logging.debug("--- hit ---") dp[r][c+i] = 1 # update max if c+i+1 - r > len(lp): lp = s[r:c+i+1] #logging.debug("mid[%s][%s](letters in the middle) is palandrome:%s" % (r+1, c+i-1, mid)) except: pass break # logging.debug("=== dp ===") # for row in dp: # logging.debug(row) return lp import logging logging.basicConfig(level=logging.INFO, format="%(message)s") #logging.basicConfig(level=logging.DEBUG, format="%(message)s") s = "babad" s = "cbbd" # time-exceeded s = "kyyrjtdplseovzwjkykrjwhxquwxsfsorjiumvxjhjmgeueafubtonhlerrgsgohfosqssmizcuqryqomsipovhhodpfyudtusjhonlqabhxfahfcjqxyckycstcqwxvicwkjeuboerkmjshfgiglceycmycadpnvoeaurqatesivajoqdilynbcihnidbizwkuaoegmytopzdmvvoewvhebqzskseeubnretjgnmyjwwgcooytfojeuzcuyhsznbcaiqpwcyusyyywqmmvqzvvceylnuwcbxybhqpvjumzomnabrjgcfaabqmiotlfojnyuolostmtacbwmwlqdfkbfikusuqtupdwdrjwqmuudbcvtpieiwteqbeyfyqejglmxofdjksqmzeugwvuniaxdrunyunnqpbnfbgqemvamaxuhjbyzqmhalrprhnindrkbopwbwsjeqrmyqipnqvjqzpjalqyfvaavyhytetllzupxjwozdfpmjhjlrnitnjgapzrakcqahaqetwllaaiadalmxgvpawqpgecojxfvcgxsbrldktufdrogkogbltcezflyctklpqrjymqzyzmtlssnavzcquytcskcnjzzrytsvawkavzboncxlhqfiofuohehaygxidxsofhmhzygklliovnwqbwwiiyarxtoihvjkdrzqsnmhdtdlpckuayhtfyirnhkrhbrwkdymjrjklonyggqnxhfvtkqxoicakzsxmgczpwhpkzcntkcwhkdkxvfnjbvjjoumczjyvdgkfukfuldolqnauvoyhoheoqvpwoisniv" ans = Solution().longestPalindrome(s) logging.info(ans)
よっしゃこれでいけるだろうと思ったのですが、
多分最初の初期化の処理が結局O(n**3)になっちゃっているので
処理時間超過しているのではなんて思ったのですが、最後のループはbreakで常に終了するのでO(n**2)だと
思っています。
ちょっとここで他の人たちの解答を見ることにしました。
あまり悩んで一つの問題に時間をかけるよりは一定時間悩んだら次に行くようにします。
斜めにループできますか?プログラミング で書いた通り斜めの操作とい技を
覚えました。これを元に、
ではこれで longest palindrome substringを再実装していきます。
import logging class Solution: def longestPalindrome(self, S: str) -> str: logging.basicConfig(level=logging.INFO, format="%(message)s") logging.debug("palandrome(%s)" % S) dp = [[0]*len(S) for _ in range(len(S))] for i in range(len(S)): dp[i][i] = 1 result = "" for offset in range(len(S)): r = 0 c = 0 + offset while r < len(S) and c < len(S): #logging.debug(dp[r][c], end=",") if (r+1 < len(S) and c-1 >= 0 and dp[r+1][c-1] == 1) or (c-1 < r+1): if S[r] == S[c]: logging.debug("oh yes, this is palandrome") if c+1 - r > len(result): result = S[r:c+1] dp[r][c] = 1 r+=1 c+=1 return result
とコーディングして、いざ提出してみると
1000文字ちょうどのサンプルで弾かれてしまいました
ということで、
timeitで時間を測って削られるだけ削りました
0.4秒から0.27秒まで落とせたので、自信を持って提出、ドリャ!
import time def timeit(func): def wrapped(*args, **kwargs): start = time.time() ret = func(*args, **kwargs) elapsed = time.time() - start print("elapsed: %s" % elapsed) return ret return wrapped class Solution: @timeit def longestPalindrome(self, S: str) -> str: length = len(S) dp = [[0]*length for _ in range(length)] result = "" for offset in range(length): r = 0 c = 0 + offset while r < length and c < length: if (r+1 < length and c-1 >= 0 and dp[r+1][c-1] == 1) or (c-1 < r+1): if S[r] == S[c]: # This is palindrome dp[r][c] = 1 if c+1 - r > len(result): result = S[r:c+1] r+=1 c+=1 return result
やっと通りました。
もちろんtimeitの部分はコメントアウトして提出しました
驚いたのは、len(S)の部分が多用されていたのですが、これは事項時間に影響しないと
考えていたのですが、実はloopでO(n**2)でlen(S)が実行されるのは効いていたようです。
これからは細かい点ですが、len(S)は実行時間を食う対象として考えます。
で、パフォーマンスなのですが、以下の通り最低ラインなので
ここはもう一踏ん張りして、最適な解を取りに行きたいと思います。
ということで、一応自分の頭で
よぎった、外側に拡張していアイデアをちゃんと書けている人が沢山いるので
彼らか学び、次回は自分でもかけるようになるべく
練習して言いたいと思います。
逆に言えば自分の弱みは、アイデアを思いつてもそれをスラスラとコードに書き落とすことができないという
コーディング・実装力のなさです。これは練習で補っていきたいと思います
参考にするソースはこちらですね
一個一個愚直に拡張していくパターンです。
最初に思いついた時は具体的な速度O(what)になるのか?
ぼんやりでストップするのではなく、O(what)になるイメージをちゃんと掴めるところまで
実装をイメージできるかは非常に大切で、ここまでイメージが湧かないと面接などの限られた時間で能力を示すには意味がないと思います。
今回であればO(n)でなめてO(n)で拡張する(expand)するイメージをもつことでO(n**2)で計算できる
""" dp使わずに拡大していく方法 """ class Solution: def expand_palindrome(self, S, i): l = i - 1 r = i + 1 result = S[i] while l >= 0 and r < len(S) and S[l] == S[r]: # check l and r within valid range # this is palindrome if r+1-l > len(result): result = S[l:r+1] l -= 1 r += 1 return result def expand_palindrome_2(self, S, i, j): l = i - 1 r = j + 1 if i < 0 or j >= len(S): return "" if S[i] != S[j]: return "" result = S[i:j+1] while l >= 0 and r < len(S) and S[l] == S[r]: # check l and r within valid range # this is palindrome if r+1-l > len(result): result = S[l:r+1] l -= 1 r += 1 return result @timeit def longestPalindrome(self, S: str) -> str: if len(S) <= 1: return S result = S[0] # palindrome starting with 1 char for i, c in enumerate(S): tmp = self.expand_palindrome(S, i) if len(tmp) > len(result): result = tmp # palindrome starting with 1 char for i, c in enumerate(S): tmp = self.expand_palindrome_2(S, i, i+1) if len(tmp) > len(result): result = tmp return result
上記の実装ももう少し削れるとは思いますが
ご覧のように、前よりはよくなりました。
このlongest palindrome substringという問題は
かなりてこづりました。しかしながら全部自分で実装することろまで持って行ったことで
1。longest最長とか聞かれたら、DPを連想し、とりあえずDP表を作る、という持っていき方
2。斜めの操作
3。今回であればO(n)でなめてO(n)で拡張する(expand)するイメージをもつことでO(n**2)で計算できる
とうい3つの学びを習得できました
以上です