Вопросы на собеседовании для JavaScript-программиста

Предлагаем вашему вниманию широкий список вопросов, которые могут задать на собеседовании JavaScript-программисту. Все задачи решены на JS, ES5 и ES6.

После каждого решения приведена ссылка на Codepen, где вы можете самостоятельно опробовать решение конкретной задачи.

Массивы

1.1 У вас есть массив целых чисел, найдите наибольшее произведение из трёх чисел данного массива.

var unsortedArray = [-10, 7, 29, 30, 5, -10, -70];

computeProduct(unsortedArray); // 21000

function sortIntegers(a, b) {
  return a - b;
}

// Наибольшее произведение - это (min1 * min2 * max1 || max1 * max2 * max3)
function computeProduct(unsorted) {
  var sortedArray = unsorted.sort(sortIntegers),
    product1 = 1,
    product2 = 1,
    array_n_element = sortedArray.length - 1;

  // Получаем произведение трёх наибольших элементов уже отсортированного массива
  for (var x = array_n_element; x > array_n_element - 3; x--) {
      product1 = product1 * sortedArray[x];
  }

  product2 = sortedArray[0] * sortedArray[1] * sortedArray[array_n_element];

  if (product1 > product2) return product1;

  return product2;
}

Решение на Codepen: https://codepen.io/kennymkchan/pen/LxoMvm?editors=0012

Нахождение пропущенного элемента

1.2 Неотсортированный массив содержит (n-1) чисел из последовательности {1,2,...,n} (границы определены), найдите недостающий элемент массива за время o(n).

// Число, возвращаемое функцией - 8
var arrayOfIntegers = [2, 5, 1, 4, 9, 6, 3, 7];
var upperBound = 9;
var lowerBound = 1;

findMissingNumber(arrayOfIntegers, upperBound, lowerBound); // 8

function findMissingNumber(arrayOfIntegers, upperBound, lowerBound) {
  // Проходим через массив и находим сумму чисел
  var sumOfIntegers = 0;
  for (var i = 0; i < arrayOfIntegers.length; i++) {
    sumOfIntegers += arrayOfIntegers[i];
  }

  /* Находим теоретическую сумму последовательных чисел, используя вариацию суммы Гаусса*/
  // Формула: [(N * (N + 1)) / 2] - [(M * (M - 1)) / 2];
  // N - верхняя граница, а M - нижняя граница

  upperLimitSum = (upperBound * (upperBound + 1)) / 2;
  lowerLimitSum = (lowerBound * (lowerBound - 1)) / 2;

  theoreticalSum = upperLimitSum - lowerLimitSum;

  return theoreticalSum - sumOfIntegers;
}

Решение на Codepen: http://codepen.io/kennymkchan/pen/rjgoXw?editors=0012

Удаление повторяющихся значений

1.3 Удалите все одинаковые значения в массиве, возвращая массив, состоящий из уникальных элементов.

// Реализация на ES6
var array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];

Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]

// Реализация на ES5
var array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];

uniqueArray(array); // [1, 2, 3, 5, 9, 8]

function uniqueArray(array) {
  var hashmap = {};
  var unique = [];

  for(var i = 0; i < array.length; i++) {
    /* Если возвращаемое значение (unique) - undefined, то оно приравнивается к false. */
    if(!hashmap.hasOwnProperty(array[i])) {
      hashmap[array[i]] = 1;
      unique.push(array[i]);
    }
  }

  return unique;
}

Решение на Codepen: http://codepen.io/kennymkchan/pen/ZLNwze?editors=0012

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

var array = [7, 8, 4, 9, 9, 15, 3, 1, 10];
// [7, 8, 4, 9, 9, 15, 3, 1, 10] возвращаемое значение будет `11`, как разница между `4` и `15`
// Примечание: это не `14`, как разница между `15` и `1`, так как 15 стоит перед 1.

findLargestDifference(array);

