LeetCode 945, 742, 721, 662, 437

243 阅读5分钟

LeetCode 945 Minimum Increment to Make Array Unique

链接:leetcode.com/problems/mi…

方法1:贪心

时间复杂度:O(nlogn) 想法:这个应该说是最直观的做法。这个题比方说拿到所有的数,可能有很多数是相同的,都堆在一个地方,那么如果想把这一堆的数字全都分开,比方说数字是6,有5个,那就得变成6、7、8、9、10.然后原来的数组里可能原本是有8的,那么8就得变成11,以此类推。所以直接给数组排序,然后来搞一个变量叫need,它代表的是下一个要遍历的数应该变成的数。如果need小于当前的数,need = num + 1,代表之前给出的need太小了,那当前遍历到的这个数显然是不需要变化的,这时候如果继续往下遍历,我们至少会期望这下一个数变成num + 1,所以这个地方更新一下need。否则的话当前的数没有达到need,就计算一下变成need需要加多少,加在res上,然后还是更新need。这个做法非常直观,但是排序成为了算法的瓶颈。 代码:

class Solution {
    public int minIncrementForUnique(int[] nums) {
        int res = 0, need = 0;
        Arrays.sort(nums);
        
        for (int num : nums) {
            if (need < num) {
                need = num + 1;
            }
            else {
                res += need - num;
                need++;
            }
        }
        
        return res;
    }
}

方法2:并查集

时间复杂度:O(n + 2 * max(nums)) 想法:其实当时考虑到一堆数可能有相同值,会堆在一起,所以也考虑了并查集的做法,但当时没有想清楚具体该怎么写,就是谁来做一个集合的代表节点。这个题当中代表节点,find出来的值就是第一种写法当中的need,只不过是在这里用并查集写的。一开始对于并查集,father[x] = x。每次用find求出need来,然后res加上所对应的需要的改动,然后把x所在的集合合并到x+1所在的集合上面去。 代码:

class Solution {
    private int[] father = new int[200010];
    
    public int minIncrementForUnique(int[] nums) {  
        int res = 0;
        
        for (int i = 0; i < 200010; i++) {
            father[i] = i;
        }
        
        for (int num : nums) {
            int x = find(num);
            res += x - num;
            father[x] = x + 1;
        }
        
        return res;
    }
    
    private int find(int x) {
        if (father[x] == x) {
            return x;
        }
        
        return father[x] = find(father[x]);
    }
}

LeetCode 742 Closest Leaf in a Binary Tree

链接:leetcode.com/problems/cl…

方法:BFS

时间复杂度:O(n) 想法:这题说难倒也不至于...但是主要就是我一开始觉得对于树的题,树是递归性质这么好的一种结构,应该有比建完图再BFS更优雅的递归写法,然而我想多了。只能说,见到树的题目心理上不要觉得这么做不行,反正时间复杂度是O(n)。说回这题,值为k的节点只有一个,所以开一个递归函数建立回边,具体到代码上就是做出一个child指向parent的哈希表。在找到k节点之前一直这么做。这个地方就有两种写法,因为k节点的子树当中是没有必要建立回边的,因此一种写法就是说这个递归函数里面范围TreeNode,就是找到k节点之后返回,否则null,这样写会稍稍快一点点,因为不用给k的子树建立回边。当然如果想写的简单点,就直接void函数建立回边,把整个树的回边全建完,并且开一个全局变量存k节点,走到某个点值为k之后就把这个全局变量设上。第二种比较好些,我们这里给出第一种写法的代码。 代码:

