面试热题(缺失的第一个正数)

news2025/1/2 0:15:27

给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。

请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。

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

 尝试的路途是痛苦的,不断的尝试新方法,错何尝不是一种乐趣

  • 纯暴力(双层for循环)
 public int firstMissingPositive(int[] nums) {
     for (int i = 1; i <= nums.length; i++) {
            boolean has = false;
            for (int j = 0; j < nums.length; j++) {
                if (nums[j] == i) {
                    has = true;
                    break;
                }
            }
            if (!has) {
                //没有找到这个数,直接返回
                return i;
            }
        }
        return nums.length + 1;
    }

       第一层循环遍历[1,nums.length]的元素,第二层元素查看当前数组中是否存在,第一个不存在的就是第一个缺失的整数,这种纯暴力搜,案例肯定能过,但是时间复杂度过高,一般都会超时

  • 排序+二分查找

       二分查找的时间复杂度是O(logn)的,这种一般是不会超时,但是我们要先将数组进行排序,因为二分查找的前提条件是具有单调性

 Arrays.sort(nums);

       遍历[1,nums.length]中的元素,通过二分搜索去在排序后的数组中查找,第一次没有查到就是第一个缺失的整数

for(int i=1;i<=nums.length;i++){
      int res=binarySearch(nums,i);
      if(res==-1){
          return i;
      }
    }

 普通的二分搜索:

public int binarySearch(int[] nums,int target){
        int left=0;
        int right=nums.length-1;
        while(left<=right){
            int mid=left+(right-left)/2;
            if(nums[mid]==target){
                return mid;
            }else if(nums[mid]>target){
                right=mid-1;
            }else{
                left=mid+1;
            }
        }
        return -1;
    }

  • 哈希表
           利用哈希表对原数组进行一个存储,遍历[1,nums.length]中的元素,如果在set中不存在,就是第一个缺少的整数
     
     public int firstMissingPositive(int[] nums) {
        int len = nums.length;
            Set<Integer> hashSet = new HashSet<>();
            for (int num : nums) {
                hashSet.add(num);
            }
            for (int i = 1; i <= len; i++) {
                if (!hashSet.contains(i))
                    return i;
            }
            return len + 1;}

  • 位图

       假设我们使用一个位图(bitmap)来表示集合,其中每一位代表一个元素是否存在于集合中。但是这种位图只适合集合数量不是太多的情况,显然这道题不满足这个条件

错误实例:

 public int firstMissingPositive(int[] nums) {
        int len = nums.length;
    int hash = 0;
    for (int num : nums) {
        if (num > 0&&num<=nums.length) {  // 忽略非正整数
            hash |= 1 << (num - 1);//将当前元素加入位图
        }
    }

    for (int i = 1; i <= len + 1; i++) {
        //判断当前元素是否存在于位图
        if ((hash & (1 << (i - 1))) == 0) {
            return i;
        }
    }

    return len + 1; 
 }

 基本也能过个80%+,但是我们怎么才能巧妙的利用这种方式去解决这个问题呢?

public int firstMissingPositive(int[] nums) {
       public int firstMissingPositive(int[] nums) {
     int length = nums.length;
        int bit[] = new int[((length - 1)>>5) + 1];
        for (int i = 0; i < nums.length; i++) {
            int digit = nums[i];
            //数组必须在1到length之间才有效
            if (digit >= 1 && digit <= length) {
                int index = (digit - 1)>>5;
                //x%N  如果N是2的次数可以写成 x&(N-1)
                bit[index] |= (1 << ((digit - 1) & 31));
            }
        }
        //最后在执行一遍循环,查看对应位置的元素是否正确,如果不正确直接返回
        for (int i = 0; i < nums.length; i++) {
            if ((bit[i >>5] & (1 << (i & 31))) == 0)
                return i + 1;
        }
        return length + 1; 
 }
    }

  • 置换

置换顾名思义就是通过不断的交换将数组中的值和索引相对应

       通过的不断的置换,对应的位置与相对应的索引进行匹配完成,再遍历原数组,第一个数值和索引不匹配的就是第一个缺失的整数

 public int firstMissingPositive(int[] nums) {
      if(nums==null||nums.length==0){
          return 0;
      }
      //置换 值和索引相匹配
      for(int i=0;i<nums.length;i++){
          while(nums[i]>=1&&nums[i]<=nums.length&&nums[nums[i]-1]!=nums[i]){
              int temp=nums[nums[i]-1];
              nums[nums[i]-1]=nums[i];
              nums[i]=temp;
          }
      }
      for(int i=0;i<nums.length;i++){
          if(nums[i]!=i+1){
              return i+1;
          }
      }
      return nums.length+1;
    }

  • 对应法

      我们可以把每个元素存放到对应的位置,比如1存放到数组的第一个位置,3存放到数组的第3个位置, 如果是非正数或者大于数组的长度的值,我们不做处理,最后在遍历一遍数组,如果位置不正确,说明这个位置没有这个数,我们就直接返回

image.png

image.png

