算法通过村第三关-数组黄金笔记|数组难解

news2024/12/23 6:28:49

文章目录

  • 前言
    • 数组中出现超过一半的数字
    • 数组中只出现一次的数字
    • 颜色的分类问题(荷兰国旗问题)
      • 基于冒泡排序的双指针(快慢指针)
      • 基于快排的双指针(对撞指针)
  • 总结


前言


提示:苦不来自外在环境中的人、事、物,只是自内的妄想和执着。 --金惟纯

这里整理一些比较经典的题目,巩固一下数组的学习。

数组中出现超过一半的数字

题目介绍参考:剑指 Offer 39. 数组中出现次数超过一半的数字 - 力扣(LeetCode)
在这里插入图片描述
对于这种题目,如果没有思路的话,我们可以先把常见的数据结构和算法策略过一般,这里参考以前的文章巩固一下。算法通关村第一关-链表白银挑战笔记|公共子节点_师晓峰的博客-CSDN博客
在这里插入图片描述
我们想一下,查找统计出所有出现的个数,大于一半就可以,这不就是一种想法🥰,排序行不行呢,对数组进行排序,超过一半必定是中位数。

如果不了解中位数这个概念的:
🐟聪明回答:

在数学中,中位数指的是一组数字排序后的中间值,即将一组数字从小到大排列,中间的那个数就是中位数。如果一组数字有奇数个,那么中位数就是排序后的中间数;如果一组数字有偶数个,那么中位数是中间两个数的平均值。中位数可以用来表示一组数据的中心趋势,较为稳定而不易受极端值的影响。

但是如果你不放心的话可以在遍历一遍数组,确定这个数组是否超过一半,所以第二种方法就出来了。这种方法的时间复杂度取决于排序算法的时间复杂度,最快的话O(nlogn),排序的代价比较高,我们试想一下还有别的方法吗💡

我们使用Hash行不行呢?我们先创建一个HashMap的key是元素的值,value是已经出现的次数,然后遍历数组来统计所有元素出现的次数,最后在遍历一边Hash,找到次数超过一半的数字,这不又一种方法出来了。

我们展示一下代码:

	/**
     * 方法1 基于Hash
     * @param array
     * @return
     */
    public static int moreThanHalfNum(int[] array) {
        // 参数校验
        if (array == null || array.length == 0) {
            return 0;
        }
        HashMap<Integer, Integer> res = new HashMap<>();
        int len = array.length;
        for (int i = 0; i < len; i++) {
            res.put(array[i], res.getOrDefault(array[i], 0) + 1);
            if (res.get(array[i]) > len / 2){
                return array[i];
            }
        }
        return 0;
    }

当然采用Hash的方法可以解决,但是这里最多给70,着并不是最优的结果,那么有没有巧妙的方法呢?

拓展:采用上面你的算法时间复杂度为O(n),但是这是用空间复杂度O(n)换来的,那么有没有空间复杂度为O(1)且时间负责度为O(n)的呢?💡

你听说过摩尔投票法则吗?它用来解决多数问题(中位数)可以说是一把利刃。

🐟聪明的回答:

摩尔投票法(Moore’s Voting Algorithm)是一种用于在数组或列表中查找出现次数超过一半的元素的算法。该算法基于以下观察:如果一个元素出现次数超过一半,那么它在数列中出现的次数一定比其他所有元素出现次数之和还要多。

算法步骤如下:

  1. 初始化两个变量:候选元素(candidate)和计数器(count),候选元素用于保存当前被选中的元素,计数器用于统计候选元素的出现次数。
  2. 遍历整个数组或列表,对于每一个元素:
    • 如果计数器为0,将当前元素设为候选元素,将计数器设为1。
    • 否则,如果当前元素与候选元素相同,计数器加1;否则,计数器减1。
  3. 完成遍历后,最后留下的候选元素就是候选元素,但这并不代表它一定是超过一半的元素,只是候选元素的可能性更高。
  4. 最后,再次遍历整个数组或列表,统计候选元素的出现次数。如果它的出现次数确实超过一半,那么它就是超过一半的元素;否则,不存在超过一半的元素。

