Отладка самой длинной возрастающей подпоследовательности - Ruby

Я работаю над следующей проблемой через Leetcode:

Учитывая несортированный массив целых чисел, найдите длину самой длинной увеличивающейся подпоследовательности.

Например, учитывая [10, 9, 2, 5, 3, 7, 101, 18], самая длинная возрастающая подпоследовательность равна [2, 3, 7, 101], поэтому длина равна 4. Обратите внимание, что может быть больше, чем одна комбинация LIS, вам нужно только вернуть длину.

Ваш алгоритм должен работать в сложности O(n2).

Продолжение: Не могли бы вы улучшить его до O(n log n) сложности времени?

Попытка реализовать первое решение методом грубой силы, которое, по сути, является рекурсивным способом генерирования каждой увеличивающейся подпоследовательности, затем захватывает самую большую подпоследовательность. После этого я буду использовать динамическое программирование для оптимизации. Я немного запутался в реализации грубой силы - вот мой код:

def length_of_lis(nums)
    1 + max_len(nums, 1, nums.first)
end

def max_len(nums, index, prev_number)
    current_num = nums[index]
    max = 0 
    return 0 if index >= nums.length     
    if current_num > prev_number
        max = [1 + max_len(nums, index + 1, current_num), max].max
    else
        max = [max_len(nums, index + 1, prev_number), max].max
    end 
    max = [1 + max_len(nums, index + 1, current_num), max].max

    max
end

Теперь я знаю, что это, очевидно, неправильно, но я рассчитывал на то, что на каждом номере происходит пара вещей. Либо 1) ему передается функция от предыдущего номера, и если этот текущий номер больше предыдущего номера, то продолжите эту функцию для LIS. 2) Создайте новую подпоследовательность LIS под текущим номером.

Как вы можете сказать, это создает две одинаковые строки, и я не уверен, как структурировать код так, чтобы две отдельные вещи происходили, а переменная max содержала окончательное значение. Любые идеи о том, как настроить этот код соответственно?

1 ответ

Я использовал динамическое программирование, чтобы получить оптимальное решение.

Код

def construct_sequence(longest, max_i)
  a = []
  loop do
    a << max_i
    max_i = longest[max_i][:next]
    return a if max_i.nil?
  end
end      

def longest_increasing_sequence(arr)
  longest = Array.new(arr.size)
  longest[arr.size-1] = { length: 1, next: nil }
  (arr.size-2).downto(0) do |i|
    best_seq = { length: 1, next: nil }
    (i+1).upto(arr.size-1) do |j| 
      next if arr[j] <= arr[i]
      h = longest[j]      
      best_seq = { length: 1 + h[:length], next: j } if h[:length] >= best_seq[:length]
    end
    longest[i] = best_seq
  end
  max_i = (0..arr.size-1).max_by { |i| longest[i][:length] }
  construct_sequence(longest, max_i)
end

пример

arr = [10, 9, 2, 5, 3, 7, 101, 18]
a = longest_increasing_sequence(arr)
  #=> [2, 3, 5, 6] 
a.each_with_object({}) { |n,h| h[n] = arr[n] }
  #=> {2=>2, 3=>5, 5=>7, 6=>101}

Для второго примера я построил следующий 20-элементный псевдослучайный массив.

arr = (1..99).to_a.sample(20)
  #=> [80, 75, 13, 12, 85, 16, 41, 89, 93, 56, 74, 18, 37, 24, 27, 63, 47, 83, 25, 44]

longest_increasing_sequence возвращает массив индексов arr которые составляют самую длинную возрастающую последовательность.

a = longest_increasing_sequence(arr)
  #=> [2, 5, 11, 13, 14, 15, 17]
a.each_with_object({}) { |n,h| h[n] = arr[n] }
  #=> {2=>13, 5=>16, 11=>18, 13=>24, 14=>27, 15=>63, 17=>83}

объяснение

Существует один этап для каждого элемента массива. Переменная состояния - это индекс массива, где начинается самая длинная возрастающая последовательность ("LIS"). Мы начинаем с последнего элемента массива, который arr[19] в приведенном выше примере. Если там начинается последовательность возрастания ("IS"), она также заканчивается там. Длина этой последовательности 1,

Затем мы возвращаемся к этапу 18. Есть две возможности: LS, начинающийся на этом этапе, имеет длину 1 или продолжается на этапе 19 (если последовательность увеличивается), и в этом случае она имеет длину 2,

В целом, если LIS начинается с индекса i, это может закончиться или продолжиться, с j являясь следующим индексом в ЛИС, где i+1 <= j <= arr.size-1 а также arr[i] < arr[j], Для любого такого j мы уже вычислили LIS, если последовательность начинается с индекса j Итак, мы знаем, что из индекса j, i а также j использовать одну и ту же LIS, если следующий элемент LIS начинается с i является j, Поэтому, чтобы получить LIS, начиная с i мы берем размер самой большой LIS для j между i+1 а также arr.size-1 для которого arr[i] < arr[j] и добавить 1, если нет индексов j для которого arr[i] < arr[j] в этом случае ЛИС для i заканчивается в i,

Решение для динамического программирования основывается на принципе оптимальности, который здесь заключается в том, что если индекс i является членом ЛИС, коллекция индексов j, j > i которые также являются членами этой LIS не зависит от индексов j, j < i которые являются членами этой ЛИС. Другими словами, оптимальный путь вперед от индекса i не зависит от того, как мы туда попали.

Чтобы показать детали расчетов, я добавил несколько операторов put longest_increasing_sequence:

def longest_increasing_sequence(arr)
  longest = Array.new(arr.size)
  longest[arr.size-1] = { length: 1, next: nil }
  puts "longest[#{arr.size-1}]=#{longest[arr.size-1]}"
  (arr.size-2).downto(0) do |i|
    best_seq = { length: 1, next: nil }
    puts "i=#{i}"
    puts "  initial best_seq=#{best_seq}"
    (i+1).upto(arr.size-1) do |j| 
      puts "    j=#{j}, arr[#{i}]=#{arr[i]}, arr[#{j}]=#{arr[j]}"   
      next if arr[j] <= arr[i]
      h = longest[j]      
      puts "    h=#{h}"
      puts "    j=#{j} provides new best_seq=#{{length: 1 + h[:length], next: j }}" if
        h[:length] >= best_seq[:length]
      best_seq = { length: 1 + h[:length], next: j } if h[:length] >= best_seq[:length]
    end
    longest[i] = best_seq
  end
  longest.each_index { |i| puts "i=#{i}: #{longest[i]}" }
  max_i = (0..arr.size-1).max_by { |i| longest[i][:length] }
  construct_sequence(longest, max_i)
end

arr = [60, 29, 56, 46, 37, 57, 28, 44]
longest_increasing_sequence(arr)

longest[7]={:length=>1, :next=>nil}
i=6
  initial best_seq={:length=>1, :next=>nil}
    j=7, arr[6]=28, arr[7]=44
    h={:length=>1, :next=>nil}
    j=7 provides new best_seq={:length=>2, :next=>7}
i=5
  initial best_seq={:length=>1, :next=>nil}
    j=6, arr[5]=57, arr[6]=28
    j=7, arr[5]=57, arr[7]=44

i=4
  initial best_seq={:length=>1, :next=>nil}
    j=5, arr[4]=37, arr[5]=57
    h={:length=>1, :next=>nil}
    j=5 provides new best_seq={:length=>2, :next=>5}
    j=6, arr[4]=37, arr[6]=28
    j=7, arr[4]=37, arr[7]=44
    h={:length=>1, :next=>nil}
i=3
  initial best_seq={:length=>1, :next=>nil}
    j=4, arr[3]=46, arr[4]=37
    j=5, arr[3]=46, arr[5]=57
    h={:length=>1, :next=>nil}
    j=5 provides new best_seq={:length=>2, :next=>5}
    j=6, arr[3]=46, arr[6]=28
    j=7, arr[3]=46, arr[7]=44
i=2
  initial best_seq={:length=>1, :next=>nil}
    j=3, arr[2]=56, arr[3]=46
    j=4, arr[2]=56, arr[4]=37
    j=5, arr[2]=56, arr[5]=57
    h={:length=>1, :next=>nil}
    j=5 provides new best_seq={:length=>2, :next=>5}
    j=6, arr[2]=56, arr[6]=28
    j=7, arr[2]=56, arr[7]=44

i=1
  initial best_seq={:length=>1, :next=>nil}
    j=2, arr[1]=29, arr[2]=56
    h={:length=>2, :next=>5}
    j=2 provides new best_seq={:length=>3, :next=>2}
    j=3, arr[1]=29, arr[3]=46
    h={:length=>2, :next=>5}
    j=4, arr[1]=29, arr[4]=37
    h={:length=>2, :next=>5}
    j=5, arr[1]=29, arr[5]=57
    h={:length=>1, :next=>nil}
    j=6, arr[1]=29, arr[6]=28
    j=7, arr[1]=29, arr[7]=44
    h={:length=>1, :next=>nil}
i=0
  initial best_seq={:length=>1, :next=>nil}
    j=1, arr[0]=60, arr[1]=29
    j=2, arr[0]=60, arr[2]=56
    j=3, arr[0]=60, arr[3]=46
    j=4, arr[0]=60, arr[4]=37
    j=5, arr[0]=60, arr[5]=57
    j=6, arr[0]=60, arr[6]=28
    j=7, arr[0]=60, arr[7]=44
i=0: {:length=>1, :next=>nil}
i=1: {:length=>3, :next=>2}
i=2: {:length=>2, :next=>5}
i=3: {:length=>2, :next=>5}
i=4: {:length=>2, :next=>5}
i=5: {:length=>1, :next=>nil}
i=6: {:length=>2, :next=>7}
i=7: {:length=>1, :next=>nil}
  #=> [1, 2, 5] 
Другие вопросы по тегам