Это линеаризованное дерево? (Издание в ширину)

11

Фон

Немеченое дерево может выглядеть так:

   o
 / | \
o  o  o
|    / \
o   o   o

Чтобы линеаризовать это дерево, мы сначала помечаем каждый узел oчислом его дочерних узлов:

   3
 / | \
1  0  2
|    / \
0   0   0

а затем запишите числа в списке в порядке дыхания, означая строку за строкой и слева направо:

[3, 1, 0, 2, 0, 0, 0]

Это уникальное и однозначное представление дерева выше, означающее, что никакие два разных чистых дерева не будут иметь одинаковые линеаризации и что мы можем восстановить исходное дерево из списка.

Хотя каждое дерево соответствует определенному целочисленному списку, не каждый целочисленный список представляет допустимое линеаризованное дерево: например [2, 0, 0, 0], не представляет допустимое дерево, если мы пытаемся его линеаризовать, мы получаем это дерево

[2,0,0,0] -> 2 [0,0,0] -> 2 [0,0] -> 2 [0]
            / \          / \        / \
                        0          0   0

но все равно есть 0в списке и некуда его поставить. Точно так [2, 0]же не является допустимой линеаризацией дерева, поскольку у линеаризованного дерева есть пустое дочернее пятно:

  2
 / \
0

задача

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

Входные данные: непустой список неотрицательных целых чисел.

Вывод: истинное значение, если список является линеаризацией дерева, иначе ложное значение.

Testcases

Truthy
[0]
[2, 0, 0]
[1, 1, 1, 1, 1, 0]
[3, 1, 0, 2, 0, 0, 0]
[2, 0, 2, 2, 0, 0, 2, 0, 0]
[3, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0]
[1, 5, 3, 0, 2, 1, 4, 0, 1, 0, 0, 2, 1, 0, 0, 1, 1, 0, 0, 0, 0, 2, 1, 0, 0, 1, 0]
Falsy
[0, 1]
[2, 0]
[2, 0, 0, 0]
[1, 0, 1]
[3, 2, 1, 0]
[2, 0, 0, 2, 0, 0]
[4, 1, 0, 3, 0, 0, 0, 0]
[4, 2, 0, 3, 1, 0, 0, 0, 0, 0]
Laikoni
источник

Ответы:

4

Haskell, 44 байта

f[n:k]=iterate f[k]!!n
f _=[]
g x=f[x]==[[]]

Определяет функцию, gкоторая принимает список и возвращает логическое значение. Посмотрите, как пройти все тестовые случаи .

объяснение

Это основано на том факте, что линеаризации первой глубины и первой ширины производят одинаковые массивы. Смотрите ответы Мартина для деталей; в основном они дают одинаковое арифметическое условие для массива.

Функция fполучает входной список, заключенный в одноэлементный список. Он выталкивает один номер nиз списка, а затем вызывает себя nв оставшемся списке, чтобы обработать дочерние элементы выделенного узла (сначала глубина). Появление пустого списка приводит к тому [], что я использую в качестве состояния ошибки. Функция gпроверяет, что конечным результатом является [[]]уникальное ошибочное состояние без необработанных узлов. Если бы Haskell был напечатан слабо, я мог бы просто использовать 0или что-то в качестве состояния ошибки, и мне не пришлось бы переносить ввод в другой список.

Zgarb
источник
3

Mathematica, 38 байт

Last@#<0<=Min@Most@#&@Accumulate[#-1]&

Основная идея заключается в том, что мы отслеживаем количество узлов для заполнения. Каждый элемент в списке использует один узел и добавляет столько детей, сколько у него есть. Таким образом, каждый элемент iменяет общее количество на i-1. Этот счет выключен на один, потому что должен начинаться с 1(корень), а не 0.

Для того, чтобы дерево было действительным, мы a) никогда не сможем опуститься ниже 0по всему списку, потому что у нас не будет места для размещения текущего узла, и b) нужно закончить -1в конце, иначе у нас останутся неиспользованные узлы.

