The Bear's Den

Enter at your own risk

The Last Peak

Task 1: Peak Positions

Submitted by: Mohammad Sajid Anwar


You are given an array of integers, @ints.

Find all the peaks in the array, a peak is an element that is strictly greater than its left and right neighbours. Return the indices of all such peak positions.

Example 1

Input: @ints = (1, 3, 2)
Output: (1)

Example 2

Input: @ints = (2, 4, 6, 5, 3)
Output: (2)

Example 3

Input: @ints = (1, 2, 3, 2, 4, 1)
Output: (2, 4)

Example 4

Input: @ints = (5, 3, 1)
Output: (0)

Example 5

Input: @ints = (1, 5, 1, 5, 1, 5, 1)
Output: (1, 3, 5)

Solution

Surrounding the given numbers with \(-\infty\) turns the first and the last element into inner elements that have a smaller border neighbor. Allowing floating point numbers using a precision of 1e-6.

For all inner elements \(x_i\) find the signs \(s_i^- = \operatorname{sgn}(x_i - x_{i-1})\) and \(s^+_i = \operatorname{sgn}(x_{i+1} - x_i)\) of the differences between the element and its predecessor and successor. There is a peak at a position \(i\) if and only if \(s^-_i = 1\) and \(s^+_i = -1\), which is equivalent to \(s^-_i - s^+_i = 2\).

use strict;
use warnings;
use PDL;
use PDL::NiceSlice;

sub peak_positions {
    my $i = pdl -inf, @_, -inf;
    my $s = ($i(1:-1) - $i(0:-2))->mult(1e6)->clip(-1, 1);

    which $s(0:-2) - $s(1:-1) == 2;
}

See the full solution to task 1.

Task 2: Last Visitor

Submitted by: Mohammad Sajid Anwar


You are given an integer array @ints where each element is either a positive integer or -1.

We process the array from left to right while maintaining two lists:

@seen: stores previously seen positive integers (newest at the front)
@ans: stores the answers for each -1

Rules:

If $ints[i] is a positive number -> insert it at the front of @seen
If $ints[i] is -1:
Let $x be how many -1s in a row we’ve seen before this one.

If $x < len(@seen) -> append seen[x] to @ans

Else -> append -1 to @ans

At the end, return @ans.

Example 1

Input: @ints = (5, -1, -1)
Output: (5, -1)

@seen = (5)
First  -1: @ans = (5)
Second -1: @ans = (5, -1)

Example 2

Input: @ints = (3, 7, -1, -1, -1)
Output: (7, 3, -1)

@seen = (3, 7)
First  -1: @ans = (7)
Second -1: @ans = (7, 3)
Third  -1: @ans = (7, 3, -1)

Example 3

Input: @ints = (2, -1, 4, -1, -1)
Output: (2, 4, 2)

Example 4

Input: @ints = (10, 20, -1, 30, -1, -1)
Output: (20, 30, 20)

Example 5

Input: @ints = (-1, -1, 5, -1)
Output: (-1, -1, 5)

Solution

\[p_k = \begin{cases} 1& \text{if } I_k \ge 0 \\ 0& \text{otherwise} \end{cases}\] \[l_k = \sum_{i=0}^k p_i\] \[x_k = \begin{cases} x_{k - 1} + 1& \text{ if } k \gt 0 \text{ and } p_k = p_{k - 1} \\ 0& \text{otherwise} \end{cases}\] \[o_k = \sum_{i = k + 1}^{n - 1} p_i = \sum_{i = 0}^{n - 1} p_i - \sum_{i = 0}^k p_i = l_{n - 1} - l_k\]

Putting everything together results in the formula:

\[a_i = s_{y_{m_i}}\]

Translated to PDL this could be written as:

use strict;
use warnings;
use PDL;
use PDL::NiceSlice;

sub last_visitor {
    my $ints = long @_;
    my $p    = $ints >= 0;
    my $seen = append $ints(-1:0)->where_both($p(-1:0));
    my $len  = $p->cumusumover;

    $seen($p(*1)->enumvec + $len(-1) - $len)->(!$p;?);
}

Note that all non-negative numbers including zero are inserted into the seen array. Negative numbers other than \(-1\) may be used, too. These will then be appended to the answer array instead of \(-1\) according to their reversed position in the list.

See the full solution to task 2.


If you have a question about this post or if you like to comment on it, feel free to open an issue in my github repository.