LeetCode 652, 644, 406, 341

330 阅读4分钟

LeetCode 652 Find Duplicate Subtrees

链接:leetcode.com/problems/fi…

方法:递归、哈希、树的编号

时间复杂度:O(n),n为树节点个数 想法:参考自花花酱www.youtube.com/watch?v=JLK… 。想法是对树结构进行哈希,使得相同结构的树,哪怕不是同一棵、地址不一样,也能对应到相同的值。这道题的其中一个做法是用二叉树序列化,但显然太麻烦。这里使用的是给二叉树一个编号id的方法。每一个树,都用一个字符串代表(在下面代码优化为long类型数字),他可以是当前节点的值 + 左边的id + ","+ 右边的id。这样递归到叶子节点,就会导致相同值的叶子节点,哈希出来的字符串是相同的,那么递归一路返回就最后导致相同结构的树对应的字符串完全相同。还有一个counts的哈希表,是从id映射到个数,表示能对应到这个id的树有多少个。所以说每次发现ids这个map里面没有哈希出来的字符串时,就新开一个id,为ids.size() + 1。而每次counts里面得到一个值为2的key-value pair的时候,说明有某个id被重复用到了,说明以当前的节点为根的子树遇上了重复,加入结果中。 另外一个学习点:映射树结构的时候,直接搞字符串拼接比较慢,观察到里面只跟root.val, left id, right id有关,并且题目的数字数据范围允许,因此直接把三个值拼接成一个long类型的值,这样查询比拼接字符串然后查找字符串快多了。 代码:

class Solution {
    public List<TreeNode> findDuplicateSubtrees(TreeNode root) {
        List<TreeNode> res = new ArrayList<>();
        Map<Long, Integer> ids = new HashMap<>();
        Map<Integer, Integer> counts = new HashMap<>();
        getId(root, res, ids, counts);
        return res;
    }
    
    private int getId(TreeNode root, List<TreeNode> res, Map<Long, Integer> ids, Map<Integer, Integer> counts){
        if (root == null) {
            return 0;
        }
        
        long key = ((long)root.val << 32) | (getId(root.left, res, ids, counts) << 16) | (getId(root.right, res, ids, counts));
        
        int id = 0;
        if (ids.containsKey(key)) {
            id = ids.get(key);
        }
        else {
            id = ids.size() + 1;
            ids.put(key, id);
        }
        
        int tmp = counts.getOrDefault(id, 0) + 1;
        counts.put(id, tmp);
        if (tmp == 2) {
            res.add(root);
        }
        
        return id;
    }
}

LeetCode 644 Maximum Average Subarray II

链接:leetcode.com/problems/ma…

方法:二分

时间复杂度:O(nlogL),其中n是数组长度,L是数组当中值的值域范围 想法:这个题写的是hard,但其实倒也没有那么难做,它考察了几个很好的知识点,相当于说是几个medium的综合题,那如果你对这几个题中的任何一个做的不是很熟练的话可能这题就会GG。首先是想到用二分答案来做。这题的两个变量是最大平均值v和要求的子数组最小长度k,其中被要求满足的子数组最小长度k是自变量,在此要求下的最大平均值v是变量,我们的二分答案是来二分变量。非常直观的想法是对于子数组最小长度k的要求越低,能够达到的子数组当中的最大平均值就越大。这样先想清楚了问题的第一部分。 问题的第二部分:在一个数组中,给定子数组的最小长度,怎样高效地计算有没有这样的子数组,使得平均值大于等于某个值target?这里有一个很巧妙的做法,就是我不带着原数组和平均值来找麻烦,我先在原数组里把所有数减掉target,这样就把问题转化为有没有长度>=k的子数组,和>=0。这个就比刚才的问题简单多了,这样我们就扔掉了“平均值”这个麻烦事,可以专心解决前缀问题。搞两个算前缀的,一个叫leftSum,从最左边开始加,一个叫rightSum,在它右边k个单位处。然后每次都更新一下这俩值,维护leftSum的最小值。所以只要rightSum在任何时候>=了leftSum的最小值,我们就找到了这样的子数组,问题就解决了。这个方法需要学习掌握,我记得好像还有哪里用过,但我忘了,有时间找出那道题来再更新这个地方。 代码:

class Solution {
    public double findMaxAverage(int[] nums, int k) {
        double left = -10000.0, right = 10000.0;
        while (left + 1e-5 < right) {
            double mid = (left + right) / 2;
            if (canFind(nums, k, mid)) {
                left = mid;
            }
            else {
                right = mid;
            }
        }
        
        return left;
    }
    