function findLargestDifference(array) {
  // Если в массиве всего один элемент, то задача не имеет смысла
  if (array.length <= 1) return -1;

  // currentMin будет отвечать за наименьший элемент
  var currentMin = array[0];
  var currentMaxDifference = 0;

  // Мы будем проходить по массиву раз за разом, находя наибольшую разность
  // Записываем наименьший элемент, так как он впоследствии нам пригодится

  for (var i = 1; i < array.length; i++) {
    if (array[i] > currentMin && (array[i] - currentMin > currentMaxDifference)) {
      currentMaxDifference = array[i] - currentMin;
    } else if (array[i] <= currentMin) {
      currentMin = array[i];
    }
  }

  // Если разность равна нулю или меньше нуля, то задача не имеет смысла
  if (currentMaxDifference <= 0) return -1;

  return currentMaxDifference;
}

Решение на Codepen: http://codepen.io/kennymkchan/pen/MJdLWJ?editors=0012

И ещё задачи на массивы

1.5 У вас есть массив целых чисел, выведите такой массив, что output[i] равен произведению всех элементов массива за исключением i-ого. (Решите за O(n) без операции деления).

var firstArray = [2, 2, 4, 1];
var secondArray = [0, 0, 0, 2];
var thirdArray = [-2, -2, -3, 2];

productExceptSelf(firstArray); // [8, 8, 4, 16]
productExceptSelf(secondArray); // [0, 0, 0, 0]
productExceptSelf(thirdArray); // [12, 12, 8, -12]

function productExceptSelf(numArray) {
  var product = 1;
  var size = numArray.length;
  var output = [];

  // Из первого массива: [1, 2, 4, 16]
  // В данном случае, последний элемент находится уже на правильном
  // месте, чтобы просто умножить его на 1 в следующем шаге
  for (var x = 0; x < size; x++) {
      output.push(product);
      product = product * numArray[x];
  }

  var product = 1;
  for (var i = size - 1; i > -1; i--) {
      output[i] = output[i] * product;
      product = product * numArray[i];
  }

  return output;
}

Решение на Codepen: http://codepen.io/kennymkchan/pen/OWYdJK?editors=0012

Пересечение двух массивов

1.6 Найдите пересечение двух массивов. Пересечение - это общие элементы, которые присутствуют в обоих массивах. Элементы должны быть уникальны.

var firstArray = [2, 2, 4, 1];
var secondArray = [1, 2, 0, 2];

intersection(firstArray, secondArray); // [2, 1]

function intersection(firstArray, secondArray) {
  /* Здесь логика состоит в том, чтобы создать hashmap с элементами 
первого массива в качестве ключей */
  /* После этого вы можете проверить, существует ли элемент в хэше.
 Сделать это можно за время O(1) благодаря hashmap */
  // Если элемент существует, то добавляем его к новому массиву

  var hashmap = {};
  var intersectionArray = [];

  firstArray.forEach(function(element) {
    hashmap[element] = 1;
  });

  secondArray.forEach(function(element) {
    if (hashmap[element] === 1) {
      intersectionArray.push(element);
      hashmap[element]++;
    }
  });

  return intersectionArray;

}

Решение на Codepen: http://codepen.io/kennymkchan/pen/vgwbEb?editors=0012

Строки

2.1 У вас есть строка. Ваша задача - перевернуть каждое слово в строке. "Welcome to this Javascript Guide!" должно стать "emocleW ot siht tpircsavaJ !ediuG".

var string = "Welcome to this Javascript Guide!";

// Вывод будет таким: "!ediuG tpircsavaJ siht ot emocleW"
var reverseEntireSentence = reverseBySeparator(string, "");

// Вывод будет таким: "emocleW ot siht tpircsavaJ !ediuG"
var reverseEachWord = reverseBySeparator(reverseEntireSentence, " ");

function reverseBySeparator(string, separator) {
  return string.split(separator).reverse().join(separator);
}

Решение на Codepen: http://codepen.io/kennymkchan/pen/VPOONZ?editors=0012

2.2 У вас есть две строки. Определите, являются ли они анаграммами друг к другу. "Mary" - анаграмм к "Army".

var firstWord = "Mary";
var secondWord = "Army";

isAnagram(firstWord, secondWord); // true

