关于面试

来源:6-12 删除二分搜索树的任意元素

慕妹2978617

2020-03-22

老师您好,我有回来了,上次看到这里的时候我提问了一个问题图片描述
这个时候我的学习就是看着视频1.5倍速,看完视频简单理解加上看的认真,代码细节就记下来额。然后背着写下课程的代码。然后就结束了,以为这样就可以了。然后重数组到Tree,越到后面就越吃力,因为前面的代码都是背下来的,缺乏自己的理解。等到后面多个知识点一起运用的时候,对数据和方法的理解,递归的理解都不是很好。这次我又从第二章重新看了一遍,放慢倍速,在老师讲完将要实现算法的的思想后,在写代码之前,我会根据算法的思想,去代码实现,然后单步调试,用纸笔去写去思考。当然,也会遇到不对的,遇到不对的就接着看视频,看看老师如何实现,对比老师的实现,我的问题在哪里,如果我实现的对了,也会看一遍,加深印象。同时我看完后会在去看下《漫画算法》这本书,根据课程的讲解,找到对应的章节,在看下数据结构这部分,最后,在找些简单的leetcode的算法,加固一下学习。
第一遍看到第六章可能我只学会了20%,那这次应该可以达到70%。因为还是有的不理解,感觉很绕。但是这次之前的leetcode困惑的问题有些可以自己搞定了,有的看别人的解法加上找资料,可以理解更多了一点,虽然还是有很多不理解,但是要好很多,信心和积极性也要高了。
谢谢老师上次对我的回复。这次我也把课程中第六章,说的算法都非递归实现了,还有之前最困扰我的就是这部分代码图片描述我到现在也没太明白,不过我有了自己的理解,还希望老师帮我看下。在我的基础上,分析下我的分析对嘛!理解的哪里有问题?

还有一个问题,我买了老师的两个课程还有一个玩转算法面试-- Leetcode真题分门别类讲解 ,我是一个工作2年多的Android,想继续找Android,马上就要面试了,因为还有别的要看,我现在看到第六章,为了面试这2个课程我改如何看,上班以外的空闲时间20天我要看完本课程第六章以后的本分,加上另一个课程我该为了面试如何选取。

最后附上非递归实现代码

public class BST<E extends Comparable<E>> {


    private class Node {

        public E e;

        private Node left;

        private Node right;


        public Node(E e) {
            this.e = e;
            this.left = null;
            this.right = null;
        }

        @Override
        public String toString() {
            return e.toString();
        }
    }


    private Node root;

    private int size;


    public BST() {

        root = null;
        size = 0;
    }

