算法通过村第三关-数组黄金笔记|数组难解
文章目录
- 前言
- 数组中出现超过一半的数字
- 数组中只出现一次的数字
- 颜色的分类问题(荷兰国旗问题)
- 基于冒泡排序的双指针(快慢指针)
- 基于快排的双指针(对撞指针)
- 总结
前言
提示:苦不来自外在环境中的人、事、物,只是自内的妄想和执着。 --金惟纯
这里整理一些比较经典的题目,巩固一下数组的学习。
数组中出现超过一半的数字
题目介绍参考:剑指 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)是一种用于在数组或列表中查找出现次数超过一半的元素的算法。该算法基于以下观察:如果一个元素出现次数超过一半,那么它在数列中出现的次数一定比其他所有元素出现次数之和还要多。
算法步骤如下:
- 初始化两个变量:候选元素(candidate)和计数器(count),候选元素用于保存当前被选中的元素,计数器用于统计候选元素的出现次数。
- 遍历整个数组或列表,对于每一个元素:
- 如果计数器为0,将当前元素设为候选元素,将计数器设为1。
- 否则,如果当前元素与候选元素相同,计数器加1;否则,计数器减1。
- 完成遍历后,最后留下的候选元素就是候选元素,但这并不代表它一定是超过一半的元素,只是候选元素的可能性更高。
- 最后,再次遍历整个数组或列表,统计候选元素的出现次数。如果它的出现次数确实超过一半,那么它就是超过一半的元素;否则,不存在超过一半的元素。
下面给出一个例子来解释摩尔投票法:
假设我们有一个数组:[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),也称为“排他性或”操作,是一种逻辑运算,用于比较两个值的不同之处。异或操作有以下几条规则:
- 同一个值与自身进行异或操作结果为0:A ⊕ A = 0
- 任意值与0进行异或操作结果不变:A ⊕ 0 = A
- 异或操作满足交换律:A ⊕ B = B ⊕ A
- 异或操作满足结合律:(A ⊕ B) ⊕ C = A ⊕ (B ⊕ C)
- 异或操作满足自反性:A ⊕ B ⊕ A = B
举例来说明:
- 与自身进行异或操作:7 ⊕ 7 = 0
- 与0进行异或操作:5 ⊕ 0 = 5
- 交换律:3 ⊕ 5 = 5 ⊕ 3
- 结合律:(2 ⊕ 4) ⊕ 6 = 2 ⊕ (4 ⊕ 6)
- 自反性: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)
这个问题很典型,双指针问题,当然可以采用多种方式的双指针解决,我们研究第一种与冒泡类似的,第二种与快排类似。
基于冒泡排序的双指针(快慢指针)
冒泡排序我们都知道,就是根据大小逐步和后面的比较,慢慢调整到整体有序的状态,这种方法是比较稳定的排序方法。
当然我们可以这样考虑:
- 第一次遍历,我们将数组中所有0交换到数组的头部
- 第二次遍历,只需要处理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++;}}}
总结
注意:双指针问题,边界和条件。
相关文章:

算法通过村第三关-数组黄金笔记|数组难解
文章目录 前言数组中出现超过一半的数字数组中只出现一次的数字颜色的分类问题(荷兰国旗问题)基于冒泡排序的双指针(快慢指针)基于快排的双指针(对撞指针) 总结 前言 提示:苦不来自外在环境中的人、事、物,…...
【2023】LeetCode HOT 100——矩阵
目录 1. 矩阵置零1.1 C++实现1.2 Python实现1.3 时空分析2. 螺旋矩阵2.1 C++实现2.2 Python实现2.3 时空分析3. 旋转图像3.1 C++实现3.2 Python实现3.3 时空分析4. 搜索二维矩阵 II4.1 C++实现4.2 Python实现4.3 时空分析1. 矩阵置零 🔗 原题链接:...

springboot源码方法
利用LinkedHashSet移除List重复的数据protected final <T> List<T> removeDuplicates(List<T> list) {return new ArrayList<>(new LinkedHashSet<>(list));} SpringFactoriesLoader#loadFactoryNames 加载配置文件...
基于java街球社区网站设计与实现
摘 要 本文主要讲述了基于SpringBootVue模式的街球社区网站的设计与实现。这里所谓的街球社区网站是通过类似于百度贴吧之类的网上论坛使得所有的街球爱好者有一个可以互相交流的平台,并使所有用户可以在社区进行教学视频的观看以及相关体育运动产品的选购,平台的盈利主要靠…...
定时产生不同频率方波
/*----------------------------------------------- 内容:通过定时产生不同频率方波 ------------------------------------------------*/ #include<reg52.h> //包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义 /*-…...

Java“牵手”天猫商品sku信息API接口数据,天猫API接口申请指南
天猫平台商品sku属性信息接口是开放平台提供的一种API接口,通过调用API接口,开发者可以获取天猫商品的标题、价格、库存、月销量、总销量、库存、详情描述、图片等详细信息 。 获取商品销量接口API是一种用于获取电商平台上商品sku属性数据的接口&#…...