function isAnagram(first, second) {
  // Для удобства переведём все символы в нижний регистр
  var a = first.toLowerCase();
  var b = second.toLowerCase();

  /* Отсортируем строки и присоединим финальный массив к строкам. Сравним результаты */
  a = a.split("").sort().join("");
  b = b.split("").sort().join("");

  return a === b;
}

Решение на Codepen: http://codepen.io/kennymkchan/pen/NdVVVj?editors=0012

2.3 Проверьте, является ли строка палиндромом. "racecar" - палиндром. "race car" должен тоже считаться за палиндром. Регистр должен учитываться.

isPalindrome("racecar"); // true
isPalindrome("race Car"); // true

function isPalindrome(word) {
  // Уберём все символы, не являющиеся буквами
  var lettersOnly = word.toLowerCase().replace(/\s/g, "");

  // Сравним строку с её перевёрнутой версией
  return lettersOnly === lettersOnly.split("").reverse().join("");
}

Решение на Codepen: http://codepen.io/kennymkchan/pen/xgNNNB?editors=0012

Изоморфные строки

2.4 Проверьте, являются ли две строки изоморфными.
Две строки называются изоморфными, когда в строке A можно заменить конкретный символ на любой другой для получения строки B. Порядок символов должен остаться неизменным. Каждый последовательный символ в строке A сравнивается с каждым последовательным символов в строке B.

'paper' и 'title' вернёт true (p = t, a = i, e = l, r = e).

'egg' и 'sad' вернёт false.

'dgg' и 'add' вернёт true.

isIsomorphic("egg", 'add'); // true
isIsomorphic("paper", 'title'); // true
isIsomorphic("kick", 'side'); // false

function isIsomorphic(firstString, secondString) {

  /* Проверка на то, имеют ли две строки одинаковую длину. Если нет, то они не изоморфны по определению. */
  if (firstString.length !== secondString.length) return false

  var letterMap = {};

  for (var i = 0; i < firstString.length; i++) {
    var letterA = firstString[i],
        letterB = secondString[i];

    if (letterMap[letterA] === undefined) {
      letterMap[letterA] = letterB;
    } else if (letterMap[letterA] !== letterB) {
      /* Если letterA уже существует, но не указывает на letterB, 
это значит, что A указывает на больше чем одну букву в строке B. */
      return false;
    }
  }
  // Если программа вышла из цикла, значит все условия соблюдены!
  // Две строки изоморфны!
  return true;
}

Решение на Codepen: http://codepen.io/kennymkchan/pen/mRZgaj?editors=0012

Стэки и очереди

3.1 Реализуйте два метода: enqueue и dequeue, используя только два стэка

var inputStack = []; // Первый стэк
var outputStack = []; // Второй стэк

// Для реализации enqueue просто сделаем push в первый стэк
function enqueue(stackInput, item) {
  return stackInput.push(item);
}

function dequeue(stackInput, stackOutput) {
  /* Перевернём стэк таким образом, что первый элемент выходного 
стэка - последний элемента входного. После этого делаем pop выходного стэка */
  if (stackOutput.length <= 0) {
    while(stackInput.length > 0) {
      var elementToOutput = stackInput.pop();
      stackOutput.push(elementToOutput);
    }
  }

  return stackOutput.pop();
}

Решение на Codepen: http://codepen.io/kennymkchan/pen/mRYYZV?editors=0012

