数据结构与算法--第一个只出现一次的字符

第一个只出现一次的字符

  • 题目:在字符串中找出第一个只出现一次的字符,比如输入“wersdfxvsdfwer”,则输出x。

  • 方法一:

    • 还是老规矩,初始想法是从头遍历每一个字符,每遍历一个字符都与后面n-1个字符比较
    • 如果发现后面字符中包含相同字符则查询下一个字符
    • 如果后面字符都没有相同的字符,则放回当前字符
    • 方法的时间复杂度是O(n2)
  • 方法一是最简单的方式,时间复杂度最大。接下来开始优化改这个算法

  • 方法二:

    • 与次数相关,我们可以将每个字符与出现的次数存储起来,需求开辟额外的存储空间
    • 显然key,value形式的存储第一个想到的是java中的Map结构
    • 通过Map,我们可以通过字符串key,来找到他对应的次数value
    • 有如下实现:
/*** 找出数组中第一个只出现一次的字符 HashMap* @author liaojiamin* @Date:Created in 10:22 2021/6/10*/
public class FindNotRepeatChar {public static void main(String[] args) {System.out.println(findNotRepeatCharHashMap("wersdfxvsdfwer"));
}/*** HashMap情况* */public static char findNotRepeatCharHashMap(String str){if(str == null){return '\u0000';}if(str.length() == 1){return str.charAt(0);}Map<Character, Integer> map = new HashMap<>();char[] target = str.toCharArray();for (int i = 0; i < target.length; i++) {Character key = target[i];if(map.containsKey(key)){map.put(key, map.get(key) + 1);}else {map.put(key, 1);}}for (int i = 0; i < target.length; i++) {if(map.get(target[i]) == 1){return target[i];}}return '\u0000';}}
  • 如上算法的实现是没问题的,得到的第一个是x,但是此时我们是借助了java的HashMap的api来实现我们的算法

  • 如果只能用基础数据结构以及自己实现的方法呢(如此变态的要求),我们接着来优化。

  • 方法三:

    • 在方法二的基础上我们其实只需要解决HashMap存在的顺序问题,能否自定义一个哈希表
    • 因为题目的特殊性,我们需要将先出现的字符放在某个数据结构的前面
    • 那么自定义一个哈希表,直接通过hashCode来指定对应的位置
    • 因为字符char,在C++中是1个字节,8位 28 = 256,在java中是2个字节,16位,65535
    • 那么我们创建一个长度为65535的数组,每个字母根据其ASCII码作为数组下标对应的一个数字,二数组中存储的是每个字符出现的次数
    • 这样我们创建了一个大小为65535,以字符ASCII码作为键值的哈希表
    • 还有一个小的关键点在于,字符的ASCII编码可以直接通过Integer.valueOf得到,恰好,Character的HashCode方法也是直接转int,他是是相等的
    • 如上代码有如下实现:
/*** 找出数组中第一个只出现一次的字符* @author liaojiamin* @Date:Created in 10:22 2021/6/10*/
public class FindNotRepeatChar {public static void main(String[] args) {System.out.println(findNotRepeatChar("wersdfxvsdfwer"));}/*** 自定义Hash表方法* */public static char findNotRepeatChar(String str){if(str == null){return '\u0000';}if(str.length() == 1){return str.charAt(0);}int[] charValue = new int[256];char[] target = str.toCharArray();for (int i = 0; i < target.length; i++) {Integer position = new Character(target[i]).hashCode();charValue[position] += 1;}for (int i = 0; i < target.length; i++) {Integer position = new Character(target[i]).hashCode();if(charValue[position] == 1){return target[i];}}return '\u0000';}
}
  • 以上代码能正确得到我们需要的结果,并且第一次遍历,在哈希表中更新一个字符出现的次数时间是O(1)。如果字符串长度为n,那么一次扫描时间复杂度是O(n)

  • 第二次遍历得到的Hash表同样可以O(1)时间复杂度得到一个字符的次数,所以时间复杂度依然是O(1)

  • 这样总的来说时间复杂度还是O(1)

  • 同时我们需要一个 65535 的整数数组,由于数组的大小是个常数,也就是数组大小不会和目标字符串的长度相关,那么空间复杂度是O(1)

  • 问题:

    • 在方法三种,的确可以在纯英文字符的情况下得到确定值,算法也是正确的,但是实际上工作中字符远比65535个多,而且hashCode也会有冲突的时候,比如***存在中英文混合情况,方法二就无法求解***
  • 方法四:

    • 基于方法二的基础上我们解决中英文混用造成的冲突以及顺序问题
    • 我想到了HashMap中用到的哈希冲突解决方法,分离链表发
    • 我们定义一个链表数组,每次冲突后,将冲突元素添加到链表尾部
    • 然后依次遍历找出为 1 的节点即可
    • 如下实现:
