LeetCodeに挑戦:DP系問題 Medium (その2)

LeetCodeの方で動的計画法に関するスレッド、Dynamic Programming Questions Thread を見つけましたので、今回はその中にある Medium 問題を解いてみたいと思います。(DP系問題 Medium (その1)は こちら

139. Word Break

(問題)空でない文字列 s と、空でない単語のリストを持つ辞書 wordDict が与えられます。s を1つ以上の辞書が持つ単語で、スペース区切りとして、分割できるかを判断して下さい。

(例)Input:  s = “leetcode”, wordDict = [“leet”, “code”]
Output: ture
   説明:”leetcode” は、”leet code” として分割できるため、true を返します。

(解答例)以下に解答例を示します。

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        # (1)
        dp = [ 0 for _ in range(len(s)+1)]
        dp[0] = 1
        # (2)
        for i in range(len(s) + 1):
			      # (3)
            if dp[i] == 0:
                continue
            # (4)
            for w in wordDict:
                # (5)
                if len(w) <= len(s[i:]):
                    #(6)
                    if w == s[ i : (i + len(w))]:
                        dp[i + len(w)] = 1
        # (7)
        return dp[-1]

今回の問題で配列 dp は、文字列 s の各位置までが分割可能かを記録する配列として使用します。分割できる場合は 1 を記入し、できない場合は(初期値である)0 となります。配列 dp の最後 dp[-1] が 1 であれば、その文字列 s は分割可能である事になります。

それでは、上記のコードを個別に見ていきます。

        # (1)
        dp = [ 0 for _ in range(len(s)+1)]
        dp[0] = 1

ここでは、配列 dp を初期化しています。dp の要素数は文字列の数 +1としており、先頭 dp[0] をダミーとして、1を代入しています。

        # (2)
        for i in range(len(s) + 1):
			      # (3)
            if dp[i] == 0:
                continue

(2)からは、forループで dp を更新していきます。dp の要素数は先述したように、文字列 s の長さ +1 となっています。(3)の if 文では dp[ i ] の値が 0 の場合は、処理を以降の処理を抜けています。これは、文字列 s のこの部分まででは、分割することができない事が確定していますので、以降の処理は不要なためです。

            # (4)
            for w in wordDict:
                # (5)
                if len(w) <= len(s[i:]):
                    #(6)
                    if w == s[ i : (i + len(w))]:
                        dp[i + len(w)] = 1

(4)からは、dp[ i ] = 1となっていますので、文字列 s の以降の要素も調べて行きます。まず forループで、与えられた wordDict 内のすべての単語を調べます。(5)で、各単語の長さが、文字列 s の以降の文字の長さ以下であることを確認しています。さらに (6)で、各単語が文字列 s の以降の文字と一致するかを調べ、一致した場合は dp に 1を書き込みます。その書き込み先は、現在の位置 i に各単語の長さを足した部分です。

        # (7)
        return dp[-1]

最後に配列 dp の最後の要素を返してプログラムを終了します。


221. Maximal Square

(問題)0 と 1で満たされた 2次元の 2値行列が与えられます。1 のみを含む最大の正方形を見つけ、その面積を返しなさい。

(例)Input:  
   1 0 1 0 0
   1 0 1 1 1
   1 1 1 1 1
   1 0 0 1 0

   Output: 4
   説明:以下で、太字で示す部分の正方形が最大の面積を持つものです。
   1 0 1 0 0
   1 0 1 1 1
   1 1 1 1 1
   1 0 0 1 0

(解答例)以下に解答例を示します。

class Solution:
    def maximalSquare(self, matrix: List[List[str]]) -> int:
        # (1)
        H = len(matrix)
        if H == 0:
            return 0
        W = len(matrix[0])
        # (2)
        dp = [ 0 for w in range(W)]
        mymax = 0
        mycandi = 1
        # (3)
        for h in range(H):
            for w in range(W):
                if matrix[h][w] == '0':
                    dp[w] = 0
                else:
                    dp[w] += int(matrix[h][w])
            # (4)
            mycnt = 0
            maxcnt = 0
            for ww in range(W):
                # (5)
                if dp[ww] >= mycandi:
                    mycnt += 1
                    if mycnt > maxcnt:
                        maxcnt = mycnt
                else:
                    mycnt = 0
            # (6)
            if maxcnt >= mycandi :
                mymax = mycandi
                mycandi = mymax + 1
        # (7)
        return (mymax * mymax)

 今回も、63. Unique Paths II の場合のように、2次元配列の探索結果を、1次元配列に記録しながら解いていきます。

それでは、上記のコードを個別に見ていきます。

        # (1)
        H = len(matrix)
        if H == 0:
            return 0
        W = len(matrix[0])

(1)では、与えられる配列 matrix の行数を H として取得します。また、H = 0 となる場合は、空の配列が与えられたことになるので 0 を返してプログラムを終了します。さらに、 matrix の列数を W として取得します。

        # (2)
        dp = [ 0 for w in range(W)]
        mymax = 0
        mycandi = 1

(2)では、配列および各変数の宣言と初期化を行います。配列 dp の要素数は marix の列数と同じにします。さらに mymax は答えとなる値(正確には面積ではなく、辺の値です)を格納する変数で、初期値はとりあえず、何もできないとして 0 を代入します。また、mycandi は 、次の目標として 辺 mymax + 1 の正方形が存在するか?を調べたいので、1 を代入しています。

        # (3)
        for h in range(H):
            for w in range(W):
                if matrix[h][w] == '0':
                    dp[w] = 0
                else:
                    dp[w] += int(matrix[h][w])

(3)からの 2重のforループで matrix を調べていきます。行 h ごとに、’1′ が続けば dp[w] の値は +1 加算され、’0′ が現れた時点で0に戻します。
例題の場合は、次のように処理されます。

 (h) dp[0] dp[1] dp[2] dp[3] dp[4]
0 1 0 1 0 0
1 2 0 2 1 1
2 3 1 3 2 2
3 4 0 0 3 0

各行(h) の処理が終了するごとに、(そこまでで)できる最大の正方形の辺を、以下の処理で、探索していきます。

            # (4)
            mycnt = 0
            maxcnt = 0
            for ww in range(W):
                # (5)
                if dp[ww] >= mycandi:
                    mycnt += 1
                    if mycnt > maxcnt:
                        maxcnt = mycnt
                else:
                    mycnt = 0

(4)では、まず2つの変数、mycnt maxcnt を 0 で初期化します。mycnt は、列方向に mycandi 以上の値が連続して出現している回数を記録するもので、 mycandi 以上の値の連続が途切れた場合は 0 に戻します( (5)の処理 )。そうすると、for ループの処理が終了した時点で mycnt に格納されている値は、例題では dp[4] の時点での連続数です。しかし、今知りたいのは最大の連続数であるため、maxcnt を用意しておき、最大の連続数を記録しておきます。

            # (6)
            if maxcnt >= mycandi :
                mymax = mycandi
                mycandi = mymax + 1

(6) では maxcnt maycandi 以上であれば、目標としていた辺は存在していた事になるので、mycandi に +1 を加算し目標を更新しています。

        # (7)
        return (mymax * mymax)

最後に、求められている答えは面積であったため、 辺 x 辺 の値を返します。


300. Longest Increasing Subsequence

(問題)整数のソートされていない配列が与えられます。最も長い、増加するサブシーケンスの長さを見つけなさい。

(例)Input:  [ 10, 9, 2, 5, 3, 7, 101, 18]

   Output: 4

   説明:最も長い、増加するサブシーケンスは [ 2, 3, 7, 101 ] であるため、長さは4です。

   注意:考えられるサブシーケンスは複数存在する場合がありますが、必要なのは長さを返すことだけです。
      また、 アルゴリズムは  O(n2) の計算量で実行する必要があります。

(解答例)以下に解答例を示します。

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        # (1)
        N = len(nums)
        if N == 0:
            return 0
        # (2)
        dp = [1 for n in range(N)]
        myans = 0
        # (3)
        for i in range(N):
            for j in range(i):
                if nums[j] < nums[i]:
                    dp[i] = max(dp[i], dp[j] + 1)
            myans = max(myans, dp[i])
        # (4)            
        return myans

今回の問題では、2つの forループを使って、配列 dp を更新していきます。

それでは、上記のコードを個別に見ていきます。

        # (1)
        N = len(nums)
        if N == 0:
            return 0

ここでは、与えられる配列 nums の長さを、変数 N として取得しています。よって N = 0 となる場合は、どんなサブシーケンスもつくることはできませんので 0 を返して、プログラムを終了します。

        # (2)
        dp = [1 for n in range(N)]
        myans = 0

(2)では、配列 dp を初期化しています。dp の要素数は N とし、また初期値は 1 にしています。これは、数値が1つ存在するのならばサブシーケンスは少なくとも1つはつくる事ができるからです。また変数 myans は最終的な答えを格納するための変数です。

        # (3)
        for i in range(N):
            for j in range(i):
                if nums[j] < nums[i]:
                    dp[i] = max(dp[i], dp[j] + 1)
            myans = max(myans, dp[i])

(3)からは、2重の forループで dp を更新していきます。外側(変数 i を使用)の forループで、nums を順にみていき、内側(変数 j を使用)の forループで 0(先頭)から i までをみています。そうして、nums[ j ] < nums[ i ] となった場合、つまり増加するサブシーケンスをつくれる場合に dp を更新します。更新の方法は、基本的には dp[ j ] + 1 です。これは、現時点で nums[ i ] よりも値が小さい nums[ j ] が存在しているの、nums[ j ] まで作成可能なサブシーケンスの数 dp[ j ] に nums[ i ] が加わるためです。そして今、得たいのは最長のサブシーケンスですので、絶えず最大値が記録されるように、max(dp[ i ], dp[ j ] + 1)としています。また、後から max(dp) として、答えを取得することもできますが、計算量を減らすために内側の forループを抜けた時点で、myans を最大値で更新しておきます。

        # (4)            
        return myans

最後に, myans を返してプログラムを終了します。

LeetCodeに挑戦:DP系問題 Medium (その2)” に対して1件のコメントがあります。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です