3.2 Напишите функцию, которая будет проверять, являются ли фигурные скобки сбалансированными, используя стэки. {} - считается блоком. {}{} - сбалансированные скобки. {{{}} - несбалансированные.

var expression = "{{}}{}{}"
var expressionFalse = "{}{{}";

isBalanced(expression); // true
isBalanced(expressionFalse); // false
isBalanced(""); // true

function isBalanced(expression) {
  var checkString = expression;
  var stack = [];

  // Если входная строка пуста, то технически всё сбалансировано
  if (checkString.length <= 0) return true;

  for (var i = 0; i < checkString.length; i++) {
    if(checkString[i] === '{') {
      stack.push(checkString[i]);
    } else if (checkString[i] === '}') {
      if (stack.length > 0) {
        stack.pop();
      } else {
        return false;
      }
    }
  }

  if (stack.pop()) return false;
  return true;
}

Решение на Codepen: http://codepen.io/kennymkchan/pen/egaawj?editors=0012

Рекурсия

4.1 Напишите рекурсивную функцию, которая переводит десятичное число в двоичное. Если входное число - 4, выходным будет 100.

decimalToBinary(3); // 11
decimalToBinary(8); // 1000
decimalToBinary(1000); // 1111101000

function decimalToBinary(digit) {
  if(digit >= 1) {
    if (digit % 2) {
      return decimalToBinary((digit - 1) / 2) + 1;
    } else {
      return decimalToBinary(digit / 2) + 0;
    }
  } else {
   
    return '';
  }
}

Решение на Codepen: http://codepen.io/kennymkchan/pen/OWYYKb?editors=0012

4.2 Напишите рекурсивную функцию, которая выполняет бинарный поиск

function recursiveBinarySearch(array, value, leftPosition, rightPosition) {
  // Value DNE
  if (leftPosition > rightPosition) return -1;

  var middlePivot = Math.floor((leftPosition + rightPosition) / 2);
  if (array[middlePivot] === value) {
    return middlePivot;
  } else if (array[middlePivot] > value) {
    return recursiveBinarySearch(array, value, leftPosition, middlePivot - 1);
  } else {
    return recursiveBinarySearch(array, value, middlePivot + 1, rightPosition);
  }
}

Решение на Codepen: http://codepen.io/kennymkchan/pen/ygWWmK?editors=0012

Числа

5.1 У вас есть целое число, определите, является ли оно является степенью двойки.

isPowerOfTwo(4); // true
isPowerOfTwo(64); // true
isPowerOfTwo(1); // true
isPowerOfTwo(0); // false
isPowerOfTwo(-1); // false

//Без проверки на ноль
function isPowerOfTwo(number) {
  // `&` ипользует побитовую запись n.
  // В данном случае, входное число - 4; выражение идентично:
  // `return (4 & 3 === 0)`
  /* 4 в двоичном виде - 100, а 3 - 011. & 
проверяет на то, чтобы оба числа в двоичном виде имели еденицу на одном и том же месте.
В таком случае будет возвращено 1, иначе - 0. В этом случае, результатом будет 000. */
  // таким образом, 4 удовлетворяет условиям.

  return number & (number - 1) === 0;
}

//С проверкой на ноль
function isPowerOfTwoZeroCase(number) {
  return (number !== 0) && ((number & (number - 1)) === 0);
}

Решение на Codepen: http://codepen.io/kennymkchan/pen/qRGGeG?editors=0012

JavaScript

6.1 Объясните, что такое "поднятие" в JavaScript.

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

6.2 Объясните функционал директивы use strict;

директива use strict; говорит о том, что код должен быть выполнен в "строгом режиме". Один из плюсов данного режима - это то, что он предотвращает использование необъявленных переменных. Наиболее старые версии JavaScript будут игнорировать данную директиву.

// Пример "строгого режима"
"use strict";

catchThemAll();
function catchThemAll() {
  x = 3.14; // На этом моменте будет ошибка
  return x * x;
}

6.3 Объясните, что такое event bubbling и как его предотвратить.
Even bubbling - это концепт, при котором порядок выполнения событий должен подниматься по структуре DOM-дерева.

Один из вариантов предотвращения - "event.stopPropagation()" или "event.cancelBubble", для IE версии ниже 9.

6.4 Какова разница между == и === в JavaScript?

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

// Пример "==" и "===".
0 == false; // true
0 === false; // false

2 == '2'; // true
2 === '2'; // false

6.5 Какова разница между null и undefined?

Null в JavaScript может быть присвоен переменной, а undefined лишь показывает, что переменная была объявлена, но не была инициализирована.

6.6 В чём разница между прототипным наследованием и классическим наследованием?

В классическом наследовании классы неизменны, поддерживают множественное наследование, могут содержать интерфейсы, final классы и abstract классы. Прототипы же куда более гибки в том плане, что они могут быть изменены.

Другие статьи по теме

Подборка материалов по JavaScript

10 вещей, которые стоит знать каждому JavaScript-разработчику

Комментарии

ВАКАНСИИ

Добавить вакансию
Разработчик C++
Москва, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