今天刷代码随想录,在使用字符串拼接时,发现String类确实比StringBuilder慢了不是,总结了StringBuilder类(详见下面文章内容,点击可跳转),还有在做后两题时,发现了Java中集合作为参数和基本数据类型作为参数在底层的逻辑是不一样的,集合名(对象引用)作为参数,方法内部与外界公用一个对象,而基本数据类则不同(详见下面文章内容,点击可跳转)。
110.平衡二叉树
题目链接:110. 平衡二叉树
递归法(后序遍历):
与二叉树最大深度有点像,也是后序遍历,理解为后序遍历的应用吧,注意平衡二叉树的定义:是每个节点的左右子树高度差不能超过1。所以需要每个节点都判断,而如何修改求高度的后序遍历使得能够做出这道题呢,其实就是在“左右中”的中这里,加一个判断,如果左右子树高度差大于1那么返回 -1 这个特殊值,然后在求左右子树高度这里,也加一个判断,如果左/右子树高度为 -1 说明次树一定不是平衡二叉树,那么一路返回-1即可。下面是代码:
代码:
public boolean isBalanced(TreeNode root) {
// 1. 后序遍历法
if(getHeight(root) == -1) {
return false;
}else{
return true;
}
}
// 1. 参数和返回值:
// 根据左右子树高度差超过1返回-1作为异常值,然后一路返回制根节点,在主函数中判断是否存在-1即可
public int getHeight(TreeNode root) {
// 2. 循环终止条件:只关心什么时候停止,而不管什么时候返回-1,什么时候返回-1交给“单层循环逻辑”来判断
if(root == null) {
return 0;
}
// 3. 单层循环逻辑
int leftHeight = getHeight(root.left); // 左
if(leftHeight == -1){
return -1;
}
int rightHeight = getHeight(root.right);// 右
if(rightHeight == -1){
return -1;
}
// 中:
int height = 0;
if(Math.abs(leftHeight - rightHeight) > 1) {
height = -1; // 真正控制-1产生的地方
}else {
height = 1 + Math.max(leftHeight, rightHeight); // 控制高度产生的地方
}
return height;
}
257. 二叉树的所有路径
题目链接:257. 二叉树的所有路径
一开始也是没有什么思路,没想到前序遍历还能这么操作,确实符合前序遍历往回退可以保存到之前变丽果的元素,重在理解回溯。
代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
List<String> result = new ArrayList<>();
if(root == null) {
return result;
}
List<String> path = new ArrayList<>();
getPaths(root, path, result);
return result;
}
public void getPaths(TreeNode node, List<String> path, List<String> result) {
path.add(new String().valueOf(node.val)); // 中
if(node.left == null && node.right == null) { // 如果遇到叶子结点,则证明需要把路径加入到结果集中
String temp = "";
for(int i = 0; i < path.size() - 1; i++) { // 除了最后一个元素,其他每个元素之后都要加上->
temp += path.get(i);
temp += "->";
}
temp += path.get(path.size() - 1); // 加上最后一个
result.add(temp);
}
if(node.left != null) {
getPaths(node.left, path, result);
path.remove(path.size() - 1); // 回溯,把刚刚加入过结果集里的节点值出栈,是,例如,path:[1,2,5],到了叶子节点5,此时回溯,删除path里的5,然后由于递归,最终会回到1这个根节点位置,然后此时path里的元素只有[1],这样,就达到了记录路径的目的
}
if(node.right != null) {
getPaths(node.right, path, result);
path.remove(path.size() - 1);
}
}
}
小结:
1. 关于path,也是可以一开始就定义为String类型,后面要加入的时候,new一个新的String类型,然后调用String的valueOf();方法,将int型转化为String类型
2. 对于要频繁用到字符串拼接,可以用 StringBuilder的append方法 这样代码更快(亲测在lettcode上快1ms)
总结StringBuilder:
- append(Object obj): 追加任意对象的字符串表示形式到当前字符串构建器中。
- insert(int index, Object obj): 在指定位置插入任意对象的字符串表示形式。
- delete(int start, int end): 删除从指定位置开始的特定长度的字符序列。
- deleteCharAt(int index): 删除指定位置的字符。
- replace(int start, int end, String str): 替换从指定位置开始的特定长度的字符序列为新字符串。
- setCharAt(int index, char ch): 设置指定位置的字符。
- substring(int start, int end): 返回一个新字符串,包含当前字符串构建器中从指定位置开始的特定长度的字符序列。
- reverse(): 反转当前字符串构建器中的字符序列。
- length(): 返回当前字符串构建器中的字符序列的长度。
- toString(): 返回表示当前字符串构建器中字符序列的字符串。
404.左叶子之和
题目链接:404. 左叶子之和
BFS(迭代)法
就是正常层序遍历各个节点(我的一篇文章中详细讲过层序遍历法遍历二叉树),当遇到一个节点的左节点的左孩子和右孩子都为空,则这个节点的左节点为左叶子。(哈哈哈哈自己看测试用例挤出来的,显然做出来还是有点丑陋的)
代码:
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
// 1. BFS层序遍历法
if(root == null) {
return 0;
}
Queue<TreeNode> queue = new ArrayDeque<>();
int result = 0;
queue.add(root);
// 正常层序遍历各个节点
while(!queue.isEmpty()) {
int len = queue.size();
while(len > 0) {
TreeNode temp = queue.poll();
if(temp.left != null) {
if(temp.left.left == null && temp.left.right == null) { // 如果左节点的左节点和右节点为空,说明此节点为左叶子
result += temp.left.val;
}
queue.add(temp.left);
}
if(temp.right != null) {
queue.add(temp.right);
}
len--;
}
}
return result;
}
}
DFS(递归)法:
关键是理解什么是左叶子,上面用BFS解决的时候也说了判断方法,下面引用代码随想录里的概括:节点A的左孩子不为空,且左孩子的左右孩子都为空(说明是叶子节点),那么A节点的左孩子为左叶子节点。
用递归时的bug
理解了什么是左叶子节点后,自己写了下面这段代码:
class Solution {
// 2. 递归法
public int sumOfLeftLeaves(TreeNode root) {
int result = 0;
if(root == null) {
return result;
}
getLeftSum(root, result);
return result;
}
public void getLeftSum(TreeNode node, int result) {
if(node == null) {
return;
}
if(node.left!=null && (node.left.left == null && node.left.right == null)){
result += node.left.val;
} // 中:判断此节点的左孩子是否为左叶子
getLeftSum(node.left, result);
getLeftSum(node.right, result);
}
}
但是测试结果如下图:
很容易能发现错误,就是getLeftSum中result累加的结果没有映射到主方法sumOfLeftLeaves中的result中去。
哎~这时我心中就有疑惑了,在上面257. 二叉树的所有路径的题解中,明明也是类似的方法,在主方法binaryTreePaths中创建了List集合result,调用getPaths,最终返回的result,就是结果啊,说明:在调用方法中result的结果映射到外界去了,这涉及到Java的一个小知识点了。
关于Java中方法的参数问题:
就是关于对象的引用(引用指result集合名,是List类的实例对象)作为方法的参数,传入是按值传入,而且是这个值对应的是对象在堆中位置的引用,所以通过这个引用,在方法中可以访问并修改对象(result)的状态。
而关于这一点,我说的一直是“对象的引用”,对于基本数据类型(如int、double等)和String。它们也是按值传递的,但是传递的是值的拷贝,而不是引用的拷贝。因此,在方法内部修改这些类型的参数不会影响到原始变量。
总结:
基本数据类型和String在外传参给方法,方法内部的修改不会影响外面。而对象引用(对象实例的名字)作为方法的参数,在方法内部修改的是与外界相同的一个引用(如果试图在方法内部让这个引用指向一个新的对象,将不会影响到外界引用,因为引用值的拷贝是局部的。)
OK,废话一大堆,代码解释:
public class PassByValueExample {
static class MutableObject {
int value;
MutableObject(int value) {
this.value = value;
}
void changeValue(int newValue) {
this.value = newValue;
}
}
public static void main(String[] args) {
// 对象引用传递示例
MutableObject obj = new MutableObject(10);
System.out.println("修改对象前:" + obj.value);
modifyObject(obj);
System.out.println("修改对象后:" + obj.value); // 对象状态已被修改
// 基本数据类型传递示例
int primitive = 5;
System.out.println("修改基本数据类型前:" + primitive);
modifyPrimitive(primitive);
System.out.println("修改基本数据类型后:" + primitive); // 基本数据类型值未改变
// String传递示例
String str = "原始字符串";
System.out.println("修改字符串前:" + str);
modifyString(str);
System.out.println("修改字符串后:" + str); // 字符串值未改变
}
public static void modifyObject(MutableObject obj) {
obj.changeValue(20); // 修改对象状态
// 注意:以下代码不会影响到main方法中的obj引用
// obj = new MutableObject(30);
}
public static void modifyPrimitive(int value) {
value = 10; // 修改局部变量的值,不影响外部变量
}
public static void modifyString(String str) {
str = "已修改的字符串"; // 修改局部变量的引用,不影响外部变量
// 注意:String是不可变的,我们不能修改String对象的内容,只能改变引用
}
}
输出结果:
修改对象前:10
修改对象后:20
修改基本数据类型前:5
修改基本数据类型后:5
修改字符串前:原始字符串
修改字符串后:原始字符串
真正的题解代码:
class Solution {
// 2. 递归法
int result = 0;
public int sumOfLeftLeaves(TreeNode root) {
if(root == null) {
return result;
}
getLeftSum(root);
return result;
}
private void getLeftSum(TreeNode node) {
if(node == null) {
return;
}
if(node.left!=null && (node.left.left == null && node.left.right == null)){
result += node.left.val;
} // 中:判断此节点的左孩子是否为左叶子
getLeftSum(node.left);// 左
getLeftSum(node.right);// 右
}
}
但其实,遇到这种,要返回基本数据类型的递归,往往会让方法返回累加值作为结果,修改后代码如下:
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
return getLeftSum(root);
}
public int getLeftSum(TreeNode node) {
if (node == null) {
return 0;
}
int sum = 0;
if (node.left != null && node.left.left == null && node.left.right == null) {
sum += node.left.val;
}
sum += getLeftSum(node.left);
sum += getLeftSum(node.right);
return sum;
}
}