669. Trim a Binary Search Tree

Given a binary search tree and the lowest and highest boundaries as L and R, trim the tree so that all its elements lies in [L, R] (R >= L). You might need to change the root of the tree, so the result should return the new root of the trimmed binary search tree.
Example 1:
Input:
1
/
0 2
L = 1
R = 2
Output:
1
\
2

通过这道题我明白了「形参,实参」这个概念。
1.形参变量只有在被调用时才分配内存单元,在调用结束时, 即刻释放所分配的内存单元。因此,形参只有在函数内部有效。 函数调用结束返回主调函数后则不能再使用该形参变量。

一开始我的代码:

        if (root == null) return null;
        if (root.val < L) {
            root = root.right;
            return trimBST(root, L, R);
        }
        if (root.val > R) {
            root = root.left;
            return trimBST(root, L, R);
        }
        if (root.left != null && root.left.val < L) {
            root.left = root.left.right;
        }
        if (root.right != null && root.right.val > R) {
            root.right = root.right.left;
        }
        root.left = trimBST(root.left, L, R);
        root.right = trimBST(root.right, L, R);
        return root;
    }

注意

        root.left = trimBST(root.left, L, R);
        root.right = trimBST(root.right, L, R);

起初我想不通为什么要给root.left和root.right赋值,明明我已经在递归中改变了它的孩子的内存了啊(root = root.right)。但是由于进入了一个新的函数,这样做其实并没有改变它孩子的内存,而是制造了一个新的引用(栈内存)指向了root.right;而如果这时我改变root.right的值,root.right会指向另一块内存,始终不会对root造成影响。

    public TreeNode trimBST(TreeNode root, int L, int R) {
        if (root == null) return null;
        if (root.val < L) {
            return trimBST(root.right, L, R);
        }
        if (root.val > R) {
            return trimBST(root.left, L, R);
        }
        if (root.left != null && root.left.val < L) {
            root.left = root.left.right;
        }
        if (root.right != null && root.right.val > R) {
            root.right = root.right.left;
        }
        root.left = trimBST(root.left, L, R);
        root.right = trimBST(root.right, L, R);
        return root;
    }

Or:

    public TreeNode trimBST(TreeNode root, int L, int R) {
        if (root == null) return null;
        
        if (root.val < L) return trimBST(root.right, L, R);
        if (root.val > R) return trimBST(root.left, L, R);
        
        root.left = trimBST(root.left, L, R);
        root.right = trimBST(root.right, L, R);
        
        return root;
    }

Dec 7 2017 review

今天又蒙逼了,不知怎么又绕内存/引用的问题上了,我写了一段测试代码,发现在函数里改变一个对象,那个对象竟然真的被改变了。比如我初始化一个数组是{1,2,3}(数组是对象,它是new出来的),在函数里改变数组第三个元素的值,我预想的是,因为函数和它的参数是保存在方法区的,函数参数中的数组,其实只是一个指向原来arr的引用,所以我在函数中改变arr又没有return回去,按理说是数组还会是{1,2,3}吧,但事实并非如此,函数执行完后arr被改变了。我的世界观又一次崩塌了。。。


669. Trim a Binary Search Tree_第1张图片
image.png

然后我看了下这个blog
里的例子,看到String拼接那部分才发现原因。其实这个我早就知道,只是长期不刷算法忘记了。。我传入的int数组,参数里的arr确实只是一个引用没错,但是我改变的不是这个对象的引用,我改变的是它里面的元素。

我在上面说过,「而如果这时我改变root.right的值,root.right会指向另一块内存,始终不会对root造成影响。」为什么会指向另一块内存,而不是直接在原来的堆内存上修改?用形参实参来解释其实不太严谨,下面是我看的另外一个问题得出的思考:
静态方法会导致内存泄露吗?

这种写法不会造成内存泄漏。为什么不会呢?要想造成内存泄漏,你的工具类对象本身要持有指向传入对象的引用才行!但是当你的业务方法调用工具类的静态方法时,会生产一个称为栈帧(stack frame)的东西(每次方法调用,JVM都会生成一个栈帧),当方法调用结束返回的时候,当前方法栈帧就已经被弹出了并且被释放掉了。 整个过程结束时,工具类对象本身并不会持有传入对象的引用。

也就是说之前理解的并不对,传入的参数并不是对象的引用,而是一个栈帧(stack frame),或者说栈帧的一部分,我猜是放在stack frame的局部变量变里。对象的引用存在虚拟机栈,栈帧也存放在虚拟机栈,其实也就是通常理解的栈内存,每个栈帧都有自己的局部变量表。可以这么理解,对象的引用劫持了对象,指向了对象在堆内存中的首地址,通过等于号赋值的都是引用传递,但是栈帧中的那个「参数」没有改变堆内存的权利,如果要改变它指向的对象,只能自己重新创建一块内存。

每当一个java方法被执行时都会在虚拟机中新创建一个栈帧,方法调用结束后即被销毁。
栈帧存储空间为虚拟机栈,每一个栈帧都有自己的局部变量表、操作数栈和指向当前方法所属的类的运行时常量池的引用。
在每个线程中,只有目前正在执行的那个方法的栈帧是活动的。这个栈帧就称为当前栈帧(current frame),这个栈帧对应的方法称之为当前方法(current method)。定义这个方法的类就称之为当前类(current class)。对局部变量表和操作数栈的各种操作,通常都指的是对当前栈帧的局部变量表和操作数栈进行的操作。如下图所示,

如果用全局变量,就不会有这种问题了。

我感觉我真的有点钻牛角尖了。。
贴一个我的回答:
https://www.zhihu.com/question/27764932/answer/272648864

ref:
https://www.cnblogs.com/editice/p/5420716.html
http://blog.csdn.net/zgrjkflmkyc/article/details/8774511

你可能感兴趣的:(669. Trim a Binary Search Tree)