遇到字符串拼接用它就对啦!什么你居然不知道Java中对象作为方法参数和基本数据类型作为参数的区别?有巨坑!

今天刷代码随想录,在使用字符串拼接时,发现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
  1. append(Object obj): 追加任意对象的字符串表示形式到当前字符串构建器中。
  2. insert(int index, Object obj): 在指定位置插入任意对象的字符串表示形式。
  3. delete(int start, int end): 删除从指定位置开始的特定长度的字符序列。
  4. deleteCharAt(int index): 删除指定位置的字符。
  5. replace(int start, int end, String str): 替换从指定位置开始的特定长度的字符序列为新字符串。
  6. setCharAt(int index, char ch): 设置指定位置的字符。
  7. substring(int start, int end): 返回一个新字符串,包含当前字符串构建器中从指定位置开始的特定长度的字符序列。
  8. reverse(): 反转当前字符串构建器中的字符序列。
  9. length(): 返回当前字符串构建器中的字符序列的长度。
  10. 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;  
    }  
}

  • 27
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值