LoginSignup
0
0

More than 1 year has passed since last update.

[Ruby][アルゴリズム問題]回文の文字列

Last updated at Posted at 2023-03-20

頭の体操と勉強にアルゴリズム系の問題を解いてみました。

【問題】

文字列sが与えられた場合、sで最も長い回文部分文字列を返します。

【例】

Input: s = "babad"
Output: "bab"
Note: "aba" is also a valid answer.

自分で考えてみた回答(うまく書けず長くなってしまいました)

難しいポイントは
"aabaa"のような1文字を真ん中に回文となる場合と
"aabbaa"のように、2文字を真ん中に回文となる場合があることでしょうか。

def longest(s)
  # 1文字〜3文字の場合
  return s if s.size <= 1
  if s.size <= 3 && s[0] == s[s.size - 1]
    return s
  elsif s.size <= 3 && (s[0] == s[1] || s[1] == s[s.size - 1])
    return s[0]
  end

  # 4文字以上の場合
  # 文字列を配列に変換する
  arr = s.split("")
  longest_values = []
  arr.each.with_index do |str, i|
    if str == arr[i+1] && longest_values.empty?
      # 2文字の回文
      longest_values = (str*2).chars
    end
    if i > 0 && i != arr.size
      # 配列のi番目を真ん中の値として、左右対象となっているかを比べる場合
      left = arr[0..i-1]
      right = arr[i+1..arr.size-1]

      # 配列のi番目とi+1番目を真ん中の値として、左右対象となっているかを比べる
      left_include_self = arr[0..i]

      values_1 = left_right_symmetry?(left, right, arr, i)
      values_2 = left_right_symmetry?(left_include_self, right, arr, i)

      values = []
      if values_1.size > values_2.size
        values = values_1
      else
        values = values_2
      end

      if !values.empty? && values.size > longest_values.size
        longest_values = values
      end
    end
  end
  return longest_values.join
end

def left_right_symmetry?(left, right, arr, index)
    # 配列の数を少ない方に合わせ、左側はreverseする
    min_number = [left.size, right.size].min
    left_arranged = left.last(min_number).reverse
    right_arranged = right.first(min_number)

    values = []
    # 左右を比較し、対象になっている値をvaluesに加えていく
    min_number.times do |n|
      if left_arranged[n] == right_arranged[n]
        if values.empty? && (left.size + right.size != arr.size)
          values.push(arr[index])
        end
        values.unshift(left_arranged[n])
        values.push(right_arranged[n])
      end
    end
    return values
end

上記のコードで下記のテストは通ったので大丈夫そうです。

  test_patterns = {
    'aabaa' => 'aabaa',
    'aaccbb' => 'aa',
    'aabbcbbaa' => 'aabbcbbaa',
    'abababa' => 'abababa',
    'ab' => 'a',
    'aa' => 'aa',
    'aabbaacd' => 'aabbaa'
  }

もっと良いコード、良い回答あるかな?と思い、chatGPTに聞いてみると…
こんな回答が返ってきました。

def longest_palindrome(s)
  # 特殊な文字列 "$#" を挿入して、偶数長の回文も扱えるようにする
  t = "$#" + s.chars.join("#") + "#"
  
  # tの各文字を中心とする最長回文の半径を格納する配列
  p = Array.new(t.length, 0)
  
  # 右端が最も右にある回文の中心と半径
  center, max_right = 0, 0
  
  # p[i]を求める
  (1...t.length).each do |i|
    # 右端が最も右にある回文がiを含む場合、p[i]の初期値を更新する
    if max_right > i
      mirror = 2 * center - i
      p[i] = [max_right - i, p[mirror]].min
    end
    
    # iを中心とする回文を左右に広げていく
    while t[i + p[i] + 1] == t[i - p[i] - 1]
      p[i] += 1
    end
    
    # iを中心とする回文の右端が最も右にある場合、centerとmax_rightを更新する
    if i + p[i] > max_right
      center = i
      max_right = i + p[i]
    end
  end
  
  # pの最大値とそのインデックスを求める
  max_len = p.max
  center_idx = p.index(max_len)
  
  # s上での最長回文部分文字列を求める
  start_idx = (center_idx - max_len) / 2
  end_idx = start_idx + max_len
  return s[start_idx...end_idx]
end

chatGPTが試したテストは下記

puts longest_palindrome("babad")  # => "bab"
puts longest_palindrome("cbbd")   # => "bb"
puts longest_palindrome("a")      # => "a"
puts longest_palindrome("ac")     # => "a"

chatGPTの考え方おもしろいですね。
文字列の間に"#"などの文字を入れると、たしかに1文字中心での回文と2文字中心での回答どちらも一気に調べられそうです。
(ふと#が含まれている文字列は大丈夫なのかな?と思ったのですが問題なく返ってきました。すごい)

chatGPTに自分のコードを添削してもらってそちらを参考にしてみるのも勉強になります(が、バグっちゃうことあるので注意です)

0
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0