    private boolean canFind(int[] nums, int k, double target) {
        double leftSum = 0, rightSum = 0, minLeftSum = 0;
        for (int i = 0; i < k; i++) {
            rightSum += (nums[i] - target);
        }
        
        for (int i = k; i <= nums.length; i++) {
            if (rightSum >= minLeftSum) {
                return true;
            }
            if (i < nums.length) {
                rightSum += (nums[i] - target);
                leftSum += (nums[i - k] - target);
                minLeftSum = Math.min(minLeftSum, leftSum);
            }
        }
        
        return false;
    }
}

LeetCode 406 Queue Reconstruction by Height

链接:leetcode.com/problems/qu…

方法:贪心

时间复杂度:O(n2) 想法:主要是看能不能观察到吧。每个pair第一个元素是高度,第二个元素是前面有多少人高度>=他。一个非常笼统的想法是,肯定是个子高的人比较好处理,因为个子高的人前面比他个子高的人比较少,所以相对来说状态没那么复杂,所以做这道题应该从个子高的开始放。然后对于个子一样的人,第二个元素小的说明他站的更靠前一点,所以这样的也应该先处理。那么根据上述的方案对原数组排序。排序之后发现其实直接把每个元素插入到结果list的对应的index上就完了,这里的index就是每个人的pair的第二个元素。为什么这样是对的呢?因为我们按照之前说的排序,当一个元素放的时候,后面还没放的元素没有比他个子还高的,当前已经在list当中的元素要么是跟他一样高,要么比他高,那这时候,比方说现在处理的人,pair第二个元素是3,说明前面有三个人>=他的高度,那就直接插入到list第三个位置就满足要求了。 代码:

class Solution {
    public int[][] reconstructQueue(int[][] people) {
        Arrays.sort(people, (a, b) -> {
            if (a[0] != b[0]) {
                return b[0] - a[0];
            }
            return a[1] - b[1];
        });
        
        List<int[]> lst = new ArrayList<>();
        for (int[] p : people) {
            int index = p[1];
            lst.add(index, p);
        }
        
        int[][] res = new int[lst.size()][2];
        int tt = 0;
        for (int[] p : lst) {
            res[tt++] = p;
        }
        
        return res;
    }
}

LeetCode 341 Flatten Nested List Iterator

链接:leetcode.com/problems/fl…

方法1:全展平

想法:我一开始的做法...不知道它要考什么,就直接在constructor里面把NestedInteger全拍扁成Integer放进list了。 代码:

public class NestedIterator implements Iterator<Integer> {
    
    private List<Integer> lst;
    private int i;
    private int size;

    public NestedIterator(List<NestedInteger> nestedList) {
        this.lst = new ArrayList<>();
        dfs(lst, nestedList);
        this.i = 0;
        this.size = lst.size();
    }
    
    private void dfs(List<Integer> lst, List<NestedInteger> nestedList) {
        for (NestedInteger ni : nestedList) {
            if (ni.isInteger()) {
                lst.add(ni.getInteger());
            }
            else {
                dfs(lst, ni.getList());
            }
        }
    }

    @Override
    public Integer next() {
        return this.lst.get(i++);
    }

    @Override
    public boolean hasNext() {
        return this.i < this.size;
    }
}

方法2:栈

想法:这种想法是倒着把原本的list中的元素入栈,然后根据题目当中所说的调用方法,它每次会先调hasNext(),再调next(),那每次在hasNext()里面把栈顶拍平,保证栈顶元素一定要是Integer,这样的话在next里面直接pop出来栈顶的值即可。 代码:

public class NestedIterator implements Iterator<Integer> {
    
    private Stack<NestedInteger> stack = new Stack<>();

    public NestedIterator(List<NestedInteger> nestedList) {
        for (int i = nestedList.size() - 1; i >= 0; i--) {
            stack.push(nestedList.get(i));
        }
    }

    @Override
    public Integer next() {
        return stack.pop().getInteger();
    }

    @Override
    public boolean hasNext() {
        while (!stack.isEmpty()) {
            NestedInteger head = stack.peek();
            if (head.isInteger()) {
                return true;
            }
            
            stack.pop();
            List<NestedInteger> lst = head.getList();
            for (int i = lst.size() - 1; i >= 0; i--) {
                stack.push(lst.get(i));
            }
        }
        
        return false;
    }
}