    public int getSize() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }


    public void add(E e) {

        //非递归
        if (root == null) {
            root = new Node(e);
        } else {
            Node cur = root;
            while (true) {
                if (cur.e.compareTo(e) < 0) {// 在右子树上
                    if (cur.right == null) {
                        cur.right = new Node(e);
                        size++;
                        return;
                    } else {
                        cur = cur.right;
                    }
                } else if (cur.e.compareTo(e) > 0) {//再左子树上
                    if (cur.left == null) {
                        cur.left = new Node(e);
                        size++;
                        return;
                    } else {
                        cur = cur.left;
                    }

                } else {
                    return;
                }
            }
        }


        //递归写法
//        if (root == null) {
//            root = new Node(e);
//        }else {
//            add(root, e);
//        }

    }

    public boolean contains(E e) {

        Node cur = root;
        if (cur == null)
            return false;

        while (cur != null) {
            if (cur.e.equals(e)) {
                return true;
            } else if (cur.e.compareTo(e) > 0) {//在左子树上
                cur = cur.left;
            } else {//进入这个条件一定是在右子树上
                cur = cur.right;
            }
        }
        //如果在循环中没有返回 那么一定是没有找到 那么这里返回false
        return false;

        //递归实现
//        return contains(root, e);
    }

    private boolean contains(Node root, E e) {

        if (root == null) {
            return false;
        }

        if (root.e.equals(e)) {
            return true;
        } else if (root.e.compareTo(e) > 0) {//在左子树上
            return contains(root.left, e);
        } else {// (root.e.compareTo(e) < 0) 一定是在右子树上
            return contains(root.right, e);
        }
    }

    private Node add(Node node, E e) {
        if (node == null) {
            size++;
            return new Node(e);
        }

        if (node.e.compareTo(e) < 0) {// 在右子树上
            node.right = add(node.right, e);
        } else if (root.e.compareTo(e) > 0) {//再左子树上
            node.left = add(node.left, e);
        }
        return node;
    }


    /**
     * 前序遍历
     */
    public void preOrder() {

        preOrder(root);
    }

    /**
     * 前序遍历的非递归遍历
     */
    public void preOrderUn() {

        if (root == null) {
            return;
        }


        Stack<Node> stack = new Stack<>();

        stack.push(root);

        while (!stack.isEmpty()) {
            Node node = stack.pop();

            System.out.println(node.e);
            if (node.right != null) {
                stack.push(node.right);
            }

            if (node.left != null) {
                stack.push(node.left);
            }

        }

    }

    private void preOrder(Node root) {

        if (root == null) {
            return;
        }

        System.out.println(root.e);
        preOrder(root.left);
        preOrder(root.right);
    }

    /**
     * 递归中序遍历
     */
    public void inOrder() {
        inOrder(root);
    }

    private void inOrder(Node root) {
        if (root == null) {
            return;
        }

        inOrder(root.left);
        System.out.println(root.e);
        inOrder(root.right);
    }

    public void inOrderUn() {
        inOrderUn(root);
    }

    private void inOrderUn(Node root) {
        if (root == null) {
            return;
        }

        Stack<Node> stack = new Stack<>();
        stack.push(root);

        Node cur = root;
        while (!stack.isEmpty()) {

            while (cur.left != null) {
                stack.push(cur.left);
                cur = cur.left;
            }

            Node pop = stack.pop();
            System.out.println(pop.e);
            if (pop.right != null) {
                stack.push(pop.right);
                cur = pop.right;
            }
        }
    }

    /**
     * 递归后序遍历
     */
    public void postOrder() {
        postOrder(root);
    }

    private void postOrder(Node node) {
        if (node == null) {
            return;
        }

        postOrder(node.left);
        postOrder(node.right);
        System.out.println(node.e);
    }


    public void postOrderUn() {
        postOrderUn(root);
    }

    /**
     * https://blog.csdn.net/qq_39445165/article/details/90749343?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
     * <p>
     * 非递归后序遍历
     *
     * @param root
     */
    private void postOrderUn(Node root) {
        Stack<Node> src = new Stack<>();
        Stack<Node> pos = new Stack<>();

        src.push(root);

        while (!src.isEmpty()) {
            Node pop = src.pop();
            pos.push(pop);

            if (pop.left != null) {
                src.push(pop.left);
            }

            if (pop.right != null) {
                src.push(pop.right);
            }
        }

        while (!pos.isEmpty()) {
            System.out.println(pos.pop().e);
        }
    }

    /**
     * 查找二分搜索树的最小值
     */
    public E minimum() {
        if (size == 0) {
            throw new IllegalArgumentException("空树");
        }

        return minimum(root).e;
    }

    /**
     * 循环查找
     *
     * @param root
     * @return
     */
    private Node minimum(Node root) {
        if (root.left == null) {
            return root;
        }
        return minimum(root.left);
    }

    /**
     * 查找最大值
     */
    public E maximum() {
        if (size == 0) {
            throw new IllegalArgumentException("空树");
        }
        return maximum(root).e;
    }

    private Node maximum(Node root) {
        if (root.right == null) {
            return root;
        }
        return maximum(root.right);
    }

    /**
     * 删除最小值
     */
    public E removeMin() {
        E e = minimum();
        removeMin(root);
        return e;
    }


    /**
     * 删除掉以node为根的二分搜索树中的最小节点
     * 返回删除节点后新的二分搜索树的根
     * <p>
     * TODO 删除最大节点逻辑相同
     */
    private Node removeMin(Node root) {
        if (root.left == null) {//进入这个分支说明找到了最小节点 就是root
            //这时候root.right可能为空 但是不影响逻辑
            Node rightNode = root.right;
            //将带删除的root节点 right的这指向 为null
            root.right = null;
            //此时递归进入 是已root为根节点 那么 新的根节点就是rightNode;
            size--;
            return rightNode;
        }

        //递归最后 返回的是此时根节点的左子树的最小值已经删除掉了 那么此时根节点的左子树就是返回来的新的节点
        //如果是空的话 也是正确的
        root.left = removeMin(root.left);

        //最后返回递归结束的根节点  就是删除最小值的根节点
        return root;
    }

    public E removeMax() {
        E e = maximum();

        removeMax(root);
        return e;
    }

    private Node removeMax(Node root) {
        if (root.right == null) {
            Node leftNode = root.left;
            root.left = null;
            size--;
            return leftNode;
        }

        root.right = removeMax(root.right);
        return root;
    }


    /**
     * 删除任意元素
     */
    public void remove(E e) {
        remove(root, e);
    }

    /**
     * 删除掉以node为根的二分搜索树中值为e的节点, 递归算法
     * 返回删除节点后新的二分搜索树的根
     */
    private Node remove(Node node, E e) {

        if (node == null) {//循环到最后都没有找到e元素
            return null;
        }

        if (e.compareTo(node.e) > 0) {//元素在右子树上
            //这个方法本身是删除以root为节点后的根据节点 因为传入的是右子树
            // 那么此时节点的右子树 应该接上删除节点后的根节点
            node.right = remove(node.right, e);
            //然后返回这个根节点
            return node;
        } else if (e.compareTo(node.e) < 0) {//元素在左子树上
            //同上
            node.left = remove(node.left, e);
            //然后返回这个根节点
            return node;
        } else {//e==node.e  找到了这个节点 那么判断删除节点是否有子节点

            if (node.left == null) {
                Node rightNode = node.right;
                node.right = null;
                size--;
                //返回去 这是的rightNode因为在右节点上 那么比e要大 所以进入(e.compareTo(node.e) < 0)这个分支
                //然后用node.left接上 再一直递归返回
                return rightNode;
            }

            if (node.right == null) {
                Node leftNode = node.left;
                node.left = null;
                size--;
                //返回去 这是的rightNode因为在右节点上 那么比e要大 所以进入(e.compareTo(node.e) < 0)这个分支
                //然后用node.left接上 再一直递归返回
                return leftNode;
            }

            //都没有return返回 走到这里 说明 左右子树都不是空

            //最小节点存起来
            Node successor = minimum(node);
            successor.right = removeMin(node.right); //查找右子树的最小节点  这里也可以换成查找左子树的最大节点
            successor.left = node.left;

            node.left = node.right = null;
            return successor;

        }
    }

    public int maxDepth(Node root, int left, int right) {

        if (root == null) {
            return 0;
        }

        left = maxDepth(root.left, left + 1, right);
        right = maxDepth(root.right, left, right + 1);

        return Math.max(left + 1, right + 1);
    }

    /**
     * 二分搜索树的层序遍历
     */
    public void levelOrder() {

        Queue<Node> q = new LinkedList<>();
        q.add(root);
        while (!q.isEmpty()) {
            Node cur = q.remove();
            System.out.println(cur.e);

            if (cur.left != null)
                q.add(cur.left);
            if (cur.right != null)
                q.add(cur.right);
        }
    }

    public int maxDepth() {
        return maxDepth(root, 0, 0);
    }

    @Override
    public String toString() {

        StringBuilder res = new StringBuilder();

        outputDepth(root, 0, res);
        return res.toString();
    }

    private void outputDepth(Node node, int depth, StringBuilder res) {

        if (node == null) {
            String s = output(depth) + "null\n";
            res.append(s);
            return;
        }
        res.append(output(depth)).append(node.e).append("\n");
        outputDepth(node.left, depth + 1, res);
        outputDepth(node.right, depth + 1, res);
    }

    private String output(int depth) {

        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < depth; i++) {
            builder.append("--");
        }

        return builder.toString();
    }


    public static void main(String[] args) {
//        int[] arr = {12, 3, 6, 9, 5, 2, 8, 15};
        int[] arr = {5, 3, 6, 4, 2};
//        int[] arr = {9, 5, 3, 7, 6, 8};
//        int[] arr = {6, 12, 19, 18, 17, 21, 20};

        BST<Integer> bst = new BST<>();
        for (int i = 0; i < arr.length; i++) {
            bst.add(arr[i]);
        }
        System.out.println(bst);

        System.out.println(bst.contains(0));

        System.out.println("==========递归前序遍历");
        bst.preOrder();
        System.out.println("==========非递归前序遍历");
        bst.preOrderUn();
        System.out.println("==========");

        System.out.println("==========递归中序遍历");
        bst.inOrder();
        System.out.println("==========非递归中序遍历");
        bst.inOrderUn();
        System.out.println("==========");

        System.out.println("==========递归后序遍历");
        bst.postOrder();
        System.out.println("==========非递归后序遍历");
        bst.postOrderUn();
        System.out.println("==========");


        System.out.println("Depth======" + bst.maxDepth());

        System.out.println("==========查找最小值");
        System.out.println(bst.minimum());

        System.out.println("==========查找最大值");
        System.out.println(bst.maximum());
        System.out.println("==========删除最小节点");
        bst.removeMin();
        bst.inOrder();
        System.out.println("==========删除最大节点");
        bst.removeMax();
        bst.inOrder();


        System.out.println("==========删除任意节点");
        bst.inOrder();
        System.out.println("=========");
        bst.remove(6);
        bst.inOrder();
    }
}



