9
\$\begingroup\$

Solve this problem in the fewest number of bytes of code possible.

We have a data variable that contains user input data. Data is a list of integers. Write the code that finds the longest bitonic subarray in the data list.

A bitonic subarray is a sub-list that first increases and then decreases (or always increases or always decreases).

enter image description here

Example:

[1,2,5,5,9,9,3,2]
[1,2,3]
[-5,3,2,0,-4]

Constraints and additional rules: If there are several longest bitonic subarrays in the array, then return the first one.

Test cases:

[1] => [1]
[-1,2,1,3,1,4,1,5,1] => [-1,2,1]
[3,2,3,4,5,3,2,1,5,2,3] => [2,3,4,5,3,2,1]  
[1,2,3,2,3,4,5,3,2,1,5,2,3] => [2,3,4,5,3,2,1]
[1,2,5,6,3,2,1,0,6,3,2,1,0] => [1,2,5,6,3,2,1,0]
[3,3,2,2,3,3,4,4,4,5,5,5,5,3,3,2,2,1,1,5,5,2,3] => [2,2,3,3,4,4,4,5,5,5,5,3,3,2,2,1,1]
[1,2,3,4,5] => [1,2,3,4,5]
[5,4,3,2,1] => [5,4,3,2,1]

P.S.: I really would like to see a one-line C# answer

\$\endgroup\$
7
  • 2
    \$\begingroup\$ Are the input-integers guaranteed to be non-negative? If not, please add some test cases that contain negative integers. \$\endgroup\$ Commented yesterday
  • \$\begingroup\$ Maybe other answers such as 1,3,1 is also valid for second testcase? \$\endgroup\$
    – tsh
    Commented yesterday
  • \$\begingroup\$ @tsh "If there are several longest bitonic subarrays in the array, then return the first one." \$\endgroup\$ Commented yesterday
  • 1
    \$\begingroup\$ Suggested test case: [1,2,2,1,5,5,5,5,5,1,3,3,3,1,4,4,4,4,1] => [5,5,5,5,5,1,3,3,3]. Currently, the only test case with equal runs lets you greedily assume that they're "going in the direction of" the last change between runs. One solution I was sketching in my head fails this, and it seems that tsh's Python solution does as well (after increasing the recursion limit in the header). \$\endgroup\$ Commented yesterday
  • 1
    \$\begingroup\$ @UnrelatedString Are you sure your output is valid? [5,1,3] is decreasing and then increasing, which isn't a valid bitonic sub-array. If the 1 in the middle would be a 7 the output would be [1,5,5,5,5,5,7,3,3,3,1] instead. For which my answer seems to fail unfortunately, since I output [1,5,5,5,5,5,7,3] instead.. :( Will fix it. \$\endgroup\$ Commented yesterday

9 Answers 9

4
\$\begingroup\$

05AB1E, 15 bytes

ŒRéR.ΔÔ¬š¥dÔJR!

(Don't) try it online (it's too slow to even handle the example test case).
Verify all test cases with a small addition of 3.£ to speed things up substantially.

Explanation:

Œ               # Get all sub-lists of the (implicit) input-list
 R              # Reverse this list of sub-lists †
  é             # Then sort it from shortest to longest
   R            # Reverse that again so it's from longest to shortest
    .Δ          # Pop and find the first/longest sub-list that's truthy for:
      Ô         #  Connected-uniquify the sub-lists ††
       ¬š       #  Prepend its first item
                #  (edge case for inputs of length 1)
         ¥      #  Pop and get the forward-differences
          d     #  Check for each if they're non-negative (>=0)
           Ô    #  Connected-uniquify this list of checks
            J   #  Join them together
             R  #  Reverse it
              ! #  Factorial †††
                #  (only 1 is truthy in 05AB1E, so this will only be truthy if the
                #  `ÔJR` was "0", "1", or "01" ††††, and falsey otherwise)
  • † The R before the éR is to output the first instead of last longest bitonic sub-list if there are multiple valid ones of the same length.
  • †† The Ô is so we won't have any equal adjacent values for our checks, except for the one added by ¬š.
  • ††† ! is the main bottleneck for why it's so slow, since the numbers can become very large for invalid sub-lists that go up and down a bunch of times. The 3.£ in the test suite link will only keep the last three digits after the JR, so we won't have to do the factorial on very large binary-strings.
  • †††† 0 are decreasing lists; 1 are increasing lists; 01 are first increasing and then decreasing lists.