下面给出一个例子来解释摩尔投票法:
假设我们有一个数组:[2, 4, 5, 2, 2]。

  • 遍历第一个元素2,将其设为候选元素,计数器为1。
  • 遍历第二个元素4,与候选元素不同,计数器减1。
  • 遍历第三个元素5,与候选元素不同,计数器减1。
  • 遍历第四个元素2,与候选元素相同,计数器加1。
  • 遍历第五个元素2,与候选元素相同,计数器加1。
    此时,计数器为2,最后剩下的候选元素是2。
    再次遍历整个数组,统计元素2的出现次数,发现它出现了3次,大于数组长度的一半,所以2就是超过一半的元素。
	/**
     * 方法二:比较特殊的计数法
     *
     * @param array
     * @return
     */
    public static int moreThanHalfNum2(int[] array) {
        if (array == null || array.length == 0) {
            return 0;
        }
        int count = 0;
        Integer candidate = null;
        for (int num : array) {
            if (count == 0) {
                candidate = num;
            }
            count += (num == candidate) ? 1 : -1;
        }
        // check 记得在检查一边
        count = 0;
        int len = array.length;
        for (int num : array) {
            if (num == candidate) {
                count++;
            }
            if (count >= len / 2) {
                return candidate;
            }
        }
        return candidate;
    }

Q&A

Q : 这里问什么要在检查一边,可以不检查?会出现什么问题?

A :必须再检查一边,这里是确保candidate一定是超出一半的数,不检查投出的结果不一定正确[1,2,3],有结果,但是不符合要求。

数组中只出现一次的数字

参考题目介绍:136. 只出现一次的数字 - 力扣(LeetCode)
在这里插入图片描述
在这里插入图片描述
这个题用Set集合解决比较好,Set集合不存储重复元素,这个是该集合的特性。题目说明其他元素都是出现两次,我们刚好可以利用这个操作,当要添加元素的key与集合中已存在的数出现重复的时候,我们就不进行操作,并且将这个key一起删掉,确保只存在一个数,这样遍历一边就可以知道答案了。【注意:确保集合有元素 】

	/**
     * 基于集合寻找
     *
     * @param arr
     * @return
     */
    public static Integer findOneNum(int[] arr) {
        // 校验参数
        if (arr == null || arr.length == 0){
            return null;
        }
        // 特殊处理
        if (arr.length == 1) {
            return arr[0];
        }
        HashSet<Integer> res = new HashSet<>();
        for(int i = 0; i < arr.length; i++){
            if (!res.add(arr[i])){
                res.remove(arr[i]);
            }
        }
        // check 确保set集合存在元素
        if (res.size() == 0){
            return null;
        }
        return (Integer) res.toArray()[0];
    }

当然这个方法也不是最优解,算法就是这么奇妙,有时后令人讨厌,有时候让你欣喜。提示一下,可以想一想位运算来解决:你知道异或这个操作吗?

🐟聪明回答:

计算机中的异或操作(XOR),也称为“排他性或”操作,是一种逻辑运算,用于比较两个值的不同之处。异或操作有以下几条规则:

  1. 同一个值与自身进行异或操作结果为0:A ⊕ A = 0
  2. 任意值与0进行异或操作结果不变:A ⊕ 0 = A
  3. 异或操作满足交换律:A ⊕ B = B ⊕ A
  4. 异或操作满足结合律:(A ⊕ B) ⊕ C = A ⊕ (B ⊕ C)
  5. 异或操作满足自反性:A ⊕ B ⊕ A = B

举例来说明:

  1. 与自身进行异或操作:7 ⊕ 7 = 0
  2. 与0进行异或操作:5 ⊕ 0 = 5
  3. 交换律:3 ⊕ 5 = 5 ⊕ 3
  4. 结合律:(2 ⊕ 4) ⊕ 6 = 2 ⊕ (4 ⊕ 6)
  5. 自反性:2 ⊕ 4 ⊕ 2 = 4

