1:Map/Set里面的元素组织顺序和插入元素顺序没有任何关系。
2:for each代码底层也是靠迭代器来实现的,Iterable接口就提供了一个方法,这个方法就好返回一个迭代器对象。
了解Set接口
代码实现:
public class TestSet {
public static void main(String[] args) {
//1.实例化Set
Set<String> set = new HashSet<>();
//2.用add往set中插入元素
set.add("hello");
set.add("world");
set.add("java");
//3.判断set中某个值是否存在
System.out.println(set.contains("world"));
//4.删除set中的某个值
set.remove("world");
System.out.println(set.contains("world"));
//5.打印set
System.out.println(set);
//6.遍历打印set(如果要想循环遍历set中的元素,就需要使用迭代器了)
/*迭代器的泛型参数,要和集合类中保存的元素参数保持一致,Iterator是一个接口,
* 集合类内部自己实现自己版本的迭代器类,不同的集合类,内部的迭代器类型不同,
* 迭代方式也不同(迭代器的实现细节也不同),但是我们用的时候统一用Iterator,感觉不到它的差异*/
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {//hasNext()判断是否有下一个元素
String next = iterator.next();
System.out.println(next);
}
}
}
面试题
1:
解答:
class Solution {
public int singleNumber(int[] nums) {
//1.创建一个Map统计每个数字出现的次数,key表示当前数字,value表示这个数字出现的次数
Map<Integer, Integer> map = new HashMap<>();
//2.数组,完成统计Map中每个数字出现的次数
for (int x:nums) {
Integer value = map.get(x);
if (value == null) {
//当前这个数字之前在map中不存在,使对应的value置1
map.put(x,1);
} else {
//当前这个数字前面已经存在过了,把value再加1即可
map.put(x,value + 1);
}
}
//3.遍历map,找到出现次数为1的数字
for (Map.Entry<Integer,Integer> entry: map.entrySet()) {
if (entry.getValue().equals(1)) {
/*getValue得到的是Integer包装类,通过equals判定相当于对1进行自动装箱,比较两个Integer
* 如果写成 == 1,相当于对Integer自动装箱,比较两个 ==*/
return entry.getKey();
}
}
//理论上讲,只要输入数组给的是正确内容,这里的return 0是不会触发的
return 0;
}
}
解2:按位异或的方式:a ^ b ^ b = a a ^ 0 = a
思想:初始值为0,把所有数字都往一起进行异或,由于大部分数字是出现两次,两次异或同一个数字就抵消~~,所有数字都异或完成之后,最后的结果就是只存在一次的数字。
解2代码实现如下:
public int singleNumber(int[] nums) {
int ret = 0;
for (int x:nums) {
ret ^= x;
}
return ret;
}
如果要是有两个整数只出现一次咋办呢?a 和 b
1:把所有的数字^到一起,得到的结果相当于a ^ b
2:a ^ b肯定不为0,就可以从异或结果中找到某个值为1的bit位。
代码实现如下:
public int[] singleNumber2(int[] nums) {
//1:先把所有数字异或到一起
int ret = 0;
for (int x:nums) {
ret ^= x;
}
//2.此时的异或结果相当于 a ^ b,值一定不为0。就可以从中找到某个为1的bit位。
int bit = 1;
int i = 0;
for (; i < 32; i++) {
if ((ret& (bit << i)) != 0) {
break;
}
}
//循环结束之后,bit中的值,就是刚找到某一位为1的值,然后再进行分组,分组操作能保证两点;
//1:让a和b分散到不同组
//2:让值相同的元素一定在同一组
int a = 0;
int b = 0;
for (int x: nums) {
if ((x & (bit << i)) != 0) {
//第一组,指定位为1
a ^= x;
} else {
//第二组,指定位为0
b ^= x;
}
}
int[] array = {a,b};
return array;
}
力扣算法第138题:复制带随机指针的链表
思路:1:一种简单粗暴的思路:先把链表按照普通链表的方式复制一份,遍历每个节点,看该节点random指针指向的位置相对于头结点的偏移量是几(从头结点出发走几步能到达random指向位置),然后根据这个偏移量决定新链表中的每个节点的指向。(这是一种代码实现起来比较麻烦的思路)
2:更简单的方法:创建一个Map<Node,Node> key表示旧链表上的节点,value表示旧链表对应节点的拷贝(也就是新链表的节点)
代码实现:
public class MapInterview {
//整个node的static内部类
static class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
public Node copyRandomList(Node head) {
//1.遍历旧链表,把旧链表每个节点依次插入到map中,key是旧链表节点,value是拷贝出来的新节点
Map<Node,Node> map = new HashMap<>();
for (Node cur = head;cur != null; cur = cur.next) {
map.put(cur,new Node(cur.val));//此时新链表中的val值拷贝出来了,但是next和random仍然是null
}
//2.再次遍历链表,修改新链表节点中的next和random
for (Node cur = head;cur != null; cur = cur.next) {
//先从map中找到该cur对应的新链表节点
Node newCur = map.get(cur);
newCur.next = map.get(cur.next);
newCur.random = map.get(cur.random);
}
//需要返回新链表的头节点
return map.get(head);
}
}
for each 这种东西,相当于“语法糖”,语法糖:指的是某个语法特性,没有也完全ok,如果有了的话,可以让代码更简单(程序员心里甜甜的),lambda也是语法糖,包装类自动装箱,自动拆箱,也算语法糖。
力扣算法第771题:宝石与石头
代码实现:
public int numJewelsInStones(String J,String S) {
//1.先遍历J把所有的宝石类型加入到一个Set中
Set<Character> set = new HashSet<>();
for (char c:J.toCharArray()) {
set.add(c);
}
//2.遍历s拿到每个元素之后,再去set中查找,如果能找到,就说明是宝石
int ret = 0;
for (char c : S.toCharArray()) {
if (set.contains(c)) {
ret++;
}
}
return ret;
}
牛客旧键盘:
坏掉的键:按下键没有输出
题目理解:给出一个"预期输出的字符串",再给出一个“实际输出的字符串”,找到哪些字符预期要输出但是实际没输出的,这样的字符就是坏掉的键。键盘上某个键坏了,对应大写字母和小写字母都无法输出,需要把A和a当成同一种情况。
代码实现如下:
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
public class BrokenKeyboard {
public static void main(String[] args) {
/*该题的思路:
* 1:循环读入两个字符串,第一个字符串是预期输出的内容;第二个字符串是实际输出的内容
* 2:把读入的两个字符串全都转成大写(根据题目的输出示例)
* 3:题目中的主要任务是判定预期输出的哪些字符在实际输出的字符串中有没有存在。先搞一个Set把实际输出的
* 每个字符都存进去,然后再遍历预期输出字符串,看看哪个字符在这个Set中不存在
* 【注意事项】:最后还得对坏掉的键进行去重(同样可以使用Set来去重,因为Set中的key是不允许重复的)
* */
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
// 1:循环读入两个字符串,第一个字符串是预期输出的内容;第二个字符串是实际输出的内容
String expected = scanner.next();
String actual = scanner.next();
//2.把读入的两个字符串全都转成大写(根据题目的输出示例)。
expected = expected.toUpperCase();
actual = actual.toUpperCase();
//3.创建一个Set保存实际输出的字符
Set<Character> actualSet = new HashSet<>();
for (int i = 0;i < actual.length();i++) {
//注意Set中的内容是不会重复的,如果add的时候发现这个元素已经存在,那么add这个操作就不
//再进行了,会继续进行下面的add,不会抛出异常,也不会影响到原有Set中的内容,
actualSet.add(actual.charAt(i));
}
//4.遍历预期输出的字符串,看看哪个字符没有被实际输出
Set<Character> brokenKeySet = new HashSet<>();//这个brokenKeySet用来存放坏键,能够去重
for (int i = 0; i < expected.length();i++) {
char c = expected.charAt(i);
if (actualSet.contains(c)) {
//当前字符对应的按键是一个好的键,不打印,继续往下走
continue;
}
//走到这儿,剩下的就是坏了的键,要注意输出格式的问题,空格,换行的有无,这些细节都很重要
if (brokenKeySet.contains(c)) {
//此处的brokenKeySet用于辅助去重,防止同一个坏键被打印多次
continue;
}
System.out.print(c);
brokenKeySet.add(c);
}
}
}
}
力扣算法第692题:
代码实现如下:
static class MyComparator implements Comparator<String> {
private Map<String,Integer> map;
public MyComparator(Map<String, Integer> map) {
this.map = map;
}
@Override
public int compare(String o1, String o2) {
int count1 = map.get(o1);
int count2 = map.get(o2);
if (count1 == count2) {
return o1.compareTo(o2);//这里就按字典序进行排序,因为String自身也实现了Comparable,自带字典序
//的比较功能,compareTo就是在使用String默认的比较规则
}
return count2 - count1;//这里可以这样理解:sort本身是按升序排列
//以下两种方式相当于重新定义排列顺序
/*1:count1 - count2 仍然是升序排列
* 2:count2 - count1 降序排列
* */
}
}
public List<String> topKFrequent(String[] words,int k) {
//1.先统计每个单词出现的次数,用map将每个单词及其出现次数存起来
Map<String,Integer> map = new HashMap<>();
for (String s: words) {
Integer count = map.getOrDefault(s,0);
map.put(s,count + 1);//这里map如果再次put就会把原来的给覆盖更新
}
//2.把刚才这里统计到的字符串内容放到ArrayList中,KeySet相当于得到了一个Set,Set中存放的就是所有的key
ArrayList<String> arrayList = new ArrayList<>(map.keySet());
//3.按照刚才字符串出现次数,针对arrayList进行排序
//sort默认是按照元素的自身大小进行升序排序(String 的字典序),但是根据题意,此处我们需要按照字符串出现次数
//来降序排序,也就需要通过自制比较器来自定制比较规则。
Collections.sort(arrayList,new MyComparator(map));//在比较要用到每个单词出现的次数,但是每个单词出现的次数在
//map里,所以要把map给传过去
//4.获取到前k个元素
return arrayList.subList(0,k);
}
通过匿名内部类的方式求解:
static class MyComparator implements Comparator<String> {
private Map<String,Integer> map;
public MyComparator(Map<String, Integer> map) {
this.map = map;
}
@Override
public int compare(String o1, String o2) {
int count1 = map.get(o1);
int count2 = map.get(o2);
if (count1 == count2) {
return o1.compareTo(o2);//这里就按字典序进行排序,因为String自身也实现了Comparable,自带字典序
//的比较功能,compareTo就是在使用String默认的比较规则
}
return count2 - count1;//这里可以这样理解:sort本身是按升序排列
//以下两种方式相当于重新定义排列顺序
/*1:count1 - count2 仍然是升序排列
* 2:count2 - count1 降序排列
* */
}
}
public List<String> topKFrequent(String[] words,int k) {
//1.先统计每个单词出现的次数,用map将每个单词及其出现次数存起来
Map<String,Integer> map = new HashMap<>();
for (String s: words) {
Integer count = map.getOrDefault(s,0);
map.put(s,count + 1);//这里map如果再次put就会把原来的给覆盖更新
}
//2.把刚才这里统计到的字符串内容放到ArrayList中,KeySet相当于得到了一个Set,Set中存放的就是所有的key
ArrayList<String> arrayList = new ArrayList<>(map.keySet());
//3.按照刚才字符串出现次数,针对arrayList进行排序
//sort默认是按照元素的自身大小进行升序排序(String 的字典序),但是根据题意,此处我们需要按照字符串出现次数
//来降序排序,也就需要通过自制比较器来自定制比较规则。
// Collections.sort(arrayList,new MyComparator(map));//在比较要用到每个单词出现的次数,但是每个单词出现的次数在
//map里,所以要把map给传过去
//4.获取到前k个元素
Collections.sort(arrayList, new Comparator<String>() {
@Override//下面的代码创建了一个“匿名内部类”:你不知道这个类的名字是啥,但是你知道这个类实现了Comparator接口,
//“匿名内部类”:这个类只需要用一次,用完就丢
public int compare(String o1, String o2) {
int count1 = map.get(o1);//匿名内部类优势与其他类的地方在于:他可以直接用所在邻域内的变量,而不需要通过构造器传递
int count2 = map.get(o2);
if (count1 == count2) {
return o1.compareTo(o2);//按字典序进行排列
}
return count2 - count1;
}
});
return arrayList.subList(0,k);
}
该题lambda表达式的求解:
static class MyComparator implements Comparator<String> {
private Map<String,Integer> map;
public MyComparator(Map<String, Integer> map) {
this.map = map;
}
@Override
public int compare(String o1, String o2) {
int count1 = map.get(o1);
int count2 = map.get(o2);
if (count1 == count2) {
return o1.compareTo(o2);//这里就按字典序进行排序,因为String自身也实现了Comparable,自带字典序
//的比较功能,compareTo就是在使用String默认的比较规则
}
return count2 - count1;//这里可以这样理解:sort本身是按升序排列
//以下两种方式相当于重新定义排列顺序
/*1:count1 - count2 仍然是升序排列
* 2:count2 - count1 降序排列
* */
}
}
public List<String> topKFrequent(String[] words,int k) {
//1.先统计每个单词出现的次数,用map将每个单词及其出现次数存起来
Map<String,Integer> map = new HashMap<>();
for (String s: words) {
Integer count = map.getOrDefault(s,0);
map.put(s,count + 1);//这里map如果再次put就会把原来的给覆盖更新
}
//2.把刚才这里统计到的字符串内容放到ArrayList中,KeySet相当于得到了一个Set,Set中存放的就是所有的key
ArrayList<String> arrayList = new ArrayList<>(map.keySet());
//3.按照刚才字符串出现次数,针对arrayList进行排序
//sort默认是按照元素的自身大小进行升序排序(String 的字典序),但是根据题意,此处我们需要按照字符串出现次数
//来降序排序,也就需要通过自制比较器来自定制比较规则。
// Collections.sort(arrayList,new MyComparator(map));//在比较要用到每个单词出现的次数,但是每个单词出现的次数在
//map里,所以要把map给传过去
//4.获取到前k个元素
Collections.sort(arrayList, (o1, o2) -> {//lambda表达式本质上就是一个匿名方法,o1,o2的类型就是String类型,剩下
//大括号中方法的具体实现就和匿名内部类的完全一样
int count1 = map.get(o1);
int count2 = map.get(o2);
if (count1 == count2) {
return o1.compareTo(o2);//按字典序进行排列
}
return count2 - count1;
});
return arrayList.subList(0,k);
}
Arrays.sort:是针对数组排序,也可以通过第二个参数指定比较规则(比较器对象)
Collections.sort:针对集合类排序。