ФТК СЮТ

Решаем задания от Google. Часть 1

21.05.2020Симаков Михаил3 просмотров

Всем привет! ? Думаю, многим известно, что такое Google (если не известно можете загуглить). Помимо того, чтобы обрабатывать миллионы поисковых запросов, Google ещё и устраивает конкурсы. Даже больше, если вы не житель Крыма, Сирии, Северной Кореи (не думаю) и стран эмбарго США, то у вас есть уникальная возможность поучаствовать в этих конкурсах лично. Наверно, стоит сразу предупредить, что эта статья для тех, кто уже примерно разбирается в алгоритмах и тому подобном, так как конкурс и статья будут о программировании.

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

Обратный отсчёт

Сразу пометка: все названия и задания будут в моём переводе. В оригинале конкурс на английском.

Вот смысл первой задачи:

Нам на вход даётся массив, состоящий из N натуральных чисел.

Непрерывный подмассив представляет из себя отсчёт m, если он имеет длину m и содержит целые числа m, m-1, m-2 .... 2, 1 в таком порядке. Например, [3, 2, 1] - это 3-отсчёт.

Наша задача найти, сколько в заданном массиве отсчётов длины M.

Пример входа:

Так как это программа, а не человек, то и данные подаются строго определённым образом.

В первой строке идут 2 числа: длина массива (N) и длина отсчётов для поиска (M). Во второй строке идёт сам массив. Пример входа:

12 3

3 5 7 1 4 2 7 123 5 4 6 4

Давайте решать!

Итак, я надеюсь, что все поняли смысл задачи. Если вы чего-то не поняли, пишите в комментарии или перечитайте предыдущий раздел.

Начнём с самого простого. Нам нужно считать данные и как-то обработать их. Для программирования я буду использовать язык Python, но всю суть буду объяснять.

data = list(map(int, input().split(' ')))

nums = list(map(int, input().split(' ')))

Это кусок кода для считывания данных. Здесь нет ничего сложного. Функцией input(), я считываю то, что ввёл пользователь, то есть получаю в нашем первом примере 12 3. Теперь, нужно как-то разбить эти числа. Для этого я использую функцию.split(' ') она позволит разбить наш текст на 2 части. Между скобками я указываю символ, с помощью которого выполняю разбиение. Эта функция вернёт нам ["12", "3"]. Вроде бы то, что нужно!.. Но нет, есть проблема. Это вовсе не числа, а строки, так как функция .split(' ') разбивает на строки. Поэтому я использую функцию map(), которая берёт этот массив (в Python это называют список) и каждый элемент обрабатывает функцией, указанной первым аргументом. В нашем случае, это функция int. Она превращает строки в числа. Но, к нашему сожалению, у нас получится что-то такое: . Не к добру это... Поэтому мы используем функцию list, которая превращает эти несколько символов в то, что нам нужно: [12, 3].

** Звуки всеобщего ликования. Звук надписи "Конец", а потом звук надписи "Нет! Это только начало" **

Аналогично мы обрабатываем вторую строку и получаем массив: [3, 5, 7, 1, 4, 2, 7, 123, 5, 4, 6, 4].

Теперь можно с этим работать.

Давайте сделаем цикл, который будет перебирать все числа по порядку.

for num in range(data[0]):


Если переводить с языка Python на русский получится:

На самом деле, получится цикл, который переберёт все числа от 0 до N - 1, записывая каждое число в переменную num.

Чем же нам поможет такой цикл? Он поможет тем, что в нём мы можем сделать ещё один цикл! Он будет перебирать следующие за этим числа и проверять, являются ли эти несколько чисел отсчётом. Выглядит этот цикл так:

for w in range(data[1]):

Такой цикл переберёт все числа от 0 до M - 1, записывая их в переменную W.

Это нам позволит проверить являются ли M чисел, следующих за числом, перебором. Для этого мы добавим одно условие и переменную flag. Весь кусок будет выглядеть вот так:

flag = True

