「力扣」第 448 题:找到所有数组中消失的数字(桶的思想、哈希表、抽屉原理)

这一小节和大家分享的是「力扣」第 448 题:找到所有数组中消失的数字 的做法。

给定一个范围在 [ 1 , n ] [1, n] ( n n 为数组的大小) 的整型数组,数组中的元素有一些出现了两次,另一些只出现一次。

题目要求我们:

1、找到所有在 [ 1 , n ] [1, n] 范围之间没有出现在数组中的数字。

2、您能在不使用额外空间且时间复杂度为 O ( n ) O(n) 的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。

题目给出了一个示例:输入数组是:[4, 3, 2, 7, 8, 2, 3, 1]

它的长度为 8 8 ,根据题目意思,数组里的数值的范围就在 [1, 8]。我们从 1 1 开始到 8 8 一个一个地看,发现数字 2 2 3 3 出现了 2 2 次, 数字 5 5 6 6 分别出现了 1 1 次。因此输出:[5, 6]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-all-numbers-disappeared-in-an-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路分析

1、有了第 41 题的经验,题目告诉我们数字的范围是 [1, n] ,因此依然可以将数组本身当成哈希表,遍历到一个数的时候,我们就把它放到它应该放置的位置上。根据这个思路解题;

这个思路是符合线性时间复杂度 O ( N ) O(N) 要求的。

2、题目还要求我们不使用额外空间。我们知道在交换两个数组元素的的过程中,需要使用到一个额外的空间,那么怎么突破这个限制呢。这需要一些经验和位运算的知识。我说需要经验的意思就是这其实并不容易想到,如果一下子没有想到这个办法其实也没有关系。

  • 使用异或运算的性质交换两个变量的值。

这个异或运算的性质其实就是异或运算定义本身。由于异或运算是在二进制下不进位的加法运算,因此:

(1)如果 a ^ b = c,那么 a ^ c = bb ^ c = a 同时成立,利用这一条,可以用于交换两个变量的值;

(2)一个数 num 异或上同一个数(a 2 2 次,它的值不变:nums ^ a ^ a = num

于是交换两个变量的值,例如 ab,不使用第三个变量,可以这样做:

a = a ^ b
b = a ^ b
a = a ^ b

它的道理是这样的:

第 1 个数 第 2 个数
a b
ab b
ab a
b a

但是这种做法有一个坑,我们等将完这道题再说。

我们使用一个具体的例子分析:

  • 我们的目的是让 nums[i] 位于下标为 nums[i] - 1 的位置上。

最好的样子就是:

数值:[1, 2, 3, 4]
下标:[0, 1, 2, 3]

说明:让作为数值的 4 放在下标为 3 的位置上,这里数值和索引位置有一个长度为 1 的偏移。

假设当前遍历看到的 nums[i]4,我们需要看一下索引位置 3 上的元素是不是 4

(1)如果数组下标 3 的位置上不是数字 4 ,就需要把当前看到的 4 交换到数组下标 3 的位置上;

(2)如果索引位置 3 上已经是 4 了,当前看到的 4 不用管它,它是多余的,此时什么都不用做,继续看下一个数。

nums[i] 是数值,而 nums[i] - 1 表示索引位置。

(可以使用额外空间的写法)

Java 代码:

import java.util.ArrayList;
import java.util.List;

public class Solution {

    public List<Integer> findDisappearedNumbers(int[] nums) {
        int len = nums.length;
        List<Integer> res = new ArrayList<>(2);

        for (int i = 0; i < len; i++) {
            // 数字 4 应该在下标 3 的位置上
            while (nums[i] != nums[nums[i] - 1]) {
                swap(nums, i, nums[i] - 1);
            }
        }

        for (int i = 0; i < len; i++) {
            if (nums[i] != i + 1) {
                res.add(i + 1);
            }
        }
        return res;
    }

    private void swap(int[] nums, int index1, int index2) {
        int temp = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = temp;
    }
}

(不使用额外空间的写法)

Java 代码:

import java.util.ArrayList;
import java.util.List;

public class Solution {

    public List<Integer> findDisappearedNumbers(int[] nums) {
        List<Integer> res = new ArrayList<>(2);

        for (int i = 0; i < nums.length; i++) {
            // 数字 4 应该在下标 3 的位置上
            while (nums[i] != nums[nums[i] - 1]) {
                swap(nums, i, nums[i] - 1);
            }
        }

        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != i + 1) {
                res.add(i + 1);
            }
        }
        return res;
    }

    private void swap(int[] nums, int index1, int index2) {
        if (index1 == index2) {
            return;
        }
        nums[index1] = nums[index1] ^ nums[index2];
        nums[index2] = nums[index1] ^ nums[index2];
        nums[index1] = nums[index1] ^ nums[index2];
    }

    public static void main(String[] args) {
        Solution7 solution7 = new Solution7();
        int[] nums = {4, 3, 2, 7, 8, 2, 3, 1};
        List<Integer> res = solution7.findDisappearedNumbers(nums);
        System.out.println(res);
    }
}
发布了446 篇原创文章 · 获赞 334 · 访问量 124万+

猜你喜欢

转载自blog.csdn.net/lw_power/article/details/104338231