5

A sequence in which the value of elements first decrease and then increase is called V- Sequence. In a valid V-Sequence there should be at least one element in the decreasing and at least one element in the increasing arm.

For example, "5 3 1 9 17 23" is a valid V-Sequence having two elements in the decreasing arm namely 5 and 3, and 3 elements in the increasing arm namely 9, 17 and 23 . But none of the sequence "6 4 2" or "8 10 15" are V-Sequence since "6 4 2" has no element in the increasing part while "8 10 15" has no element in the decreasing part.

Given a sequence of N numbers find its longest (not necessarily contiguous) sub-sequence which is a V-Sequence ?

Is it possible to do this in O(n)/O(logn)/O(n^2) ?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189

2 Answers2

4

The solution is quite similar to the solution of the longest-non-decreasing subsequence. The difference is that now for each element you need to store two values - what is the length of the longest V sequence starting from this element and what is the length of the longest decreasing subsequence starting at this. Please take a look at the solution of the typical non-decreasing subsequence solution and I believe this should be a good enough tip. I believe the best complexity you can achieve is O(n*log(n)), but a solution of complexity O(n^2) is easier to achieve.

Hope this helps.

Ivaylo Strandjev
  • 69,226
  • 18
  • 123
  • 176
0

Here is an implementation in Python based on izomorphius' very helpful hint above. This builds on this implementation of the increasing subsequence problem. It works by, as izomorphius says, keeping track of "the best V's found so far" as well as "the best increasing sequences found so far". Note that extending a V, once it has been identified, is no different from extending a decreasing sequence. Also there has to be a rule to "spawn" new candidate V's from previously found increasing subsequences.

from bisect import bisect_left

def Vsequence(seq):
    """Returns the longest (non-contiguous) subsequence of seq that
    first increases, then decreases (i.e. a "V sequence").

    """
    # head[j] = index in 'seq' of the final member of the best increasing
    # subsequence of length 'j + 1' yet found
    head = [0]
    # head_v[j] = index in 'seq' of the final member of the best
    # V-subsequence yet found
    head_v = []
    # predecessor[j] = linked list of indices of best increasing subsequence
    # ending at seq[j], in reverse order
    predecessor = [-1] * len(seq)
    # similarly, for the best V-subsequence
    predecessor_v = [-1] * len(seq)
    for i in xrange(1, len(seq)):

        ## First: extend existing V's via decreasing sequence algorithm.
        ## Note heads of candidate V's are stored in head_v and that
        ## seq[head_v[]] is a non-increasing sequence
        j = -1  ## "length of best new V formed by modification, -1"
        if len(head_v) > 0:
            j = bisect_left([-seq[head_v[idx]] for idx in xrange(len(head_v))], -seq[i])

            if j == len(head_v):
                head_v.append(i)
            if seq[i] > seq[head_v[j]]:
                head_v[j] = i

        ## Second: detect "new V's" if the next point is lower than the head of the
        ## current best increasing sequence.
        k = -1  ## "length of best new V formed by spawning, -1"
        if len(head) > 1 and seq[i] < seq[head[-1]]:
            k = len(head)

            extend_with(head_v, i, k + 1)

            for idx in range(k,-1,-1):
                if seq[head_v[idx]] > seq[i]: break
                head_v[idx] = i

        ## trace new predecessor path, if found
        if k > j:
            ## It's better to build from an increasing sequence
            predecessor_v[i] = head[-1]
            trace_idx = predecessor_v[i]
            while trace_idx > -1:
                predecessor_v[trace_idx] = predecessor[trace_idx]
                trace_idx=predecessor_v[trace_idx]
        elif j > 0:
            ## It's better to extend an existing V
            predecessor_v[i] = head_v[j - 1]

        ## Find j such that:  seq[head[j - 1]] < seq[i] <= seq[head[j]]
        ## seq[head[j]] is increasing, so use binary search.
        j = bisect_left([seq[head[idx]] for idx in xrange(len(head))], seq[i])

        if j == len(head):
            head.append(i)  ## no way to turn any increasing seq into a V!
        if seq[i] < seq[head[j]]:
            head[j] = i

        if j > 0: predecessor[i] = head[j - 1]

    ## trace subsequence back to output
    result = []
    trace_idx = head_v[-1]
    while (trace_idx >= 0):
        result.append(seq[trace_idx])
        trace_idx = predecessor_v[trace_idx]

    return result[::-1]

Some example output:

>>> l1
[26, 92, 36, 61, 91, 93, 98, 58, 75, 48, 8, 10, 58, 7, 95]
>>> Vsequence(l1)
[26, 36, 61, 91, 93, 98, 75, 48, 10, 7]
>>> 
>>> l2
[20, 66, 53, 4, 52, 30, 21, 67, 16, 48, 99, 90, 30, 85, 34, 60, 15, 30, 61, 4]
>>> Vsequence(l2)
[4, 16, 48, 99, 90, 85, 60, 30, 4]
Community
  • 1
  • 1
gcbenison
  • 11,723
  • 4
  • 44
  • 82