Мы получаем эту промежуточную сумму оставшихся узлов с помощью Accumulate[#-1](которая вычисляет суммы префиксов входного списка минус один). И затем мы проверяем, что последний элемент и только последний элемент -1с:

Last@#<0<=Min@Most@#

Обратите внимание, что проверки того, что последний элемент является отрицательным, достаточно, поскольку мы никогда не сможем уменьшить более чем на единицу 1, поэтому, если последние значения были бы -2или ниже, было бы невозможно, чтобы остальные минимальные значения были неотрицательными.

Мартин Эндер
источник
2

Сетчатка , 29 байт

\d+
$*
^(?<-1>(1)*,)*$(?(1)!)

Попробуйте онлайн! (Первая строка включает набор тестов, разделенных переводом строки.)

объяснение

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

\d+
$*

Это просто преобразует входные данные в унарные, превращая каждое целое число nв n1.

^(?<-1>(1)*,)*$(?(1)!)

Вот где происходит настоящее волшебство. Это довольно короткое регулярное выражение, которое соответствует только действительным деревьям, но его механика довольно тонкая.

Я использую группы балансировки для отслеживания количества узлов, которые являются способом работы со стеками внутри регулярного выражения.

Во-первых, конечно, такой стек никогда не может иметь отрицательную глубину, поэтому мы не можем получить представление -1в конце, как в решении Mathematica. Тем не менее, мы можем заметить, что конечный элемент ввода должен быть нулем в допустимом стеке (иначе мы не могли бы в итоге -1). Оказывается, что это на самом деле экономит байты , чтобы проверить и что мы в конце концов на ноль и ноль остальных узлов.

Итак, вот разбивка регулярного выражения:

^        e# Anchor the match to the beginning of the string.
(?<-1>   e# Each repetition of this group will match one number. 
         e# We can ignore the <-1> for now.
  (1)*   e#   Match each unary digit of the current number, pushing
         e#   a capture onto stack 1. This increments our total of
         e#   remaining nodes by 1 for each child.
  ,      e#   Match a comma. Note that this requires that there is at
         e#   least one more number in the list.
)*       e# At the end of the repetition the <-1> pops one capture from
         e# the stack. This is the node that the current number itself
         e# takes up.
$        e# Match the end of the string. This requires the input to end
         e# in a zero, because the last thing we matched was a comma.
(?(1)!)  e# Make sure that stack 1 is empty, so that we don't have any
         e# unused nodes.
Мартин Эндер
источник
1

CJam (20 байтов)

{X0@{+\(_0>{\}*}/|!}

Набор онлайн-тестов . Это анонимный блок, который принимает массив в стеке и оставляет 0 или 1 в стеке.

рассечение

В псевдокоде это:

p = 1
q = 0
foreach (i in input):
  q += i
  if (--p <= 0):      # in practice, if (--p == 0):
      p, q = q, p
return (p | q) == 0   # i.e. p == 0 && q == 0

qнакапливает сумму меток узлов на текущем уровне в дереве; pотсчитывает количество узлов, оставшихся на текущем уровне.

Питер Тейлор
источник
{X0@{+\(_{\}&}/|!}Думаю?
Мартин Эндер,
Также кажется, что вы должны быть в состоянии сохранить байт, используя полную программу, чтобы избежать @.
Мартин Эндер,
1

Лабиринт , 17 байт

(
+?
;-)
,_"
@@,!

Попробуйте онлайн!

Истинный вывод есть, -1а ложный вывод пуст. Определить правду и ложь в Лабиринте немного сложно, потому что ветви Лабиринта преимущественно троичные. Однако единственный способ построить условно с надежно двумя ветвями, вы можете сделать только это:

>"F
 T

В этом случае я бы подумал о том, чтобы двигаться вперёд (потому что направление движения не изменилось) и повернуть правдиво. Они соответствуют нулю и отличны от нуля соответственно. Причина, по которой я использую пустой вывод для представления нуля, заключается в том, что если бы вы перенаправили вывод обратно в другую программу Labyrinth, оператор ввода ?фактически выдвинул бы ноль, если вход пустой, поэтому я считаю пустую строку допустимой представление нуля.

объяснение

Алгоритм все тот же, что и в моих ответах Mathematica и Retina, но из-за потока управления Лабиринта, в этот раз он работает немного иначе:

  • Мы здесь не работаем с общим счетчиком. Вместо этого мы а) работаем с отрицательным счетчиком и б) инициализируем его -11изначально, чтобы мы хотели, чтобы счетчик был отрицательным по всему списку, и достигли нуля на последнем входе. Это на самом деле упрощает поток управления здесь.
  • Вместо создания полного списка и проверки, содержит ли он неправильное значение, существует три возможных условия завершения:

    1. Мы нажимаем EOF до того, как достигнем полного нуля. В этом случае остаются неиспользованные узлы, и мы ничего не печатаем.
    2. Мы достигаем нуля, и мы в EOF. В этом случае у нас есть правильное дерево.
    3. Мы достигли нуля и еще не в EOF. В этом случае у нас закончились узлы перед тем, как покрыть все элементы, и мы ничего не печатаем.

Что касается фактического кода, мы начинаем в верхнем левом углу. (Превращает неявный ноль на вершине стека в -1, который будет нарастающим итогом. Затем мы входим в очень узкий основной цикл программы +?-)"_,;+:

+   Add the top two values. This does nothing on the first iteration,
    but gets rid of a helper-zero on subsequent iterations.
?   Read and push integer.
-   Subtract it from running total.
)   Increment.
"   No-op. There is a branch at this point. If the running total is zero,
    we move straight ahead onto the , (see below). Otherwise, the loop continues.