异或操作在计算机中有很多应用,其中一个常见的应用是用于交换两个变量的值。例如,假设有两个变量a和b,我们可以使用异或操作进行交换如下:

a = a ⊕ b;
b = a ⊕ b;
a = a ⊕ b;

在经过以上操作后,a和b的值就被交换了。

看到了吗?我们可以根据这个自反性质得到我们想要的答案,是不是非常简单😁。

遍历一边就能拿到想要的结果,代码展示:

	/**
     * 基于位运算
     * 0 ^ * = *
     * A ^ B ^ A = B
     * @param arr
     * @return
     */
    public static int findOneNum2(int[] arr) {
        int res = 0;
        for(int num : arr){
            res ^= num;
        }
        return res;
    }

颜色的分类问题(荷兰国旗问题)

参考题目介绍:75. 颜色分类 - 力扣(LeetCode)
在这里插入图片描述
在这里插入图片描述
那我们就来认识一下荷兰的国旗吧:
在这里插入图片描述
感兴趣的可以看看历史:荷兰和俄罗斯的国旗为什么高度相似?到底是谁抄袭谁? (baidu.com)

这个问题很典型,双指针问题,当然可以采用多种方式的双指针解决,我们研究第一种与冒泡类似的,第二种与快排类似。

基于冒泡排序的双指针(快慢指针)

冒泡排序我们都知道,就是根据大小逐步和后面的比较,慢慢调整到整体有序的状态,这种方法是比较稳定的排序方法。

当然我们可以这样考虑:

  1. 第一次遍历,我们将数组中所有0交换到数组的头部
  2. 第二次遍历,只需要处理1和2。

漂亮的双指针代码如下:

 public static void sortColors(int[] nums) {
        // 定义快慢指针
        int n = nums.length;
        int left = 0;
        // 先处理 0 把0移到最左边
        for(int right = 0; right < n; right++) {
            if (nums[right] == 0){
                swap(nums,left,right);
                left++;
            }

        }
        // 接着处理1 把1移到次走遍
        for(int right = left; right < n; right++){
            if (nums[right] == 1){
                swap(nums,left,right);
                left++;
            }
        }

    }
	
	// 采用位运算的方式交换
    public static void swap(int[] nums, int left, int right) {
        nums[left] = nums[left] ^ nums[right];
        nums[right] = nums[left] ^ nums[right];
        nums[left] = nums[left] ^ nums[right];
    }

这里解决的话效果还是可以的,但是如果再进一步问你,可以一次遍历就解决吗?你就要考虑第二种方法了。

基于快排的双指针(对撞指针)

如果要求一次遍历就解决问题,我们要怎么想办法呢?隐约觉得需要使用三个指针才可以:

  • left指针,表示left左侧的元素都是0
  • right指针,便是right右侧的元素都是2
  • index指针,从头到尾遍历数组,根据nums[index]是0还是2决定与left交换还是和right交换

index的位置上的元素代表我们将要处理的数字。index为1,我们不需要做什么,直接+ 1,如果是0,放在左边,如果是2,放在右边,当index == right的时候就可以停止了。

我们画图表示一下:
在这里插入图片描述
这里面的重点在于index的位置为2的时候进行交换后为right–,index不做处理,当然这里考虑到了,index 为 1的情况,所以先不动,1 比较特殊,跳过去就没法处理了。

那么问题来了,index 为0的时候执行交换的话index++,如果存在都会被交换到右边,这里只需要处理1和0的问题就可以了。

代码如下:

 	 /**
     * 采用位运算的方式交换
     * @param nums
     * @param left
     * @param right
     */
    public static void swap(int[] nums, int left, int right) {
        nums[left] = nums[left] ^ nums[right];
        nums[right] = nums[left] ^ nums[right];
        nums[left] = nums[left] ^ nums[right];
    }

    public static void sortColors(int[] nums) {
        // 定义快慢指针
        int left = 0 , index = 0,right = nums.length - 1;
        while(index <= right) {
            if (nums[index] == 2){
                swap(nums,index,right--);
            }else if (nums[index] == 0){
                swap(nums,index++,left++);
            }else {
                index++;
            }
        }
    }