for w in range(data[1]):

   if not (w + num < data[0] and nums[num + w] == (data[1] - w)):

      flag = False

      break

Сначала мы записываем в переменную flag значение true. Это пригодится позже. Затем мы перебираем циклом все числа от 0 до N - 1. Затем идёт условие: если число в массиве под номером num + w не равно M - w, то мы делаем пометку, что в этом массиве нет отсчёта, начинающегося с числа под номером num. Небольшое объяснение условия: перебирая числа с помощью первого цикла, мы как бы перебираем возможное место начала отсчёта, а при переборе вторым циклом мы проверяем есть ли отсчёт, начинающийся с этого места в массиве (в любом отсчёте элемент под номером N (нумерация идёт с нуля) является числом L - N, где L - длина отсчёта). Если мы понимаем, что нет отсчёта, начинающегося с этого числа, мы выходим из цикла с помощью команды break. Затем идёт вот такое условие:

if flag:

   num += (data[1] - 1)

    answer += 1

Здесь нам и нужна переменная flag. С помощью неё мы понимаем, найден ли отсчёт в этом месте массива. Если в переменную записано значение True, значит в этом месте отсчёт. В таком случае я прибавляю к ответу 1. Также я увеличиваю num (номер текущего начального элемента для проверки) на длину найденного отсчёта - 1, так как все эти элементы входят в найденный отсчёт и нам незачем их проверять.

Если вы не запутались до этого места, это очень хорошо, ведь после того, как вся программа отработает на выходе, в переменной answer, мы получим верный ответ!

Оптимизация

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

1. Так как отсчёт имеет определённую длину, то он не может начинаться с элемента, находящегося к концу массива ближе, чем длина отсчёта. Например, если отсчёт длины 3, то второй элемент с конца проверять нет смысла, так как отсчёт начинаться с него не может. Значит цикл первый цикл:

for num in range(data[0]): 

мы можем превратить в:

for num in range(data[0] - data[1] + 1):

2. Также, отсчёт всегда начинается с числа равного его длине. Например, отчёт длины 3 всегда начинается с 3. Поэтому, нам даже не нужно начинать перебор вторым циклом, если число не равно длине отсчёта. При этом добавится простое условие:

if nums[num] == data[1]:

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

Решение, предложенное Google

После соревнования Google выкладывает своё решение задачи. В этом случае оно отличается от моего варианта. Сейчас я кратко разберу их решение.

answer_counter = 0

decreasing_counter = 0

for (i = 1 to  N) {   ** N - количество чисел в массиве

   if (A[i] == A[i - 1] - 1) {   ** A - массив

      decreasing_counter = decreasing_counter + 1

   } else {

      decreasing_counter = 0

   }

   if (A[i] == 1 and decreasing_counter >= K - 1) {   ** K - длина отсчёта для поиска

      answer_counter = answer_counter + 1

   }

}

print answer_counter

Это решение написано на псевдокоде и переведено с английского. Его смысл в том, что мы перебираем все числа в массиве подряд и, если текущее число равно (предыдущему - 1), то мы увеличиваем значение переменной decreasing_counter на 1 (фактически в этой переменной хранится количество подряд идущих чисел в массиве, начиная с текущего числа), иначе - в decreasing_counter записываем 0.

Так как отсчёт любой длины заканчивается числом 1, то если текущий элемент массива равен 1, то, чтобы это был отсчёт длины K, за нашим числом должно идти K - 1 подряд идущих чисел. Именно это количество хранится в переменной decreasing_counter, значит, если значение этой переменной равно K - 1, то перед нами отсчёт. Мы увеличиваем ответ на 1 и следуем дальше по массиву.

Заключение

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

Также я хочу опробовать опросы, которые только недавно добавил, поэтому голосуйте!

Ссылки

  1. https://codingcompetitions.withgoogle.com/kickstart
  2. https://codingcompetitions.withgoogle.com/kickstart/round/000000000019ff43/00000000003380d2

Комментарии

Загружаем…

Войдите, чтобы оставить комментарий.

← Ко всем статьям