_   Push a zero. This is necessary to prevent the IP from turning south.
,   Read a character. This will either be the next separator (some positive
    number) or EOF (-1). If it's EOF, the IP turns south and the program
    terminates. Otherwise, the loop continues.
;   Discard the separator.

Это оставляет только те случаи, когда мы в какой-то момент сократили промежуточную сумму до нуля. IP перемещается в нижний правый ,угол и читает другой символ, чтобы проверить, достигли ли мы EOF. Если нет, значение будет положительным, и IP поворачивается на запад в направлении, @и программа завершается. Если мы достигли EOF, IP поворачивается на восток и печатает -1с !. После этого IP-адрес пробирается к левому нижнему @краю по слегка странному пути, чтобы завершить программу.

Мартин Эндер
источник
0

Python, 82 байта

lambda l:len(l)==sum(l)+1 and not any(list(l[x]>=len(l)-x for x in range(len(l))))

Нужно больше тестовых случаев.

Sparr
источник
Вам не нужно использовать приведение, listесли это, по крайней мере, Python 2, и, переставив и инвертировав второе условие, вы можете получить его до 70 байт:lambda l:all(l[x]<len(l)-x for x in range(len(l)))and len(l)==sum(l)+1
Kade
^ В связи с этим, вы можете изменить тело all, x<len(l)-y for y,x in enumerate(l)чтобы сохранить еще 2 байта, чтобы получить его до 68.
Kade
Сейчас я не буду заниматься этим, потому что не думаю, что это точное решение. Спасибо за советы.
Спарр
0

Pyth, 13 байт

qxsM._tMQ_1tl

Мы начнем с вычисления текущей заполненности дерева во всех точках входного представления. Эта часть идеи в значительной степени заимствована у Мартина Эндера, так что благодаря ему.sM._tMQ

Получив этот список, мы проверяем, является ли первый индекс, содержащий -1( x..._1), длину ввода минус один ( q...tl(Q)).

Не веришь, что это работает? Попробуй сам!

Стивен Х.
источник