image.png

 public int firstMissingPositive(int[] nums) {
 for (int i = 0; i < nums.length; i++) {
            //如果在指定的位置就不需要修改
            if (i + 1 == nums[i])
                continue;
            int x = nums[i];
            if (x >= 1 && x <= nums.length && x != nums[x - 1]) {
                swap(nums, i, x - 1);
                i--;//抵消上面的i++,如果交换之后就不++;
            }
        }
        //最后在执行一遍循环,查看对应位置的元素是否正确,如果不正确直接返回
        for (int i = 0; i < nums.length; i++) {
            if (i + 1 != nums[i])
                return i + 1;
        }
        return nums.length + 1;
    }

    //交换两个数的值
    public void swap(int[] A, int i, int j) {
        if (i != j) {
            A[i] ^= A[j];
            A[j] ^= A[i];
            A[i] ^= A[j];
        }
    }
  • 标记法

  1. 因为数组中中按道理只允许出现[1,nums.length]的数字,所以我们首先可以先对数组中的元素进行处理,将大于等于数组长度和小于等于0的元素值改为nums.length+1(这里只要不是合理区间内的值都可以)
  2. 遍历数组中的每一个元素,加假如遍历到3,因为数组中的索引是从0开始的,所以我们要把索引为2的值变为负数(负数相当于一个标志,如果一个索引的值为负数,证明该数已经出现过),如果相同的数岂不是给一个数加负号两次,负负得正,就相当于没有出现,所以我们为了避免这种情况,每次取负数的时候,都将原数字取绝对值以后再进行取反
  3. 遍历修改后的数字,碰到第一个非负数,该数对应的索引+1就是我们缺失的第一个正数(正数说明没有其值进行标记)
