I received an email digest from a popular blogging platform which included after the first few serious and news worthy links some more click-baity content. The article that caught my eye is titled something along the lines of “Questions and Answers to Algorithm Interviews”. In my experience, with few exceptions, articles under the general theme of interview questions are not very good, they’re watered down versions of the real thing, but they must draw clicks because every blog has one. So an article like that surrounded by click-bait must surely fit that description. However, since this is an email digest, perhaps I was too quick to judge, and I decided to click on the link. It was unfortunately as I expected.
我从一个流行的博客平台收到了一封电子邮件摘要,其中包括在头几个严重的和值得新闻关注的链接之后的一些点击相关内容。 引起我注意的文章标题为“算法面试的问题与解答”。 以我的经验,几乎没有例外,以面试问题为主题的文章不是很好,它们淡化了真实内容,但它们必须吸引点击,因为每个博客都有一个。 因此,类似点击诱饵这样的文章必须确实符合该描述。 但是,由于这是一封电子邮件摘要,所以也许我判断的太快了,所以我决定单击该链接。 不幸的是,正如我所料。
So here’s my take on some of the questions that article covered.
因此,这是我对本文涵盖的一些问题的看法。
Find the missing number in a given integer array of 1 to 100
在给定的1到100的整数数组中找到缺失的数字
The author’s solution that I won’t post here because it’s wrong, fails if the missing number is 1
由于错了,我不会在此处发布作者的解决方案,如果缺少的数字为1
,则会失败
Possible solutions are:
可能的解决方案是:
- Linear solution O(n) 线性解O(n)
We know that the sequence is numbered 1...100
so that means that for each number n
, then n === i+1
, so let’s look for the first number does not match this condition:
我们知道序列的编号为1...100
,这意味着对于每个数字n
,然后n === i+1
,所以让我们寻找第一个数字与该条件不匹配:
function findMissingNum(arr) {
let i = 0;
while (arr[i] === ++i);
return i;
}
- Alternative O(n) 替代O(n)
Gauss’ formula tells us that the sum of the first n
integers is n (n+1) / 2
高斯公式告诉我们,前n
整数的和为n (n+1) / 2
So we know that the sum of the numbers from 1 to 100 is 5050. We can subtract all the numbers in the array from 5050 and the result is the missing term. While this works, it still visits every number, where the previous solution stops when it finds the missing term.
因此,我们知道从1到100的数字总和为5050。我们可以从5050中减去数组中的所有数字,结果是缺失项。 在执行此操作时,它仍然会访问每个数字,找到丢失的术语后,先前的解决方案将在该位置停止。
function findMissingNum(arr) {
return arr.reduce((acc, v) => acc - v, 5050);
};
- Optimal solution: Binary search O(log n) 最佳解决方案:二元搜索O(log n)
We know something very important about our array, it is sorted, so we can use a binary search. if arr[i] — i === 1
then the missing number is to the right of i
in the second half of the array, if arr[i] — i ===2
then the missing number is to the left of i, in the first half of the array. Think of a binary search as looking for a word in a dictionary.
我们知道关于数组非常重要的事情,它是经过排序的,因此我们可以使用二进制搜索。 如果arr[i] — i === 1
则丢失的数字在数组的后半部分中i
的右侧,如果arr[i] — i ===2
则丢失的数字在i的左侧,位于数组的前半部分。 将二进制搜索视为在词典中寻找单词。
function findMissingNum(arr, low = 0, high = arr.length - 1) {
const middle = Math.floor((low + high) / 2);
if (low <= high) {
if (arr[middle] - middle === 2) {
return findMissingNum(arr, low, middle - 1);
} else if (arr[middle] - middle === 1) {
return findMissingNum(arr, middle + 1, high);
}
}
return low + 1; // index of missing number + 1
}
Find the largest and smallest number in an unsorted array of integers
在未排序的整数数组中找到最大和最小的数字
For this question, the author iterates through the array and checks each element. This is how I would write this algorithm:
对于这个问题,作者遍历数组并检查每个元素。 这就是我编写此算法的方式:
function findMinMax(array) {
let min = array[0];
let max = array[0];
for (let i = 1; i < array.length; i++) {
min = Math.min(min, array[i]);
max = Math.max(max, array[i]);
}
return [min, max];
}
There’s another approach, not necessarily better, where we can just apply Math.max and Math.mix to the entire array.
还有另一种方法,不一定更好,我们可以将Math.max和Math.mix应用于整个数组。
function findMinMax(array) {
return [
Math.min.apply(null, array),
Math.max.apply(null, array)
];
}
This will be much faster, but if the array becomes too big, it will fail with a “Maximum call stack size exceeded” because these are recursive operations. To handle larger arrays safely, we need to divide and conquer.
这将更快,但是如果数组太大,它将失败并显示“超出最大调用堆栈大小”,因为这些是递归操作。 为了安全地处理更大的数组,我们需要分而治之。
Divide and conquer means splitting the data into smaller chunk and repeating the operation over and over, so we’re going to split our array into sub arrays and find the min and max for the first chunk, and then the next, etc, etc. There are two types of divide and conquer algorithms, iterative and recursive.
分而治之意味着将数据分成较小的块并一遍又一遍地重复操作,因此我们将把数组分成子数组,并找到第一个块的最小值和最大值,然后是下一个块,依此类推。划分和征服算法有两种,迭代算法和递归算法。
Iterative:
迭代式:
function findMinMax(arr) {
let queue = [];
while (arr.length > 100000) {
const chunk = arr.splice(0, 100000);
queue = queue.concat([
Math.min.apply(null, chunk),
Math.max.apply(null, chunk),
]);
}
const remaining = queue.concat(arr.slice());
return [Math.min.apply(null, remaining), Math.max.apply(null, remaining)];
}
Recursive:
递归:
function findMinMax(arr) {
if (arr.length <= 100000) {
return [Math.min.apply(null, arr), Math.max.apply(null, arr)];
}function minMax(arr1, arr2) {
const union = arr1.concat(arr2);
const min = Math.min.apply(null, union);
const max = Math.max.apply(null, union);
return [min, max];
}const middle = Math.floor(arr.length / 2);
return minMax(
findMinMax2(arr.slice(0, middle)),
findMinMax2(arr.slice(middle))
);
}
Unfortunately, they both come at a trade-off, they consume more memory and will be slower for larger arrays. If we’re sure the array will more often than not be larger than 100,000 then the first solution is the best, it runs in linear time without requiring any additional space. If most times the array is less than 100,000 items, then a divide and conquer will be faster. We’ll give a preference to the iterative version as the recursive version has a recursion within a recursion and we want to avoid overflowing the call stack.
不幸的是,它们都需要权衡取舍,它们消耗更多的内存,并且对于较大的阵列来说会比较慢。 如果我们确定数组经常大于或等于100,000,则第一个解决方案是最佳方案,它可以线性运行,而无需任何额外空间。 如果大多数时候该数组少于100,000个项目,那么分而治之会更快。 我们将优先考虑迭代版本,因为递归版本在递归内具有递归,并且我们希望避免调用堆栈溢出。
There are other solutions as well, like a min-max heap, or a quick select which allow to retrieve very quickly the smallest and largest values or the n-th smallest/largest elements (quick select).
还有其他解决方案,例如min-max堆或快速选择,它们允许非常快速地检索最小和最大值或第n个最小/最大元素(快速选择)。
So this is seemingly and easy question, but with much more depth than appears at first glance.
因此,这是一个看似简单的问题,但其深度要比乍看起来的要深得多。
Return an array showing the cumulative sum at each index of an array of integers
返回一个数组,该数组显示整数数组每个索引处的累积和
The author iterates over an array and for each element, adds it to its previous element and stores the result in a new array. Unfortunately, more space is needed, as a whole new array is created (O(n)
) . There’s a more efficient way to do this by modifying the array in place (O(1)).
作者遍历数组,并为每个元素添加到其先前的元素,并将结果存储在新数组中。 不幸的是,由于创建了一个新数组( O(n)
),因此需要更多空间。 通过修改数组(O(1)),有一种更有效的方法。
function cumulativeSum(array) {
for (let i = 1; i < array.length; i++) {
array[i] += array[i - 1];
}
return array;
}
Remove all duplicates from an array of integers
从整数数组中删除所有重复项
In their answer, the author makes the assumption that the array is sorted, and says to sort it if need be. They then use a stack and push on top of the stack the next element of the array if the last element on the stack is different from the array. That’s immediately an O(n log n) time complexity, and a O(n) space complexity. We can do this in constant O(1), with simpler logic. We’re going to create a Set from the array, as a Set can only hold unique elements, then convert the set back to an array.
在他们的回答中,作者假设数组已排序,并说如果需要对数组进行排序。 然后,他们使用堆栈,如果堆栈上的最后一个元素与数组不同,则将堆栈的下一个元素推到堆栈顶部。 这就是O(n log n)的时间复杂度和O(n)的空间复杂度。 我们可以使用更简单的逻辑在常量O(1)中执行此操作。 我们将根据数组创建一个Set,因为Set仅可容纳唯一元素,然后将其转换回数组。
- Quick es6 version 快速es6版本
const uniques = (arr) => [...new Set(arr)];
- Longer form 更长的形式
function uniques(arr) {
return Array.from(new Set(arr));
}
- Hashmap and filter instead of a Set O(n) 哈希图和过滤器,而不是Set O(n)
function uniques(arr) {
const v = {};
return arr.filter((n) => {
if (v[n] === null) {
return false;
}
v[n] = null;
return true;
});
}
翻译自: https://medium.com/@adostes/beware-of-algorithm-interview-articles-d6998ad1200f