总结

注意:双指针问题,边界和条件。

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

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

相关文章

最新人工智能源码搭建部署教程/ChatGPT程序源码/AI系统/H5端+微信公众号版本源码

一、AI系统 如何搭建部署人工智能源码、AI创作系统、ChatGPT系统呢&#xff1f;小编这里写一个详细图文教程吧&#xff01; SparkAi使用Nestjs和Vue3框架技术&#xff0c;持续集成AI能力到AIGC系统&#xff01; 1.1 程序核心功能 程序已支持ChatGPT3.5/GPT-4提问、AI绘画、…

Vue3.0极速入门- 目录和文件说明

目录结构 以下文件均为npm create helloworld自动生成的文件目录结构 目录截图 目录说明 目录/文件说明node_modulesnpm 加载的项目依赖模块src这里是我们要开发的目录&#xff0c;基本上要做的事情都在这个目录里assets放置一些图片&#xff0c;如logo等。componentsvue组件…

加快edge网页的下载速度

1、 edge://flags/#enable-parallel-downloading、 2、 点击enabled

Leetcode每日一题:1448. 统计二叉树中好节点的数目(2023.8.25 C++)

目录 1448. 统计二叉树中好节点的数目 题目描述&#xff1a; 实现代码与解析&#xff1a; dfs 原理思路&#xff1a; 1448. 统计二叉树中好节点的数目 题目描述&#xff1a; 给你一棵根为 root 的二叉树&#xff0c;请你返回二叉树中好节点的数目。 「好节点」X 定义为&…

谈谈子网划分的定义、作用、划分方式以及案例

个人主页&#xff1a;insist--个人主页​​​​​​ 本文专栏&#xff1a;网络基础——带你走进网络世界 本专栏会持续更新网络基础知识&#xff0c;希望大家多多支持&#xff0c;让我们一起探索这个神奇而广阔的网络世界。 目录 一、子网划分的定义 二、子网掩码的作用 1、…

[管理与领导-51]:IT基层管理者 - 8项核心技能 - 6 - 流程

前言&#xff1a; 管理者存在的价值就是制定目标&#xff0c;即目标管理、通过团队&#xff08;他人&#xff09;拿到结果。 要想通过他人拿到结果&#xff1a; &#xff08;1&#xff09;目标&#xff1a;制定符合SMART原则的符合业务需求的目标&#xff0c;团队跳一跳就可以…

【mysql是怎样运行的】-EXPLAIN详解

文章目录 1.基本语法2. EXPLAIN各列作用1. table2. id3. select_type4. partitions5. type 1.基本语法 EXPLAIN SELECT select_options #或者 DESCRIBE SELECT select_optionsEXPLAIN 语句输出的各个列的作用如下&#xff1a; 列名描述id在一个大的查询语句中每个SELECT关键…

Unity——后期处理举例

Post Processing&#xff08;后期处理&#xff09;并不属于特效&#xff0c;但现代的特效表现离不开后期处理的支持。本文以眩光&#xff08;Bloom&#xff09;为例&#xff0c;展示一种明亮的激光的制作方法 1、安装后期处理扩展包 较新的Unity版本已经内置了新版的后期处理扩…