/*** 找出数组中第一个只出现一次的字符* @author liaojiamin* @Date:Created in 10:22 2021/6/10*/
public class FindNotRepeatChar {public static void main(String[] args) {System.out.println(findNotRepeatCharCompatibleChina("wersdfxxv我sdfwer"));}
/*** 异常情况:无法保证中文,英文数据在数组中顺序* 包含中文情况* */public static char findNotRepeatCharCompatibleChina(String str){if(str == null){return '\u0000';}if(str.length() == 1){return str.charAt(0);}ListNode listArray[] = new ListNode[str.length()];char[] target = str.toCharArray();for (int i = 0; i < target.length; i++) {Integer position = (new Character(target[i]).hashCode())%str.length();if(listArray[position] == null){listArray[position] = new ListNode(String.valueOf(target[i]), 1);}else {ListNode listNode = MyLinkedList.search(listArray[position], String.valueOf(target[i]));if(listNode != null){listNode.setValue(listNode.getValue()+1);}else {MyLinkedList.addToTail(listArray[position], String.valueOf(target[i]), 1);}}}for (int i = 0; i < listArray.length; i++) {if(listArray[i] != null){ListNode header = listArray[i];while (header != null){if(header.getValue() == 1){return header.getKey().charAt(0);}header = header.getNext();}}}return '\u0000';}}
  • 如上,算法参照HashMap的思想对hash表进行处理,算法能得到正确的值。

  • 我们试图通过一个 字符串大小的链表数组来存储对应存量数据,其中涉及到HashCode%str.length取模得到对应位置

  • 虽然获取数组位置的时候回有冲突,并且不能保证顺序,但是我们每次都通过原始数组去查找遍历,依然可以得到第一个出现一次的字符

  • 方法中用的链表ListNode,以及链表对应的方法 MyLinkedList 都是自定义的方法,可以在之前的文章:数据结构与算法–链表实现以及应用 找到详细的实现以及说明。

  • 方法的时间时间复杂度两次遍历都是O(n)

  • 在每次遍历有hash冲突的节点时候,我们需要调用 MyLinkedList.search 找到当前key值对应的节点,此处复杂度取决于hash冲突的多少

  • 因此时间复杂度应该大于O(n)

  • 空间复杂度额外存储于字符串长度正相关也是O(n)

  • 方法五

    • 在方法四中算法是正确,但是有一定的复杂度,涉及到hash冲突解决,取模定位,链表节点查询这种复杂的操作
    • 因为我们收到方法三的定式思维影响用的hash表的结构存储,其实完全不用
    • 我们可以用链表存储,不用hash作为key,直接用对应的字符作为key,这样可以用少于O(n)的空间来存储
    • 如上分析有如下实现:
/*** 找出数组中第一个只出现一次的字符* @author liaojiamin* @Date:Created in 10:22 2021/6/10*/
public class FindNotRepeatChar {public static void main(String[] args) {System.out.println(findNotRepeatCharCompatibleChinaLinkList("哈哈wersvdfxx我v我sdfwer去"));}/*** 用链表解决* */public static char findNotRepeatCharCompatibleChinaLinkList(String str) {if (str == null) {return '\u0000';}if (str.length() == 1) {return str.charAt(0);}//初始化链表ListNode listNode  = new ListNode(String.valueOf(str.charAt(0)), 1);char[] target = str.toCharArray();for (int i = 1; i < target.length; i++) {ListNode header = MyLinkedList.search(listNode, String.valueOf(target[i]));if(header != null){header.setValue(header.getValue() + 1);}else {MyLinkedList.addToTail(listNode, String.valueOf(target[i]), 1);}}for (int i = 0; i < target.length; i++) {ListNode targetNode = MyLinkedList.search(listNode, String.valueOf(target[i]));if(targetNode != null && targetNode.getValue() == 1){return target[i];}}return '\u0000';}
}
  • 如上用链表存储的实现方式时间复杂度与之前一样,也是大于O(n),但是在代码复杂度上减少很多
  • 此方法不涉及到hash冲突解决等问题,都是直接存储,空间复杂度是小于O(n)的

上一篇:数据结构与算法–丑数
下一篇:数据结构与算法–数组中的逆序对

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

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

相关文章

使用 docker 编译运行 abp 项目

在前面的两篇文章中&#xff0c;介绍了如何在华为鲲鹏架构及其Euler系统上运行dotnet core, 使用docker运行了默认的mvc模板项目&#xff0c;这篇文章继续介绍在docker中运行更复杂的dotnet core项目&#xff0c;这里以业内鼎鼎大名的abp vnext框架&#xff0c;版本 2.6 为例。…

数据结构与算法--数组中的逆序对

题目&#xff1a;在数组中的两个数字如果签名一个数字大于后面的数组&#xff0c;则这两个数字组成一个逆序对。输入一个数组&#xff0c;求出这个数组中的逆序对的总数。 案例&#xff1a;输入数组{7,5&#xff0c;6,4}中一共有5个逆序对分别是{7,6}&#xff0c;{7,5}&#x…

用了这么多年的泛型,你对它到底有多了解?

现代程序员写代码没有人敢说自己没用过泛型&#xff0c;这个泛型模板T可以被任何你想要的类型替代&#xff0c;确实很魔法很神奇&#xff0c;很多人也习以为常了&#xff0c;但就是这么有趣的泛型T底层到底是怎么帮你实现的&#xff0c;不知道有多少人清楚底层玩法&#xff0c;…

数据结构与算法--两个链表中第一个公共节点

链表中第一个公共节点 公节点定义&#xff1a;同一个节点在两个链表中&#xff0c;并不是节点值相同题目&#xff1a;输入两个节点&#xff0c;找出他们的第一个公共节点&#xff0c;节点定义如需 /*** 链表元素节点** author liaojiamin* Date:Created in 12:17 2021/3/5*/ …

ASP.NET Core技术研究-全面认识Web服务器Kestrel

因为IIS不支持跨平台的原因&#xff0c;我们在升级到ASP.NET Core后&#xff0c;会接触到一个新的Web服务器Kestrel。相信大家刚接触这个Kestrel时&#xff0c;会有各种各样的疑问。今天我们全面认识一下ASP.NET Core的默认Web服务器Kestrel。一、初识Kestrel首先&#xff0c;K…

数据结构与算法--二叉堆(最大堆,最小堆)实现及原理

二叉堆&#xff08;最大堆&#xff0c;最小堆&#xff09;实现及原理 二叉堆与二叉查找树一样&#xff0c;堆也有两个性质&#xff0c;即结构性质和堆性质。和AVL树一样&#xff0c;对堆的一次操作必须到堆的所有性质都被满足才能终止&#xff0c;也就是我们每次对堆的操作都必…

Blazor WebAssembly 3.2.0 已在塔架就位 将发射新一代前端SPA框架

最美人间四月天&#xff0c;春光不负赶路人。在充满无限希望的明媚春天里&#xff0c;一路风雨兼程的.NET团队正奋力实现新的突破。根据计划&#xff0c;新一代基于WebAssembly 技术研发的前端SPA框架Blazor 将于5月19日在微软Build大会升空。目前&#xff0c;Blazor 的测试工作…

数据结构与算法--最小的k个数

最小的k个数 题目&#xff1a;输入n个整数&#xff0c;找出其中最小的k个数&#xff0c;例如输入4,5&#xff0c;6,7&#xff0c;8,9这六个数字&#xff0c;则最小的4个是4,5&#xff0c;6,7 方案一 还是最直观的方法&#xff0c;先排序&#xff0c;最快的是快排O(nlog2n)&a…

如何将 Azure 上的 Ubuntu 19.10 服务器升级到 20.04

点击上方蓝字关注“汪宇杰博客”导语Ubuntu 20.04 LTS 已经正式推出了。作为一名软粉&#xff0c;看到新版鲍叔毒瘤&#xff0c;我当然是激动万分&#xff0c;抱着批判的态度&#xff0c;第一时间很不情愿的更新了我的服务器。4月23日发布的 Ubuntu 20.04 是个 LTS 版。其 Linu…

死磕 Java 8 的日期处理

TIME Java 8 推出了全新的日期时间API并且已经很久了&#xff0c;因为业务中遇到的时间处理的还是不多&#xff0c;因此用的也少&#xff0c;而且大多是用封装好的时间共计包&#xff0c;就更少接触java8 的时间类型API了&#xff0c;因此对他不是很熟&#xff0c;今天遇到了一…

我想快速给WPF程序添加托盘菜单

我想...1 简单要求&#xff1a;使用开源控件库在XAML中声明托盘菜单&#xff0c;就像给控件添加ContextMenu一样封装了常用命令&#xff0c;比如&#xff1a;打开主窗体、退出应用程序等TerminalMACS我在TerminalMACS中添加了托盘菜单&#xff0c;最终实现的托盘菜单效果&#…

【半译】在ASP.NET Core中创建内部使用作用域服务的Quartz.NET宿主服务

在我的上一篇文章《在ASP.NET Core中创建基于Quartz.NET托管服务轻松实现作业调度》&#xff0c;我展示了如何使用ASP.NET Core创建Quartz.NET托管服务并使用它来按计划运行后台任务。不幸的是&#xff0c;由于Quartz.NET API的工作方式&#xff0c;在Quartz作业中使用Scoped依…

mysql技术分享-- 视图是什么

视图 最近遇到mysql锁相关问题&#xff0c;在查阅资料时候&#xff0c;经常能看到在锁的解释中总有视图的概念出现&#xff0c;因此我觉得有必要先去了解一下视图相关的详细信息&#xff0c;有助于我对mysql锁相关的理解。视图&#xff08;View&#xff09;是一个命名的虚拟表…

在 Visual Studio 2019 中为 .NET Core WinForm App 启用窗体设计器

当我们在使用 Visual Studio 2019 非预览版本开发 Windows Forms App (.NET Core) 应用程序时是不能使用窗体设计器的。即使在窗体文件上右击选择“显示设计器”菜单&#xff0c;仍旧只能看到代码&#xff0c;无法打开窗体设计器。根据微软开发者博客的描述&#xff0c;我们可以…