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

【Python】増加部分と減少部分がそれぞれ異なる2つの配列からなる最長ビトニックシーケンスを求める方法

2つの配列が与えられたとき、次の条件を満たす最長のビトニックシーケンス(バイトニック配列)を求める問題を考えてみましょう。「増加する部分」は1つ目の配列の部分列であり、「減少する部分」は2つ目の配列の部分列である必要があります。

たとえば、入力が A = [2, 6, 3, 5, 4, 6]、B = [9, 7, 5, 8, 4, 3] である場合、出力は [2, 3, 4, 6, 9, 7, 5, 4, 3] となります。

解法のアプローチ

この問題は、最長増加部分列(LIS: Longest Increasing Subsequence)を求めるアルゴリズムを応用することで効率的に解けます。基本的なアイデアは以下のとおりです。

  • 配列Aに対してLISを計算し、その結果をビトニックシーケンスの「増加部分」とします。
  • 配列Bを逆順にしてからLISを計算すると、元のBにおける最長の「減少部分」が得られます。
  • tails_idx 配列と二分探索を使うことで、O(n log n) の時間計算量でLISを求められます。
  • prev_idx 配列に直前の要素のインデックスを記録しておき、後から実際のシーケンスを復元します。

アルゴリズムの手順

1. 二分探索関数 index_ceiling() を定義する

引数として arr、T、left、right、key を受け取ります。T は tails_idx 配列に相当し、key 以上の値となる最小の位置を二分探索で見つけて返します。

  • right - left > 1 の間、次を繰り返します。
    • mid := left + (right - left) // 2
    • arr[T[mid]] >= key なら right := mid、そうでなければ left := mid
  • 最後に right を返します。

2. 最長増加部分列を求める関数 long_inc_seq() を定義する

  • n := A のサイズ
  • tails_idx := サイズ n の配列(0で初期化)
  • prev_idx := サイズ n の配列(-1で初期化)
  • length := 1
  • i を 1 から n-1 まで繰り返します。
    • A[i] < A[tails_idx[0]] の場合:tails_idx[0] := i
    • A[i] > A[tails_idx[length - 1]] の場合:prev_idx[i] := tails_idx[length - 1]、tails_idx[length] := i、length を 1 増やす
    • それ以外の場合:pos := index_ceiling(A, tails_idx, -1, length - 1, A[i]) を求め、prev_idx[i] := tails_idx[pos - 1]、tails_idx[pos] := i とする
  • i := tails_idx[length - 1] とし、i >= 0 の間、answer の末尾に A[i] を追加しながら i := prev_idx[i] で要素を辿ります。

3. メイン処理

  • n1 := A のサイズ、n2 := B のサイズ
  • long_inc_seq(A) を呼び出す
  • answer を反転させる
  • B を反転させる
  • long_inc_seq(B) を呼び出す
  • answer を返す

実装例

理解を深めるために、以下のPythonコードを見てみましょう。

answer = []
def index_ceiling(arr,T, left,right, key):
    while (right - left > 1):
       mid = left + (right - left) // 2;
       if (arr[T[mid]] >= key):
          right = mid
       else:
          left = mid
    return right
def long_inc_seq(A):
    n = len(A)
    tails_idx = [0]*(n)
    prev_idx = [-1]*(n)
    length = 1
    for i in range(1, n):
       if (A[i] < A[tails_idx[0]]):
          tails_idx[0] = i
       elif (A[i] > A[tails_idx[length - 1]]):
          prev_idx[i] = tails_idx[length - 1]
          tails_idx[length] = i
          length += 1
       else:
          pos = index_ceiling(A, tails_idx, -1, length - 1, A[i])
          prev_idx[i] = tails_idx[pos - 1]
          tails_idx[pos] = i
    i = tails_idx[length - 1]
    while(i >= 0):
       answer.append(A[i])
       i = prev_idx[i]
def long_bitonic(A,B):
    n1 = len(A)
    n2 = len(B)
    global answer
    long_inc_seq(A)
    answer = answer[::-1]
    B = B[::-1]
    long_inc_seq(B)
A = [2, 6, 3, 5, 4, 6]
B = [9, 7, 5, 8, 4, 3]
long_bitonic(A,B)
print(answer)

入力

[2, 6, 3, 5, 4, 6], [9, 7, 5, 8, 4, 3]

出力

[2, 3, 4, 6, 9, 7, 5, 4, 3]

まとめ

このアルゴリズムでは、二分探索を用いたLIS計算によって、配列Aからの最長増加部分列と、反転した配列Bからの最長増加部分列(= 元のBにおける最長減少部分列)を組み合わせることで、目的のビトニックシーケンスを O(n log n) の計算量で効率的に求めることができます。

  1. Pythonで2つの異なるBST(二分探索木)から指定した合計値となるペアを検索する方法

    2つの二分探索木(BST)とある合計値が与えられたとき、その合計値に一致するペアを探します。ただし、各ペアの要素は異なるBSTに属している必要があります。例として、sum = 12 が与えられた場合を考えてみましょう。この場合、出力は [(6, 6), (7, 5), (9, 3)] となります。解法のアプローチこの問題は、以下の手順で効率的に解くことができます。各木を中順走査(in-order traversal)して、昇順にソートされたリストを作成します。1つ目のリストは先頭(最小値)から、2つ目のリストは末尾(最大値)から両端ポインタ方式で走査します。2つの要素の合計が目標値と等しければ

  2. Pythonで2つのソート済み配列から最も近いペアを見つける方法

    この記事では、昇順にソートされた2つの配列から「目標値に最も近い合計を持つペア」を見つける問題と、その効率的な解法について詳しく解説します。問題文問題: ソート済みの2つの配列と目標値 x が与えられます。各配列から1つずつ要素を選んで作るペアのうち、その合計が x に最も近くなる組み合わせを見つけてください。解き方のポイント:二ポインタ法すべてのペアを総当たりで調べると計算量は O(m×n) になりますが、配列がソート済みであることを活かせば、二ポインタ法によって O(m+n) まで高速化できます。手順は以下の通りです。片方の配列は先頭から、もう片方の配列は末尾から走査を開始します。現在のペ