关于HashMap和LinkedHashMap的两道题

Leecode136.只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4

这种找只出现一次的xxx,一般会想到用HashMap来实现。这道题也是。
不过此题干较为简单,因为只有一个元素出现了一次,别的元素都确定出现了两次。不要求顺序等,因此如果用HashSet,进入集合时判断集合中是否存在对应元素,如果存在就删除,否则放入集合。这样集合中就只会装只出现了一次的那个元素了。

class Solution {
    public int singleNumber(int[] nums) {
	HashSet<Integer> set=new HashSet<>();
		 int k=0;
		for (int i = 0; i < nums.length; i++) {
			if(set.contains(nums[i])){
				set.remove(nums[i]);
			}
			else{
				set.add(nums[i]);
			}
		}
		for (int j : set) {  
		       k=j;  
		} 
		return k;
    }
}

也可以用HashMap来实现,第一种实现方式是键为数组元素值,值为数组元素个数。这样最后寻找个数为1的元素即可。
具体:判断集合是否存在元素值,不存在设为1,存在在原来值的基础上加1。
第二种实现方式是键为数组元素值,值为布尔类型。
第三种直接像set那样把HashMap中出现过的值remove掉也可。

若问为什么要这么麻烦,因为题目可能变化,可能出现一次的元素不只一个,重复的元素也不一定只重复两次。可能要求输出的顺序。

如此题:剑指 Offer 50. 第一个只出现一次的字符
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。
示例:
s = “abaccdeff”
返回 "b"

此题如果用set和map的remove方法显然不可行,因为元素可能重复奇数次。
法一:用HashMap来实现,键为数组元素值,值为对应数组元素个数。这样最后寻找个数为1的元素即可。

class Solution {
    public char firstUniqChar(String s) {
	Map<Character,Integer> map=new HashMap<>();
		for(int i=0;i<s.length();i++){
			if(map.containsKey(s.charAt(i))){
				map.put(s.charAt(i), map.get(s.charAt(i))+1);
			}
			else{
				map.put(s.charAt(i), 1);
			}
		}
		char k=' ';
		for(int i=0;i<s.length();i++){
			if(map.get(s.charAt(i))==1){
				k=s.charAt(i);
				break;
			}
		}
		return k;
    }
}

但是这里需要注意的一点是:

在使用如下的方式遍历HashMap里面的元素时
for (Entry<String, String> entry : hashMap.entrySet()) {
MessageFormat.format("{0}={1}",entry.getKey(),entry.getValue());
}
发现得到的元素不是按照之前加入HashMap的顺序输出的。HashMap散列图、Hashtable散列表是按“有利于随机查找的散列(hash)的顺序”。并非按输入顺序。遍历时只能全部输出,而没有顺序。甚至可以rehash()重新散列,来获得更利于随机存取的内部顺序。

为了按照字符串顺序输出第一个只出现一次的元素,便不能直接遍历哈希表,那样遍历出来的顺序并不是插入顺序。解决方法是直接遍历字符串就可以了。在字符串的顺序中寻找第一个值为1的元素。
如果想使得遍历Map可行,那便可使用LinkedHashMap,稍后介绍。

HashMap中还有一个方法:getOrDefault() 方法。

getOrDefault() 方法获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值。
getOrDefault() 方法的语法为:
hashmap.get(Object key, V defaultValue)

表中有key则key对应的值+1,无key则0+1;

在此处可以巧妙地借助一下这个方法。稍简便一些差别不大。

class Solution {
    public char firstUniqChar(String s) {
		Map<Character,Integer> map=new HashMap<>();
		for(int i=0;i<s.length();i++){
			
			map.put(s.charAt(i), map.getOrDefault(s.charAt(i), 0)+1);
			
		}
		char k=' ';
		for(int i=0;i<s.length();i++){
			if(map.get(s.charAt(i))==1){
				k=s.charAt(i);
				break;
			}
		}
		return k;
    }
}

实际上,此题只要出现一次的元素就可以了,并不关心元素出现了几次,因为value不必要计算出次数,可以用一个布尔类型的变量代替:如果集合中没有此元素作为键,则将(元素值,true)放入集合,如果集合中有此键值,则放入(元素值,false) (HashMap键值不能重复,新的会覆盖旧的,好像是这样,具体原理不知,待研究) 这样就简单了很多呢。

class Solution {
    public char firstUniqChar(String s) {
	Map<Character,Boolean> map=new HashMap<>();
		for(int i=0;i<s.length();i++){
			if(map.containsKey(s.charAt(i))){
				map.put(s.charAt(i), false);
			}else{
				map.put(s.charAt(i), true);
			}
			
		}
		char k=' ';
		for(int i=0;i<s.length();i++){
			if(map.get(s.charAt(i))){
				k=s.charAt(i);
				break;
			}
		}
		return k;
    }
}

因为在有些场景中,我们确需要用到一个可以保持插入顺序
的Map。庆幸的是,JDK为我们解决了这个问题,它为HashMap提供了一个
子类 —— LinkedHashMap。虽然LinkedHashMap增加了时间和空间上的开
销,但是它通过维护一个额外的双向链表保证了迭代顺序。特别地,该迭
代顺序可以是插入顺序,也可以是访问顺序。因此,根据链表中元素的顺
序可以将LinkedHashMap分为:保持插入顺序的LinkedHashMap 和 保持访
问顺序的LinkedHashMap,其中LinkedHashMap的默认实现是按插入顺序排
序的。
本质上,LinkedHashMap = HashMap + 双向链表,也就是说,HashMap和双向链表合二为一即是LinkedHashMap。也可以这样理解,LinkedHashMap 在不对HashMap做任何改变的基础上,给HashMap的任意两个节点间加了两条连线(before指针和after指针),使这些节点形成一个双向链表。在LinkedHashMapMap中,所有put进来的Entry都保存在HashMap中,但由于它又额外定义了一个以head为头结点的空的双向链表,因此对于每次put进来Entry还会将其插入到双向链表的尾部。

class Solution {
    public char firstUniqChar(String s) {
		Map<Character,Boolean> map=new LinkedHashMap<>();
		for(int i=0;i<s.length();i++){
			if(map.containsKey(s.charAt(i))){//用map.get(s.charAt(i))不行
				map.put(s.charAt(i), false);
			}else{
				map.put(s.charAt(i), true);
			}
			
		}
	/*	char k=' ';
		for(char ch:map.keySet()){
			k=ch;
			break;
		}
		return k;*/
		for(Map.Entry<Character, Boolean> d : map.entrySet()){
	           if(d.getValue()) return d.getKey();
	        }
	        return ' ';
    }
}

哈希表是去重的,即哈希表中键值对数量≤ 字符串 s 的长度。因此,相比于方法一,方法二减少了第二轮遍历的循环次数。当字符串很长(重复字符很多)时,方法二则效率更高。

对了,第一道题的巧解是不用额外空间的异或运算,可以说非常巧妙。异或:相同为0,不同为1.因此一个数与0异或还为那个数(1异或0为1,0异或0为0)。两个相同的数异或为0。异或还满足交换律结合律,把一数组的元素相互异或一下,再换换位置,最后留下来的那个就是只出现一次的数了。

class Solution {
    public int singleNumber(int[] nums) {
	int a=0;
		for (int i = 0; i < nums.length; i++) {
			a=a^nums[i];
		}
		return a;
    }
}

异或是^符号,好像在汉明距离里见到过它,再议。(总是忘真是绝了)
如果我遇到题1,就用异或或者set方法。
如果我遇到题2,就用LinkedHashMap的布尔方法。记住entrySet或者keySet

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值