\$\endgroup\$
1
  • \$\begingroup\$ A wonderful solution. I'm rewriting it in C#. It turned out to be quite long. 590 characters, but in one line. Thank you very much. \$\endgroup\$
    – Pavel
    Commented yesterday
4
\$\begingroup\$

Python 2, 87 bytes

def f(a,*p):i=iter(map(cmp,a[1:],a));return a*((-1in i)>(1in i))or f(*p+(a[:-1],a[1:]))

Try it online!

Notice that this answer is in Python 2 instead of Python 3. They have several differences:

  • cmp is available in Python 2, but not in Python 3. Using cmp, it return -1, 0, 1 based on its two operand. When both operand are int, it compare them, and return -1 for less than, 1 for greater than. When one is int while the other is None, None is considered less than int.
  • map is different in Python 2 and Python 3. Use map(lambda *p:p, [2], [1, 2]) in Python 2, you will get [(2, 1), (None, 2)]. However, list(map(lambda *p:p, [2], [1, 2])) in Python 3 returns [(2, 1)]. Python 2 will padding extra None's at end of the shorter one.
  • Then, map(cmp, a[1:], a) will return an array that compare each number with its next one, and always have an extra -1 at the end of the return value. Since there is always a -1, an output is valid iff it contains no 1 after first -1 occurred. Convert it to iter and -1 in i will consume the iter until first -1 find (which will always be True as discussed above). And we try to find 1 in i. Notice that here i is the part followed by -1, not the whole compare result. The array a is valid if no 1 found in the following part. So 1 in i should be False. So (-1 in i) > (1 in i) could be used to test if given array is valid.
  • If the given array is valid, a*(...)(...) returns a, and otherwise it return something falsy, and fallback to following searching. f(*p+(a[:-1],a[1:])) loop following possible substrings first by length, then by first occurrence. Sadly, you cannot use f(*a,a[:-1],a[1:]) in Python 2, which is valid in Python 3.
\$\endgroup\$
4
\$\begingroup\$

JavaScript (Node.js), 92 bytes

x=>(g=i=>(e=x.slice(i,n+i||1/0)).some(t=u=c=>t?u>(u=c)&&++t:u<(u=c))?g(n+i?i+1:!--n):e)(n=0)

Try it online!

\$\endgroup\$
3
\$\begingroup\$

R, 150 bytes

\(x)`if`(length(x)-1,{y=rle(x)
p=rle(cumsum(c(T,diff(diff(y$v)>0))>0))
q=which.max(p$l)
r=cumsum(p$l)[q]+1
inverse.rle(lapply(y,`[`,(r-p$l[q]):r))},x)

Attempt This Online!

Two things I couldn't figure out (and would love if someone has ideas for):

  • It failed for the first test case, so I added 20 bytes to handle the case where x is length 1. Is there a way to avoid having to handle this case specially?
  • It failed when there were repeated values at the end of the answer, which I solved by run-length encoding the input and converting back at the end. But converting back takes 27 bytes (on top of the 9 to encode). Is there a shorter way to un-encode it?

Commented:

\(x) `if`( # if statement to handle case where length(x) == 1
  length(x)-1, # F if length 1, T otherwise
  {
    # run-length encoding to avoid problems with repeated values
    y = rle(x) 
    # get lengths of bitonic stretches
    p = rle(
      # give a unique number to each potential bitonic stretch
      # i.e., increment group number whenever it starts increasing
      cumsum(c(T, diff(
        diff(y$v) > 0
      )) > 0)
    )
    # index of longest bitonic stretch in p
    q = which.max(p$l)
    # index of end of longest bitonic stretch in y
    r = cumsum(p$l)[q] + 1
    # inverse of run-length encoding
    inverse.rle(
      # lapply is needed to subset both the $length and $values of y
      lapply(y, `[`, 
        # indices of longest bitonic subarray in y
        (r-p$l[q]):r
      )
    )
  },
  x
)
\$\endgroup\$
2
\$\begingroup\$

Charcoal, 50 bytes

≔⟦⟧θFA«⊞θιW№⭆EΦθμ⁻λ§θμ⎇λ‹λ⁰ω10≔Φθμθ¿›LθLυ≔⮌⮌θυ»⭆¹υ

