LeetCode 945 Minimum Increment to Make Array Unique
方法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
方法: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
方法:并查集
时间复杂度:不是特别好计算,里面又有扫描,又有对子数组的排序。 想法:给一种比较暴力的写法。因为涉及合并的问题,很容易想到并查集。这个题重要的点在于怎么样比较高效的把这一通东西写完。这种写法当中我们使用原本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
方法: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
方法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);
}
}