1

My task is to solve alternating sub string problem with a recursive dynamic programming approach:

Consider a sequence A = a1, a2, a3, ... an of integers. A subsequence B of a is a sequence B = b1, b2, .... ,bn which is created from A by removing some elements but by keeping the order. Given an integer sequence A, the goal is to compute anb alternating subsequence B, i.e. a sequence b1, ... bn such that for all i in {2, 3, ... , m-1}, if b{i-1} < b{i} then b{i} > b{i+1} and if b{i-1} > b{i} then b{i} < b{i+1}

So far I need to check on every recursive step if I want to take the element and look for the next alternating number or if I simply take the next number and start with both possibles of alternation.

s index from left e end ( len(Array)) A array g(A,s) a function which get next greater or smaller integer.

my recursive formula is: V(A, s, e) = max( V(A, g(A,s),e), V(A, s+1, e) ) +1

V(A, g(A,s),e) takes the element and continues with next alternating one

V(A, s+1, e) leaves the element and start new sequence at next element

assuming that my implementation and approach is correct I suggest the runntime to O(n^2) since we need to know every combination ones.

Without the mesmerization part it would be O(2^n) like a binary trees leaf amount.

Is this analysis correct? Is might be just correct for the formula but not for the code...

the function getsmaller and getbigger are 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)))
2
  • The question and task description are unclear. Commented Aug 12, 2019 at 2:51
  • added the problem set Commented Aug 12, 2019 at 8:11

1 Answer 1

1

Let's consider a sequence going left from A[i], with direction up first.

First, there could not be a higher element, A[j] to the left of A[i] that ends a longer sequence, because if there were, we could always switch that element with A[i] and end up with an up-first sequence of the same length.

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

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

Secondly, there could not be a lower element, A[j] to the left, ending a longer up-first sequence, and an element in between, A[k], that's higher than A[i], because then we could just add both A[i] and the higher element and get a sequence longer by two.

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

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

So, looking left, the longest up-first sequence ending at A[i] is either (1) the same length or longer than the sequence ending at the next higher element to the left, or (2) the same length as the sequence ending at the lowest element of any contiguous, monotonically increasing subarray that reaches A[i].

Now, consider an element, A[r], the first higher to the right of A[i], for which we would like to find the longest down-first sequence ending at it. As we've shown, elements to the left of A[i] that end an up-first sequence and are either higher or lower than A[i] can already be accounted for when calculating a result for A[i], therefore it remains the only cell of interest for calculating the longest down-first sequence ending at A[r] (looking to the left). This points to an O(n) dynamic program.

JavaScript code:

// 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('')
}

Update

Heh, there's a simpler O(n) recurrence here: https://www.geeksforgeeks.org/longest-alternating-subsequence/

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.