public int firstMissingPositive(int[] nums) {
     //对入参进行判断
     if(nums==null||nums.length==0){
         return 0;
     }
     //将数组中大于数组长度的和小于等于零的值进行替换
     for(int i=0;i<nums.length;i++){
        if(nums[i]<=0||nums[i]>nums.length+1){
            nums[i]=nums.length+1;
        }
     }
     //遍历每一个元素,进行映射,符号代表该索引的整数已经出现过
     for(int i=0;i<nums.length;i++){
         int num=Math.abs(nums[i]);
         if(num<=nums.length){
         nums[num-1]=-Math.abs(nums[num-1]);
     }
}
   
     for(int i=0;i<nums.length;i++){
         if(nums[i]>0){
             return i+1;
         }
     }
     return nums.length+1;
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/878618.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

嵌入式Linux开发实操(八):UART串口开发

串口可以说是非常好用的一个接口,它同USB、CAN、I2C、SPI等接口一样,为SOC/MCU构建了丰富的接口功能。那么在嵌入式linux中又是如何搭建和使用UART接口的呢? 一、Console接口即ttyS0 ttyS0通常做为u-boot(bootloader的一种,像是Windows的BIOS),它需要一个交互界面,一般…

开源数据库Mysql_DBA运维实战 (DCL/日志)

SQL&#xff08;Structured Query Language 即结构化查询语言&#xff09; a.DDL语句 数据库定义语言&#xff1a; 数据库&#xff0c;表&#xff0c;视图&#xff0c;索引&#xff0c;存储过程&#xff0c;函数&#xff0c;创建删除ALTER(CREATE DROP ALTER) b.DML语句 数…

Mybatis 简单试用

1. 创建项目 IDEA创建一个基本的maven项目。 导入依赖&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xs…

测试人进阶技能:单元测试报告应用指南

为什么需要单元测试 从产品角度而言&#xff0c;常规的功能测试、系统测试都是站在产品局部或全局功能进行测试&#xff0c;能够很好地与用户的需要相结合&#xff0c;但是缺乏了对产品研发细节&#xff08;特别是代码细节的理解&#xff09;。 从测试人员角度而言&#xff0…

Python程序设计基础:函数(二)

文章目录 一、lambda()函数二、递归函数三、变量的作用域 一、lambda()函数 lambda()函数是一种简便的&#xff0c;将函数定义在同一行的函数方法。lambda()实际上生成了一个函数对象&#xff08;匿名函数&#xff09;&#xff0c;它主要用于需要函数对象作为参数或函数比较简…

实时安全分析监控加强网络安全

网络犯罪分子只需几分钟&#xff0c;有时甚至几秒钟即可泄露敏感数据。但是&#xff0c;IT 团队可能无法在数周内发现这些违规行为。通常&#xff0c;这些违规行为是由外部方或客户发现的&#xff0c;到那时为时已晚。随着网络漏洞的激增&#xff0c;对安全分析的需求空前高涨。…

运行python安装包没找到

一、错误信息 ImportError: dlopen(/Users/menghuiding/Library/Python/3.8/lib/python/site-packages/PIL/_imaging.cpython-38-darwin.so, 0x0002): tried: /Users/menghuiding/Library/Python/3.8/lib/python/site-packages/PIL/_imaging.cpython-38-darwin.so (mach-o fil…

less基本使用

1 less中的变量 //对值进行声明 link-color: #ccc//定义变量名称 .{sleName} {}bg: background-color; //定义属性名称 .container {{bg}: red; }2 继承&#xff08;复用重复样式&#xff09; //继承必须位于选择器最后 //继承选择器名不能为变量 .a:hover:extend(.b) {}.a {…

使用ggplot2绘制热图(1)

#使用ggplot2绘制热图 library(ggplot2) #清空 rm(listls()) gc() # 创建数据集 data <- mtcars # 计算相关系数矩阵 cor_matrix <- cor(data) # 将相关系数矩阵转换为长格式&#xff08;用于绘制热图&#xff09; cor_data <- reshape2::melt(cor_matrix) str(cor_da…

揭秘国产chatGPT大语言模型能力PK之001:ChatGLM1-2

一、问题清单: 1.我女朋友的老公应该叫我什么&#xff1f; 2.为什么我的女朋友还有老公&#xff1f; 3.你觉得我应该怎么做&#xff1f; 4.我想要获得爱情&#xff0c;我女朋友也爱我&#xff0c;我女朋友也爱她的老公&#xff0c;但他的老公恨我&#xff0c;我该怎么办&am…

基于微服务+Java+Spring Cloud +Vue+UniApp +MySql实现的智慧工地云平台源码

基于微服务JavaSpring Cloud VueUniApp MySql开发的智慧工地云平台源码 智慧工地概念&#xff1a; 智慧工地就是互联网建筑工地&#xff0c;是将互联网的理念和技术引入建筑工地&#xff0c;然后以物联网、移动互联网技术为基础&#xff0c;充分应用BIM、大数据、人工智能、移…

【深度学习】NLP中的对抗训练

在NLP中&#xff0c;对抗训练往往都是针对嵌入层&#xff08;包括词嵌入&#xff0c;位置嵌入&#xff0c;segment嵌入等等&#xff09;开展的&#xff0c;思想很简单&#xff0c;即针对嵌入层添加干扰&#xff0c;从而提高模型的鲁棒性和泛化能力&#xff0c;下面结合具体代码…

剑指offer(C++)-JZ56:数组中只出现一次的两个数字(算法-位运算)

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 题目描述&#xff1a; 一个整型数组里除了两个数字只出现一次&#xff0c;其他的数字都出现了两次。请写程序找出这两个只出现一…

DoIP学习笔记系列:(五)“安全认证”的.dll从何而来?

文章目录 1. “安全认证”的.dll从何而来?1.1 .dll文件base1.2 增加客户需求算法传送门 DoIP学习笔记系列:导航篇 1. “安全认证”的.dll从何而来? 无论是用CANoe还是VFlash,亦或是编辑cdd文件,都需要加载一个与$27服务相关的.dll(Windows的动态库文件),这个文件是从哪…

ES踩坑记录之UNASSIGNED分片无法恢复

问题背景 换节点 我们线上有一套ES集群&#xff0c;三台机器&#xff0c;共运行了6个节点。一直在线上跑了几个月也一直没出什么问题。然而好巧不巧&#xff0c;就在昨天&#xff0c;集群中的3号节点磁盘出现故障&#xff0c;导致机器直接瘫痪。本来大家觉得问题不大&#xf…

Lua学习记录

Lua基础了解 Lua的注释通过 (-- 单行注释&#xff0c;--[[ ]] 多行注释)可以不加&#xff1b; 多个变量赋值&#xff0c;按顺序赋值&#xff0c;没有则为nil&#xff1b; function的简单用法&#xff0c;多个返回值配合多重赋值&#xff0c;以end为结束标志 Lua下标从1开始&…

R语言生存分析(机器学习)(1)——GBM(梯度提升机)

GBM是一种集成学习算法&#xff0c;它结合了多个弱学习器&#xff08;通常是决策树&#xff09;来构建一个强大的预测模型。GBM使用“Boosting”的技术来训练弱学习器&#xff0c;这种技术是一个迭代的过程&#xff0c;每一轮都会关注之前轮次中预测效果较差的样本&#xff0c;…

二叉树题目:二叉树的直径

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;二叉树的直径 出处&#xff1a;543. 二叉树的直径 难度 3 级 题目描述 要求 给定二叉树的根结点 root \texttt{root} root&#xff0c;返回其直径…

Docker 基本管理(一)

目录 一、虚拟化简介 1.1.虚拟化概述 1.2.cpu的时间分片&#xff08;cpu虚拟化&#xff09; 1.3.cpu虚拟化性性能瓶颈 1.4.虚拟化工作原理 1.5 虚拟化类型 1.6 虚拟化功能 ​二、Docker容器概述 2.1 docker是什么&#xff1f; 2.2 使用docker有什么意义&#xff…

nginx上web服务的基本安全优化、服务性能优化、访问日志优化、目录资源优化和防盗链配置简介

一.基本安全优化 1.隐藏nginx软件版本信息 2.更改源码来隐藏软件名和版本 &#xff08;1&#xff09;修改第一个文件&#xff08;核心头文件&#xff09;&#xff0c;在nginx安装目录下找到这个文件并修改 &#xff08;2&#xff09;第二个文件 &#xff08;3&#xff09;…