[Leetcode] 272. Closest Binary Search Tree Value II

这一题,可以非完全的转换成为:给你一个排好序的数组,在里面找距离target最近的k个数。之所以可以这样转换,是因为对于一个bst来说,inorder可以给你一个排好序的数组

那给你一个排好序的数组,怎么求距离target最近的k个数呢?其实有两种办法。

1. 顺序遍历右移,保持一个size为k的window(因为距离target最近的k个数肯定是连续的)。找到一个临界点,这个临界点的定义是,window里最大的数(最右边)与target的距离开始大于最小的数和target的距离。那么解集就会在这个临界点的window或者右移之前的一个window出现。

2. 贪心法:保持一个头指针和尾指针,比较头指针和尾指针和target的距离,头指针距离大一点就往右移一格,反之则是尾指针往左移一格。直到头指针和尾指针中间有k个元素为止(包括头和尾指针)

2.b 贪心法:这是上面的做法的反向做法。做法2是从外往里收缩。而这个2.b是找到排好序的数组里找到距离target最近的元素的位置,然后用类似的原理外扩:头指针的距离近一些,就左移一格,否则则是尾指针右移一格。直到头尾指针中间有k个元素。

所以,其实,最朴实的一种做法就是先inorder 扫出一个数组来,然后,用上述三种方式的任意一种即可。

    public List<Integer> closestKValues(TreeNode root, double target, int k) {
        return _closestKValues3(root, target, k);
    }
    
    private List<Integer> _closestKValues3(TreeNode root, double target, int k) {
        Stack<TreeNode> treeStk = new Stack<TreeNode>();
        List<Integer> inOrdered = new ArrayList<Integer>();
        while (!treeStk.isEmpty() || root != null) {
            if (root != null) {
                treeStk.push(root);
                root = root.left;
            } else {
                root = treeStk.pop();
                inOrdered.add(root.val);
                root = root.right;
            }
        }
        
        int head = 0, tail = inOrdered.size() - 1;
        while (tail - head >= k) {
            double headDist = Math.abs((double)inOrdered.get(head) - target);
            double tailDist = Math.abs((double)inOrdered.get(tail) - target);
            if (headDist > tailDist) {
                head++;
            } else {
                tail--;
            }
        }
        
        return inOrdered.subList(head, tail + 1);
    }

其实还可以一边遍历一边确定k个数,用第一个方法那样。譬如说下面这段代码:
 

    private List<Integer> _closestKValues2(TreeNode root, double target, int k) {
        Stack<TreeNode> treeStack = new Stack<TreeNode>();
        LinkedList<Integer> result = new LinkedList<Integer>();
        while (root != null || !treeStack.isEmpty()) {
            if (root != null) {
                treeStack.push(root);
                root = root.left;
            } else {
                root = treeStack.pop();
                if (result.size() < k) {
                    result.add(root.val);
                } else if (Math.abs((double)result.getFirst() - target) > Math.abs((double)root.val - target)) {
                    result.removeFirst();
                    result.add(root.val);
                } else {
                    break;
                }
                
                root = root.right;
            }
        }
        
        return result;
    }

那么,在balanced tree下实现小于O(n)的情况,到底是如何做到呢?复杂度是多少咧?
好,我能理解的范围内,是O(logn + k)。原理还是没有逃离上面的描述,是2.b的进阶做法。首先O(logn)找到距离target最近的数。因为是Balanced的,所以O(logn)是可以保证的(如果不是balanced的话,最坏的情况依旧是O(n))。难点就在于如何从中心往两边扩散。这就高端了,具体请参见:https://leetcode.com/problems/closest-binary-search-tree-value-ii/discuss/129442/Two-Stack-Iterators-in-Java-O(logN-+-K)。。。

咳咳。这确实挺高端的,它的做法是如何循环的从中间某个节点开始做inorder,关键点就在于如何build up stack。其实在那个链接里,只需要关注stackBuild的isSuc的情况即可。这就是正向in order(左中右)的方法。也就是如果在搜寻距离最近的点的过程里,往左走了,就把节点push进stack,否则就略过。原因很简单,在in order里,如果先往左走了,还需要往右走,但如果往右走了,就不需要再考虑往左走了。至于stackNext这个函数,其实就是一个tree iterator很常见的做法。就是把上面代码循环的部分拆解开来了而已。在链接里的两个函数里,isSuc表示的是正向inorder,否则是反向inOrder,也就是右中左,正向in order走的是ascending的排序序列,反向in order走的是descending的排序序列。所以当找到最接近的点,然后一边反向 in order走,越走越小,另一边正向 in order走,越走越大, 这样就可以达到2.b贪心法的效果。这样吧,给出自己写的代码,和上面link有不一样的哦。真的。。。

    private List<Integer> _closestKValues4(TreeNode root, double target, int k) {
        Stack<TreeNode> ascStk = buildStack(root, target, true);
        Stack<TreeNode> descStk = buildStack(root, target, false);
        List<Integer> result = new LinkedList<Integer>();
        while (result.size() < k) {
            if (ascStk.isEmpty() ||
                (!descStk.isEmpty() && Math.abs(ascStk.peek().val - target) >= Math.abs(descStk.peek().val - target))) {
                result.add(descStk.peek().val);
                iterateStack(descStk, false);
            } else {
                result.add(ascStk.peek().val);
                iterateStack(ascStk, true);
            }
        }
        
        return result;
    }

    private void iterateStack(Stack<TreeNode> nodeStk, boolean asc) {
        TreeNode node = nodeStk.pop();
        node = asc ? node.right : node.left;
        while (node != null) {
            nodeStk.push(node);
            node = asc ? node.left : node.right;
        }
    }
    
    private Stack<TreeNode> buildStack(TreeNode root, double target, boolean asc) {
        Stack<TreeNode> nodeStk = new Stack<TreeNode>();
        while (root != null) {
            if (root.val >= target) {
                if (asc) nodeStk.push(root);
                root = root.left;
            } else {
                if (!asc) nodeStk.push(root);
                root = root.right;
            }
        }
        
        return nodeStk;
    }

猜你喜欢

转载自blog.csdn.net/chaochen1407/article/details/81706523