利用递归来实现链表的所有操做

/**
 * 利用递归来实现链表的所有操做
 */
public class DummyLinkedList<E> {

    private class Node {

        public E e;

        public Node next;

        public Node(E e) {
            this.e = e;
            next = null;
        }

        public Node(E e, Node next) {
            this.e = e;
            this.next = next;
        }

        @Override
        public String toString() {
            return e.toString();
        }
    }


    private Node dummyHead;
    private int size;

    public DummyLinkedList() {
        dummyHead = new Node(null);
        size = 0;
    }

    public DummyLinkedList(E e) {
        dummyHead = new Node(null);
        dummyHead.next = new Node(e);
        size = 0;
    }

    public int getSize() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public void add(int index, E e) {

        if (index < 0 || index > size) {//size指向的是最后一个元素的下一个元素 那么index=size表示向尾部添加元素
            throw new IllegalArgumentException("下标越界");
        }

        add(dummyHead, index, 0, e);
    }

    /**
     * @param head    头节点
     * @param index   用户要插入的index
     * @param current 表示 当前执行到那个index
     * @param e       要插入的元素
     */
    private void add(Node head, int index, int current, E e) {
        if (current == index) {
            size++;
            head.next = new Node(e, head.next);
            return;
        }
        add(head.next, index, current + 1, e);
    }

    public void addFirst(E e) {
        add(0, e);
    }

    public void addLast(E e) {
        add(size - 1, e);
    }

    public E remove(int index) {

        return remove(dummyHead, index, 0);

    }

    private E remove(Node head, int index, int current) {
        if (current == index) {
            Node delNode = head.next;
            head.next = head.next.next;
            delNode.next = null;
            return delNode.e;
        }

        E e = remove(head.next, index, current + 1);
        return e;
    }

    public E removeFirst() {
        return remove(0);
    }

    public E removeLast() {
        return remove(size - 1);
    }

    public void set(int index, E e) {
        set(dummyHead.next, index, 0, e);
    }

    private void set(Node head, int index, int current, E e) {
        if (current == index) {
            head.e = e;
            return;
        }

        set(head.next, index, current + 1, e);
    }

    public boolean contains(E e) {

        return contains(dummyHead.next, e);
    }

    private boolean contains(Node head, E e) {
        if (head == null) {
            return false;
        } else {
            if (head.e.equals(e)) {
                return true;
            }
        }
        return contains(head.next, e);
    }


    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        for (Node node = dummyHead.next; node != null; node = node.next) {
            builder.append(node.e).append("->");
        }
        builder.append("NULL");
        return builder.toString();
    }

    public static void main(String[] args) {
        DummyLinkedList<Integer> linkedList = new DummyLinkedList<>();
        for (int i = 0; i < 3; i++) {
            linkedList.addFirst(i);
            System.out.println(linkedList);
        }

        linkedList.addFirst(333);
        System.out.println(linkedList);


        linkedList.add(2, 666);
        System.out.println(linkedList);
        linkedList.remove(2);
        System.out.println(linkedList);

        System.out.println(linkedList.contains(333));
        System.out.println(linkedList.contains(555));
    }
}
写回答

2回答

liuyubobobo

2020-03-23

关于 BST 非递归实现,我简单看了一下,思路上没有大的问题。但是代码细节是否存在隐含的问题,还是建议你自己测试一下。在这个课程中,我在完成所有数据结构的基本逻辑之后,都会设计简单的测试用例测试一下,这些测试的方式,你可以参考。同时,在下一章,你就会看到,我会将我们写的 BST 封装成集合或者映射类。然后,就可以用来解决 Leetcode 上需要使用集合或者映射的问题。使用这个方式,可以用 Leetcode 帮助我们测试我们自己的代码。(将本来使用的 Java 中的 Set 类或者 Map 类 替换成我们自己的类)。


对于链表的递归写法,我在这个课程的补充代码中给了一版我的实现,可以参考:https://github.com/liuyubobobo/Play-with-Data-Structures/blob/master/05-Recursion/Optional-01-Recursive-LinkedList/src/LinkedListR.java 


关于我的课程结合面试如何学习,可以参考这里:http://coding.imooc.com/learn/questiondetail/54345.html 


如果你没有购买《算法与数据结构》,也没有关系,主要是自学一下排序就好。


另外,看你面试的企业是大厂还是中厂。如果是大厂,多刷一些题。大厂的算法问题更偏算法设计,相对比较灵活;如果是中厂,多关注一下底层实现,比如快排的实现。


在临近面试前,最好看看你面试的厂子的面试真题,找找感觉。如果只有 20 天,时间稍微有点儿紧,可能不够特别系统的学习了。也可以根据以往的面试题,做查缺补漏式的突击。


最后,面试不仅仅考算法,切记!


提前预祝面试成功。加油!:)

0
0

慕妹2978617

提问者

2020-03-22

文中关于任意删除元素的不理解的地方忘记上传图片了,老师帮我分析下

//img.mukewang.com/szimg/5e7725630849169310801440.jpg

//img.mukewang.com/szimg/5e772563081e003d10801440.jpg


0
2
liuyubobobo
抱歉,通过这个截图,我没有看出你的具体不理解的问题是?
2020-03-23
共2条回复

玩转数据结构

动态数组/栈/队列/链表/BST/堆/线段树/Trie/并查集/AVL/红黑树…

6221 学习 · 1704 问题

查看课程