Задание 1 ПермПроверка
Решение:
Это хорошее задание для собеседования. У него есть несколько очевидных простых решений, но все они имеют более-менее плохую сложность, есть пара очень элегантных, но неправильных решений, так что у нас есть широкое поле для экспериментов и улучшений.
Наиболее очевидным решением этой задачи является сортировка массива и проверка на наличие в нем недостающих элементов. Но сложность этого решения равна сложности бинарной сортировки, то есть O(N * LogN). Он выглядит слишком большим для такой простой задачи.
Мы можем улучшить решение и использовать дополнительный индекс, где мы будем отмечать все найденные элементы массива, а затем проверять отметки, чтобы определить, являются ли элементы последовательностью или нет. Например, мы знаем, что диапазон элементов равен [1..1 000 000 000], но мы можем игнорировать его, потому что если у нас есть последовательность в массиве, диапазон элементов не может превышать [1..100 000], поэтому мы определяем бит установить с размером [1..100,000] и установить в true все биты этого набора, проиндексированные значениями массива. Затем проверьте, все ли элементы битового массива верны, у нас есть последовательность в данном массиве. Конечно, если мы встречаем в данном массиве значение больше 100 000, это означает, что это тоже не последовательность. См. пример кода C++ в следующем задании FrogRiverOne
Есть несколько элегантных, но неправильных решений. Один основан на том, что мы можем вычислить сумму всех элементов последовательности по формуле (размер * (1 + размер)) / 2, где размер — размер последовательность. Затем мы считаем реальную сумму элементов и, если она равна расчетной, это последовательность. К сожалению, можно создать массив элементов, который не является последовательностью, но имеет ту же сумму, что и последовательность. Например, 1,2,3 — это последовательность с суммой 6, а 1,1,4 — не последовательность с той же суммой.
На сайте Codility есть блестящее решение этой задачи, прошедшее все тесты. Но это неправильно. Он основан на битовой магии. Фактически это своего рода решение задачи 1 OddOccurrencesInArray урока 2, но в данном случае мы будем генерировать пары для каждого числа данного массива. Затем применяем решение из OddOccurrencesInArray к массиву наших пар и проверяем, есть ли у каждого числа своя пара. Если да, это означает, что данные массивы являются перестановкой последовательности. Нам даже не нужно сохранять наши сгенерированные пары. Мы можем использовать счетчик цикла i.
В С++ это выглядит так:
bool is_permutation(vector<int> &v) { unsigned xorsum = 0; int v_size = v.size(); for (size_t i = 0; i < v_size; i++) { xorsum = (i + 1) ^ static_cast<unsigned>(v[i]) ^ xorsum; } return xorsum == 0; }
Для входного массива [1,2,3] у нас будут следующие итерации:
xorsum: 0 ^ (i + 1): 1 ^ v[i]: 1 = 0
xorsum: 0 ^ (i + 1): 2 ^ v[i]: 2 = 0
xorsum: 0 ^ (i + 1): 3 ^ v[i]: 3 = 0
xorsum == 0 - это перестановка
Для входного массива [3,2,1] у нас будут следующие итерации:
xorsum: 0 ^ (я + 1): 1 ^ v [я]: 3 = 2
xorsum: 2 ^ (i + 1): 2 ^ v[i]: 2 = 2
xorsum: 2 ^ (i + 1): 3 ^ v[i]: 1 = 0
xorsum == 0 - это перестановка
Для входного массива [1,2,4] у нас будут следующие итерации:
xorsum: 0 ^ (i + 1): 1 ^ v[i]: 1 = 0
xorsum: 0 ^ (i + 1): 2 ^ v[i]: 2 = 0
xorsum: 0 ^ (i + 1): 3 ^ v[i]: 4 = 7
xorsum == 7 НЕ перестановка
Подробное описание работы этой битовой магии смотрите в решении для OddOccurrencesInArray в уроке 2.
Я был очень взволнован, когда нашел его, но он рассматривает такие массивы, как 1,2,5,2, как последовательность.
Я уже отправил отчет об ошибке в Codility, надеюсь, они исправят его. Но я предполагаю, что если он прошел все тесты, они знают об этом решении и считают его правильным. :)
Реально работающее решение выглядит не столь элегантно и основано на идее индексов, описанной выше. Но мы можем не тратить дополнительную память на индекс, а использовать входной массив для сохранения индексов. Мы будем использовать тот факт, что у нас в массиве только положительные числа, а в качестве индексов будем использовать отрицательные числа. Конечно, исходные данные в массиве будут потеряны, но мы можем добавить небольшой фрагмент кода, который будет восстанавливать исходные данные после использования индекса. Этот алгоритм работает для последовательностей, которые начинаются с любого числа, но задача от Codility требует начинать с 1, поэтому мы должны добавить проверку и вернуть false, если неправильно пройти тесты.
#include <algorithm> int solution(vector<int> &v) { int v_size = v.size(); // Find minimum and maximum elements of the array auto min_max = std::minmax_element(v.begin(), v.end()); int min_element = *min_max.first; // Check to pass Codility tests if(min_element != 1) { return false; } int max_element = *min_max.second; //if this is a sequence then max-min+1 elements must be equal // to number of given elements if ( (max_element - min_element + 1) != v_size) { return false; } // pass through the whole array for (int i = 0; i < v_size; ++i) { int j; // auxiliary index if (v[i] < 0) { j = -v[i] - min_element; } else { j = v[i] - min_element; } if (v[j] > 0) { v[j] = -v[j]; } else { // if the value at index j is negative then it is repeated return false; } } // If we didn't meet a negative element then this is a sequence return true; }
Задание 2 FrogRiverOne
Решение:
На самом деле нам нужно проверить, содержит ли данный массив переставленную последовательность элементов от 1 до X. Элементы могут повторяться. Лучший способ здесь — использовать битовый массив для индексации данного массива, как это описано в предыдущем решении, а затем проверить, является ли данный массив последовательностью.
В С++ это выглядит так:
int solution(int x, vector<int> & v) { auto v_size = v.size(); vector<bool> bitmap(v_size+1, false); if (v_size >= x) { auto total = x; for (int i = 0; i < v_size; i++) { if (!bitmap[v[i]]) { bitmap[v[i]] = true; total--; if (total == 0) { return i; } } } } return -1; }
Задание 3 МаксСчетчики
Решение:
В этой задаче нет никаких хитростей. Нам просто нужно составить массив счетчиков, увеличить их по заданному алгоритму и распечатать. Скучная задача.
Код С++ выглядит так:
vector<int> max_counters(int n, vector<int> &v) { int max_current_counter = 0; int max_global_counter = 0; // index of the current counter in the counters array int counter_index; vector<int> counters(n, 0); int v_size = v.size(); for(int i = 0; i < v_size; ++i) { counter_index = v[i] - 1; if(v[i] <= n && v[i] >= 1) { counters[counter_index] = max(max_global_counter, counters[counter_index]) + 1; max_current_counter = max(max_current_counter, counters[counter_index]); } else if(v[i] == n + 1) { max_global_counter = max_current_counter; } } int counters_size = counters.size(); for(int i = 0; i < counters_size; ++i) { counters[i] = max(counters[i], max_global_counter); } return counters; }
Задача 4 Пропущенное целое
Решение:
Еще одно скучное задание. Мы будем использовать тот же подход с битовым массивом для индексации данного массива. Затем мы проверяем наши индексы и возвращаем первый, который не был помечен. Это недостающий элемент.
int miss_int(vector<int> &v) { int v_size = v.size(); vector<bool> present(v_size+1, false); for (int i = 0; i < v_size; ++i) { if ( (v[i] > 0) && (v[i] <= v_size) ) { present[v[i]] = true; } } // Find the first element which is not // appeared in the given array for (int i = 1; i <= v_size; ++i) { if (!present[i]) { return i; } } // If the given array is a sequence like [1, 2, 3] return v_size + 1; }
Вернитесь к оглавлению.