представлять
традиционныйпроцесс собеседованияОбычно пишется самым простым способомстраница экрана телефонаВопросы начинаются, а затем в течение дняПолевые работыпроверитьВозможность кодированияа такжекультурное соответствиеПроводить. Почти без исключения решающим фактором являетсяВозможность кодирования. В конце концов, инженеры получают деньги за создание рабочего программного обеспечения в конце дня. Как правило, мы будем использоватьбелая доскачтобы проверить эту способность кодирования. Более важным, чем получение правильного ответа, является четкий мыслительный процесс. Кодирование похоже на жизнь, и правильный ответ не всегда очевиден, но хороших аргументов обычно достаточно.эффективныйизрассуждениеКомпетенция означает потенциал учиться, адаптироваться и развиваться. Лучшие инженеры всегда растут, а лучшие компании всегда внедряют инновации.
Алгоритмический вызовявляются эффективными способами развития способностей, потому что всегда есть более чем один способ справиться с ними. Это открывает возможности для принятия решений и алгоритмического принятия решений. При решении алгоритмических задач мы должны ставить перед собой задачу смотреть с разных точек зрения.Определение проблемы, а затем взвесить различные способывыгодаа такжедефект. Имея достаточно связей, мы можем даже увидеть истину Вселенной;Не существует «идеального» решения.
действительно мастералгоритмпросто чтобы понятьданныеа такжеструктураОтношение между. Отношения между структурами данных и алгоритмами подобны «инь» и «ян».стаканквода. Без стакана воду не удержать. Без структур данных у нас нет объектов, которые можно использовать для логики. Без воды стакан будет пуст из-за недостатка вещества. Без алгоритмов объекты не могут быть преобразованы или «потреблены».
Для более глубокого анализа структур данных вы можете обратиться к: Data Structures in JavaScript:
введение
Применительно к коду алгоритм — это простоструктура данныхизвойтиПреобразовать в определенныйструктура данныхизвыходизfunction
. алгоритмвнутреннийизлогикаопределяет, как конвертировать. Во-первых, вход и выход должны бытьЧистоопределяется какмодульный тест. Это требует полного понимания проблемы, которую нельзя недооценивать, поскольку тщательный анализ проблемы может решить проблему естественным образом без написания кода.
Как только вы хорошо разберетесь в предметной области, вы можете приступить к работе над решением.Мозговой штурм.Какие переменные требуются? Сколько петель требуется и какие типы петель требуются? Есть ли умные встроенные методы, которые могут помочь? Какие крайние случаи необходимо учитывать?Сложная и повторяющаяся логика только усложняет чтение и понимание.Могут ли вспомогательные функции быть абстрагированы или абстрагированы?Алгоритмы часто должны быть масштабируемыми.Как будет работать функция при увеличении размера ввода? Должен ли быть какой-то механизм кэширования?А оптимизация производительности (времени) обычно требует жертвовать пространством памяти (увеличивая потребление памяти).
Чтобы конкретизировать вопрос, нарисуемДиаграмма!
Когда высокоуровневая структура в решении начнет проявляться, мы можем начать писатьПоддельный код. Сделать реальное впечатление на интервьюеру, ПожалуйстаприоритетПодумайте о рефакторинге кода имультиплекс. Иногда функции, которые ведут себя одинаково, могут быть объединены в более общую функцию, которая может принимать дополнительные аргументы. В других случаях депараметризация лучше. сохранить функциючистыйЭто также предусмотрительно для простоты тестирования и обслуживания. Другими словами, при разработке алгоритмаАрхитектураа такжеШаблоны проектированияна ваше рассмотрение.
Если что-то непонятно, пожалуйстазадавать вопросыПроиллюстрировать!
Big O (сложность алгоритма)
Чтобы оценить сложность алгоритма во время выполнения, рассчитайте необходимоеколичество операцийРаньше мы обычно ставилиразмер вводаЭкстраполируйте до бесконечности, чтобы оценить масштабируемость алгоритма. В этом наихудшем случае верхней границы времени выполнения мы можем игнорировать коэффициенты, а также дополнительные члены и оставить только факторы доминирующей функции. Поэтому почти все масштабируемые алгоритмы можно описать всего несколькими типами.
Оптимальный и идеальный алгоритм основан на измерениях времени и пространства.постоянныйизменения курса. То есть его совершенно не волнуют изменения размера ввода. Субоптимальный алгоритм — это алгоритм для времени или пространства.логарифмизменения скорости, опять же соответственнолинейный,Линейный логарифм,вторичныйа такжепоказательтип. Хуже всегофакториализменения курса. существуетBig-OВ обозначениях:
- постоянный: O(1)
- логарифм: O(log n)
- линейный: O(n)
- Линейный логарифм: O(n log n)
- вторичный: O(n²)
- показатель: O(2^n)
- факториал: O(n!)
Диаграмма:bigocheatsheet.com
Когда мы рассматриваем компромисс между временной и пространственной сложностью алгоритма, Big-OАсимптотический анализявляется незаменимым инструментом. Однако Big O игнорирует постоянные факторы, которые могут иметь влияние в реальной практике. Кроме того, временная и пространственная сложность оптимизации алгоритмов может увеличить реальное время разработки или негативно сказаться на читабельности кода. Интуиция относительно того, что действительно незначительно, одинаково важна при разработке структуры и логики алгоритма.
Массивы
Наиболее чистые алгоритмы обычно используютстандартный объект. Пожалуй, самое важное в информатике — этоArrays
. В JavaScript ни один другой объект не имеет больше полезных методов, чем массивы. Методы массива, о которых стоит помнить:sort
,reverse
,slice
, так же какsplice
. массив из0-й индексНачните вставлять элементы массива. Это означает, что позиция последнего элемента массиваarray.length — 1
. массивпоказатель(втолкнуть) лучший вариант, но длявставлять, удалять(без хлопков) ипоискОжидание — это очень плохо. В JavaScript массивы могут бытьдинамичныйувеличивать.
соответствующийBig O :
- показатель: O(1)
- вставлять: O(n)
- удалять: O(n)
- поиск грубой силы: O(n)
- Уточните поиск: O(log n)
Прочитайте полный MDN оArraysДокументация также стоит.
Есть еще массивSets
а такжеMaps
. существуетset, элемент должен бытьТолькоиз. существуетmap, элементы представлены лексикографическим отношениемключа такжестоимостьсочинение. Конечно,Objects
(и их литералы) также могут хранить пары ключ-значение, но ключи должны бытьstrings
Типы.
ObjectКонструктор объекта создает оболочку объектаdeveloper.mozilla.org
повторять
а такжеArrays
Тесно связано использование цикловтраверсOни. В JavaScript мы можем использоватьпятьразныеструктура управленияповторять. Самый настраиваемыйfor
цикл, мы можем использовать его для перебора массива практически в любом порядкепоказатель. Если не уверенколичество итераций, мы можем использоватьwhile
а такжеdo while
Цикл, пока не будет выполнено условие, удовлетворяющее определенному условию. Для любого объекта мы можем использоватьfor in
а такжеfor of
loop для перебора его «ключей» и «значений» соответственно. Чтобы получить и «ключ», и «значение», мы можем использовать егоentries()
метод. мы можем пройтиbreak
заявление в любое времяразорвать петлю break
или используйтеcontinue
утверждениепрыгать в. В большинстве случаев поgenerator
Функция для управления итерацией — лучший вариант.
Родной способ перебора всех элементов массива:indexOf
,lastIndexOf
,includes
,fill
а такжеjoin
. Кроме того, мы можем предоставить следующие методы回调函数
:findIndex
,find
,filter
,forEach
,map
,some
,every
а такжеreduce
.
рекурсия
в основополагающем документеChurch-Turing Thesis, доказано, что любую итеративную функцию можно переписать рекурсивной функцией и наоборот. Иногда рекурсивные методы более лаконичны, ясны и элегантны. просто используйте этоfactorial
Функция факториальной итерации является примером:
const **factorial** = number => {
let product = 1;
for (let i = 2; i <= number; i++) {
product *= i;
}
return product;
};
использоватьrecursive
Чтобы написать рекурсивную функцию, вам нужно толькоодна линиякод!
const **factorial** = number => {
return number < 2 ? 1 : number * factorial(number - 1);
};
Все рекурсивные функции имеютОбщий режим. Их всегда вызываетрекурсивная частьи тот, который не называет себябазовый вариантсочинение. Когда функция вызывает сама себя, она отправляет новый执行上下文
подтолкнуть к执行堆栈
внутри. Эта ситуация будет продолжаться до тех пор, покабазовый вариант,ПотомКучавсплывают один за другимкаждый контекст. Таким образом, небрежное использование рекурсии может привести к ужасному времени выполнения.堆栈溢出
ошибка.
factorial
Пример кода для факториальной функции:
Наконец-то мы готовы взяться за любой алгоритмический вызов! 😉
Популярные алгоритмические задачи
В этом разделе мы рассмотрим 22 в порядке сложности.часто задаваемые вопросыпроблема с алгоритмом. Мы обсудим различные подходы, их плюсы и минусы, а также временную сложность в эксплуатации. Самые элегантные решения часто используют специальные «хитрости» или проницательность. Имея это в виду, давайте начнем!
1. Перевернуть строку
поставить данный一串字符
так каквойти, напишите функцию, которая будет передавать строкуобратныйВозврат после порядка символов.
describe("String Reversal", () => {
it("**Should reverse string**", () =\> {
assert.equal(reverse("Hello World!"), "!dlroW olleH");
});
});
анализировать:
Если мы знаем «трюки», решение не имеет значения. Хитрость заключается в том, чтобы понять, что мы можем использоватьмножествоВстроенный методreverse
. Во-первых, у нас естьнитьиспользоватьsplit
метод созданиямассив символов, то мы можем использоватьreverse
метод, наконец, используйтеjoin
Метод вернет перегруппировку массива символовнить. Это решение может быть сделано в одной строке кода! Хотя это и не так элегантно, проблема также может быть решена с помощью новейшего синтаксиса и вспомогательных функций. использовать новыйfor of
Перебор каждого символа в строке показывает, что мы знакомы с последним синтаксисом. В качестве альтернативы мы можем использовать массивreduce
метод, который избавляет нас от необходимости хранить временные примитивы.
Каждый символ данной строки "посещается" один раз. Хотя этот визит будет происходить несколько раз,времяможно нормализовать клинейныйвремя. И поскольку нет необходимости сохранять отдельное внутреннее состояние, поэтомупространстводапостоянныйиз.
2. Палиндром
палиндромозначает单词
или短语
вперед и назадЧтение такое же. Напишите функцию, которая проверяет, является ли заданное входное значение палиндромом.
describe("Palindrome", () => {
it("**Should return true**", () =\> {
assert.equal(isPalindrome("Cigar? Toss it in a can. It is so tragic"), true);
});
it("**Should return false**", () =\> {
assert.equal(isPalindrome("sit ad est love"), false);
});
});
анализировать:
Ключевым моментом здесь является осознание: мы решаем на основе того, что узнали в предыдущей задаче. Кроме того, нам нужно вернуть布尔
стоимость. это как правильнонеобработанная строкавернутьтройное уравнениеТак же просто проверить. мы также можеммножествоиспользовать новыйevery
способ проверитьПервыйа такжепоследнийв порядке ли символыСимметрично к центрусоответствовать. Однако это привело бы к вдвое большему количеству проверок, чем необходимо. Как и в случае с предыдущей проблемой, сложность выполнения этой задачи одинакова как во времени, так и в пространстве.
Если мы хотим расширить нашу функцию для проверки всейфразаКак сделать? мы можем создатьвспомогательная функция, это правильно字符串
использоватьрегулярное выражениеа такжеreplace
метод удаления неалфавитных символов. Если регулярные выражения не разрешены, мы создаемдопустимые символысостоит из数组
используется как фильтр.
3. Целочисленная инверсия
учитывая整数
,обратныйпорядок чисел.
describe("Integer Reversal", () => {
it("**Should reverse integer**", () =\> {
assert.equal(reverse(1234), 4321);
assert.equal(reverse(-1200), -21);
});
});
анализировать:
Хитрость здесь заключается в том, чтобы сначала передать числа через встроенныйtoString
метод в字符串
. Затем мы можем просто повторно использоватьперевернуть строкулогика алгоритма. После того, как числа поменялись местами, мы можем использовать глобальныйparseInt
функция для преобразования строки обратно в целое число и использованияMath.sign
для обработки чисел. Этот подход можно свести к одной строке кода!
Поскольку мы повторно использовалиперевернуть строкуСложность времени и пространства выполнения этого алгоритма такая же, как и раньше.
4. Fizz Buzz
учитывая数字
В качестве входных значений выведите все целые числа от 1 до заданного числа. Однако, когда целое число делится на 2, выводится «шипение», когда оно делится на 3, выводится «жужжание», когда оно делится и на 2, и на 3, выводится «шипение жужжание».
анализировать:
когда мы осознаемОператор по модулюЗадача этого классического алгоритма становится очень простой, когда его можно использовать для проверки разделимости (делимости). Оператор по модулю берет остаток от двух чисел и возвращает остаток от деления двух чисел. Таким образом, мы можем просто перебрать каждое целое число и проверить, равен ли их остаток при делении на 2 и 30
. Это показывает наши математические способности, потому что мы знаем, что когда число может бытьa
а такжеb
Когда он делимый, он также может быть разделен на ихЛКМДелимый.
Опять же, временная и пространственная сложность этого алгоритма такая же, как и раньше, потому что доступ к каждому целому числу и его проверка осуществляются один раз, но внутреннее состояние не нужно сохранять.
5. Наиболее распространенные персонажи
Дан персонаж, состоящий из字符串
, который возвращает строкуСамый частыйиз字符
.
describe("Max Character", () => {
it("**Should return max character**", () =\> {
assert.equal(max("Hello World!"), "l");
});
});
анализировать:
Хитрость заключается в том, чтобы создать таблицу, которая отслеживает, сколько раз каждый символ встречается в строке. Эту форму можно использовать对象字面量
создавать, с字符
как объектный литералключ, появляющийся вместе с персонажами次数
так какстоимость. Затем мы просматриваем таблицу, сохраняя каждую по паре ключ-значениевременный 变量
найти наиболее часто встречающиеся символы.
Хотя мы использовали два отдельных цикла для перебора двух разных входных значений (нитьа такжекарта персонажей), но временная сложность все ещелинейныйиз. Хотя цикл предназначен для строк, в конце концов, есть ограничение на размер карты символов, так как символы любого языкаограниченноеодин. По той же причине при сохранении внутреннего состояния, как бы ни росла входная строка, сложность пространства также увеличивается.постоянныйиз. Временные примитивы также незначительны в больших масштабах.
6. Anagrams
Анаграммы включенытот же персонажиз单词
или短语
. Напишите функцию, которая проверяет это函数
.
describe("Anagrams", () => {
it("**Should implement anagrams**", () =\> {
assert.equal(anagrams("hello world", "world hello"), true);
assert.equal(anagrams("hellow world", "hello there"), false);
assert.equal(anagrams("hellow world", "hello there!"), false);
});
});
анализировать:
Очевидным способом является созданиекарта персонажей, который подсчитывает количество символов в каждой входной строке. После этого мы можем сравнить карты, чтобы увидеть, совпадают ли они. Логика создания карт символов может быть извлечена ввспомогательная функцияЭто облегчает повторное использование. Чтобы быть более точным, мы должны сначала удалить все несимволы в строке, а затем перевести оставшиеся символы в нижний регистр.
Как мы видим, карта символов имеетлинейныйвременная сложность ипостоянныйкосмическая сложность. Точнее, этот методO(n + m)
сложность, так как проверяются две разные строки.
Другой более элегантный способ заключается в том, что мы можем просто排序
, а затем проверьте, равны ли они! Однако у него есть недостаток, заключающийся в том, что для сортировки обычно требуетсялинейныйвремя.
7. Гласные
учитывая字符串
тип слова или фразы,рассчитать 元音
количество .
describe("Vowels", () => {
it("**Should count vowels**", () =\> {
assert.equal(vowels("hello world"), 3);
});
});
анализировать:
Самый простой способ - использоватьрегулярное выражениеВозьми все гласные и посчитай их. Если регулярные выражения не разрешены, мы можем просто перебрать каждый символ и проверить, является ли он буквой причины. Но сначала преобразуйте строку внижний регистр.
Оба методалинейныйвременная сложность ипостоянныйПространственная сложность, поскольку каждый символ нужно проверять один раз, а временными примитивами можно пренебречь.
8. Блок массива
Для данного大小
из数组
, массивэлементразделить на заданный размермножествоТип列表
.
describe("Array Chunking", () => {
it("**Should implement array chunking**", () =\> {
assert.deepEqual(chunk(\[1, 2, 3, 4\], 2), \[\[1, 2\], \[3, 4\]\]);
assert.deepEqual(chunk(\[1, 2, 3, 4\], 3), \[\[1, 2, 3\], \[4\]\]);
assert.deepEqual(chunk(\[1, 2, 3, 4\], 5), \[\[1, 2, 3, 4\]\]);
});
});
анализировать:
Очевидный способ сделать это — сохранить ссылку на последний «фрагмент» и проверить его размер при переборе элементов массива, чтобы увидеть, следует ли помещать элемент в последний фрагмент. Более элегантным решением является использование встроенногоslice
метод. Это устраняет необходимость в «кавычках», делая код чище. Это можно сделать с помощьюwhile
петля илиfor
Реализован в цикле, который увеличивается шагами заданного размера.
Эти алгоритмы имеютлинейныйВременная сложность, поскольку к каждому элементу массива нужно обращаться один раз. у них также естьлинейныйпространственная сложность из-за необходимости хранить массив типа «chunk», размер которого изменяется при изменении входного значения.
9. Обратный массив
задан произвольный тип数组
,обратныйПорядок массива.
describe("Reverse Arrays", () => {
it("**Should reverse arrays**", () =\> {
assert.deepEqual(reverseArray(\[1, 2, 3, 4\]), \[4, 3, 2, 1\]);
assert.deepEqual(reverseArray(\[1, 2, 3, 4, 5\]), \[5, 4, 3, 2, 1\]);
});
});
анализировать:
Конечно, самое простое решение — использовать встроенныйreverse
метод. Но это слишком потрепанно! Если этот метод не разрешен, мы можем просто перебрать весь массив в цикле, иобменЭлементы в начале и в конце массива. Это означает, что нам нужно временно хранить в памятиОдинЭлементы массива. Для того, чтобы избежать этой необходимости царапать, мы можем использовать массив элементов симметричного положенияприсвоение структуры.
Хотя пройдена только половина входного массива, временная сложность по-прежнемулинейный, потому что Big O приблизительно игнорирует коэффициенты.
10. Обратные слова
учитывая词组
,обратныйПорядок символов каждого слова во фразе.
describe("Reverse Words", () => {
it("**Should reverse words**", () =\> {
assert.equal(reverseWords("I love JavaScript!"), "I evol !tpircSavaJ");
});
});
анализировать:
Мы можем использовать метод разделения для создания массива отдельных слов. Затем для каждого слова мы используемперевернуть строкулогика, чтобы изменить свои символы. Другой способобратныйПереберите каждое слово и сохраните результат во временной переменной. В любом случае нам нужно подготовить все перевернутые слова, прежде чем соединить их вместе в конце.
Поскольку каждый символ проходится один раз, а размер требуемой временной переменной пропорционален входной строке, сложность времени и пространства равналинейныйиз.
11. Сделайте первую букву заглавной
учитывая词组
, для каждого словазаглавные буквы.
describe("Capitalization", () => {
it("**Should capitalize phrase**", () =\> {
assert.equal(capitalize("hello world"), "Hello World");
});
});
анализировать:
Обходной путь заключается в повторении каждого символа, когда предыдущий символ повторяемого символапространство, использовать текущий символtoUpperCase
метод, чтобы сделать его в верхнем регистре. из-застроковый литералв JavaScript естьнеизменный, поэтому нам нужно восстановить входную строку, используя соответствующий метод капитализации. Этот подход требует, чтобы мы всегда использовали первый символ с большой буквы. Еще один более лаконичный способ — поместить входную строкуsplit
в одинмассив слов. Затем выполните итерацию по массиву, начав первый символ каждого элемента с заглавной буквы и, наконец, соединив слова вместе. По тем же непреложным причинам нам необходимо сохранятьвременный массивдля сохранения правильно написанных заглавных слов.
Оба путилинейныйвременная сложность, потому что каждая строка проходится один раз. Они такжелинейныйПространственная сложность , потому что сохраняется временная переменная, которая растет пропорционально входной строке.
12. Шифр Цезаря
учитывая短语
, добавляя каждый символзаменятьдвигаться вперед или назад по заданному алфавиту整数
новые персонажи. При необходимости сдвиг должен переноситься на начало или конец алфавита.
describe("Caesar Cipher", () => {
it("**Should shift to the right**", () =\> {
assert.equal(caesarCipher("I love JavaScript!", 100), "E hkra FwrwOynelp!");
});
it("**Should shift to the left**", () =\> {
assert.equal(caesarCipher("I love JavaScript!", -100), "M pszi NezeWgvmtx!");
});
});
анализировать:
Во-первых, нам нужно создатьсимволы алфавитасостоит из数组
, чтобы вычислить результат перемещения символов. Это означает, что мы должны поставить输入字符串
Преобразовать в нижний регистр. Мы можем легко использовать обычныеfor
Цикл для отслеживания текущего индекса. Нам нужно построить символ, содержащий сдвинутые символы для каждой итерации.新字符串
. Обратите внимание, что когда мы сталкиваемся с неалфавитным символом, мы должны немедленно добавить его в конец нашей результирующей строки и использоватьcontinue
Оператор переходит к следующей итерации. Одна ключевая вещь, которую нужно понять, это то, что мы можем использовать模运算符
Имитирует поведение циклического счета до начала или конца массива алфавита при сдвиге более чем на 26. Наконец, нам нужно проверить регистр в исходной строке перед добавлением результата в результирующую строку.
Поскольку необходимо получить доступ к каждому символу входной строки, а результирующую строку необходимо создать на основе входной строки, временная и пространственная сложность этого алгоритмалинейныйиз.
13. Ransom Note
учитываяmagazine段落
с однимransom段落
,судитьжурнальный абзацкаждый ли изпараграф о выкупеслова в .
const magazine =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum";
describe("Ransom Note", () => {
it("**Should return true**", () =\> {
assert.equal(ransomNote("sit ad est sint", magazine), true);
});
it("**Should return false**", () =\> {
assert.equal(ransomNote("sit ad est love", magazine), false);
});
it("**Should return true**", () =\> {
assert.equal(ransomNote("sit ad est sint in in", magazine), true);
});
it("**Should return false**", () =\> {
assert.equal(ransomNote("sit ad est sint in in in in", magazine), false);
});
});
анализировать:
Очевидный способ сделать это — разделить журнал и абзацы с требованием выкупа на одно слово.множество, а затем проверьте, существует ли каждое слово с требованием выкупа в абзаце журнала. Однако временная сложность этого методавторичныйда илиO(n * m)
, что означает, что этот метод работает плохо. Если мы сначала создадим таблицу слов для абзаца журнала, а затем проверим, существует ли в этой таблице каждое слово в абзаце ансома, мы можем добитьсялинейныйвременная сложность. Это потому, что вобъект картыПоиск всегда можно найти впостоянныйзавершено в срок. Но мы пожертвуем сложностью пространства, потому что нам нужно хранить отображаемый объект в памяти.
В коде это означает, что нам нужно создать подсчет слов в каждом абзаце журнала, а затем проверить, что «хэш-таблица» содержит правильное количество слов-выкупов.
14. Среднее, медиана и мода (наиболее часто встречающееся число)
данный номер数组
, чтобы вычислить эти числаСредняя стоимость,медианаа такжеMode.
const **stat1** = new Stats(\[1, 2, 3, 4, 4, 5, 5\]);
const **stat2** = new Stats(\[1, 1, 2, 2, 3, 3, 4, 4\]);
describe("Mean", () => {
it("**Should implement mean**", () =\> {
assert.equal(Stats.round(stat1.mean()), 3.43);
assert.equal(Stats.round(stat2.mean()), 2.5);
});
});
describe("Median", () => {
it("**Should implement median**", () =\> {
assert.equal(stat1.median(), 4);
assert.equal(stat2.median(), 2.5);
});
});
describe("Mode", () => {
it("**Should implement mode**", () =\> {
assert.deepEqual(stat1.mode(), \[4, 5\]);
assert.deepEqual(stat2.mode(), \[\]);
});
});
анализировать:
С точки зрения сложности, найдите набор чиселСредняя стоимостьАлгоритм самый простой. Статистически,平均值
Определение - это набор чисела такжеРазделить на набор чиселколичество. Поэтому мы можем просто использовать массивreduce
метод суммировать его и делить на его长度
. Сложность выполнения этого алгоритма в зависимости от времени составляетлинейныйДа, парное пространствопостоянныйиз. Потому что каждое число нужно суммировать в процессе обхода, но нет необходимости сохранять переменную в памяти.
найти коллекциюмедианаАлгоритмическая сложность средняя. Во-первых, нам нужно отсортировать массив, но если длина коллекции равна количеству элементов, нам нужна дополнительная логика для обработки двух чисел в середине. В этом случае нам нужно вернутьСредняя стоимость. Поскольку этот алгоритм нуждается в сортировке, онЛинейный логарифмвременная сложность, и поскольку для хранения отсортированного массива требуется память,линейныйкосмическая сложность.
оказатьсяmodeАлгоритм самый сложный. из-заmode
Определяется как наиболее часто встречающееся число или числа, нам необходимо поддерживатьТаблица частот. Ситуация усложняется тем, что если каждое значение встречается одинаковое количество раз, мода отсутствует. Это означает, что в коде нам нужно создатьхеш-картачтобы вычислить частоту каждого «числа», а затем пройти по этой карте, чтобы найти самую высокую частоту одного или нескольких чисел, конечно, может не быть никакого режима. Поскольку каждое число должно храниться и подсчитываться в памяти, этот алгоритмлинейныйвременная и пространственная сложность.
15. Множественное суммирование
Учитывая набор чисел, верните два числа, которые удовлетворяют «сумма двух чисел равна заданному和
"извсе комбинации. Каждый номер можно использовать более одного раза.
describe("Two Sum", () => {
it("**Should implement two sum**", () =\> {
assert.deepEqual(twoSum(\[1, 2, 2, 3, 4\], 4), \[\[2, 2\], \[3, 1\]\]);
});
});
анализировать:
Очевидным решением является созданиевложенный цикл, цикл сравнивает каждое число с другими числами в той же группе. Те комбинации, которые удовлетворяют заданной сумме после того, как сумма может быть задвинута вмассив результатовсередина. Однако такое вложение вызываетпоказательтип временной сложности, что очень неуместно для больших входных значений.
Хитрый трюк состоит в том, чтобы поддерживать массив, содержащий «соответствие» каждого числа, когда мы перебираем входной массив, проверяя, существует ли уже аналог каждого числа. Поддерживая такой массив, мы жертвуем эффективностью использования пространства, чтобы получитьлинейныйвременная сложность.
16. Максимизация прибыли
Для заданного набора цен акций в хронологическом порядке найтисамый низкий 买入价
а такжеСамый высокий 卖出价
сделатьмаксимизация прибыли.
describe("Max Profit", () => {
it("**Should return minimum buy price and maximum sell price**", () =\> {
assert.deepEqual(maxProfit([1, 2, 3, 4, 5]), [1, 5]);
assert.deepEqual(maxProfit([2, 1, 5, 3, 4]), [1, 5]);
assert.deepEqual(maxProfit([2, 10, 1, 3]), [2, 10]);
assert.deepEqual(maxProfit([2, 1, 2, 11]), [1, 11]);
});
анализировать:
Так же мы можем построитьвложенный цикл, цикл проверяет все возможные комбинации цен спроса и предложения, чтобы определить, какая пара приносит наибольшую прибыль. На практике мы не можем продать перед покупкой, поэтому не каждую комбинацию нужно проверять. В частности, для данной цены покупки мы можем игнорировать все цены, предшествующие цене продажи. Следовательно, временная сложность этого алгоритма лучше, чемвторичныйтип.
Однако, немного подумав, мы можем решить проблему всего одним циклом по массиву цен. Ключевым моментом является понимание того, что цена предложения никогда не должна быть ниже цены предложения; если это так, мы должны покупать акции по более низкой цене. То есть в коде мы можем просто поддерживатьВременное логическое значениечтобы указать, что мы должны изменить цену покупки на следующей итерации. Этот элегантный подход требует только одного цикла и, следовательно, имеетлинейныйвременная сложность ипостоянныйкосмическая сложность.
17. Sieve of Eratosthenes
для данного数字
, найти все числа от нуля до этого числапростое число.
describe("Sieve of Eratosthenes", () => {
it("**Should return all prime numbers**", () =\> {
assert.deepEqual(primes(10), \[2, 3, 5, 7\]);
});
});
анализировать:
На первый взгляд, мы могли бы перебрать каждое число и просто использовать оператор по модулю, чтобы проверить все возможные делимости. Однако легко подумать, что этот метод очень неэффективен, а временная сложность хуже, чем у квадратичной формы. К счастью, изобретатель географииEratosthenes of CyreneТакже был обнаружен эффективный метод определения простых чисел.
В коде первым шагом является создание массива размером с заданное число и инициализация каждого из его элементов какtrue
. Другими словами, массивпоказательпредставляет все возможные простые числа, и предполагается, что каждое числоtrue. Затем мы создаемfor
цикл для итерации от 2 до заданного числаквадратный кореньчисло между, используя массивключевая интерполяцияустановить значение элемента, соответствующее каждому пройденному числу, которое меньше, чем кратное данному числу, какfalse. По определению произведение любого целого числа не может быть простым, 0 и 1 здесь игнорируются, так как они не влияют на делимость. Наконец, мы можем просто отфильтровать вселожное значение, чтобы получить все простые числа.
Пожертвовав эффективностью использования пространства для поддержания внутренней «хеш-таблицы», этот Эратосфенситобудет лучше, чем временная сложностьвторичныйтип илиO(n * log (log n))
.
18. Формула общего члена Фибоначчи
реализует возврат данного索引
кудаЧисла Фибоначчииз函数
.
describe("Fibonacci", () => {
it("**Should implement fibonacci**", () =\> {
assert.equal(fibonacci(1), 1);
assert.equal(fibonacci(2), 1);
assert.equal(fibonacci(3), 2);
assert.equal(fibonacci(6), 8);
assert.equal(fibonacci(10), 55);
});
});
анализировать:
Поскольку числа Фибоначчи представляют собой сумму первых двух, проще всего это сделать с помощьюрекурсия. Последовательность Фибоначчи предполагает, что первые два элемента равны 1 и 1, поэтому мы можем создать нашубазовый вариант. Для индексов больше 2 мы можем вызвать первые два элемента нашей собственной функции. Несмотря на элегантный вид, этот рекурсивный метод ужасно неэффективен.показательвременная сложность илинейныйкосмическая сложность. Поскольку стек вызовов требуется для каждого вызова функции, использование памяти растет экспоненциально, поэтому происходит очень быстрый сбой.
Итеративный подход менее элегантен, но имеет лучшую временную сложность. Зациклив, постройте полную последовательность Фибоначчи с верхними N элементами (N — заданное значение индекса), которая может достичьлинейныйвременная и пространственная сложность.
19. Memoized Fibonacci
Реализовать последовательность Фибоначчиэффективныйрекурсивная функция.
describe("Memoized Fibonacci", () => {
it("**Should implement memoized fibonacci**", () =\> {
assert.equal(fibonacci(6), 8);
assert.equal(fibonacci(10), 55);
});
});
анализировать:
Поскольку последовательность Фибоначчи делает избыточные вызовы самой себе, она может резко измениться от вызовазапоминаниеизвлечь выгоду из стратегии. Другими словами, если мы вызовем функциютайниквсе входные и выходные значения, количество вызовов будет уменьшено долинейныйвремя. Конечно, это означает, что мы жертвуем дополнительной памятью.
В коде мы можем реализовать внутри самой функциизапоминаниеметод, или мы можем абстрагировать его в функцию полезности более высокого порядка, которая может украсить любойзапоминаниефункция.
20. Рисуем лестницу
на заданную длину步幅
,использовать# and ' 'Распечатайте «лестницу».
describe("Steps", () => {
it("**Should print steps**", () =\> {
assert.equal(steps(3), "# \\n## \\n###\\n");
assert.equal(_steps(3), "# \\n## \\n###\\n");
});
});
анализировать:
Ключевым моментом является осознание того, что по мере того, как мы движемся вниз по шагу,#
Количество будет продолжатьсяУвеличивать, и количество ' ' будет соответствующимуменьшать. если мы имеемn
шаг, чтобы двигаться, глобальная область действияn
Рядn
Список. Это означает, что сложность времени выполнения зависит как от времени, так и от пространства.вторичныйТип.
Опять же, мы обнаружили, что это также может быть решено рекурсивно. Кроме того, нам нужно пройтидополнительные параметрызаменить необходимые временные переменные.
21. Рисуем пирамиду
на заданное количество阶层
,использовать#а также' 'Распечатайте «Пирамиду».
describe("Pyramid", () => {
it("**Should print pyramid**", () =\> {
assert.equal(pyramid(3), " # \\n ### \\n#####\\n");
assert.equal(_pyramid(3), " # \\n ### \\n#####\\n");
});
});
анализировать:
Ключевым моментом здесь является понимание того, что когда высота пирамидыn
когда ширина2 * n — 1
. Затем, когда мы рисуем вниз, просто сохраняйте центральную симметрию.Увеличивать #количество и соответствующееуменьшать ' 'количество. Поскольку алгоритм начинается с2 * n - 1
* n
Обход строит пирамиду, поэтому его сложность времени выполнения и сложность пространства二次
Тип.
Опять же, мы можем обнаружить, что рекурсивный вызов здесь может использовать предыдущий метод: нужно передатьДополнительные переменныезаменить необходимые временные переменные.
22. Спиральная квадратная матрица
создать данный大小
изфаланга, так что элементы квадратной матрицы следуютспиральный порядокрасположение.
describe("Matrix Spiral", () => {
it("**Should implement matrix spiral**", () =\> {
assert.deepEqual(spiral(3), \[\[1, 2, 3\], \[8, 9, 4\], \[7, 6, 5\]\]);
});
});
анализировать:
Хотя это очень сложный вопрос, метод на самом деле простотекущая строкаа такжетекущий столбецизначалотак же какконецположение для создания临时变量
. Таким образом, мы можем следовать направлению спирали.приращениетраверс起始行
а также起始列
а такжеСнижатьсятраверс结束行
а также结束列
к центру площади.
Поскольку алгоритм итеративно строит заданный размерквадратныйматрица, сложность выполнения которой зависит как от времени, так и от пространствавторичныйТип.
Алгоритмы структуры данных
Теперь, когда структуры данных образуют «кирпичики» алгоритма, стоит подробно изучить общие структуры данных.
Опять же, для быстрого анализа высокого уровня, проверьте:
Data Structures in JavaScript
For Frontend Software Engineers medium.com
очередь
учитывая два队列
в качестве входных данных создайтеновыйочередь.
describe("Weaving with Queues", () => {
it("**Should weave two queues together**", () =\> {
const one = new Queue();
one.enqueue(1);
one.enqueue(2);
one.enqueue(3);
const two = new Queue();
two.enqueue("one");
two.enqueue("two");
two.enqueue("three");
const result = weave(one, two);
assert.equal(result.dequeue(), 1);
assert.equal(result.dequeue(), "one");
assert.equal(result.dequeue(), 2);
assert.equal(result.dequeue(), "two");
assert.equal(result.dequeue(), 3);
assert.equal(result.dequeue(), "three");
assert.equal(result.dequeue(), undefined);
});
анализировать:
队列
В классе должен быть хотя бы один入列(enqueue)
метод, а出列(dequeue)
метод иpeek
метод. Затем мы используемwhile
цикл, цикл судитpeekСуществует ли он, если он существует, мы позволяем ему выполнятьсяисключать из очереди,Потомзачисленк нашему новому队列
середина.
Временная и пространственная сложность этого алгоритмаO(n + m)
Не потому, что нам нужно перебрать две разные коллекции и сохранить их.
куча
использовать двакучавыполнитьQueue
Добрый.
describe("Queue from Stacks", () => {
it("**Should implement queue using two stacks**", () =\> {
const queue = new Queue();
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
assert.equal(queue.peek(), 1);
assert.equal(queue.dequeue(), 1);
assert.equal(queue.dequeue(), 2);
assert.equal(queue.dequeue(), 3);
assert.equal(queue.dequeue(), undefined);
});
});
анализировать:
Мы можем инициализировать два стека от одногоконструктор классаНачинать. Потому чтокучасреди самыхназадВставленные записи будут наиболееПервыйизвлекается, нам нужно перейти к последней записи, чтобы выполнить «удаление из очереди» или «просмотр» для имитацииочередьповедение: большинствоПервыйВставленная запись будет самойПервыйбыл вывезен. Мы можем сделать это, используя второй стеквременныйСохраняет все элементы в первом стеке до конца. После «заглянуть» или «удалить из очереди» мы просто перемещаем все обратно в первый стек. Чтобы поставить запись в очередь, мы можем просто поместить ее в первый стек.
Несмотря на то, что мы используем два стека и должны выполнить цикл дважды, алгоритм по-прежнему асимптотичен по временной и пространственной сложности.линейныйиз. Хотя мы используем два стека и должны выполнить цикл дважды, этот алгоритм по-прежнему асимптотическиlinear in time and space.
связанный список
Односвязный список обычно выполняет следующие функции:
describe("Linked List", () => {
it("**Should implement insertHead**", () =\> {
const chain = new LinkedList();
chain.insertHead(1);
assert.equal(chain.head.data, 1);
});
it("**Should implement size**", () =\> {
const chain = new LinkedList();
chain.insertHead(1);
assert.equal(chain.size(), 1);
});
it("**Should implement getHead**", () =\> {
const chain = new LinkedList();
chain.insertHead(1);
assert.equal(chain.getHead().data, 1);
});
it("**Should implement getTail**", () =\> {
const chain = new LinkedList();
chain.insertHead(1);
assert.equal(chain.getTail().data, 1);
});
it("**Should implement clear**", () =\> {
const chain = new LinkedList();
chain.insertHead(1);
chain.clear();
assert.equal(chain.size(), 0);
});
it("**Should implement removeHead**", () =\> {
const chain = new LinkedList();
chain.insertHead(1);
chain.removeHead();
assert.equal(chain.size(), 0);
});
it("**Should implement removeTail**", () =\> {
const chain = new LinkedList();
chain.insertHead(1);
chain.removeTail();
assert.equal(chain.size(), 0);
});
it("**Should implement insertTail**", () =\> {
const chain = new LinkedList();
chain.insertTail(1);
assert.equal(chain.getTail().data, 1);
});
it("**Should implement getAt**", () =\> {
const chain = new LinkedList();
chain.insertHead(1);
assert.equal(chain.getAt(0).data, 1);
});
it("**Should implement removeAt**", () =\> {
const chain = new LinkedList();
chain.insertHead(1);
chain.removeAt(0);
assert.equal(chain.size(), 0);
});
it("**Should implement insertAt**", () =\> {
const chain = new LinkedList();
chain.insertAt(0, 1);
assert.equal(chain.getAt(0).data, 1);
});
it("**Should implement forEach**", () =\> {
const chain = new LinkedList();
chain.insertHead(1);
chain.insertHead(2);
chain.forEach((node, index) => (node.data = node.data + index));
assert.equal(chain.getTail().data, 2);
});
it("**Should implement iterator**", () =\> {
const chain = new LinkedList();
chain.insertHead(1);
chain.insertHead(2);
for (let node of chain) node.data = node.data + 1;
assert.equal(chain.getTail().data, 2);
});
});
Задача № 1: середина
Возвращает связанный список без использования счетчикамедиана
describe("Midpoint of Linked List", () => {
it("**Should return midpoint of linked list**", () =\> {
const chain = new LinkedList();
chain.insertHead(1);
chain.insertHead(2);
chain.insertHead(3);
chain.insertHead(4);
chain.insertHead(5);
assert.equal(midpoint(chain).data, 3);
});
});
анализировать:
Хитрость здесь заключается в том, чтобы сделать обадваждыОбход связанного списка, где скорость одного обхода равна скорости другогодвойной. Когда быстрое перемещение достигает конца, медленное перемещение достигает середины!
Временная сложность этого алгоритмалинейный, пространственная сложностьпостоянныйиз.
Задача № 2: Цикл
Не сохраняя ссылку на узел, проверьте, является ли связанный списокцикл.
describe("Circular Linked List", () => {
it("**Should check for circular linked list**", () =\> {
const chain = new LinkedList();
chain.insertHead(1);
chain.insertHead(2);
chain.insertHead(3);
chain.head.next.next.next = chain.head;
assert.equal(circular(chain), true);
});
});
анализировать:
Многие функции связанных списков основаны на связанных списках.ЧистоКонечный узел этого утверждения. Поэтому важно следить за тем, чтобы связанный список не был циклическим. Хитрость здесь также в том, чтобы делать два прохода одновременно, один из которых в два раза быстрее другого. Если связанный список является круговым, то в конечном итоге более быстрый цикл совпадет с более медленным циклом. чтобы мы могли вернутьсяtrue
. В противном случае обход дойдет до конечной точки и мы сможем вернутьсяfalse
.
Этот алгоритм также имеетлинейныйвременная сложность ипостоянныйкосмическая сложность.
Challenge #3: From Tail
В случае, если счетчик не используется, верните расстояние связанного списка, указанное в конце связанного списка.步数
узластоимость.
describe("From Tail of Linked List", () => {
it("**Should step from tail of linked list**", () =\> {
const chain = new LinkedList();
chain.insertHead(1);
chain.insertHead(2);
chain.insertHead(3);
chain.insertHead(4);
chain.insertHead(5);
assert.equal(fromTail(chain, 2).data, 3);
});
});
анализировать:
Трюк здесь аналогичен предыдущему: мы проходим по списку дважды одновременно. Однако в этом вопросе «быстрый» обход лучше, чем «медленный» обход.утро 给定步数
Начинать. Затем мы спускаемся по связанному списку с той же скоростью, пока более быстрый не дойдет до конца. В этот момент медленное перемещение как раз достигает правильного расстояния от конца.
Этот алгоритм также имеетлинейныйвременная сложность ипостоянныйкосмическая сложность.
Дерево
Древовидная структура обычно имеет следующие функции:
describe("Trees", () => {
it("**Should add and remove nodes**", () =\> {
const root = new Node(1);
root.add(2);
assert.equal(root.data, 1);
assert.equal(root.children\[0\].data, 2);
root.remove(2);
assert.equal(root.children.length, 0);
});
it("**Should traverse by breadth**", () =\> {
const tree = new Tree();
tree.root = new Node(1);
tree.root.add(2);
tree.root.add(3);
tree.root.children\[0\].add(4);
const numbers = \[\];
tree.traverseBF(node => numbers.push(node.data));
assert.deepEqual(numbers, \[1, 2, 3, 4\]);
});
it("**Should traverse by depth**", () =\> {
const tree = new Tree();
tree.root = new Node(1);
tree.root.add(2);
tree.root.add(3);
tree.root.children\[0\].add(4);
const numbers = \[\];
tree.traverseDF(node => numbers.push(node.data));
assert.deepEqual(numbers, \[1, 2, 4, 3\]);
});
});
Задача № 1: Широта дерева
для данного树
, который возвращаетширота.
describe("Width of Tree Levels", () => {
it("**Should return width of each tree level**", () =\> {
const root = new Node(1);
root.add(2);
root.add(3);
root.children\[1\].add(4);
assert.deepEqual(treeWidths(root), \[1, 2, 1\]);
});
});
анализировать:
Дерево может пройти через堆栈
все этоломтикпровестиглубина перваяобход также может быть выполнен с помощью队列
помощь всех егоИерархияпровестисначала в ширинуобход. Поскольку мы хотим подсчитать количество нескольких узлов на каждом уровне, нам нужно начать сглубина перваяобразом, с помощью队列
сделай этосначала в ширинуобход. Хитрость здесь заключается в том, чтобы вставить специальный标记
чтобы сообщить нам, что текущий уровень пройден, чтобы мы моглисброс настроек 计数器
Используйте его для следующего уровня.
Этот метод имеетлинейныйвременная и пространственная сложность. Хотя наш计数器
представляет собой массив, но его размер никогда не может быть больше линейного.
Задача № 2: Высота дерева
для данного树
, который возвращает свойвысоко(максимальный уровень дерева).
describe("Height of Tree", () => {
it("**Should return max number of levels**", () =\> {
const root = new Node(1);
root.add(2);
root.add(3);
root.children\[1\].add(4);
assert.deepEqual(treeHeight(root), 2);
});
});
анализировать:
Мы можем напрямую повторно использовать логику первой задачи. Однако в этой задаче мы столкнемся“reset”
при увеличении нашего计数器
. Эти две логики почти идентичны, поэтому этот алгоритм также имеетлинейныйвременная и пространственная сложность. Здесь наш计数器
это просто целое число, поэтому его размер более незначителен.
Диаграмма
Пожалуйста, дождитесь последующих дополнений!(Спасибо)
Алгоритм сортировки
Существует множество алгоритмов, которые мы можем использовать для сортировки коллекций данных. К счастью, интервьюер попросил нас понять только основы и основные принципы. Например, оптимальный алгоритм может достичьпостоянныйпространственная сложность илинейныйвременная сложность. В этом духе мы проанализируем самые популярные алгоритмы в порядке сложности от наименее к наиболее эффективному.
Пузырьковая сортировка
Этот алгоритм самый простой для понимания, но наименее эффективный. он делает каждый элемент и каждый другой элементСравнивать,обменпорядок до тех пор, пока больший элемент не "пузырится" наверху. Этот алгоритм требуетвторичныйТип времени ипостоянныйКосмос.
Сортировка вставками
Как и при пузырьковой сортировке, каждый элемент сравнивается со всеми остальными элементами. Разница в том, что здесь операции не меняются местами, а «стыкуются» в правильном порядке. Фактически, он сохранит первоначальный порядок повторяющихся элементов. Этот «жадный» алгоритм по-прежнему требуетвторичныйТип времени ипостоянныйКосмос.
сортировка выбором
При переборе коллекции алгоритм находит и «выбирает»минимуми меняет местами начальный элемент с элементом в позиции индекса. Алгоритмы тоже нужнывторичныйТип времени ипостоянныйКосмос.
быстрая сортировка
Алгоритм рекурсивно выбирает элемент какось, выполняет итерацию по другим элементам в коллекции, сдвигая все меньшие элементы влево, а все большие элементы вправо, пока все элементы не будут правильно отсортированы. Алгоритм имеетвторичныйвременная сложность илогарифмпространственная сложность, поэтому на практике обычносамый быстрыйиз. Поэтому в большинстве языков программирования есть встроенная сортировка по этому алгоритму.
Сортировка слиянием
Хотя это один из самых эффективных алгоритмов, этот алгоритм сложен для понимания. ему нуженрекурсиячасть, которая делит коллекцию на отдельные единицы и требуетповторятьраздел, который соединяет отдельные блоки в правильном порядке. Этот алгоритм требуетЛинейный логарифмвремя илинейныйпространство.
сортировка по подсчету
если бы мы как-то зналимаксимальное значение, мы можем использовать этот алгоритм длялинейныйСортируйте коллекции во времени и пространстве! Максимальное значение позволяет нам создать массив такого размера длярассчитатькаждыйзначение индексаколичество вхождений. Затем просто поместите всененулевойЭлемент в подсчитанной позиции индекса извлекается в массив результатов. делая массивпостоянное времяПосмотрите, этот алгоритм, похожий на хэш, является наиболее эффективным.
Другие алгоритмы сортировки
Диаграмма:bigocheatsheet.com
алгоритм поиска
Худшие алгоритмы должны искать каждый элемент в коллекции, что стоитO(n)
время. Если коллекция уже отсортирована, на одну итерацию требуется вдвое меньше проверок, что стоитO(log n)
время, что является огромным приростом производительности для очень больших наборов данных.
бинарный поиск
Когда коллекция отсортирована, мы можемтраверсилирекурсияВводим наше возвращаемое значение и промежуточное значение, отбрасываем половину нужного нам значения, которого там нет. На самом деле наши цели могут бытьлогарифмвремя ипостоянныйнайдены в космической ситуации.
бинарное дерево поиска
Другой способ сортировки коллекции — создатьбинарное дерево поиска(БСТ). Поиск BST столь же эффективен, как и бинарный поиск. Аналогичным образом мы можем отбросить половину той части, которая, как мы знаем, не содержит ожидаемого значения на каждой итерации. На самом деле, есть еще один способ сортировки коллекции:заказВыполните обход этого дерева в глубину!
Создание BST происходит влинейныйво времени и пространстве, но поиск его требуетлогарифмвремя ипостоянныйпространство.
Чтобы убедиться, что бинарное дерево является BST, мы можем рекурсивно проверить, что каждый левый дочерний элемент всегда меньше корня (максимально возможное), а каждый правый дочерний элемент всегда больше, чем корень.каждый кореньв корне (наименее возможно). Этот метод песни требуетлинейныйвремя ипостоянныйпространство.
Суммировать
В современной веб-разработкефункцияявляется ядром веб-опыта.структура данныхпринимается и возвращается функцией, аалгоритмопределяет внутренний механизм. Порядок величины структуры данных алгоритма определяется выражениемкосмическая сложностьописание, порядок количества вычислений определяется выражениемвременная сложностьописывать. На практике сложность выполнения выражается какBig-Oобозначение, чтобы помочь инженерам сравнить все возможные решения. Наиболее эффективная среда выполненияпостоянныйвремя, независимо от размера входящих и исходящих значений; наименее эффективный метод требует вычисленийпоказательвремя и место. Истинное владение алгоритмами и структурами данных означает способность одновременнолинейныйа такжесистемавывод.
Теоретически каждая проблема имеетповторятьа такжерекурсиярешение. Итерационный алгоритм начинается снизудинамичныйблизкое решение. Рекурсивный алгоритм состоит в декомпозиции сверхудублировать подвопрос. В целом рекурсивная схема более понятна и проста в реализации, но итеративная схема проще для понимания и требует меньше памяти. пройти черезпервоклассная функцияа такжепоток управленияструктура, JavaScript изначально поддерживает обе схемы. Как правило, для достижения более высокой производительности приносится в жертву некоторая эффективность использования пространства или производительностью приходится жертвовать, чтобы уменьшить потребление памяти. Правильный баланс между ними должен быть определен в соответствии с фактическим поведением и окружающей средой. К счастью, большинство фейсбуков больше обеспокоеныПроцесс вычислительного рассужденияа не результаты.
Чтобы произвести впечатление на интервьюера, постарайтесь найти возможности, чтобы воспользоватьсяАрхитектурный дизайна такжеШаблоны проектированияулучшитьвозможность повторного использованияа такжеремонтопригодность. Если вы ищете руководящую должность, владение основами и основными принципами так же важно, как и опыт проектирования системного уровня. Однако лучшие компании также оцениваюткультурное соответствиеПроводить. Поскольку никто не идеален, необходима правильная команда. Что еще более важно, некоторые вещи в этом мире невозможно достичь в одиночку. То, что мы все создаем вместе, часто приносит наибольшее удовлетворение и вознаграждает.