чередующаяся подстрока с использованием (рекурсивного) динамического программирования

Моя задача - решить проблему чередующейся подстроки с помощью рекурсивного динамического программирования:

Рассмотрите последовательность A = a1, a2, a3, ... an целых чисел. Подпоследовательность B последовательности a — это последовательность B = b1, b2, ....,bn, которая создается из A путем удаления некоторых элементов, но с сохранением порядка. Для заданной целочисленной последовательности A цель состоит в том, чтобы вычислить b чередующейся подпоследовательности B, т. е. последовательности b1, ... bn такой, что для всех i из {2, 3, ..., m-1}, если b{i- 1} ‹ b{i}, тогда b{i} > b{i+1} и если b{i-1} > b{i}, то b{i} ‹ b{i+1}

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

s индекс слева e конец ( len(Array)) Массив g(A,s) функция, которая получает следующее большее или меньшее целое число.

моя рекурсивная формула: V(A, s, e) = max( V(A, g(A,s),e), V(A, s+1, e)) +1

V(A, g(A,s),e) берет элемент и переходит к следующему чередующемуся элементу

V(A, s+1, e) покидает элемент и начинает новую последовательность со следующего элемента

предполагая, что моя реализация и подход верны, я предлагаю время выполнения O (n ^ 2), поскольку нам нужно знать каждую комбинацию.

Без части месмеризации это было бы O (2 ^ n), как количество листьев бинарных деревьев.

Верен ли этот анализ? Возможно, это правильно для формулы, но не для кода...

функции getsmaller и getbigger равны g(A,s)

A = [5,6,5,5,5,7,5,5,5,87,5]

s = 0
e = len(A)

memo_want_small = [-1] * len(A)
memo_want_bigger = [-1] * len(A)


def getsmaller(A, s):
    erg = 0
    for i in range(s, len(A)):
        if A[i] < A[s]:
            if i is not None:
                 return i   
    return -1

def getbigger(A, s):
    for i in range(s, len(A)):
        if A[i] > A[s]:
            if i is not None:
                return i      
    return -1


def wantsmall(A, s, e):
    if s == -1: # no more alternating element
        return 0
    if s == e: # base case
        return 0
    if memo_want_small[s] is not -1:
        return memo_want_small[s]

    return_V = max(wantbigger(A, getbigger(A, s), e)  , alt(A, s+1, e)) + 1
    memo_want_small[s] = return_V
    return return_V

def wantbigger(A, s, e):
    if s == -1: # no more alternating element
        return 0
    if s == e: # base case
        return 0
    if memo_want_bigger[s] is not -1:
        return memo_want_bigger[s]

    return_V = max(wantsmall(A, getsmaller(A, s), e)  , alt(A, s+1, e)) + 1
    memo_want_bigger[s] = return_V
    return return_V

def alt(A, s, e):
    if s == e: # base case
        return 0
    return max(wantsmall(A, getsmaller(A, s), e), wantbigger(A, getbigger(A, s), e)) 



print("solution: " + str(alt(A,s,e)))

person Jeahinator    schedule 10.08.2019    source источник
comment
Вопрос и описание задачи неясны.   -  person גלעד ברקן    schedule 12.08.2019
comment
добавил набор задач   -  person Jeahinator    schedule 12.08.2019


Ответы (1)


Давайте рассмотрим последовательность, идущую налево от A[i], сначала вверх.

Во-первых, не может быть более высокого элемента, A[j] слева от A[i], который заканчивает более длинную последовательность, потому что, если бы он был, мы всегда могли бы поменять местами этот элемент с A[i] и в итоге получить последовательность такой же длины.

* Going left from A[i], up-first

     ↖
       A[j]
            ...  A[i]

Во-вторых, не может быть нижнего элемента, A[j] слева, заканчивающего более длинную последовательность вверх, и промежуточного элемента, A[k], который выше A[i], потому что тогда мы могли бы просто добавить A[i] и более высокий элемент и получить последовательность длиннее на два.

* Going left from A[i], up-first

                A[k]
            ...      ...  A[i]
     ↖
       A[j]

Таким образом, если посмотреть налево, то самая длинная последовательность вверх, заканчивающаяся на A[i], будет либо (1) той же длины или длиннее, чем последовательность, заканчивающаяся на следующем более высоком элементе слева, либо (2) той же длины, что и последовательность, заканчивающаяся на наименьший элемент любого непрерывного монотонно возрастающего подмассива, который достигает A[i].

Теперь рассмотрим элемент A[r], первый выше справа от A[i], для которого мы хотели бы найти самую длинную нисходящую последовательность, заканчивающуюся на нем. Как мы показали, элементы слева от A[i], которые заканчивают первую последовательность и выше или ниже A[i], уже могут быть учтены при вычислении результата для A[i], поэтому она остается единственной интересующей ячейкой для вычисления самая длинная последовательность вниз-сначала, заканчивающаяся на A[r] (если смотреть налево). Это указывает на O(n) динамическую программу.

Код JavaScript:

// Preprocess previous higher and lower elements in O(n)
// Adapted from https://www.geeksforgeeks.org/next-greater-element
function prev(A, higherOrLower) {
  function compare(a, b){
    if (higherOrLower == 'higher')
      return a < b
    else if (higherOrLower == 'lower')
      return a > b
  }
  
  let result = new Array(A.length)
  let stack = [A.length - 1]

  for (let i=A.length-2; i>=0; i--){ 
    if (!stack.length){ 
      stack.push(A[i])
      continue
    }

    while (stack.length && compare(A[ stack[stack.length-1] ], A[i]))
      result[ stack.pop() ] = i

    stack.push(i)
  }

  while (stack.length)
    result[ stack.pop() ] = -1

  return result
}

function longestAlternatingSequence(A){
  let prevHigher = prev(A, 'higher')
  let prevLower = prev(A, 'lower')
  let longestUpFirst = new Array(A.length)
  let longestDownFirst = new Array(A.length)
  let best = 1
  
  longestUpFirst[0] = 1
  longestDownFirst[0] = 1
  
  for (let i=1; i<A.length; i++){
    // Longest up-first
    longestUpFirst[i] = Math.max(
      A[i] >= A[i-1] ? longestUpFirst[i - 1] : -Infinity,
      prevHigher[i] != -1 ? longestUpFirst[ prevHigher[i] ] : -Infinity,
      prevHigher[i] != -1 ? 1 + longestDownFirst[ prevHigher[i] ] : -Infinity,
      1
    )
    
    // Longest down-first
    longestDownFirst[i] = Math.max(
      A[i] <= A[i-1] ? longestDownFirst[i - 1] : -Infinity,
      prevLower[i] != -1 ? longestDownFirst[ prevLower[i] ] : -Infinity,
      prevLower[i] != -1 ? 1 + longestUpFirst[ prevLower[i] ] : -Infinity,
      1
    )

    best = Math.max(best, longestUpFirst[i], longestDownFirst[i])
  }

  console.log(`${ longestUpFirst } (longestUpFirst)`)
  console.log(`${ longestDownFirst } (longestDownFirst)`)
  
  return best
}

var tests = [
  [5,6,5,5,5,7,5,5,5,87,5],
  [1,2,3,4,5,6,7,8],
  new Array(10).fill(null).map(_ => ~~(Math.random()*50))
]

for (let A of tests){
  console.log(JSON.stringify(A))
  console.log(longestAlternatingSequence(A))
  console.log('')
}

Обновлять

Хех, здесь есть более простое повторение O(n): https://www.geeksforgeeks.org/longest-alternating-subsequence/

person גלעד ברקן    schedule 18.08.2019