Java 补充知识
用stream流方法转化为int数组
文章参考:用stream流方法转化为int数组_stream转int-CSDN博客
::双冒号:把方法当做参数传到stream内部,使stream的每个元素都传入到该方法里面执行一下
如果转化的是存Integer类型数据的集合,以下的所有intValue都可以用valueOf代替
但如果是存Long或Double等数据类型的数据须用对应类型的intValue()方法
intValue()是java.lang.Number类的方法。Java中所有的数值类都继承它。也就是说 Integer,Double,Long等都有intValue()方法
Integer 用stream流转化为 int数组 //Integer[] 转换为 int[] Integer[] arr = new Integer[10]; int[] intArr = Arrays.stream(arr).mapToInt(Integer::intValue).toArray();
hashset转化为Integer数组 Set<Integer> set = new HashSet<>(); Integer[] arr = new Integer[set.size()]; set.toArray(arr);
hashset用stream流转化为int数组 Set<Integer> set = new HashSet<>(); int[] arr = set.stream().mapToInt(Integer::intValue).toArray();
ArrayList转化为Integer数组 List<Integer> list = new ArrayList<>(); Integer[] arr = new Integer[list.size()]; list.toArray(arr);
ArrayList用stream流转化为int数组 List<Integer> list = new ArrayList<>(); int[] arr = list.stream().mapToInt(Integer::intValue).toArray();
不难发现: set 或 list 转化为 Integer 数组或者 int 数组的方式是一样的
Map转化成List
文章参考:【Java 8 新特性】使用Collectors.toList()方法将Map转化成List的示例_collect.tolist-CSDN博客
提供 Java8转换 Map到 List的方法 Collectors.toList()示例。 一个映射有键和值,我们可以将所有键和值作为列表来获取。
如果我们想在一个类属性中设置key和value,然后把这个对象添加到List中,我们可以用java8的一行代码来实现Collectors.toList().
现在让我们看看怎么做。
1.使用Lambda表达式将Map转化成List 在Collectors.toList()方法中使用lambda表达式将Map转换为List,如下示例
List<String> valueList = map.values().stream().collect(Collectors.toList());
如果我们想在放入List之前对值进行排序,我们将按如下方式进行。
List<String> sortedValueList = map.values().stream() .sorted().collect(Collectors.toList());
我们还可以使用给定的比较器(Comparator.comparing())将Map转换为用户对象List
List<Person> list = map.entrySet().stream().sorted(Comparator.comparing(e -> e.getKey())) .map(e -> new Person(e.getKey(), e.getValue())).collect(Collectors.toList());
这里的Person是一个用户类。我们也可以使用Map.Entry获取Map的键和值如下。
这里的Person是一个用户类。我们也可以使用Map.Entry获取Map的键和值如下。
List<Person> list = map.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getValue)) .map(e -> new Person(e.getKey(), e.getValue())).collect(Collectors.toList());
作为比较,我们也可以使用Map.Entry.comparingByValue()和Map.Entry.comparingByKey()分别根据值和键对数据进行排序。
List<Person> list = map.entrySet().stream().sorted(Map.Entry.comparingByKey()) .map(e -> new Person(e.getKey(), e.getValue())).collect(Collectors.toList());
2.简单Map转化成List示例 SimpleMapToList.java
package com.concretepage; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class SimpleMapToList { public static void main(String[] args) { Map<Integer, String> map = new HashMap<>(); map.put(23, "Mahesh"); map.put(10, "Suresh"); map.put(26, "Dinesh"); map.put(11, "Kamlesh"); System.out.println("--Convert Map Values to List--"); List<String> valueList = map.values().stream().collect(Collectors.toList()); valueList.forEach(n -> System.out.println(n)); System.out.println("--Convert Map Values to List using sort--"); List<String> sortedValueList = map.values().stream() .sorted().collect(Collectors.toList()); sortedValueList.forEach(n -> System.out.println(n)); System.out.println("--Convert Map keys to List--"); List<Integer> keyList = map.keySet().stream().collect(Collectors.toList()); keyList.forEach(n -> System.out.println(n)); System.out.println("--Convert Map keys to List using sort--"); List<Integer> sortedKeyList = map.keySet().stream() .sorted().collect(Collectors.toList()); sortedKeyList.forEach(n -> System.out.println(n)); } }
输出
--Convert Map Values to List-- Mahesh Suresh Dinesh Kamlesh --Convert Map Values to List using sort-- Dinesh Kamlesh Mahesh Suresh --Convert Map keys to List-- 23 10 26 11 --Convert Map keys to List using sort-- 10 11 23 26
3.用户对象Map转化成List示例 MapToListOfUserObject.java
package com.concretepage; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class MapToListOfUserObject { public static void main(String[] args) { Map<Integer, String> map = new HashMap<>(); map.put(23, "Mahesh"); map.put(10, "Suresh"); map.put(26, "Dinesh"); map.put(11, "Kamlesh"); List<Person> list = map.entrySet().stream().sorted(Comparator.comparing(e -> e.getKey())) .map(e -> new Person(e.getKey(), e.getValue())).collect(Collectors.toList()); list.forEach(l -> System.out.println("Id: "+ l.getId()+", Name: "+ l.getName())); }}
Person.java
package com.concretepage; public class Person { private Integer id; private String name; public Person(Integer id, String name) { this.id = id; this.name = name; } public Integer getId() { return id; } public String getName() { return name; } }
输出
Id: 10, Name: Suresh Id: 11, Name: Kamlesh Id: 23, Name: Mahesh Id: 26, Name: Dinesh
如果我们需要按键来排序的话,我们可以使用键来排序
Comparator.comparing(e -> e.getValue())
然后输出如下。
然后输出如下。
Id: 26, Name: Dinesh Id: 11, Name: Kamlesh Id: 23, Name: Mahesh Id: 10, Name: Suresh
Stream—sort排序
使用sorted排序
public static void main(String[] args) { List<Student> list = new ArrayList<>(); Student s1 = new Student("zhangsan","beijing",30); list.add(s1); Student s2 = new Student("lisi","shanghai",29); list.add(s2); Student s3 = new Student("lining","shandong",31); list.add(s3); // forEach循环 list.stream().forEach(student -> System.out.println(student.getAge())); System.out.println("----------使用stream和sort--默认升序----------"); // sort排序:原集合不变,新集合按顺序排序 List<Student> sortList1 = list.stream().sorted((a, b) -> a.getAge().compareTo(b.getAge())).collect(Collectors.toList()); sortList1.stream().forEach(s-> System.out.println(s.getAge())); System.out.println(); System.out.println("---------使用stream和sort--降序排列-----------"); List<Student> sortDesList = list.stream().sorted(Comparator.comparingInt(Student::getAge).reversed()).collect(Collectors.toList()); sortDesList.stream().forEach(s-> System.out.println(s.getAge())); System.out.println(); System.out.println("----------不使用stream和sort------------"); // 使用集合的sort排序,集合自身排序发生变化 list.sort((a,b)->a.getAge().compareTo(b.getAge())); list.stream().forEach(student -> System.out.println(student.getAge())); System.out.println(); }
运行结果: Connected to the target VM, address: '127.0.0.1:51549', transport: 'socket' 30 29 31 ----------使用stream和sort--默认升序---------- 29 30 31 ---------使用stream和sort--降序排列----------- Disconnected from the target VM, address: '127.0.0.1:51549', transport: 'socket' 31 30 29 ----------不使用stream和sort------------ 29 30 31
刷题
530.二叉搜索树的最小绝对差
题目:给你一棵所有节点为非负值的二叉搜索树,请你计算树中任意两节点的差的绝对值的最小值。
示例:
提示:树中至少有 2 个节点。
思路及实现
题目中要求在二叉搜索树上任意两节点的差的绝对值的最小值。
注意是二叉搜索树,二叉搜索树可是有序的。
遇到在二叉搜索树上求什么最值啊,差值之类的,就把它想成在一个有序数组上求最值,求差值,这样就简单多了。
递归
那么二叉搜索树采用中序遍历,其实就是一个有序数组。
在一个有序数组上求两个数最小差值,这是不是就是一道送分题了。
最直观的想法,就是把二叉搜索树转换成有序数组,然后遍历一遍数组,就统计出来最小差值了。
把二叉搜索树转化为有序数组了,其实在二叉搜素树中序遍历的过程中,我们就可以直接计算了。
需要用一个pre节点记录一下cur节点的前一个节点。
如图:
代码如下:
class Solution { TreeNode pre;// 记录上一个遍历的结点 int result = Integer.MAX_VALUE; public int getMinimumDifference(TreeNode root) { if(root==null)return 0; traversal(root); return result; } public void traversal(TreeNode root){ if(root==null)return; //左 traversal(root.left); //中 if(pre!=null){ result = Math.min(result,root.val-pre.val); } pre = root; //右 traversal(root.right); } }
迭代
下面给出其中的一种中序遍历的迭代法,代码如下:
統一迭代法-中序遍历
class Solution { public int getMinimumDifference(TreeNode root) { Stack<TreeNode> stack = new Stack<>(); TreeNode pre = null; int result = Integer.MAX_VALUE; if(root != null) stack.add(root); while(!stack.isEmpty()){ TreeNode curr = stack.peek(); if(curr != null){ stack.pop(); if(curr.right != null) stack.add(curr.right); stack.add(curr); stack.add(null); if(curr.left != null) stack.add(curr.left); }else{ stack.pop(); TreeNode temp = stack.pop(); if(pre != null) result = Math.min(result, temp.val - pre.val); pre = temp; } } return result; } }
迭代法-中序遍历
class Solution { TreeNode pre; Stack<TreeNode> stack; public int getMinimumDifference(TreeNode root) { if (root == null) return 0; stack = new Stack<>(); TreeNode cur = root; int result = Integer.MAX_VALUE; while (cur != null || !stack.isEmpty()) { if (cur != null) { stack.push(cur); // 将访问的节点放进栈 cur = cur.left; // 左 }else { cur = stack.pop(); if (pre != null) { // 中 result = Math.min(result, cur.val - pre.val); } pre = cur; cur = cur.right; // 右 } } return result; } }
501.二叉搜索树中的众数
题目:给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。
假定 BST 有如下定义:
-
结点左子树中所含结点的值小于等于当前结点的值
-
结点右子树中所含结点的值大于等于当前结点的值
-
左子树和右子树都是二叉搜索树
例如:
给定 BST [1,null,2,2],
返回[2].
提示:如果众数超过1个,不需考虑输出顺序
进阶:你可以不使用额外的空间吗?(假设由递归产生的隐式调用栈的开销不被计算在内)
思路及实现
递归法
如果不是二叉搜索树
如果不是二叉搜索树,最直观的方法一定是把这个树都遍历了,用map统计频率,把频率排个序,最后取前面高频的元素的集合。
具体步骤如下:
1.这个树都遍历了,用map统计频率
至于用前中后序哪种遍历也不重要,因为就是要全遍历一遍,怎么个遍历法都行,层序遍历都没毛病!
2.把统计的出来的出现频率(即map中的value)排个序
3.取前面高频的元素
代码如下:
class Solution { public int[] findMode(TreeNode root) { Map<Integer, Integer> map = new HashMap<>(); List<Integer> list = new ArrayList<>(); if (root == null) return list.stream().mapToInt(Integer::intValue).toArray(); // 获得频率 Map searchBST(root, map); List<Map.Entry<Integer, Integer>> mapList = map.entrySet().stream() .sorted((c1, c2) -> c2.getValue().compareTo(c1.getValue())) .collect(Collectors.toList()); list.add(mapList.get(0).getKey()); // 把频率最高的加入 list for (int i = 1; i < mapList.size(); i++) { if (mapList.get(i).getValue() == mapList.get(i - 1).getValue()) { list.add(mapList.get(i).getKey()); } else { break; } } return list.stream().mapToInt(Integer::intValue).toArray(); } void searchBST(TreeNode curr, Map<Integer, Integer> map) { if (curr == null) return; map.put(curr.val, map.getOrDefault(curr.val, 0) + 1); searchBST(curr.left, map); searchBST(curr.right, map); } }
是二叉搜索树
既然是搜索树,它中序遍历就是有序的。
如图:
遍历有序数组的元素出现频率,从头遍历,那么一定是相邻两个元素作比较,然后就把出现频率最高的元素输出就可以了。
弄一个指针指向前一个节点,这样每次cur(当前节点)才能和pre(前一个节点)作比较。
而且初始化的时候pre = NULL,这样当pre为NULL时候,我们就知道这是比较的第一个元素。
此时又有问题了,因为要求最大频率的元素集合(注意是集合,不是一个元素,可以有多个众数),如果是数组上大家一般怎么办?
应该是先遍历一遍数组,找出最大频率(maxCount),然后再重新遍历一遍数组把出现频率为maxCount的元素放进集合。(因为众数有多个)
这种方式遍历了两遍数组。
那么我们遍历两遍二叉搜索树,把众数集合算出来也是可以的。
但这里其实只需要遍历一次就可以找到所有的众数。
那么如何只遍历一遍呢?
如果频率count 等于 maxCount(最大频率),当然要把这个元素加入到结果集中(以下代码为result数组)
result怎么能轻易就把元素放进去了呢,万一,这个maxCount此时还不是真正最大频率呢。
所以下面要做如下操作:
频率count 大于 maxCount的时候,不仅要更新maxCount,而且要清空结果集(以下代码为result数组),因为结果集之前的元素都失效了。
代码如下:
class Solution { ArrayList<Integer> resList; int maxCount; int count; TreeNode pre; public int[] findMode(TreeNode root) { resList = new ArrayList<>(); maxCount = 0; count = 0; pre = null; findMode1(root); int[] res = new int[resList.size()]; for (int i = 0; i < resList.size(); i++) { res[i] = resList.get(i); } return res; } public void findMode1(TreeNode root) { if (root == null) { return; } findMode1(root.left); int rootValue = root.val; // 计数 if (pre == null || rootValue != pre.val) { count = 1; } else { count++; } // 更新结果以及maxCount if (count > maxCount) { resList.clear(); resList.add(rootValue); maxCount = count; } else if (count == maxCount) { resList.add(rootValue); } pre = root; findMode1(root.right); } }
迭代法
只要把中序遍历转成迭代,中间节点的处理逻辑完全一样。
代码如下:
class Solution { public int[] findMode(TreeNode root) { TreeNode pre = null; Stack<TreeNode> stack = new Stack<>(); List<Integer> result = new ArrayList<>(); int maxCount = 0; int count = 0; TreeNode cur = root; while (cur != null || !stack.isEmpty()) { if (cur != null) { stack.push(cur); cur =cur.left; }else { cur = stack.pop(); // 计数 if (pre == null || cur.val != pre.val) { count = 1; }else { count++; } // 更新结果 if (count > maxCount) { maxCount = count; result.clear(); result.add(cur.val); }else if (count == maxCount) { result.add(cur.val); } pre = cur; cur = cur.right; } } return result.stream().mapToInt(Integer::intValue).toArray(); } }
236.二叉树的最近祖宗
题目:给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
示例 1: 输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 输出: 3 解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2: 输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 输出: 5 解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
说明:
-
所有节点的值都是唯一的。
-
p、q 为不同节点且均存在于给定的二叉树中。
思路及实现
遇到这个题目首先想的是要是能自底向上查找就好了,这样就可以找到公共祖先了。
那么二叉树如何可以自底向上查找呢?
回溯啊,二叉树回溯的过程就是从低到上。
后序遍历(左右中)就是天然的回溯过程,可以根据左右子树的返回值,来处理中节点的逻辑。
接下来就看如何判断一个节点是节点q和节点p的公共祖先呢。
首先最容易想到的一个情况:如果找到一个节点,发现左子树出现结点p,右子树出现节点q,或者 左子树出现结点q,右子树出现节点p,那么该节点就是节点p和q的最近公共祖先。 即情况一:
判断逻辑是 如果递归遍历遇到q,就将q返回,遇到p 就将p返回,那么如果 左右子树的返回值都不为空,说明此时的中节点,一定是q 和p 的最近祖先。
那么有录友可能疑惑,会不会左子树 遇到q 返回,右子树也遇到q返回,这样并没有找到 q 和p的最近祖先。
这么想的录友,要审题了,题目强调:二叉树节点数值是不重复的,而且一定存在 q 和 p。
但是很多人容易忽略一个情况,就是节点本身p(q),它拥有一个子孙节点q(p)。 情况二:
其实情况一 和 情况二 代码实现过程都是一样的,也可以说,实现情况一的逻辑,顺便包含了情况二。
因为遇到 q 或者 p 就返回,这样也包含了 q 或者 p 本身就是 公共祖先的情况。
这一点是很多录友容易忽略的,在下面的代码讲解中,可以再去体会。
递归三部曲:
-
确定递归函数返回值以及参数
需要递归函数返回值,来告诉我们是否找到节点q或者p,那么返回值为bool类型就可以了。
但我们还要返回最近公共节点,可以利用上题目中返回值是TreeNode ,那么如果遇到p或者q,就把q或者p返回,返回值不为空,就说明找到了q或者p。
-
确定终止条件
遇到空的话,因为树都是空了,所以返回空。
那么我们来说一说,如果 root == q,或者 root == p,说明找到 q p ,则将其返回,这个返回值,后面在中节点的处理过程中会用到,那么中节点的处理逻辑,下面讲解。
-
确定单层递归逻辑
值得注意的是 本题函数有返回值,是因为回溯的过程需要递归函数的返回值做判断,但本题我们依然要遍历树的所有节点。
如果递归函数有返回值,如何区分要搜索一条边,还是搜索整个树呢?
在递归函数有返回值的情况下:如果要搜索一条边,递归函数返回值不为空的时候,立刻返回,如果搜索整个树,直接用一个变量left、right接住返回值,这个left、right后序还有逻辑处理的需要,也就是后序遍历中处理中间节点的逻辑(也是回溯)。
那么为什么要遍历整棵树呢?直观上来看,找到最近公共祖先,直接一路返回就可以了。
如图:
就像图中一样直接返回7。
但事实上还要遍历根节点右子树(即使此时已经找到了目标节点了),也就是图中的节点4、15、20。
因为在如下代码的后序遍历中,如果想利用left和right做逻辑处理, 不能立刻返回,而是要等left与right逻辑处理完之后才能返回。
所以此时大家要知道我们要遍历整棵树。知道这一点,对本题就有一定深度的理解了。
那么先用left和right接住左子树和右子树的返回值:
如果left 和 right都不为空,说明此时root就是最近公共节点。这个比较好理解
如果left为空,right不为空,就返回right,说明目标节点是通过right返回的,反之依然。
这里有的同学就理解不了了,为什么left为空,right不为空,目标节点通过right返回呢?
如图:
图中节点10的左子树返回null,右子树返回目标值7,那么此时节点10的处理逻辑就是把右子树的返回值(最近公共祖先7)返回上去!
这里也很重要,可能刷过这道题目的同学,都不清楚结果究竟是如何从底层一层一层传到头结点的。
那么如果left和right都为空,则返回left或者right都是可以的,也就是返回空。
那么寻找最小公共祖先,完整流程图如下:
从图中,大家可以看到,我们是如何回溯遍历整棵二叉树,将结果返回给头结点的!
整体代码如下:
class Solution { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { if (root == null || root == p || root == q) { // 递归结束条件 return root; } // 后序遍历 TreeNode left = lowestCommonAncestor(root.left, p, q); TreeNode right = lowestCommonAncestor(root.right, p, q); if(left == null && right == null) { // 若未找到节点 p 或 q return null; }else if(left == null && right != null) { // 若找到一个节点 return right; }else if(left != null && right == null) { // 若找到一个节点 return left; }else { // 若找到两个节点 return root; } } }
总结
这道题目刷过的同学未必真正了解这里面回溯的过程,以及结果是如何一层一层传上去的。
那么我给大家归纳如下三点:
-
求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从底向上的遍历方式。
-
在回溯的过程中,必然要遍历整棵二叉树,即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断。
-
要理解如果返回值left为空,right不为空为什么要返回right,为什么可以用返回right传给上一层结果。
可以说这里每一步,都是有难度的,都需要对二叉树,递归和回溯有一定的理解。
本题没有给出迭代法,因为迭代法不适合模拟回溯的过程。理解递归的解法就够了。