Advertisement
vkazar

Untitled

Oct 5th, 2023
63
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Kotlin 10.43 KB | None | 0 0
  1. /*
  2. -- ПРИНЦИП РАБОТЫ --
  3. Идея поиска индекса элемента в отсортированном сломанном массиве в следующем:
  4. Если разделить массив на 2 части, то как минимум одна часть будет отсортирована,
  5. а вторая будет либо отсортирована (если разбиением попали на стык "сломанной" части)
  6. либо не отсортирована.
  7. Проверить наличие элемента в отсортированной части легко: искомый будет не меньше первого элемента и не больше последнего
  8. Если элемент находится в отсортированной части, то дальше его можно найти бинарным поиском
  9.  
  10. Если же элемента нет в отсортированной части, а значит можно рекурсивно запустить поиск
  11.  
  12. Поэтому алгоритм следующий:
  13. 1. Находим средний элемент (при чётном количестве элементов -- последний из левой половины)
  14. 2. Если он совпадает с искомым -- поиск завершён
  15. 3. Если он не совпадает, то проверяем левую часть на отсортированность
  16. 3.1. Если левая часть отсортирована
  17. 3.1.1. проверяем в ней наличие искомого элемента. Если находится -- запускаем бинарный поиск, конец алгоритма
  18. 3.1.2. элемент не находится в левой отсортированной части -- значит он в правой части
  19. 3.1.3. проверяем отсортированность правой части
  20. 3.1.3.1. правая часть отсортирована -- запускаем бинарный поиск, конец алгоритма
  21. 3.1.3.2. правая часть не отсортирована -- рекурсивно запускаем поиск по сломанному массиву
  22. 3.2. Если левая часть сломана
  23. 3.2.1. Значит правая часть -- отсортирована
  24. 3.2.1.1. проверяем в ней наличие искомого элемента. Если находится -- запускаем бинарный поиск, конец алгоритма
  25. 3.2.1.2. в правой части нет искомого -- значит рекурсивно запускаем поиск по левой сломанной части
  26.  
  27. -- ДОКАЗАТЕЛЬСТВО КОРРЕКТНОСТИ --
  28.  
  29. По условию задачи, изначально массив был отсортирован по возрастанию, но записан со сдвигом индексов по кругу.
  30. Т.е. с начала массива элементы возрастают, потом происходит "прыжок" вниз, после чего элементы снова возрастают до конца массива
  31. После разделения массива со сломанной сортировкой, одна часть будет отсортирована.
  32. Действительно, будь это иначе, то после "склейки" массивов обратно, в итоговом массиве окажется 2 "прыжка", что противоречит условию задачи
  33. Таким образом, после разделения поиск будет осуществлён либо в отсортированной части, либо рекурсивно в сломанной.
  34.  
  35. На каждом уровне рекурсии сломанная часть уменьшается (как минимум, проверяем середину и она в дальнейшем поиске не участвует)
  36. Таким образом, в худшем случае, разделение изначального массива будет происходить, пока не останется массив из двух или трёх элементов
  37. между которыми и происходит "разрыв". Последующее разделение приведёт к поиску в массиве из нуля или одного элемента, который по определению отсортирован
  38.  
  39. Таким образом, весь поиск сведётся к поиску в отсортированном массиве
  40. Корректность поиска в отсортированной части в доказательстве не нуждается (это не тема данной задачи)
  41.  
  42. -- ВРЕМЕННАЯ СЛОЖНОСТЬ --
  43. На вход подаётся n элементов. Каждую итерацию массив делится примерно пополам (зависит от чётности кол-ва элементов)
  44. И дальнейшая обработка происходит только на половине элементов.
  45. Итого, у алгоритма временная сложность O(log(n))
  46.  
  47. -- ПРОСТРАНСТВЕННАЯ СЛОЖНОСТЬ --
  48. Входные параметры не учитываются, поэтому они на временную сложность не влияют
  49. В алгоритме на каждой итерации используется 3 дополнительных переменных, хранящие указатели на левый, правый и средний элемент обрабатываемого подмассива. Это константная сложность O(1)
  50. Кроме того, в алгоритме используется рекурсия. Т.к. на каждой итерации рекурсии обрабатывается примерно половина объёма предыдущей итерации, зависимость глубины рекурсии от входных данных log_2 (n)
  51. Итого, у алгоритма пространственная сложность
  52. - если учитывать стек вызовов, то O(log(n))
  53. - если считать только кучу, то  O(1)
  54.  
  55. --ПОСЫЛКА--
  56. https://contest.yandex.ru/contest/23815/run-report/91532453/
  57. */
  58.  
  59. object Solution {
  60.     fun brokenSearch(arr: IntArray, k: Int): Int {
  61.         // Your code
  62.         // ヽ(´▽`)/
  63.         return arr.brokenSearch(k, 0, arr.lastIndex);
  64.     }
  65.  
  66.     /**
  67.      * Ищет индекс в отсортированном массиве
  68.      */
  69.     private fun IntArray.search(element: Int, left: Int, right: Int): Int {
  70.  
  71.         // Базовый случай: период поиска сужен до 1 элемента
  72.         if (left == right) {
  73.             //если он -- искомый, то возвращаем индекс
  74.             if (this[left] == element) return left
  75.             //иначе возвращаем -1, т.к. элемент не найден
  76.             return -1
  77.         }
  78.  
  79.         // Если промежуток пустой
  80.         if (left > right) return -1
  81.  
  82.         val middle = left + ((right - left) shr 1)
  83.  
  84.         return when {
  85.             // Если искомый слева от середины, то рекурсивно вызываем поиск слева до среднего не включая
  86.             element < this[middle] -> search(element, left, middle - 1)
  87.             // иначе искомый справа, рекурсивно вызываем поиск справа от среднего до конца
  88.             else -> search(element, middle, right)
  89.         }
  90.     }
  91.  
  92.     /**
  93.      * Ищет индекс в сломанном массиве
  94.      */
  95.     private fun IntArray.brokenSearch(element: Int, left: Int, right: Int): Int {
  96.         // Базовый случай: изначально в массиве 1 элемент
  97.         if (left == right) {
  98.             // если он -- искомый, то возвращаем индекс
  99.             if (this[left] == element) return left
  100.             // иначе возвращаем -1, т.к. элемент не найден
  101.             return -1
  102.         }
  103.         val middle = left + ((right - left) shr 1)
  104.  
  105.         // Середина разбивает массив на два подмассива, как минимум один из которых заведомо отсортирован
  106.         // Если у левого отрезка сохранена сортировка концов, то он в целом отсортирован
  107.         return if (this[left] <= this[middle]) {
  108.             // левый отсортирован
  109.             if (element in this[left] until this[middle]) {
  110.                 // искомый находится в левом отсортированном подмассиве
  111.                 search(element, left, middle - 1)
  112.             } else {
  113.                 // искомый находится в правом подмассиве
  114.                 // середина могла разбить на два отсортированных массива, проверяем правый
  115.                 if (this[middle] <= this[right]) {
  116.                     // правый отсортирован
  117.                     search(element, middle, right)
  118.                 } else {
  119.                     // правый не отсортирован
  120.                     brokenSearch(element, middle, right)
  121.                 }
  122.             }
  123.         } else {
  124.             // левый подмассив не отсортирован
  125.             // значит правый точно отсортирован
  126.             if (element in this[middle]..this[right]) {
  127.                 // искомый находится в правом отсортированном подмассиве
  128.                 search(element, middle, right)
  129.             } else {
  130.                 // искомый находится в левом неотсортированном подмассиве
  131.                 brokenSearch(element, left, middle - 1)
  132.             }
  133.         }
  134.     }
  135. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement