# Missing Number 类，元素交换，数组环形跳转

## Missing Number 类，元素交换，数组环形跳转

### [ Move Zeros](https://leetcode.com/problems/move-zeroes/)

FB 的超高频，记得先演一演，给个 swap 的写法\~

这种写法中，我们调用 swap 的次数取决于从数组第一个 0 开始，后面到底有多少个非 0 元素。

#### 一次 swap = 2次数组写入 + 1次 temp variable 写入，总写入次数为 O(3n)，总数组写入次数为 O(2n).

```java
public class Solution {
    public void moveZeroes(int[] nums) {
        if(nums == null || nums.length == 0) return;
        int ptr = 0;
        while(ptr < nums.length && nums[ptr] != 0) ptr ++;
        for(int i = ptr + 1; i < nums.length; i++){
            if(nums[i] != 0) swap(nums, i, ptr++);
        }
    }

    private void swap(int[] nums, int a, int b){
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }
}
```

然后 follow-up 肯定是如何减少写入次数 (aka 别用 swap)，后面的演技就是这样……

其实我一直觉得这个写法才是最直观的，暴力点用 swap 的写法反而 tricky 一点。。

#### 这种写法中，数组每一个位置一定会被不多不少写入一次，写入次数为 O(n)

```java
    public void moveZeroes(int[] nums) {
        if(nums == null || nums.length == 0) return;
        int ptr = 0;
        for(int i = 0; i < nums.length; i++){
            if(nums[i] != 0) nums[ptr++] = nums[i];
        }
        for(; ptr < nums.length; ptr++){
            nums[ptr] = 0;
        }
    }
```

### [Missing Number](https://leetcode.com/problems/missing-number/)

等差数列。 英文叫 arithmetic sequence，记一下，和老外面试官吹比用。。

```java
public class Solution {
    public int missingNumber(int[] nums) {
        int N = nums.length;
        int sum = 0;
        for(int num : nums) sum += num;
        return  (N + 1) * N / 2 - sum;
    }
}
```

### [First Missing Positive](https://leetcode.com/problems/first-missing-positive/)

这题挺 tricky 的，到现在也是记的九章的答案。

#### 思路就是不断试图把当前元素替换到其正确的位置上；如果目标位置元素已经正确则 do nothing，如果目标位置越界，也 do nothing. 每次交换之后指针位置不动，继续试图新一轮替换。这样一次循环之后，所有能被放到正确位置的元素，都会被放过去，而第一个位置和值不匹配的元素就是我们要寻找的目标。

#### 注意要点：

* **当前元素的目标位置 index 有可能越上下界，多加两个判断条件；**
* **如果真是 swap 了，不代表当前元素一定是正确位置，指针不要动(或者说在 for loop 里面要 i--)**

```java
public class Solution {
    public int firstMissingPositive(int[] nums) {
        for(int i = 0; i < nums.length; i++){
            if(nums[i] != i + 1 && nums[i] - 1 >= 0 && nums[i] - 1 < nums.length && nums[nums[i] - 1] != nums[i]){
                swap(nums, i, nums[i] - 1);
                i--;
            }
        }
        for(int i = 0; i < nums.length; i++){
            if(nums[i] != i + 1) return i + 1;
        }
        return nums.length + 1;
    }

    private void swap(int[] nums, int a, int b){
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }
}
```

### [Missing Ranges](https://leetcode.com/problems/missing-ranges/)

研究区间类问题的时候做过一次，挺有意思的。

#### 这题的要点在于 “不断更新 lower”，并且根据个新元素带来的区间终点决定是添加一个元素，还是一个区间。

```java
public class Solution {
    public List<String> findMissingRanges(int[] nums, int lower, int upper) {
        List<String> list = new ArrayList<>();
        for(int i = 0; i < nums.length; i++){
            int candidate = nums[i] - 1;

            if(candidate == lower) list.add("" + candidate);
            else if(candidate > lower) list.add("" + lower + "->" + candidate);

            lower = Math.max(lower, nums[i] + 1);
        }

        if(lower == upper) list.add("" + lower);
        else if(lower < upper) list.add("" + lower + "->" + upper);

        return list;
    }
}
```

### [Find the Duplicate Number](https://leetcode.com/problems/find-the-duplicate-number/)

这题是一个道理，我们依然用 first missing positive 的做法，把每个数试图换到它对应的位置上；然后 nums 数组最后的那个元素，一定是 duplicate element，因为其他元素都归位之后，它没地方去。因为如果最后一个元素是唯一元素的话，它一定会被置换到正确位置上。

#### 好吧，写完了我才发现不让修改原数组= =

```java
public class Solution {
    public int findDuplicate(int[] nums) {
        for(int i = 0; i < nums.length; i++){
            if(nums[i] != i + 1 && nums[i] - 1 >= 0 && nums[i] - 1 < nums.length && nums[nums[i] - 1] != nums[i]){
                swap(nums, i, nums[i] - 1);
                i--;
            }
        }

        return nums[nums.length - 1];
    }

    private void swap(int[] nums, int a, int b){
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }
}
```

这题还有另一种很妖孽的写法，就是把它当成 LinkedList 做。。

<https://discuss.leetcode.com/topic/25913/my-easy-understood-solution-with-o-n-time-and-o-1-space-without-modifying-the-array-with-clear-explanation>

那么这道题又跟Find the Duplicate Number有什么关系呢？ 在table中每个元素存储的是table index，根据其跳转，其实就是把table中的元素作为指针来使用。就像我们在上题中也是在child node中存parent node的index，并做跳转。 我们结合Find the Duplicate Number这道题来看。这道题讨论区most voted的解法即是一个国人大神把它转化为了Linked List Cycle来做。 find linked list cycle是一道大家都很熟悉的题。而国人大神的解法就是把given array中的每一个元素当作指针来用跳转到下一个元素。 我们用A\[1, 4, 3, 2, 5, 2, 6]来做例：

当我们扫到A\[0]时，A\[0] == 1，把它作为指针来使用，我们跳到A\[1];

A\[1] == 4, 跳到A\[4];

A\[4] == 5, 跳到A\[5];

A\[5] == 2, 跳到A\[2];

A\[2] == 3, 跳到A\[3];

A\[3] == 2, 开始循环...

这道题之所以可以把元素作为指针来使用当然也是题干出的巧。

#### 这里要注意初始化问题，fast 要初始化成 nums\[nums\[0]]，不然死循环。

```java
public class Solution {
    public int findDuplicate(int[] nums) {
        if (nums.length > 1)
        {
            int slow = nums[0];
            int fast = nums[nums[0]];
            while (slow != fast)
            {
                slow = nums[slow];
                fast = nums[nums[fast]];
            }

            fast = 0;
            while (fast != slow)
            {
                fast = nums[fast];
                slow = nums[slow];
            }
            return slow;
        }
        return -1;
    }
}
```

## (G面经)

**然后是一道关于按照固定的顺序重新排序，多少次排序才能回到初始顺序的题目，我们总是把第x个位置的元素移到第y个位置，比如【0，1，2，3】就是完全不换顺序，一次排序就回去了；【3，2，1，0】是位置3和位置0互换，位置2和位置1互换，两次排序就能回到原顺序；而【1，2，3，0】就是每次把一个位置上的元素往后移动一个位置，那显然转一圈就回来了。**

举个例子，原来的input为\[20, 34, 7, 9], 排序要求为\[1, 3, 2, 0] 一次排序后： \[20, 34, 7, 9] -> \[34, 9, 7, 20] 二次排序后: \[34, 9, 7, 20] -> \[9, 20, 7, 34] 三次排序后： \[9, 20, 7, 34] -> \[20, 34, 7, 9] (此时和初始顺序一致，所以是用了三个iteration恢复原样)

排序规则\[1, 3, 2, 0]的意思是把在位置1的元素排到第0个位置，位置3的元素排到第2个位置，位置2的元素还是在位置2，位置0的元素放到位置3去。 排序规则不变，每次只是Input被按照规则改变了顺序。

这个小技巧也可以用在原帖第二轮第二题上。

原帖中Heliuhun对于这道题给了一个很精妙的解法。我们来分析一下这个解法。 用A\[a1, a2, a3, a4, a5], B\[1,0,4,2,3]做例: 我们并不在乎A中的每个数到底是什么。我们想知道的是A中的每个数按照B来移动，最少经过多少次可以回到原位。 对于a2来说，它经历一次移动从1来到了0的位置。再经历一次移动从0回到了1的位置。 对于a4来说，它经历一次移动从3来到了2的位置。再经历一次移动从2来到了4的位置。再经历一次移动从4回到了3的位置。 可以看到A中的元素是根据B来跳转的，并且把B中的元素当作指针来使用。B中的元素形成了一个或多个封闭的环状“linked list”。 按照Heliuhun的思路，我们需要找出B中有多少封闭的环状“linked list”，再找出它们的最小公倍数。 为什么是最小公倍数呢？ 拿上面的例子来说，当a2经过两次移动后回到原位时，a4还需要一次移动才能回到原位。最少经过6次移动，a2，a4以及a1，a3，a5才能同时回到原位。

#### 跳转序列可以拆成几个独立的环，同时也知道每个环的长度，然后求所有环长度的最小公倍数？比如\[1,0,4,2,3]就是0-1-0 和 2-3-4-2两个环，所以6次shuffle后变回原来的数组

#### Least common multiple = 最小公倍数