/**
 * 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 int findClosestLeaf(TreeNode root, int k) {
        Map<TreeNode, TreeNode> back = new HashMap<>();
        TreeNode kNode = find(root, k, back);
        
        Set<TreeNode> visited = new HashSet<>();
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(kNode);
        int step = 0;
        
        while (!queue.isEmpty()) {
            TreeNode head = queue.poll();
            if (head.left == null && head.right == null) {
                return head.val;
            }
            
            if (head.left != null && !visited.contains(head.left)) {
                queue.offer(head.left);
                visited.add(head.left);
            }
            if (head.right != null && !visited.contains(head.right)) {
                queue.offer(head.right);
                visited.add(head.right);
            }
            if (back.containsKey(head) && !visited.contains(back.get(head))) {
                queue.offer(back.get(head));
                visited.add(back.get(head));
            }
            step++;
        }
        
        return -1;
    }
    
    private TreeNode find(TreeNode root, int k, Map<TreeNode, TreeNode> back) {
        if (root.val == k) {
            return root;
        }
        
        if (root.left != null) {
            back.put(root.left, root);
            TreeNode l = find(root.left, k, back);
            if (l != null) {
                return l;
            }
        }
        
        if (root.right != null) {
            back.put(root.right, root);
            TreeNode r = find(root.right, k, back);
            if (r != null) {
                return r;
            }
        }
        
        return null;
    }
}

LeetCode 721 Accounts Merge

链接:leetcode.com/problems/ac…

方法:并查集

时间复杂度:不是特别好计算,里面又有扫描,又有对子数组的排序。 想法:给一种比较暴力的写法。因为涉及合并的问题,很容易想到并查集。这个题重要的点在于怎么样比较高效的把这一通东西写完。这种写法当中我们使用原本accounts列表当中的下标当做id,来放进并查集。那么先计算出一个email->set of ids的哈希表,这样的话再遍历一遍就可以实现id之间的合并,因为对于每个email,它的对应的所有id应该全都合并掉。这样一来我们还可以计算出root id->set of emails的哈希表,然后遍历即可,把每个root id对应的emails排个序,做成最终的结果返回。 代码:

class UnionFind {
    int[] father;
    
    public UnionFind(int n) {
        father = new int[n];
        for (int i = 0; i < n; i++) {
            father[i] = i;
        }
    }
    
    public void union(int a, int b) {
        int fa = find(a);
        int fb = find(b);
        if (fa != fb) {
            father[fa] = fb;
        }
    }
    
    public int find(int x) {
        int j, fx;
        j = x;
        
        while (j != father[j]) {
            j = father[j];
        }
        
        while (x != j) {
            fx = father[x];
            father[x] = j;
            x = fx;
        }
        
        return j;
    }
}
class Solution {
    private UnionFind uf;
    
    public List<List<String>> accountsMerge(List<List<String>> accounts) {
        Map<String, List<Integer>> emailToIds = getEmailToIds(accounts);
        
        int n = accounts.size();
        uf = new UnionFind(n);
        
        for (String email : emailToIds.keySet()) {
            int root = emailToIds.get(email).get(0);
            for (int i = 1; i < emailToIds.get(email).size(); i++) {
                uf.union(emailToIds.get(email).get(i), root);
            }
        }
        
        Map<Integer, Set<String>> idToEmailSet = getIdToEmailSet(accounts);
        List<List<String>> res = new ArrayList<>();
        
        for (Integer id : idToEmailSet.keySet()) {
            List<String> lst = new ArrayList(idToEmailSet.get(id));
            Collections.sort(lst);
            lst.add(0, accounts.get(id).get(0));
            res.add(lst);
        }
        
        return res;
    }
    
    private Map<Integer, Set<String>> getIdToEmailSet(List<List<String>> accounts) {
        Map<Integer, Set<String>> res = new HashMap<>();
            
        for (int i = 0; i < accounts.size(); i++) {
            int rootId = uf.find(i);
            if (!res.containsKey(rootId)) {
                res.put(rootId, new HashSet<>());
            }
            for (int j = 1; j < accounts.get(i).size(); j++) {
                res.get(rootId).add(accounts.get(i).get(j));
            }
        }
        
        return res;
    }
    
    private Map<String, List<Integer>> getEmailToIds(List<List<String>> accounts) {
        Map<String, List<Integer>> res = new HashMap<>();
        
        for (int i = 0; i < accounts.size(); i++) {
            for (int j = 1; j < accounts.get(i).size(); j++) {
                String email = accounts.get(i).get(j);
                if (!res.containsKey(email)) {
                    res.put(email, new ArrayList<>());
                }
                res.get(email).add(i);
            }
        }
        
        return res;
    }
}

LeetCode 662 Maximum Width of Binary Tree

链接:leetcode.com/problems/ma…

方法:BFS、二叉树编号

时间复杂度:O(n) 想法:因为最大宽度是指的同一层的,那其实想到BFS是很简单的事情。这个题更多程度上我觉得考的试二叉树编号。记住这样一个编号规则:我们给二叉树的根节点标号为1。对于任一节点,假设它的编号为x,那么它的左子节点的编号为2 * x,右子节点编号为2 * x + 1。这样就能使得整个二叉树的节点都有unique的编号。对于这道题目来说,LeetCode后来又加了几个test case,导致最后编号超级大,超过int表示范围,那么其实解决办法就是offset。因为但对这道题来说我们没有必要做unique编号,我们只需要知道一层之间相对的编号即可,因此可以拿上一层的最后一个节点的编号当成offset,然后这一层的数算出来之后全部减掉offset,以此来规避int溢出。 代码:

/**
 * 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 Point {
    public TreeNode node;
    public int index;
    
    public Point(TreeNode node, int index) {
        this.node = node;
        this.index = index;
    }
}

class Solution {
    public int widthOfBinaryTree(TreeNode root) {
        if (root == null) {
            return 0;
        }
        
        if (root.left == null && root.right == null) {
            return 1;
        }
        
        Queue<Point> queue = new LinkedList<>();
        queue.offer(new Point(root, 1));
        int res = 0;
        
        while (!queue.isEmpty()) {
            int size = queue.size();
            int minIndex = Integer.MAX_VALUE, maxIndex = Integer.MIN_VALUE;
            
            int offset = queue.peek().index;
            
            for (int i = 0; i < size; i++) {
                Point head = queue.poll();
                TreeNode node = head.node;
                int curIndex = head.index;
                minIndex = Math.min(curIndex, minIndex);
                maxIndex = Math.max(curIndex, maxIndex);
                if (node.left != null) {
                    queue.offer(new Point(node.left, curIndex * 2 - offset));
                }
                if (node.right != null) {
                    queue.offer(new Point(node.right, curIndex * 2 + 1 - offset));
                }
            }
            res = Math.max(res, maxIndex - minIndex + 1);
        }
        
        return res;
    }
}

LeetCode 437 Path Sum III

链接:leetcode.com/problems/pa…

方法1:DFS

时间复杂度:O(n2) 想法:往递归上想,找出规律来直接写递归。root的pathSum值,无非就是root的左子树的所有pathSum,加上root的右子树的所有pathSum,加上严格以root为起点的所有值为targetSum的路径。所以这样就写完了大函数的递归,接下来要实现“严格以root为起点的所有值为targetSum的路径有多少”。这个也是个比较简单的递归,就不在这里赘述了。但是更大的问题在于,在大的递归函数调用的时候,它需要调的这个子函数时间复杂度是O(n),那么其实每次问题缩小的时候会有O(n)的时间损耗,最终导致时间复杂度是O(n2)。 代码:

class Solution {
    public int pathSum(TreeNode root, int targetSum) {
        if (root == null) {
            return 0;
        }
        
        return pathSum(root.left, targetSum) + pathSum(root.right, targetSum) + calFromRoot(root, targetSum);
    }
    
    private int calFromRoot(TreeNode root, int target) {
        if (root == null) {
            return 0;
        }
        
        int res = 0;
        if (root.val == target) {
            res++;
        }
        
        return res + calFromRoot(root.left, target - root.val) + calFromRoot(root.right, target - root.val);
    }
}

方法2:DFS + 哈希表

时间复杂度:O(n) 想法:想法非常类似LeetCode 560,这题如果给数组的话我保准能秒杀=_=||,但是一换成树就想不到这种做法,是真的尴尬。递归的时候带上一个HashMap,记录的是遍历到一个节点的时候,从原本树的root一直连过来的这一条长边,所有的前缀和出现的次数,那就变成跟LeetCode 560一样了。 代码:

/**
 * 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 {
    private int res = 0;
    
    public int pathSum(TreeNode root, int targetSum) {
        if (root == null) {
            return 0;
        }
        
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        dfs(root, 0, targetSum, map);
        
        return res;
    }
    
    private void dfs(TreeNode root, int cur, int target, Map<Integer, Integer> map) {
        if (root == null) {
            return;
        }
        
        cur += root.val;
        res += map.getOrDefault(cur - target, 0);
        int tmp = map.getOrDefault(cur, 0) + 1;
        map.put(cur, tmp);
        
        dfs(root.left, cur, target, map);
        dfs(root.right, cur, target, map);
        
        tmp--;
        map.put(cur, tmp);
    }
}