Python
 Computer >> コンピューター >  >> プログラミング >> Python

Pythonで最長の循環増加部分列の長さを求めるプログラム

数値のリスト nums が与えられたとき、最長の増加部分列(LIS)の長さを求めることを考えます。ただし、この問題では部分列がリストの末尾に到達した後、先頭に戻って続くことができる、いわゆる「循環」を許容する点が特徴です。

問題の例

たとえば、入力が次の場合を考えてみましょう。

nums = [6, 5, 8, 2, 3, 4]

このとき出力は 5 になります。これは、最長の増加部分列が [2, 3, 4, 6, 8] となるためです。末尾の要素から先頭へ「折り返して」部分列を構成できる点に注目してください。

解法のアプローチ

循環を扱うために、元のリストを2回連結した配列を作成し、各開始位置から標準的なLISアルゴリズム(二分探索を用いた O(n log n) の手法)を適用します。手順は以下の通りです。

  • 元のリストを2回繰り返した長さ2倍の配列 a を作成する
  • 答えを格納する変数 ans を 0 で初期化する
  • i を 0 から nums のサイズまで繰り返す
    • 空のリスト dp を用意する
    • j を i から (nums のサイズ + i - 1) まで繰り返す
      • n := a[j]
      • k := n を dp に挿入すべき最左のインデックス(二分探索)
      • k が dp のサイズと等しければ、n を dp の末尾に追加
      • そうでなければ、dp[k] を n で置き換え
      • ans := ans と dp のサイズの最大値
  • ans を返す

ここで使っているのは、二分探索(bisect モジュール)を活用した効率的なLIS計算法です。dp リストには「その時点での増加部分列の候補」が昇順に保持され、各要素に対して挿入位置を求めることで全体を O(n log n) で処理できます。

実装例

それでは、実際のPythonコードを見てみましょう。

import bisect

class Solution:
    def solve(self, nums):
        a = nums + nums          # リストを2回連結して循環を表現
        ans = 0
        for i in range(len(nums)):
            dp = []
            for j in range(i, len(nums) + i):
                n = a[j]
                k = bisect.bisect_left(dp, n)
                if k == len(dp):
                    dp.append(n)   # dp の末尾に追加
                else:
                    dp[k] = n      # 既存の値を置き換え
            ans = max(ans, len(dp))
        return ans

ob = Solution()
nums = [4, 5, 8, 2, 3, 4]
print(ob.solve(nums))

入力

[4, 5, 8, 2, 3, 4]

出力

5

計算量について

このアルゴリズムの時間計算量は O(n² log n) です。開始位置ごとに O(n log n) のLIS計算を行うためです。空間計算量は O(n) となります。なお、リストを2倍に伸ばしていますが、内側のループは len(nums) 個の要素しか走査しないため、単純な全探索よりも効率的に動作します。

まとめ

循環増加部分列の問題は、「リストの連結による循環の表現」と「二分探索を使ったLISの高速化」を組み合わせることで解決できます。競技プログラミングやアルゴリズム学習において、LISの応用問題として非常に良い練習になるテーマです。

  1. Pythonで最長アナグラム部分列の長さを求めるプログラム

    問題の概要小文字のみで構成された2つの文字列 S と T が与えられたとき、「最も長いアナグラム部分列」の長さを求めます。ここでアナグラム部分列とは、両方の文字列に共通して含まれる文字を組み合わせて作れる、同じ文字構成を持つ部分列のことです。例えば、S = helloworld、T = hellorld の場合、答えは 8 になります。これは、両方の文字列で共有できる文字(h ×1、e ×1、l ×3、o ×1、r ×1、d ×1)の合計が8文字であるためです。解法のアプローチこの問題は、各文字列における文字の出現回数を数え、その最小値を合計することで効率的に解けます。手順は以下の通りです。文

  2. Pythonで最長のバランス括弧部分列の長さを求めるプログラム

    問題概要 文字列 s が与えられます。この文字列には括弧「(」と「)」が含まれており、その中からバランスの取れた(対応関係が成立している)括弧の部分列として最も長いものを見つけ、その長さを返すことが目標です。 たとえば、入力が s = ())(()( の場合、出力は 4 になります。「(」と「)」を選び抜いて ()() というバランスの取れた部分列を作れるためです。 解法のアプローチ この問題は、文字列を後ろから走査することで線形時間で解けます。閉じ括弧を先に確保しておき、開き括弧が出てきたときに対を成立させるという発想です。手順は以下の通りです。 結果を格納する変数 res を 0 で初