「LeetCode」136.只出现一次的数字

230 阅读3分钟

「这是我参与2022首次更文挑战的第16天,活动详情查看:2022首次更文挑战」。

题目描述🌍

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例 1

输入: [2,2,1]
输出: 1

示例 2

输入: [4,1,2,1,2]
输出: 4

几类「浅尝辄止」的解法🚧

解题思路

如果不考虑时间复杂度和空间复杂度,该题有各式各样的解法:

  1. 将数组排序,利用 nums[i] != nums[i + 1] 条件就可以找到落单元素,同时需要注意落单元素可能位于数组末尾,所以需要 i == nums.length - 1 判断。
  2. 使用集合存储元素,若数组中的元素在集合中不存在则添加,存在则移除,类似层叠消融的思想,最后返回集合中的唯一元素。
  3. 将数组中所有元素添加到哈希表中,此时哈希表中元素累加值的 2 倍与数组元素累加和的差值,就是只出现了一次的元素。
  4. 通过 HashMap 统计每一个元素出现的次数,返回只出现一次的那个数字。

⭐以下 4 种方法分别对应如上 4 种解法,虽然结果都不出意外的糟糕,但有想法总归是好的。

代码

Java

// 不考虑时间复杂度与空间复杂度!
class Solution {
    // 解法一
    public int singleNumber1(int[] nums) {
        Arrays.sort(nums);
        int index = 0;
        for (int i = 0; i < nums.length; i += 2) {
            if (i == nums.length - 1 || nums[i] != nums[i + 1]) {
                index = i;
                break;
            }
        }
        return nums[index];
    }
    
    // 解法二
    public int singleNumber2(int[] nums) {
        List<Integer> list = new ArrayList<>();
        for (int num : nums) {
            if (!list.contains(num)) {
                list.add(num);
            } else {
                list.remove((Integer) num);
            }
        }
        return list.get(0);
    }
    
    // 解法三
    public int singleNumber3(int[] nums) {
        Set<Integer> hashSet = new HashSet<>();
        int arrSum = 0;
        int hashSum = 0;

        for (int num : nums) {
            // 数组求和
            arrSum += num;
            // 哈希表: 过滤重复元素
            hashSet.add(num);
        }
        // 哈希表求和
        for (Integer integer : hashSet) {
            hashSum += integer;
        }
        return hashSum * 2 - arrSum;
    }
    
    // 解法四
    public int singleNumber4(int[] nums) {
        Map<Integer, Integer> hashMap = new HashMap<>();
        // 枚举每一个元素出现的次数
        for (int num : nums) {
            Integer count = hashMap.get(num);
            hashMap.put(num, count == null ? 1 : count + 1);
        }
        // HashMap 并未实现 Iterator 接口
        int singleNumber = 0;
        for (int key : hashMap.keySet()) {
            if (hashMap.get(key).equals(1)) {
                singleNumber = key;
            }
        }
        return singleNumber;
    }
}

C++

class Solution {
public:
    int singleNumber1(vector<int> &nums) {
        sort(nums.begin(), nums.end());
        int index = 0;
        int length = nums.size();
        for (int i = 0; i < length; i += 2) {
            if (i == length - 1 || nums[i] != nums[i + 1]) {
                index = i;
                break;
            }
        }
        return nums[index];
    }

    int singleNumber2(vector<int> &nums) {
        vector<int> container{};
        for (int num: nums) {
            auto pos = find(container.begin(), container.end(), num);
            if (pos != container.end()) {
                container.erase(pos);
            } else {
                container.push_back(num);
            }
        }
        return container.front();
    }

    int singleNumber3(vector<int> &nums) {
        set<int> hash;
        int numSum = 0;
        int hashSum = 0;

        for (int num: nums) {
            numSum += num;
            hash.insert(hash.begin(), num);
        }
        hashSum = std::accumulate(hash.begin(), hash.end(), hashSum);

        return 2 * hashSum - numSum;
    }

    int singleNumber4(vector<int> &nums) {
        unordered_map<int, int> times;
        for (int num: nums) {
            if (times.find(num) == times.end()) {
                times[num] = 1;
            } else {
                times[num] += 1;
            }
        }
        int singleNumber = 0;
        for (auto i: times) {
            if (i.second == 1) {
                singleNumber = i.first;
            }
        }
        return singleNumber;
    }
};

时间复杂度:O(n)O(n)

空间复杂度:O(n)O(n)

这几类解法通俗易懂,不过都需要额外使用 O(n)O(n) 的空间,如果要做到线性时间复杂度 O(n)O(n) 且不使用额外空间即空间复杂度为 O(1)O(1),那么就需要使用 位运算 完成。

位运算:异或求解🚀

解题思路

使用异或运算求解本题前,先来科普两点异或操作:

x 为任意数字

  • x ^ 0 = x
  • x ^ x = 0

数组中除了某一个元素 x 以外都是成对出现的,如果将数组中所有元素逐个进行异或运算,那么成对(相同)的元素都会被消除为 0,最后仅剩下 0x,其异或结果必然为所求的 只出现一次的数字

代码

Java

class Solution {
    public int singleNumber(int[] nums) {
        int result = 0;
        for (int num : nums) {
            result = result ^ num;
        }
        return result;
    }
}

C++

class Solution {
public:
    int singleNumber(vector<int> &nums) {
        int ret = 0;
        for (int num: nums) {
            ret = ret ^ num;
        }
        return ret;
    }
};

时间复杂度:O(n)O(n)

空间复杂度:O(1)O(1)

知识点🌓

所谓位运算就是将数字转化为二进制数字,然后按位逐个运算。

简单复习下 Java 中提供的 7 种位运算符号:

  • &
  • |
  • 异或 ^
  • 取反 ~
  • 左移 <<
  • 右移 >>
  • 无符号右移 >>>

最后🌅

该篇文章为 「LeetCode」 系列的 No.15 篇,在这个系列文章中:

  • 尽量给出多种解题思路
  • 提供题解的多语言代码实现
  • 记录该题涉及的知识点

👨‍💻争取做到 LeetCode 每日 1 题,所有的题解/代码均收录于 「LeetCode」 仓库,欢迎随时加入我们的刷题小分队!