2022年03月 C/C++(四级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题&#xff1a;拦截导弹 某国为了防御敌国的导弹袭击&#xff0c; 发展出一种导弹拦截系统。 但是这种导弹拦截系统有一个缺陷&#xff1a; 虽然它的第一发炮弹能够到达任意的高度&#xff0c;但是以后每一发炮弹都不能高于前一发的高度。 某天&#xff0c; 雷达捕捉到敌国的…

Linux内核学习(十)—— 块 I/O 层(基于Linux 2.6内核)

目录 一、剖析一个块设备 二、缓冲区和缓冲区头 三、bio 结构体 四、请求队列 五、I/O 调度程序 系统中能够随机&#xff08;不需要按顺序&#xff09;访问固定大小数据片&#xff08;chunks&#xff09;的硬件设备称作块设备&#xff0c;这些固定大小的数据片就称作块。最…

项目进度管理(4-1)关键链法

1 关键链法产生的背景 关键链法&#xff08;Critical Chain Method&#xff0c;CCM&#xff09;起源于20世纪80年代&#xff0c;是由Eliyahu M. Goldratt在他的著作《关键链》&#xff08;"Critical Chain"&#xff09;中首次提出和阐述的。Eliyahu M. Goldratt是以…

Java 时间String转Date类型

Testpublic void testTime() throws ParseException {SimpleDateFormat format new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//法一&#xff1a;String time111 "2023-08-14 18:13:10";Date date format.parse(time111);System.out.println("法一…

4.18 TCP 和 UDP 可以使用同一个端口吗?

目录 TCP 和 UDP 可以同时绑定相同的端口吗&#xff1f; 多个 TCP 服务进程可以绑定同一个端口吗&#xff1f; 重启 TCP 服务进程时&#xff0c;为什么会有“Address in use”的报错信息&#xff1f; 重启 TCP 服务进程时&#xff0c;如何避免“Address in use”的报错信息…

Java之API详解之System类的详解

2 System类 2.1 概述 tips&#xff1a;了解内容 查看API文档&#xff0c;我们可以看到API文档中关于System类的定义如下&#xff1a; System类所在包为java.lang包&#xff0c;因此在使用的时候不需要进行导包。并且System类被final修饰了&#xff0c;因此该类是不能被继承的。…

【C++】C++ 引用详解 ⑤ ( 函数 “ 引用类型返回值 “ 当左值被赋值 )

文章目录 一、函数返回值不能是 " 局部变量 " 的引用或指针1、函数返回值常用用法2、分析函数 " 普通返回值 " 做左值的情况3、分析函数 " 引用返回值 " 做左值的情况 函数返回值 能作为 左值 , 是很重要的概念 , 这是实现 " 链式编程 &quo…

我的128天创作纪念日-东离与糖宝

文章目录 机缘收获日常成就憧憬 不知不觉我也迎来了自己的128天创作纪念日&#xff0c;一起来看看我有什么想对大家说的吧 机缘 我的写博客之旅始于参加了代码随想录算法训练营。在训练营期间&#xff0c;代码随想录作者卡尔建议我们坚持每天写博客记录刷题学习的进度和心得体…

Jira vs Trello:项目管理的深层巅峰对决

引言 项目管理在现代企业运作中起着至关重要的作用。从跨国公司的巨大项目&#xff0c;到创业公司的快速反应&#xff0c;再到个人的日常任务管理&#xff0c;一个好的项目管理工具可以有效地跟踪进度&#xff0c;优化资源分配&#xff0c;确保项目在预定时间内完成。今天&…

银河麒麟服务器、centos7服务器mysql离线安装:通过获取临时密码进行登录修改新密码

离线安装脚本 cd /home/zenglg/mysql5.7# 判断mysql是否安装# 下面这种方法必须是rpm安装的判断才有效&#xff0c;不通用# IS_INSTALLED$(rpm -qa |grep mysql)# if [ $? -eq 0 ]# 下面的判断方法是查询版本号&#xff0c;比较通用SQL_VERSIONmysql -V | grep -i -o -P 5.7.4…

Kotlin协程flow发送时间间隔debounce

Kotlin协程flow发送时间间隔debounce debounce的作用是让连续发射的数据之间间隔起来。典型的应用场景是搜索引擎里面的关键词输入&#xff0c;当用户输入字符时候&#xff0c;有时候&#xff0c;并不希望用户每输入任何一个单字就触发一次后台真正的查询&#xff0c;而是希望…

简单了解文件上传漏洞(md版)

简单了解文件上传漏洞 一、什么是文件上传漏洞二、常见功能点三、成功的前提四、文件上传的校验方式五、Pass-1六、Pass-2七、蚁剑结合msf获取目标权限 一、什么是文件上传漏洞 在文件上传的功能处&#xff0c;如果服务端未对上传的文件进行严格的验证和过滤&#xff0c;导致攻…