Merge remote-tracking branch 'origin/english' into english
101
interview/missing_elements.md
Normal file
@ -0,0 +1,101 @@
|
||||
# How to Find Missing Elements
|
||||
|
||||
**Translator: [youyun](https://github.com/youyun)**
|
||||
|
||||
**Author: [labuladong](https://github.com/labuladong)**
|
||||
|
||||
I have written several articles about mind twisters. Today, let's look at another interesting question.
|
||||
|
||||
The question is simple:
|
||||
|
||||

|
||||
|
||||
Given an arry of length n, the index should be in `[0, n)`. Since we have to put `n+1` number of elements from set `[0, n]`, there must be one element which can't fit. Find the missing element.
|
||||
|
||||
This question is not hard. It's easy to think aabout traversing after sorting. Alternatively, using a `HashSet` to store all the existing elements, and then go through elements in `[0, n]` and loop up in the `HashSet`. Both ways can find the correct answer.
|
||||
|
||||
However, the time complexity for the sorting solution is O(NlogN). The `HashSet` solution has O(N) for time complexity, but requires O(N) space complexity to store the data.
|
||||
|
||||
__Third Solution: Bit Operation__
|
||||
|
||||
The XOR operation (`^`) has a special property: the result of a number XOR itself is 0, and the result of a number with 0 is itself.
|
||||
|
||||
In addition, XOR operation satisfies the Exchange Law and Communicative Law. For instance:
|
||||
|
||||
2 ^ 3 ^ 2 = 3 ^ (2 ^ 2) = 3 ^ 0 = 3
|
||||
|
||||
We can using these special properties to find the missing element through a smart way. For example, `nums = [0,3,1,4]`:
|
||||
|
||||

|
||||
|
||||
For easier understanding, let's assume the index increments by 1 (from `[0, n)` to `[0, n]`), and let each element to be placed at the index of its value:
|
||||
|
||||

|
||||
|
||||
After doing so, all elements and their indices will be a pair except the missing element. If we can find out index 2 is missing, we can find out the missing element subsequently.
|
||||
|
||||
How to find out the missing number? __Perform XOR operations to all elements and their indices respectively. A pair of an element and its index will become 0. Only the missing element will be left.__
|
||||
|
||||
```java
|
||||
int missingNumber(int[] nums) {
|
||||
int n = nums.length;
|
||||
int res = 0;
|
||||
// XOR with the new index first
|
||||
res ^= n;
|
||||
// XOR with the all elements and the other indices
|
||||
for (int i = 0; i < n; i++)
|
||||
res ^= i ^ nums[i];
|
||||
return res;
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
Because XOR operation fulfills the Exchange Law and the Communicative Law, all pairs of numbers will become 0, left with the missing element.
|
||||
|
||||
Till now, the time complexity is O(N), and the space complexity is O(1). This is optimal. _Should we stop now?_
|
||||
|
||||
If we think so, we have become restricted by algorithms. The more knowledge we learn, the easier we might fall into stagnant mindsets. There is actually an even easier solution: __Summation of Arithmetic Progression (AP)__.
|
||||
|
||||
We can interpret the question in this way: given an arithmetic progression `0, 1, 2, ..., n` with an missing element, please find out the missing one. Consequently, the number is just `sum(0,1,..n) - sum(nums)`!
|
||||
|
||||
```java
|
||||
int missingNumber(int[] nums) {
|
||||
int n = nums.length;
|
||||
// Formula: (head + tail) * n / 2
|
||||
int expect = (0 + n) * (n + 1) / 2;
|
||||
|
||||
int sum = 0;
|
||||
for (int x : nums)
|
||||
sum += x;
|
||||
return expect - sum;
|
||||
```
|
||||
|
||||
As you can see, this is the simplest solution. But honestly, even I didn't think of this way. It may be hard for an experienced programmers to think in this way, but very easy for a secondary school student to come up with such a solution.
|
||||
|
||||
_Should we stop now?_
|
||||
|
||||
If we think so, we might still need to pay more attention to details. When we use the formula to calculate `except`, have you thought about __Integer overflow__? If the product is too big and overflowing, the final result must be wrong.
|
||||
|
||||
In the previous implementation, we subtract two sums. To avoid overflow, why not perform subtraction while summing up? Similar to our bit operation solution just now, assume `nums = [0,3,1,4]`, add an index such that elements will be paired up with indices respectively.
|
||||
|
||||

|
||||
|
||||
Let's subtract each element from its corresponding index, and then sum up the differences, the result will be the missing element!
|
||||
|
||||
```java
|
||||
public int missingNumber(int[] nums) {
|
||||
int n = nums.length;
|
||||
int res = 0;
|
||||
// Added index
|
||||
res += n - 0;
|
||||
// Summing up the differences between the remaining indices and elements
|
||||
for (int i = 0; i < n; i++)
|
||||
res += i - nums[i];
|
||||
return res;
|
||||
}
|
||||
```
|
||||
|
||||
Because both addition and subtraction satisfy the Exchange Law and the Communicative Law, we can always eliminate paired numbers, left with the missing one.
|
||||
|
||||
_We can stop by now._
|
@ -1,107 +0,0 @@
|
||||
# 如何寻找消失的元素
|
||||
|
||||
之前也有文章写过几个有趣的智力题,今天再聊一道巧妙的题目。
|
||||
|
||||
题目非常简单:
|
||||
|
||||

|
||||
|
||||
给一个长度为 n 的数组,其索引应该在 `[0,n)`,但是现在你要装进去 n + 1 个元素 `[0,n]`,那么肯定有一个元素装不下嘛,请你找出这个缺失的元素。
|
||||
|
||||
这道题不难的,我们应该很容易想到,把这个数组排个序,然后遍历一遍,不就很容易找到缺失的那个元素了吗?
|
||||
|
||||
或者说,借助数据结构的特性,用一个 HashSet 把数组里出现的数字都储存下来,再遍历 `[0,n]` 之间的数字,去 HashSet 中查询,也可以很容易查出那个缺失的元素。
|
||||
|
||||
排序解法的时间复杂度是 O(NlogN),HashSet 的解法时间复杂度是 O(N),但是还需要 O(N) 的空间复杂度存储 HashSet。
|
||||
|
||||
**第三种方法是位运算**。
|
||||
|
||||
对于异或运算(`^`),我们知道它有一个特殊性质:一个数和它本身做异或运算结果为 0,一个数和 0 做异或运算还是它本身。
|
||||
|
||||
而且异或运算满足交换律和结合律,也就是说:
|
||||
|
||||
2 ^ 3 ^ 2 = 3 ^ (2 ^ 2) = 3 ^ 0 = 3
|
||||
|
||||
而这道题索就可以通过这些性质巧妙算出缺失的那个元素。比如说 `nums = [0,3,1,4]`:
|
||||
|
||||

|
||||
|
||||
|
||||
为了容易理解,我们假设先把索引补一位,然后让每个元素和自己相等的索引相对应:
|
||||
|
||||

|
||||
|
||||
|
||||
这样做了之后,就可以发现除了缺失元素之外,所有的索引和元素都组成一对儿了,现在如果把这个落单的索引 2 找出来,也就找到了缺失的那个元素。
|
||||
|
||||
如何找这个落单的数字呢,**只要把所有的元素和索引做异或运算,成对儿的数字都会消为 0,只有这个落单的元素会剩下**,也就达到了我们的目的。
|
||||
|
||||
```java
|
||||
int missingNumber(int[] nums) {
|
||||
int n = nums.length;
|
||||
int res = 0;
|
||||
// 先和新补的索引异或一下
|
||||
res ^= n;
|
||||
// 和其他的元素、索引做异或
|
||||
for (int i = 0; i < n; i++)
|
||||
res ^= i ^ nums[i];
|
||||
return res;
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
由于异或运算满足交换律和结合律,所以总是能把成对儿的数字消去,留下缺失的那个元素的。
|
||||
|
||||
至此,时间复杂度 O(N),空间复杂度 O(1),已经达到了最优,我们是否就应该打道回府了呢?
|
||||
|
||||
如果这样想,说明我们受算法的毒害太深,随着我们学习的知识越来越多,反而容易陷入思维定式,这个问题其实还有一个特别简单的解法:**等差数列求和公式**。
|
||||
|
||||
题目的意思可以这样理解:现在有个等差数列 0, 1, 2,..., n,其中少了某一个数字,请你把它找出来。那这个数字不就是 `sum(0,1,..n) - sum(nums)` 嘛?
|
||||
|
||||
```java
|
||||
int missingNumber(int[] nums) {
|
||||
int n = nums.length;
|
||||
// 公式:(首项 + 末项) * 项数 / 2
|
||||
int expect = (0 + n) * (n + 1) / 2;
|
||||
|
||||
int sum = 0;
|
||||
for (int x : nums)
|
||||
sum += x;
|
||||
return expect - sum;
|
||||
```
|
||||
|
||||
你看,这种解法应该是最简单的,但说实话,我自己也没想到这个解法,而且我去问了几个大佬,他们也没想到这个最简单的思路。相反,如果去问一个初中生,他也许很快就能想到。
|
||||
|
||||
做到这一步了,我们是否就应该打道回府了呢?
|
||||
|
||||
如果这样想,说明我们对细节的把控还差点火候。在用求和公式计算 `expect` 时,你考虑过**整型溢出**吗?如果相乘的结果太大导致溢出,那么结果肯定是错误的。
|
||||
|
||||
刚才我们的思路是把两个和都加出来然后相减,为了避免溢出,干脆一边求和一边减算了。很类似刚才位运算解法的思路,仍然假设 `nums = [0,3,1,4]`,先补一位索引再让元素跟索引配对:
|
||||
|
||||

|
||||
|
||||
|
||||
我们让每个索引减去其对应的元素,再把相减的结果加起来,不就是那个缺失的元素吗?
|
||||
|
||||
```java
|
||||
public int missingNumber(int[] nums) {
|
||||
int n = nums.length;
|
||||
int res = 0;
|
||||
// 新补的索引
|
||||
res += n - 0;
|
||||
// 剩下索引和元素的差加起来
|
||||
for (int i = 0; i < n; i++)
|
||||
res += i - nums[i];
|
||||
return res;
|
||||
}
|
||||
```
|
||||
|
||||
由于加减法满足交换律和结合律,所以总是能把成对儿的数字消去,留下缺失的那个元素的。
|
||||
|
||||
至此这道算法题目经历九曲十八弯,终于再也没有什么坑了。
|
||||
|
||||
|
||||
坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章:
|
||||
|
||||

|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 148 KiB |
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 154 KiB |
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 154 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
BIN
pictures/missing_elements/title_en.jpg
Normal file
After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
@ -1,6 +1,6 @@
|
||||
# The key to resolving Two Sum problems
|
||||
|
||||
**Translator**: [Fulin Li](fulinli.github.io)
|
||||
**Translator**: [Fulin Li](https://fulinli.github.io/)
|
||||
|
||||
**Author**:[labuladong](https://github.com/labuladong)
|
||||
|
||||
@ -8,9 +8,9 @@ There are a series of problems with Two Sum in LeetCode, and this article will p
|
||||
|
||||
### TwoSum I
|
||||
|
||||
**The most basic form** of Two Sum problems is like this: Given an array of integers `nums`, and a specific integer `target`. You may assume that each input would have **exactly** one solution. return indices of the two numbers such that they add up to target.
|
||||
**The most basic form** of Two Sum problems is like this: Given an array of integers `nums`, and a specific integer `target`. Return indices of the two numbers such that they add up to `target`. You may assume that each input would have **exactly** one solution.
|
||||
|
||||
For example, given `nums = [3,1,3,6], target = 6`, the program should return an array `[0,2]` as 3 + 3 = 6.
|
||||
For example, given `nums = [3,1,3,6], target = 6`, the program should return an array `[0,2]` because 3 + 3 = 6.
|
||||
|
||||
So, how to solve this problem? First, the simplest method, of course, is the exhaustive search.
|
||||
|
||||
@ -27,7 +27,7 @@ int[] twoSum(int[] nums, int target) {
|
||||
}
|
||||
```
|
||||
|
||||
This method is straightforward. The time complexity is $O(n^2)$ and space complexity is $O(1)$.
|
||||
This method is straightforward. The time complexity is O(n^2) and space complexity is O(1).
|
||||
|
||||
We can use a hash table to reduce the time complexity:
|
||||
|
||||
@ -50,19 +50,19 @@ int[] twoSum(int[] nums, int target) {
|
||||
}
|
||||
```
|
||||
|
||||
In this way, because the query time of a hash table is O(1) and the time complexity of the algorithm is reduced to O(N). However, the space complexity is increased to O(N) for storing the hash table. Generally, it is more efficient than the exhaustive search method.
|
||||
In this way, because the query time of a hash table is O(1), the time complexity of the algorithm is reduced to O(N). However, the space complexity is increased to O(N) for storing the hash table. Generally, it is more efficient than the exhaustive search method.
|
||||
|
||||
**I think the objective of the two sum problems is to tell us how to use the hash table.** Let's go on to the next.
|
||||
|
||||
### TwoSum II
|
||||
|
||||
We can modify the last script slightly to design a class with two API:
|
||||
We can modify the last script slightly to design a class with two functions:
|
||||
|
||||
```java
|
||||
class TwoSum {
|
||||
// Add a 'number' to data structure
|
||||
public void add(int number);
|
||||
// Find out whether there exists two numbers and their sum is equal to 'value'
|
||||
// Find out whether there exist two numbers and their sum is equal to 'value'.
|
||||
public boolean find(int value);
|
||||
}
|
||||
```
|
||||
@ -99,11 +99,11 @@ Situation 1: After `[3,3,2,5]` is inputted in `add` function, `find(6)` is execu
|
||||
|
||||
Situation 2: After `[3,3,2,5]` is inputted in `add` function, `find(7)` is executed. Only when `key` is equal to 2 and `other` is equal to 5, it will return true.
|
||||
|
||||
Except as the two situations mentioned above, `find` will only return false.
|
||||
Except for the two situations mentioned above, `find` function will only return false.
|
||||
|
||||
What's the time complexity of this algorithm? The time complexity of `add` function is O(1), `find` function is O(N), and the space complexity is O(N), which is similar to the last problem.
|
||||
What's the time complexity of this algorithm? The time complexity of `add` function and `find` function is O(1) and O(N) respectively. The space complexity is O(N), which is similar to the last problem.
|
||||
|
||||
**However, we should take realities of the situation into account in API design.** For example, in our class, the function `find` is used very frequently and each time it requires O(N) times. It is a huge waste of time. Can we optimize the algorithm given in this situation?
|
||||
**However, we should take realities of the situation into account in API design.** For example, in our class, the function `find` is used very frequently, and each time it requires O(N) times. It is a massive waste of time. Can we optimize the algorithm given in this situation?
|
||||
|
||||
Of course, we can optimize the algorithm when `find` function is used frequently. We can refer to the brute force method in the last problem and utilize a hash set to optimize `find` function pertinently.
|
||||
|
||||
@ -129,11 +129,11 @@ In this way, all possible sum of two numbers is stored in `sum`. Every time `fin
|
||||
|
||||
### Summary
|
||||
|
||||
For TwoSum problems, one of the difficulties is that the given array is **unordered**. For an unordered array, it seems that we don't have any efficient methods and an exhaustive search method may be the only way.
|
||||
For TwoSum problems, one of the difficulties is that the given array is **unordered**. For an unordered array, it seems that we don't have any efficient methods, and an exhaustive search method may be the only way.
|
||||
|
||||
**In ordinary circumstances, we will sort the unordered array first and then consider applying the dual-pointer method.** TwoSum problems make us aware that HashMap or HashSet could help us to resolve unordered array problems.
|
||||
|
||||
Besides, the core of algorithm design is a trade-off, using different data structures to improve the algorithm performance pertinently.
|
||||
Remarkably, the essence of such method is to trade time for space, using different data structures to improve the algorithm performance pertinently.
|
||||
|
||||
Finally, if the given array in TwoSum I is ordered, how do we design the algorithm? It's very easy and you can refer to the previous article「Summary of usage of dual-pointer」:
|
||||
|
||||
|