标记法 - 缺失数字 - 力扣(LeetCode)

题目描述

给定一个包含 0, 1, 2, ..., n 中 n 个数的序列,找出 0 .. n 中没有出现在序列中的那个数。

示例 1:

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

示例 2:

输入: [9,6,4,2,3,5,7,0,1]
输出: 8

说明:
你的算法应具有线性时间复杂度。你能否仅使用额外常数空间来实现?

题解1

看到这个题的第一个思路是标记法,之前在那好像看见过,具体出处忘了了。

题意分析:题中n个元素取值为[0, n],且n个元素都不会重复,问题是让我们找到非负的元素。我们可以利用元素本身都是非负性,我们可以将存在的元素num,其下标对应的元素变成负的(0的负数仍为其本身,需特殊处理),即nums[abs[num]] = -nums[abs[num]]

代码1

/*
用value的正负来做标记,nums[idx]为负表示idx出现在序列中
*/
class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int len = nums.size();
        int res = -1, tmp = 0;
        bool flag = false;
        nums.push_back(1);//添加一个元素,避免标记时越界
        for(int i = 0; i < len; ++i){
            nums[abs(nums[i])] = - nums[abs(nums[i])];
        }
        for(int i = 0; i < len + 1; ++i){
            if(nums[i] > 0){
                res = i;
                flag = true;
                break;
            }
            if(nums[i] == 0){//对值为0的下标进行标记,用于处理特殊情况
                tmp = i;
            }
        }
        if(!flag){//当所有元素都非正时,表明缺失的下标的元素值为0
            res = tmp;
        }
        return res;
    }
};

参考LeetCode题解,题解2是求和的方法,题解3是异或的方法。

题解2

求和法。从[0, n]中取n个元素,总和为 n ( n + 1 ) 2 \frac{n*(n+1)}{2} ,减去选取元素之和,得到的就是缺失的元素。

/*
求和法
求和,再减去所选元素之和
*/
class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int len = nums.size();
        int res = -1, sum = len * (len + 1) / 2;
        for(auto num:nums){
            sum -= num;
        }
        res = sum;
        return res;
    }
};

正如该解法下的评论所言

高斯求和明显有溢出风险

所以有人对求和法进行了改进

直觉首先想到了数学方法,只需要遍历一遍数组,在把0-n这n个自然数全加起来的同时也减去nums[i],这样不但效率高,也防止了数据溢出

/*
求和法
求和,再减去所选元素之和
*/
class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int len = nums.size();
        int res = -1, sum = 0;
        for(int i = 1; i <= len; ++i){//边求和边减,防止溢出
            sum = sum + i - nums[i - 1];
        }
        res = sum;
        return res;
    }
};

题解3

既然可以先求和再减,那么只要先编码解码,采用两个互逆的操作,便可以找到缺失的元素。乘法和除法是两个互逆的操作,但用到这题溢出的可能性更大。异或也是个可逆操作。如
n u m 3 = n u m 1 n u m 2 n u m 1 = n u m 3 n u m 2 \begin{aligned} num3 = num1 \oplus num2 \\ num1 = num3 \oplus num2 \end{aligned}

异或这个很好的性质,有很大的作用,比如说硬盘的数据恢复,参照CCF 201903-3损坏的RAID5,这里用来寻找缺失的数字,类似于数据恢复。异或作为位运算,相比求和的方法,速度快的多。

/*
异或法
*/
class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int len = nums.size();
        int res = -1, sum = 0;
        for(int i = 1; i <= len; ++i){
            sum = sum ^ i ^ nums[i - 1];
        }
        res = sum;
        return res;
    }
};
发布了169 篇原创文章 · 获赞 35 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/happyeveryday62/article/details/104166488