Try it online! Link is to verbose version of code. Explanation:

≔⟦⟧θFA«

Start looping over the input values.

⊞θι

Collect the next value.

W№⭆EΦθμ⁻λ§θμ⎇λ‹λ⁰ω10

While the subarray contains a decreasing pair followed by an increasing pair...

≔Φθμθ

... remove the first element from the subarray.

¿›LθLυ

If this subarray sets a new length record, then...

≔⮌⮌θυ

... remember a clone of the subarray.

»⭆¹υ

Pretty-print the first longest valid subarray.

Incorrect 55 byte version which allows both decreasing and increasing as well as increasing and decreasing, because I misread the question:

≔⟦⟧θFA«⊞θιW⬤⪪I⊕φ²№⭆EΦθξ⁻ν§θξ⎇ν‹ν⁰ωλ≔Φθμθ¿›LθLυ≔⮌⮌θυ»⭆¹υ

Try it online! Link is to verbose version of code. Explanation: As above but allows either an increasing pair followed by a decreasing pair or a decreasing pair followed by an increasing pair but not both.

\$\endgroup\$
1
\$\begingroup\$

JavaScript (ES6), 101 bytes

a=>(g=n=>a.some((m,i)=>!(b=a.slice(i,i+n)).some(p=v=>(m=p<m?m:p)>p&p<(p=v)))/b[--n]?b:g(n))(a.length)

Try it online!

\$\endgroup\$
0
\$\begingroup\$

Jelly, 19 18 bytes

IṠo@\IŻ=2ÄkµÐƤẈÞṪḢ

Try it online!

-1 by remembering that by the time I need to bandaid the sort order I'm just in truthiness land

Probably loses to something that brute forces over all sublists directly, but porting Kevin Cruijssen's 05ab1e solution doesn't strike me as viable due to a number of substantial differences in the builtin set.

           µÐƤ        For every suffix of the input:
 Ṡ                    Take the signs of
I                     the forward deltas,
    \                 scan by
  o@                  right ? right : left,
     I                take the forward deltas of that,
      Ż               prepend 0,
          k           and partition the original suffix after the positions of
       =2             twos in that--i.e.,
IṠ    Ż               before delta signs which
     I =2             jump from -1 to 1
  o@\                 compared to the last nonzero delta sign.
         Äk           (Also partition everything after that into singletons.)
               Þ      Sort lexicographically ascending by
              Ẉ       the lengths of each partition,
                ṪḢ    and take the first partition from the last element.
\$\endgroup\$
0
\$\begingroup\$

Jelly,  15  14 bytes

ẆṚLÞ_ƝṠ¹ƇṢƑƲƇṪ

A monadic Link that accepts a list of integers and yields the first, longest bitonic sublist.

Try it online!

How?

ẆṚLÞ_ƝṠ¹ƇṢƑƲƇṪ - Link: list of integers, I
Ẇ              - all non-empty contiguous sublists of {I}
                   shortest to longest, then earliest lo latest
                   e.g. [3,1,2] -> [[3],[1],[2],[3,1],[1,2],[3,1,2]]
 Ṛ             - reverse {that}
  LÞ           - sort {that} by length
                   now shortest to longest then latest to earliest
                   e.g. [3,1,2] -> [[2],[1],[3],[1,2],[3,1],[3,1,2]]
            Ƈ  - keep those for which:
           Ʋ   -   last four links as a monad - f(Sublist):
     Ɲ         -     for neighbouring pairs of {Sublist}:
    _          -       {Left} subtract {Right}
      Ṡ        -     signs of {that}
        Ƈ      -     keep those for which:
       ¹       -       no-op (i.e. keep non-zero values)
          Ƒ    -     is {that} invariant under?:
         Ṣ     -       sort
             Ṫ - tail
\$\endgroup\$
0
\$\begingroup\$

Uiua, 35 bytes(SBCS)

⇌⊡⊢⍖⊸⍚⧻▽⊸≡◇(<2/+⧈≠▽⊸⌵±⧈-)/◇⊂⧅(□⧅□⇌)

Try it in the pad!

Takes in an array of numbers, outputs the longest bitonic subarray, □ boxed, because unboxing it with ◇ content would be one character longer.

\$\endgroup\$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.