【⑮MySQL | 视图】概述 | 创建 | 查看 | 更新 | 修改 | 删除
前言 ✨欢迎来到小K的MySQL专栏,本节将为大家带来MySQL视图概述 | 创建 | 查看 | 更新 | 修改 | 删除的分享✨ 目录 前言1.视图概述2.创建视图3.查看视图4.更新视图数据5.修改视图6.删除视图总结 1.视图概述 1.1 为什么使用视图? 视图一方面可以帮我们使…...

Linux驱动开发一、RK3568把hello编译到Linux内核中运行。‘rk_vendor_read’未定义的引用
1、在字符设备目录下建立hello目录 ~/Linux/rk356x_linux/kernel/drivers/char/hello 2、进入hello目录,新建hello.c、Makefile、Kconfig三个文件 3、Kconfig是打开make menuconfig配置界面是后的选项,这Kconfig是在字符设备下的。 config HELLOtrist…...
enable_shared_from_this
用途: enable_shared_from_this 是一个基类模板,用于解决在类成员函数中获取类对象的 shared_ptr 的需求。它提供了一种机制,使类能够安全地从成员函数内部获得指向自身的 shared_ptr。 解决对象生命周期管理问题:在某些情况下&…...

weak_ptr是怎么探知对象生死的
weak_ptr是C智能指针中的一种。它用于解决共享所有权的问题,并且可以避免因循环引用而导致的内存泄漏。 weak_ptr本身并不承担对象的所有权,它指向由shared_ptr管理的对象。与shared_ptr不同,weak_ptr并不会增加计数器来计算对象的引用次数。…...

⌈算法进阶⌋图论::拓扑排序(Topological Sorting)——快速理解到熟练运用
目录 一、原理 1. 引例:207.课程表 2. 应用场景 3. 代码思路 二、代码模板 三、练习 1、210.课程表Ⅱ🟢 2、2392.给定条件下构造举证🟡 3、310.最小高度树 🟡 一、原理 1. 引例:207.课程表 就如大学课程安排一样&…...
【Python】【数据结构和算法】保留最后N个元素
使用deque,指定maxlen参数的值为N,例如: >>> from collections import deque >>> dq deque(maxlen3) >>> dq.append(1) >>> dq.append(2) >>> dq.append(3) >>> dq.append(4) >&…...
wireshark 基本使用
在Wireshark中,你可以使用过滤器来根据接口名称定位到特定的包。下面是一些常见的过滤器示例: 根据源或目的IP地址过滤: ip.src 192.168.0.1:过滤源IP地址为192.168.0.1的包。ip.dst 192.168.0.1:过滤目的IP地址为…...
2、结构型设计模式
结构型设计模式 目录 结构型设计模式1. 代理模式1.1 概述1.2 结构1.3 静态代理1)抽象主题类 SellTickets2)真实主题类 TrainStation3)代理类 ProxyPoint4)客户端类1.4 JDK 动态代理1)代理工厂类:ProxyFactory2)客户端类3)JDK 动态代理原理4)动态代理的执行流程是什么样…...

JavaScript下载excel文件
文章目录 通过链接下载a标签下载方法注意 获取文件流请求体配置下载文件流 总结 通过链接下载 a标签 对于已知地址的目标文件,前端可以使用 a标签 来直接下载,使用a标签下载使用到两个属性 download:下载文件名href:目标文件下…...

研磨设计模式day12命令模式
目录 定义 几个参数 场景描述 代码示例 参数化设置 命令模式的优点 本质 何时选用 定义 几个参数 Command:定义命令的接口。 ConcreteCommand:命令接口的实现对象。但不是真正实现,是通过接收者的功能来完成命令要执行的操作 Receiver&#x…...
设计模式 06 适配器模式
适配器模式(Adapter Pattern)属于结构型模式 概述 结构型模式关注如何将现有的类或对象组织在一起形成更加强大的结构。 在生活中,我们经常遇到这样的一个问题:轻薄笔记本通常只有 type-c 或者 usb-a 接口,没有网口。…...

UE4/5Niagara粒子特效之Niagara_Particles官方案例:3.3->4.3
目录 3.3 Visibility Tag 左边的发射器: 发射器更新 粒子生成 粒子更新 右边的发射器 和左边发射器不同的地方 3.4 Texture Sampling 发射器更新 粒子生成 粒子更新 4.1Play Audio Per Particle 系统 第三个发射器 发射器更新 粒子生成 粒子更新 第二个…...

数据结构队列的实现
本章介绍数据结构队列的内容,我们会从队列的定义以及使用和OJ题来了解队列,话不多说,我们来实现吧 队列 1。队列的概念及结构 队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,…...
Gti的基本介绍和使用方式
Git 是一种分布式版本控制系统, 主要用于管理软件开发过程中的代码变更。其基本概念包括: 仓库 (Repository): Git中存储代码的基本单位,即一个代码库。在仓库中可以存储多个分支、标签、提交记录等。 分支 (Branch): Git中的分支是代码的不同开发方向,…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...

深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...

页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...

3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...

Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...