From 3f86ca9f2f579e5b99140a702ea4a27a2f4f42cb Mon Sep 17 00:00:00 2001 From: lmgyuan Date: Thu, 30 Jan 2025 23:39:51 -0500 Subject: [PATCH] find all java files that have >= 100 lines --- .../com/fishercoder/file_count/counter.py | 48 + .../fishercoder/file_count/firstthousand.txt | 9379 +++++++++++++++++ .../fishercoder/file_count/fourththousand.txt | 121 + .../fishercoder/file_count/secondthousand.txt | 1604 +++ .../fishercoder/file_count/thirdthousand.txt | 359 + 5 files changed, 11511 insertions(+) create mode 100644 src/main/java/com/fishercoder/file_count/counter.py create mode 100644 src/main/java/com/fishercoder/file_count/firstthousand.txt create mode 100644 src/main/java/com/fishercoder/file_count/fourththousand.txt create mode 100644 src/main/java/com/fishercoder/file_count/secondthousand.txt create mode 100644 src/main/java/com/fishercoder/file_count/thirdthousand.txt diff --git a/src/main/java/com/fishercoder/file_count/counter.py b/src/main/java/com/fishercoder/file_count/counter.py new file mode 100644 index 0000000000..05e95402ad --- /dev/null +++ b/src/main/java/com/fishercoder/file_count/counter.py @@ -0,0 +1,48 @@ +import os + +def count_lines_in_file(file_path): + """Counts the number of lines in a given file.""" + with open(file_path, 'r', encoding='utf-8') as file: + return sum(1 for _ in file) + +def read_file_content(file_path): + """Reads and returns the content of a file.""" + with open(file_path, 'r', encoding='utf-8') as file: + return file.read() + +def scan_java_files(directory): + """Scans Java files in a directory and filters those with over 100 lines.""" + java_files_over_100_lines = [] + + for root, _, files in os.walk(directory): + for file in files: + if file.endswith(".java"): + file_path = os.path.join(root, file) + line_count = count_lines_in_file(file_path) + if line_count > 100: + content = read_file_content(file_path) + java_files_over_100_lines.append((file, line_count, content)) + + return java_files_over_100_lines + +if __name__ == "__main__": + directory = "../solutions/fourththousand" + file_name = directory.split("/")[-1] + result = scan_java_files(directory) + + if result: + print("Java files with more than 100 lines:") + for file, lines, _ in result: + print(f"{file}: {lines} lines") + with open(f"./{file_name}.txt", "w", encoding='utf-8') as f: + f.write(f"Total files count: {len(result)}\n") + for file, lines, content in result: + f.write(f"{'='*50}\n") + f.write(f"File: {file}\n") + f.write(f"Line count: {lines}\n") + f.write(f"{'='*50}\n") + f.write("Content:\n") + f.write(content) + f.write("\n\n") + else: + print("No Java files with more than 100 lines found.") diff --git a/src/main/java/com/fishercoder/file_count/firstthousand.txt b/src/main/java/com/fishercoder/file_count/firstthousand.txt new file mode 100644 index 0000000000..26f29fa9ab --- /dev/null +++ b/src/main/java/com/fishercoder/file_count/firstthousand.txt @@ -0,0 +1,9379 @@ +Total files count: 65 +================================================== +File: _994.java +Line count: 154 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Queue; +import java.util.Set; + +public class _994 { + public static class Solution1 { + int[] directions = new int[] {0, 1, 0, -1, 0}; + + public int orangesRotting(int[][] grid) { + Queue rottens = new LinkedList<>(); + for (int i = 0; i < grid.length; i++) { + for (int j = 0; j < grid[0].length; j++) { + if (grid[i][j] == 2) { + rottens.add(new int[] {i, j}); + } + } + } + int times = 0; + while (!rottens.isEmpty()) { + int size = rottens.size(); + boolean counted = false; + for (int k = 0; k < size; k++) { + int[] rotten = rottens.poll(); + for (int i = 0; i < 4; i++) { + int x = rotten[0] + directions[i]; + int y = rotten[1] + directions[i + 1]; + if (x >= 0 + && x < grid.length + && y >= 0 + && y < grid[0].length + && grid[x][y] == 1) { + grid[x][y] = 2; + if (!counted) { + times++; + } + counted = true; + rottens.add(new int[] {x, y}); + } + } + } + } + for (int i = 0; i < grid.length; i++) { + for (int j = 0; j < grid[0].length; j++) { + if (grid[i][j] == 1) { + return -1; + } + } + } + return times; + } + } + + public static class Solution2 { + /* + * My completely original solution on 10/11/2021. + */ + public int orangesRotting(int[][] grid) { + int m = grid.length; + int n = grid[0].length; + Queue queue = new LinkedList<>(); + Set fresh = new HashSet<>(); + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (grid[i][j] == 1) { + fresh.add(i * n + j); + } else if (grid[i][j] == 2) { + queue.offer(new int[] {i, j}); + } + } + } + int time = 0; + int[] directions = new int[] {0, 1, 0, -1, 0}; + while (!queue.isEmpty() && !fresh.isEmpty()) { + int size = queue.size(); + time++; + for (int i = 0; i < size; i++) { + int[] curr = queue.poll(); + for (int k = 0; k < directions.length - 1; k++) { + int nextX = curr[0] + directions[k]; + int nextY = curr[1] + directions[k + 1]; + if (nextX >= 0 + && nextX < m + && nextY >= 0 + && nextY < n + && grid[nextX][nextY] == 1) { + fresh.remove(nextX * n + nextY); + if (fresh.isEmpty()) { + return time; + } + grid[nextX][nextY] = 2; + queue.offer(new int[] {nextX, nextY}); + } + } + } + } + return fresh.isEmpty() ? time : -1; + } + } + + public static class Solution3 { + /* + * My original solution on 10/29/2021. + */ + public int orangesRotting(int[][] grid) { + int m = grid.length; + int n = grid[0].length; + int freshOranges = 0; + Queue queue = new LinkedList<>(); + boolean[][] visited = new boolean[m][n]; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (grid[i][j] == 2) { + queue.offer(new int[] {i, j}); + visited[i][j] = true; + } else if (grid[i][j] == 1) { + freshOranges++; + } + } + } + int mins = 0; + int[] directions = new int[] {0, 1, 0, -1, 0}; + while (!queue.isEmpty()) { + int size = queue.size(); + boolean hasOneToRot = false; + for (int i = 0; i < size; i++) { + int[] curr = queue.poll(); + for (int j = 0; j < directions.length - 1; j++) { + int newx = directions[j] + curr[0]; + int newy = directions[j + 1] + curr[1]; + if (newx >= 0 + && newx < m + && newy >= 0 + && newy < n + && grid[newx][newy] == 1 + && !visited[newx][newy]) { + freshOranges--; + grid[newx][newy] = 2; + visited[newx][newy] = true; + queue.offer(new int[] {newx, newy}); + hasOneToRot = true; + } + } + } + if (hasOneToRot) { + mins++; + } + } + return freshOranges == 0 ? mins : -1; + } + } +} + + +================================================== +File: _146.java +Line count: 149 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +public class _146 { + public class Solution1 { + public class LRUCache { + /* + * The shortest implementation is to use LinkedHashMap: + * specify a size of the LinkedHashMap; + * override the removeEldestEntry method when its size exceeds max size: + * https://docs.oracle.com/javase/8/docs/api/java/util/LinkedHashMap.html#removeEldestEntry-java.util.Map.Entry- + * in the constructor, set the last boolean variable to be true: it means the ordering mode, + * if we set it to be true, it means in access order, false, means it's in insertion order: + * https://docs.oracle.com/javase/8/docs/api/java/util/LinkedHashMap.html#LinkedHashMap-int-float-boolean- + */ + + private Map cache; + private final int max; + + public LRUCache(int capacity) { + max = capacity; + cache = + new LinkedHashMap(capacity, 1.0f, true) { + public boolean removeEldestEntry(Map.Entry eldest) { + return cache.size() > max; + } + }; + } + + public int get(int key) { + return cache.getOrDefault(key, -1); + } + + public void put(int key, int value) { + cache.put(key, value); + } + } + } + + public class Solution2 { + public class LRUCache { + /* + * The more verbose solution is to implement a doubly linked list yourself plus a map. + * It's very straightforward to implement this, key notes here: https://docs.google.com/spreadsheets/d/1anN6L5OLhUFd1ANtqDdYY6tz2Ao2H1GESfpDiCfeWyM/edit#gid=0 + * (search for the URL of this problem to find it.) + */ + private class Node { + int key; + int value; + + LRUCache.Node prev; + LRUCache.Node next; + + Node(int k, int v) { + this.key = k; + this.value = v; + } + + Node() { + this.key = 0; + this.value = 0; + } + } + + private int capacity; + private int count; + private LRUCache.Node head; + private LRUCache.Node tail; + private Map map; + + // ATTN: the value should be Node type! This is the whole point of having a class called + // Node! + + public LRUCache(int capacity) { + this.capacity = capacity; + this.count = + 0; // we need a count to keep track of the number of elements in the cache + // so + // that we know when to evict the LRU one from the cache + this.map = new HashMap(); + head = new LRUCache.Node(); + tail = new LRUCache.Node(); + head.next = tail; + tail.prev = head; + } + + public int get(int key) { + LRUCache.Node node = map.get(key); + // HashMap allows value to be null, this is superior to HashTable! + if (node == null) { + return -1; + } else { + /*Do two operations: this makes the process more clear: + * remove the old node first, and then + * just add the node again. + * This will guarantee that this node will be at the latest position: + * the most recently used position.*/ + remove(node); + add(node); + + return node.value; + } + } + + public void put(int key, int value) { + LRUCache.Node node = map.get(key); + if (node == null) { + node = new LRUCache.Node(key, value); + map.put(key, node); + add(node); + count++; + + if (count > capacity) { + /* ATTN: It's tail.prev, not tail, because tail is always an invalid node, it + doesn't contain anything, it's always the tail.prev that is the last node in the + cache*/ + LRUCache.Node toDelete = tail.prev; + map.remove(toDelete.key); + remove(toDelete); + count--; + } + } else { + remove(node); + node.value = value; + add(node); + } + } + + private void remove(LRUCache.Node node) { + LRUCache.Node next = node.next; + LRUCache.Node prev = node.prev; + prev.next = next; + next.prev = prev; + } + + private void add(LRUCache.Node node) { + // ATTN: we'll always add the node into the first position: head.next!!!! + LRUCache.Node next = head.next; + head.next = node; + node.next = next; + node.prev = head; + next.prev = node; + } + } + } +} + + +================================================== +File: _384.java +Line count: 117 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.Random; + +public class _384 { + + public static class Solution1 { + // Note as of 7/20/2021: This solution ends in TLE on LeetCode now. + // Note: the problem states that this is a set without duplicates which makes building all + // combinations easier + + private List> combinations; + private int[] original; + private Random random; + + public Solution1(int[] nums) { + original = nums; + random = new Random(); + combinations = buildAllComb(nums); + } + + // insert next value into all possible positions, I wrote this method myself, of course it + // could be simplified to not use a queue + // but it just naturally came into my mind that I used a queue + private List> buildAllComb(int[] nums) { + List> result = new ArrayList(); + if (nums == null || nums.length == 0) { + return result; + } + + List list = new ArrayList(); + list.add(nums[0]); + Queue> q = new LinkedList(); + q.offer(list); + for (int i = 1; i < nums.length; i++) { + int qSize = q.size(); + for (int k = 0; k < qSize; k++) { + List currList = q.poll(); + for (int j = 0; j <= currList.size(); j++) { + List newL = new ArrayList(currList); + newL.add(j, nums[i]); + q.offer(newL); + } + } + } + while (!q.isEmpty()) { + result.add(q.poll()); + } + return result; + } + + /* + * Resets the array to its original configuration and return it. + */ + public int[] reset() { + return original; + } + + /* + * Returns a random shuffling of the array. + */ + public int[] shuffle() { + if (original == null || original.length == 0) { + return original; + } + int randomIndex = random.nextInt(combinations.size()); + List list = combinations.get(randomIndex); + int[] result = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + result[i] = list.get(i); + } + return result; + } + } + + public static class Solution2 { + /* + * credit: https://leetcode.com/problems/shuffle-an-array/discuss/85958/First-Accepted-Solution-Java + */ + private int[] nums; + private Random random; + + public Solution2(int[] nums) { + this.nums = nums; + this.random = new Random(); + } + + /* + * Resets the array to its original configuration and return it. + */ + public int[] reset() { + return this.nums; + } + + /* + * Returns a random shuffling of the array. + */ + public int[] shuffle() { + int[] shuffled = this.nums.clone(); + for (int i = 1; i < nums.length; i++) { + int j = random.nextInt(i + 1); + swap(shuffled, i, j); + } + return shuffled; + } + + private void swap(int[] shuffled, int i, int j) { + int tmp = shuffled[i]; + shuffled[i] = shuffled[j]; + shuffled[j] = tmp; + } + } +} + + +================================================== +File: _432.java +Line count: 146 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class _432 { + + public static class Solution1 { + + /* + * credit: https://discuss.leetcode.com/topic/65634/java-ac-all-strict-o-1-not-average-o-1-easy-to-read/2 + */ + class AllOne { + // maintain a doubly linked list of Buckets + private Bucket head; + private Bucket tail; + // for accessing a specific Bucket among the Bucket list in O(1) time + private Map countBucketMap; + // keep track of count of keys + private Map keyCountMap; + + // each Bucket contains all the keys with the same count + private class Bucket { + int count; + Set keySet; + Bucket next; + Bucket pre; + + public Bucket(int cnt) { + count = cnt; + keySet = new HashSet<>(); + } + } + + /* + * Initialize your data structure here. + */ + public AllOne() { + head = new Bucket(Integer.MIN_VALUE); + tail = new Bucket(Integer.MAX_VALUE); + head.next = tail; + tail.pre = head; + countBucketMap = new HashMap<>(); + keyCountMap = new HashMap<>(); + } + + /* + * Inserts a new key with value 1. Or increments an existing key by 1. + */ + public void inc(String key) { + if (keyCountMap.containsKey(key)) { + changeKey(key, 1); + } else { + keyCountMap.put(key, 1); + if (head.next.count != 1) { + addBucketAfter(new Bucket(1), head); + } + head.next.keySet.add(key); + countBucketMap.put(1, head.next); + } + } + + /* + * Decrements an existing key by 1. If Key's value is 1, remove it from the data structure. + */ + public void dec(String key) { + if (keyCountMap.containsKey(key)) { + int count = keyCountMap.get(key); + if (count == 1) { + keyCountMap.remove(key); + removeKeyFromBucket(countBucketMap.get(count), key); + } else { + changeKey(key, -1); + } + } + } + + /* + * Returns one of the keys with maximal value. + */ + public String getMaxKey() { + return tail.pre == head ? "" : (String) tail.pre.keySet.iterator().next(); + } + + /* + * Returns one of the keys with Minimal value. + */ + public String getMinKey() { + return head.next == tail ? "" : (String) head.next.keySet.iterator().next(); + } + + // helper function to make change on given key according to offset + private void changeKey(String key, int offset) { + int count = keyCountMap.get(key); + keyCountMap.put(key, count + offset); + Bucket curBucket = countBucketMap.get(count); + Bucket newBucket; + if (countBucketMap.containsKey(count + offset)) { + // target Bucket already exists + newBucket = countBucketMap.get(count + offset); + } else { + // add new Bucket + newBucket = new Bucket(count + offset); + countBucketMap.put(count + offset, newBucket); + addBucketAfter(newBucket, offset == 1 ? curBucket : curBucket.pre); + } + newBucket.keySet.add(key); + removeKeyFromBucket(curBucket, key); + } + + private void removeKeyFromBucket(Bucket bucket, String key) { + bucket.keySet.remove(key); + if (bucket.keySet.size() == 0) { + removeBucketFromList(bucket); + countBucketMap.remove(bucket.count); + } + } + + private void removeBucketFromList(Bucket bucket) { + bucket.pre.next = bucket.next; + bucket.next.pre = bucket.pre; + bucket.next = null; + bucket.pre = null; + } + + // add newBucket after preBucket + private void addBucketAfter(Bucket newBucket, Bucket preBucket) { + newBucket.pre = preBucket; + newBucket.next = preBucket.next; + preBucket.next.pre = newBucket; + preBucket.next = newBucket; + } + } + } +} + +/* + * Your AllOne object will be instantiated and called as such: + * AllOne obj = new AllOne(); + * obj.inc(key); + * obj.dec(key); + * String param_3 = obj.getMaxKey(); + * String param_4 = obj.getMinKey(); + */ + + +================================================== +File: _408.java +Line count: 102 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +public class _408 { + + public static class Solution1 { + public boolean validWordAbbreviation(String word, String abbr) { + if (abbr.length() > word.length()) { + return false; + } else { + char[] abbrChars = abbr.toCharArray(); + char[] wordChars = word.toCharArray(); + if (abbr.length() == word.length()) { + boolean prevDigit = false; + for (int i = 0, j = 0; i < abbrChars.length && j < wordChars.length; i++, j++) { + if (Character.isDigit(abbrChars[i]) && !prevDigit) { + prevDigit = true; + if (Character.getNumericValue(abbrChars[i]) != 1) { + return false; + } + } else if (Character.isDigit(abbrChars[i]) && prevDigit) { + return false; + } else if (abbrChars[i] != wordChars[j]) { + return false; + } else if (prevDigit) { + prevDigit = false; + } + } + return true; + } else { + StringBuilder stringBuilder = new StringBuilder(); + boolean firstDigit = true; + for (int i = 0, j = 0; i < abbrChars.length && j < wordChars.length; i++) { + while (i < abbrChars.length && Character.isDigit(abbrChars[i])) { + if (firstDigit && Character.getNumericValue(abbrChars[i]) == 0) { + return false; + } + stringBuilder.append(abbrChars[i]); + i++; + firstDigit = false; + } + firstDigit = true; + if (!stringBuilder.toString().isEmpty()) { + int number = Integer.valueOf(stringBuilder.toString()); + j += number; + stringBuilder.setLength(0); + } + if ((i >= abbrChars.length && j < wordChars.length) + || (i < abbrChars.length && j >= wordChars.length)) { + return false; + } + if (i < abbrChars.length + && j < wordChars.length + && abbrChars[i] != wordChars[j]) { + return false; + } + if (j > wordChars.length && i <= abbrChars.length) { + return false; + } + j++; + } + return true; + } + } + } + } + + public static class Solution2 { + public boolean validWordAbbreviation(String word, String abbr) { + int aLen = abbr.length(); + int wLen = word.length(); + if (aLen > wLen) { + return false; + } + int i = 0; + int j = 0; + while (i < wLen && j < aLen) { + if (word.charAt(i) == abbr.charAt(j)) { + i++; + j++; + continue; + } + + // now the two chars don't match, then the char in abbr should be a valid digit: 0 < + // x <= 9 + if (abbr.charAt(j) == '0' || !Character.isDigit(abbr.charAt(j))) { + return false; + } + + // now we count the number of letters that are abbreviated, i.e. get the number from + // abbr before next letter shows up in abbr + int num = 0; + while (j < aLen && Character.isDigit(abbr.charAt(j))) { + num = num * 10 + (abbr.charAt(j) - '0'); + j++; + } + + i += num; + } + return i == wLen && j == aLen; + } + } +} + + +================================================== +File: _721.java +Line count: 133 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Stack; + +public class _721 { + + public static class Solution1 { + /* + * credit: https://leetcode.com/articles/accounts-merge/#approach-1-depth-first-search-accepted + *

+ * Time Complexity: O(∑ai*logai) where a​i is the length of accounts[i]. + * Without the log factor, this is the complexity to build the graph and search for each component. The log factor is for sorting each component at the end. + * Space Complexity: O(∑ai) the space used by the graph and search. + * . + */ + public List> accountsMerge(List> accounts) { + Map emailToName = new HashMap(); + Map> graph = new HashMap(); + for (List account : accounts) { + String name = ""; + for (String email : account) { + if (name == "") { + name = email; + continue; + } + graph.computeIfAbsent(email, x -> new ArrayList<>()).add(account.get(1)); + graph.computeIfAbsent(account.get(1), x -> new ArrayList<>()).add(email); + emailToName.put(email, name); + } + } + + Set seen = new HashSet(); + List> ans = new ArrayList(); + for (String email : graph.keySet()) { + if (!seen.contains(email)) { + seen.add(email); + Stack stack = new Stack(); + stack.push(email); + List component = new ArrayList(); + while (!stack.empty()) { + String node = stack.pop(); + component.add(node); + for (String nei : graph.get(node)) { + if (!seen.contains(nei)) { + seen.add(nei); + stack.push(nei); + } + } + } + Collections.sort(component); + component.add(0, emailToName.get(email)); + ans.add(component); + } + } + return ans; + } + } + + public static class Solution2 { + /* + * credit: https://leetcode.com/articles/accounts-merge/#approach-2-union-find-accepted + * DSU stands for Disjoint Set Union: https://en.wikipedia.org/wiki/Disjoint-set_data_structure, a.k.a Union Find data structure. + *

+ * Time complexity: O(nlogn) + * Space complexity: O(n) + */ + public List> accountsMerge(List> accounts) { + UnionFind uf = new UnionFind(); + Map emailToName = new HashMap<>(); + Map emailToId = new HashMap<>(); + int id = 0; + for (List account : accounts) { + String name = ""; + for (String email : account) { + if (name.equals("")) { + name = email; + continue; + } + emailToName.put(email, name); + if (!emailToId.containsKey(email)) { + emailToId.put(email, id++); + } + uf.union(emailToId.get(account.get(1)), emailToId.get(email)); + } + } + + Map> map = new HashMap<>(); + for (String email : emailToName.keySet()) { + // find the index of this email first: use this email's ID to find its parent in the + // Union Find + int index = uf.find(emailToId.get(email)); + map.computeIfAbsent(index, x -> new ArrayList()).add(email); + } + for (List component : map.values()) { + Collections.sort(component); + // this is to add name to the head of the list + component.add(0, emailToName.get(component.get(0))); + } + return new ArrayList<>(map.values()); + } + + class UnionFind { + int[] parent; + int size = 10001; + + public UnionFind() { + parent = new int[size]; + for (int i = 0; i < size; i++) { + parent[i] = i; + } + } + + public int find(int x) { + if (parent[x] != x) { + parent[x] = find(parent[x]); + } + return parent[x]; + } + + public void union(int x, int y) { + parent[find(x)] = + find(y); // can be written as parent[find(y)] = find(x); they are equivalent + } + } + } +} + + +================================================== +File: _234.java +Line count: 101 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import com.fishercoder.common.classes.ListNode; +import java.util.ArrayList; +import java.util.List; + +public class _234 { + public static class Solution1 { + /* + * O(n) time + * O(1) space + */ + public boolean isPalindrome(ListNode head) { + if (head == null) { + return true; + } + + ListNode slow = head; + ListNode fast = head; + while (fast.next != null && fast.next.next != null) { + fast = fast.next.next; + slow = slow.next; + } + + ListNode secondHalfHead = reverse(slow.next); + ListNode firstHalfHead = head; + while (firstHalfHead != null && secondHalfHead != null) { + if (firstHalfHead.val != secondHalfHead.val) { + return false; + } + firstHalfHead = firstHalfHead.next; + secondHalfHead = secondHalfHead.next; + } + return true; + } + + private ListNode reverse(ListNode head) { + ListNode newHead = null; + while (head != null) { + ListNode next = head.next; + head.next = newHead; + newHead = head; + head = next; + } + return newHead; + } + } + + public static class Solution2 { + /* + * O(n) time + * O(n) space + */ + public boolean isPalindrome(ListNode head) { + int len = 0; + ListNode fast = head; + ListNode slow = head; + List firstHalf = new ArrayList<>(); + while (fast != null && fast.next != null) { + fast = fast.next.next; + firstHalf.add(slow.val); + slow = slow.next; + len += 2; + } + if (fast != null) { + len++; + } + if (len % 2 != 0) { + slow = slow.next; + } + int i = firstHalf.size() - 1; + while (slow != null) { + if (firstHalf.get(i--) != slow.val) { + return false; + } + slow = slow.next; + } + return true; + } + } + + public static class Solution3 { + /* + * O(n) time + * O(n) space + */ + public boolean isPalindrome(ListNode head) { + List list = new ArrayList<>(); + while (head != null) { + list.add(head.val); + head = head.next; + } + for (int i = 0, j = list.size() - 1; i <= j; i++, j--) { + if (list.get(i) != list.get(j)) { + return false; + } + } + return true; + } + } +} + + +================================================== +File: _449.java +Line count: 198 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import com.fishercoder.common.classes.TreeNode; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.Queue; + +public class _449 { + + public static class Solution1 { + /* + * Preorder + * Reference: https://discuss.leetcode.com/topic/97922/pre-or-post-order-with-only-keeping-one-bound-beat-98-and-95 + */ + + // Encodes a tree to a single string. + public String serialize(TreeNode root) { + if (root == null) { + return null; + } + StringBuilder stringBuilder = new StringBuilder(); + return serialize(root, stringBuilder); + } + + private String serialize(TreeNode root, StringBuilder stringBuilder) { + if (root == null) { + return null; + } + stringBuilder.append(root.val).append(" "); + serialize(root.left, stringBuilder); + serialize(root.right, stringBuilder); + return stringBuilder.toString(); + } + + // Decodes your encoded data to tree. + public TreeNode deserialize(String data) { + if (data == null || data.length() == 0) { + return null; + } + String[] values = data.split(" "); + int[] index = + new int[] {0}; /*TODO: Why must use an int array, instead of just an int?*/ + return deserialize(values, index, Integer.MAX_VALUE); + } + + private TreeNode deserialize(String[] values, int[] index, int maxValue) { + if (index[0] >= values.length || Integer.valueOf(values[index[0]]) >= maxValue) { + return null; + } + TreeNode root = new TreeNode(Integer.valueOf(values[index[0]++])); + root.left = deserialize(values, index, root.val); + root.right = deserialize(values, index, maxValue); + return root; + } + } + + public static class Solution2 { + /* + * Postorder + * Reference: https://discuss.leetcode.com/topic/97922/pre-or-post-order-with-only-keeping-one-bound-beat-98-and-95 + */ + + public String serialize(TreeNode root) { + if (root == null) { + return null; + } + StringBuilder sb = new StringBuilder(); + return serialize(root, sb); + } + + private String serialize(TreeNode root, StringBuilder sb) { + if (root == null) { + return null; + } + serialize(root.left, sb); + serialize(root.right, sb); + sb.append(root.val).append(" "); + return sb.toString(); + } + + // Decodes your encoded data to tree. + public TreeNode deserialize(String data) { + if (data == null || data.length() == 0) { + return null; + } + String[] values = data.split(" "); + int[] index = + new int[] { + values.length - 1 + }; /*TODO: This is not just one element any more like in the preorder solution above*/ + return deserialize(values, index, Integer.MIN_VALUE); + } + + private TreeNode deserialize(String[] values, int[] index, int minValue) { + if (index[0] < 0 || Integer.valueOf(values[index[0]]) < minValue) { + return null; + } + TreeNode root = new TreeNode(Integer.valueOf(values[index[0]--])); + root.right = deserialize(values, index, root.val); + root.left = deserialize(values, index, minValue); + return root; + } + } + + public static class Solution3 { + /* + * This is a generic solution that applies to both BT and BST. And also the easiest to follow. + */ + + // Encodes a tree to a single string. + public String serialize(TreeNode root) { + Queue queue = new LinkedList<>(); + StringBuilder stringBuilder = new StringBuilder(); + if (root == null) { + return stringBuilder.toString(); + } + queue.offer(root); + while (!queue.isEmpty()) { + int size = queue.size(); + for (int i = 0; i < size; i++) { + TreeNode curr = queue.poll(); + if (curr == null) { + stringBuilder.append("# "); + } else { + stringBuilder.append(curr.val + " "); + queue.offer(curr.left); + queue.offer(curr.right); + } + } + } + return stringBuilder.toString(); + } + + // Decodes your encoded data to tree. + public TreeNode deserialize(String data) { + if (data == null || data.length() == 0) { + return null; + } + String[] nodes = data.split(" "); + TreeNode root = new TreeNode(Integer.valueOf(nodes[0])); + Queue queue = new LinkedList<>(); + queue.offer(root); + for (int i = 1; i < nodes.length; i++) { + TreeNode curr = queue.poll(); + if (!nodes[i].equals("#")) { + curr.left = new TreeNode(Integer.valueOf(nodes[i])); + queue.offer(curr.left); + } + if (!nodes[++i].equals("#")) { + curr.right = new TreeNode(Integer.valueOf(nodes[i])); + queue.offer(curr.right); + } + } + return root; + } + } + + public static class Solution4 { + private static final String NULL_SYMBOL = "X"; + private static final String DELIMITER = ","; + + // Encodes a tree to a single string. + public String serialize(TreeNode root) { + + // If we have a null symbol, encode it to NULL_SYMBOL + if (root == null) { + return NULL_SYMBOL + DELIMITER; + } + + String leftSubtree = serialize(root.left); + String rightSubtree = serialize(root.right); + + return root.val + DELIMITER + leftSubtree + rightSubtree; + } + + // Decodes your encoded data to tree. + public TreeNode deserialize(String data) { + + Queue nodesLeftToSerialize = new LinkedList<>(); + nodesLeftToSerialize.addAll(Arrays.asList(data.split(DELIMITER))); + return deserializeHelper(nodesLeftToSerialize); + } + + private TreeNode deserializeHelper(Queue nodesLeft) { + + // remove the node + String nodeLeftToSerialize = nodesLeft.poll(); + // base case + if (nodeLeftToSerialize.equals(NULL_SYMBOL)) { + return null; + } + TreeNode newNode = new TreeNode(Integer.valueOf(nodeLeftToSerialize)); + newNode.left = deserializeHelper(nodesLeft); + newNode.right = deserializeHelper(nodesLeft); + return newNode; + } + } +} + + +================================================== +File: _222.java +Line count: 107 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import com.fishercoder.common.classes.TreeNode; + +public class _222 { + + public static class Solution1 { + /* + * reference: https://discuss.leetcode.com/topic/21317/accepted-easy-understand-java-solution/2 + */ + public int countNodes(TreeNode root) { + int leftH = getLeftHeight(root); + int rightH = getRightHeight(root); + if (leftH == rightH) { + return (1 << leftH) - 1; + } else { + return 1 + countNodes(root.left) + countNodes(root.right); + } + } + + private int getRightHeight(TreeNode root) { + int height = 0; + while (root != null) { + root = root.right; + height++; + } + return height; + } + + private int getLeftHeight(TreeNode root) { + int height = 0; + while (root != null) { + root = root.left; + height++; + } + return height; + } + } + + public static class Solution2 { + /* + * credit: https://leetcode.com/problems/count-complete-tree-nodes/solution/ + */ + public int countNodes(TreeNode root) { + if (root == null) { + return 0; + } + int depth = getDepth(root); + if (depth == 0) { + return 1; + } + int left = 0; + int right = (int) Math.pow(2, depth) - 1; + // here the condition needs to be not bigger than right, instead of the typical strictly + // smaller than right. + while (left <= right) { + int mid = left + (right - left) / 2; + // this is to suppose the elements on the last level are numbered from 1 to + // Math.pow(2, d) - 1, we are using + // binary search here to find the right-most number + if (exists(root, mid, depth)) { + left = mid + 1; + } else { + right = mid - 1; + } + } + // number of all nodes equals all nodes in the previous level + all the nodes on the + // last level indicated by variable "left" + return (int) Math.pow(2, depth) - 1 + left; + } + + private boolean exists(TreeNode root, int target, int depth) { + /*An example complete tree in this algorithm: + * 1 + * / \ + * 2 3 + * / \ / + * 1 2 3 (we use 1, 2, 3 at this level to indicate how this program runs instead of 4, 5, 6) + * + * first, target is 1, we found 1 <= 1 (root), so we go to root.left, after going down to the last level (depth), + * we found this target exists: node != null, we return true from this function + * */ + int left = 0; + int right = (int) Math.pow(2, depth) - 1; + for (int i = 0; i < depth; i++) { + int mid = left + (right - left) / 2; + if (target <= mid) { + root = root.left; + right = mid; + } else { + root = root.right; + left = mid + 1; + } + } + return root != null; + } + + private int getDepth(TreeNode root) { + int depth = 0; + while (root.left != null) { + root = root.left; + depth++; + } + return depth; + } + } +} + + +================================================== +File: _472.java +Line count: 156 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class _472 { + + public static class Solution1 { + private TrieNode root; + private int maxWordLen; + + public List findAllConcatenatedWordsInADict(String[] words) { + ResultType result = buildTrie(words); + root = result.root; + maxWordLen = result.maxWordLen; + + List validConcatenatedWords = new ArrayList(); + for (String word : words) { + if (word == null || word.length() == 0) { + continue; + } + remove( + word, + root); /* every word is comprised of every word itself, thus this word itself needs to be removed first for checking it*/ + int n = word.length(); + boolean[] dp = new boolean[n + 1]; + dp[0] = true; + + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= i && j <= maxWordLen; j++) { + if (!dp[i - j]) { + continue; + } + + String subWord = word.substring(i - j, i); + if (contains(subWord, root)) { + dp[i] = true; + break; + } + } + } + + if (dp[n]) { + validConcatenatedWords.add(word); + } + undoRemove(word, root); + } + return validConcatenatedWords; + } + + public ResultType buildTrie(String[] words) { + ResultType result = new ResultType(); + + TrieNode root = new TrieNode(); + int maxWordLen = 0; + + for (String word : words) { + maxWordLen = Math.max(maxWordLen, word.length()); + char[] chars = word.toCharArray(); + TrieNode node = root; + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + if (node.children[c - 'a'] == null) { + node.children[c - 'a'] = new TrieNode(); + } + node = node.children[c - 'a']; + } + node.isWord = true; + } + + result.root = root; + result.maxWordLen = maxWordLen; + return result; + } + + public class ResultType { + int maxWordLen; + TrieNode root; + } + + // Returns true if the word is in the trie. + public boolean contains(String word, TrieNode root) { + TrieNode node = root; + for (int i = 0; i < word.length(); i++) { + if (node.children[word.charAt(i) - 'a'] == null) { + return false; + } + node = node.children[word.charAt(i) - 'a']; + } + return node.isWord; + } + + // mark that word on + public void undoRemove(String word, TrieNode root) { + TrieNode node = root; + for (int i = 0; i < word.length(); i++) { + node = node.children[word.charAt(i) - 'a']; + } + node.isWord = true; + } + + // mark that word off, we are not really deleting that word + public void remove(String word, TrieNode root) { + TrieNode node = root; + for (int i = 0; i < word.length(); i++) { + node = node.children[word.charAt(i) - 'a']; + } + node.isWord = false; + } + + class TrieNode { + boolean isWord; + TrieNode[] children = new TrieNode[26]; + + public TrieNode() {} + } + } + + public static class Solution2 { + public List findAllConcatenatedWordsInADict(String[] words) { + List result = new ArrayList<>(); + Set preWords = new HashSet<>(); + /*Words could only be formed by other words that are shorter than itself, so we sort them based on their lengths first.*/ + Arrays.sort(words, (s1, s2) -> s1.length() - s2.length()); + + for (int i = 0; i < words.length; i++) { + if (canForm(words[i], preWords)) { + result.add(words[i]); + } + preWords.add(words[i]); + } + + return result; + } + + boolean canForm(String word, Set dict) { + if (dict.isEmpty()) { + return false; + } + boolean[] dp = new boolean[word.length() + 1]; + dp[0] = true; + for (int i = 1; i <= word.length(); i++) { + for (int j = 0; j < i; j++) { + if (dp[j] && dict.contains(word.substring(j, i))) { + dp[i] = true; + break; + } + } + } + return dp[word.length()]; + } + } +} + + +================================================== +File: _130.java +Line count: 169 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; + +public class _130 { + + public static class Solution1 { + + /* + * I won't call this problem hard, it's just confusing, you'll definitely want to clarify what + * the problem means before coding. This problem actually means: any grid that is 'O' but on + * the four edges, will never be marked to 'X'; furthermore, any grid that is 'O' and that is + * connected with the above type of 'O' will never be marked to 'X' as well; only all other + * nodes that has any one direct neighbor that is an 'X' will be marked to 'X'. + */ + + int[] dirs = new int[] {0, 1, 0, -1, 0}; + + public void solve(char[][] board) { + if (board == null || board.length == 0 || board[0].length == 0) { + return; + } + int m = board.length; + int n = board[0].length; + Queue queue = new LinkedList(); + // check first row and last row and mark all those '0' on these two rows to be '+' to + // let them be different from other 'O', + // at the same time, we put them into the queue to get ready for a BFS to mark all those + // adjacent 'O' nodes to '+' as well + for (int j = 0; j < n; j++) { + if (board[0][j] == 'O') { + board[0][j] = '+'; + queue.offer(new int[] {0, j}); + } + if (board[m - 1][j] == 'O') { + board[m - 1][j] = '+'; + queue.offer(new int[] {m - 1, j}); + } + } + + // check first column and last column too + for (int i = 0; i < m; i++) { + if (board[i][0] == 'O') { + board[i][0] = '+'; + queue.offer(new int[] {i, 0}); + } + if (board[i][n - 1] == 'O') { + board[i][n - 1] = '+'; + queue.offer(new int[] {i, n - 1}); + } + } + + while (!queue.isEmpty()) { + int[] curr = queue.poll(); + for (int i = 0; i < 4; i++) { + int x = curr[0] + dirs[i]; + int y = curr[1] + dirs[i + 1]; + if (x >= 0 && x < m && y >= 0 && y < n && board[x][y] == 'O') { + board[x][y] = '+'; + queue.offer(new int[] {x, y}); + } + } + } + + // now we can safely mark all other 'O' to 'X', also remember to put those '+' back to + // 'O' + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (board[i][j] == 'O') { + board[i][j] = 'X'; + } else if (board[i][j] == '+') { + board[i][j] = 'O'; + } + } + } + } + } + + public static class Solution2 { + /* + * My completely original solution on 11/1/2021, again, using a pen and paper to visualize my thought process and list out all key steps helps a lot! + * 1. scan through this board; + * 2. whenever we find an 'O', we'll do BFS to find all connected points and use the first 'O' as its head point for this entire connected region; + * 3. whenever we visit a point, mark it as visited. + */ + public void solve(char[][] board) { + int m = board.length; + int n = board[0].length; + boolean[][] visited = new boolean[m][n]; + Map> headMap = new HashMap<>(); + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (!visited[i][j] && board[i][j] == 'O') { + boolean capturable = bfs(i, j, board, visited, headMap); + if (capturable) { + capture(headMap, board); + } + } + } + } + } + + private void capture(Map> headMap, char[][] board) { + int m = board.length; + int n = board[0].length; + for (int head : headMap.keySet()) { + List list = headMap.get(head); + for (int[] p : list) { + board[p[0]][p[1]] = 'X'; + } + int x = head / m; + int y = head % n; + board[x][y] = 'X'; + } + } + + private boolean bfs( + int startI, + int startJ, + char[][] board, + boolean[][] visited, + Map> headMap) { + boolean capturable = true; + Queue queue = new LinkedList<>(); + int m = board.length; + int n = board[0].length; + queue.offer(new int[] {startI, startJ}); + int head = startI * n + startJ; + List list = headMap.getOrDefault(head, new ArrayList<>()); + list.add(new int[] {startI, startJ}); + int[] directions = new int[] {0, 1, 0, -1, 0}; + while (!queue.isEmpty()) { + int size = queue.size(); + for (int i = 0; i < size; i++) { + int[] curr = queue.poll(); + if (curr[0] == 0 || curr[0] == m - 1 || curr[1] == 0 || curr[1] == n - 1) { + capturable = false; + } + visited[curr[0]][curr[1]] = true; + for (int j = 0; j < directions.length - 1; j++) { + int newx = directions[j] + curr[0]; + int newy = directions[j + 1] + curr[1]; + if (newx >= 0 + && newx < m + && newy >= 0 + && newy < n + && !visited[newx][newy] + && board[newx][newy] == 'O') { + queue.offer(new int[] {newx, newy}); + visited[newx][newy] = true; + list.add(new int[] {newx, newy}); + } + } + } + } + if (!capturable) { + headMap.remove(head); + } else { + headMap.put(head, list); + } + return capturable; + } + } +} + + +================================================== +File: _126.java +Line count: 148 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; + +/* +* 126. Word Ladder II + +Given two words (beginWord and endWord), and a dictionary's word list, +find all shortest transformation sequence(s) from beginWord to endWord, such that: + +Only one letter can be changed at a time +Each transformed word must exist in the word list. Note that beginWord is not a transformed word. + +For example, +Given: +beginWord = "hit" +endWord = "cog" +wordList = ["hot","dot","dog","lot","log","cog"] + +Return +[ +["hit","hot","dot","dog","cog"], +["hit","hot","lot","log","cog"] +] + +Note: +Return an empty list if there is no such transformation sequence. +All words have the same length. +All words contain only lowercase alphabetic characters. +You may assume no duplicates in the word list. +You may assume beginWord and endWord are non-empty and are not the same. +*/ + +public class _126 { + + public static class Solution1 { + /* Reference: https://discuss.leetcode.com/topic/2857/share-two-similar-java-solution-that-accpted-by-oj */ + + Map> map; + List> results; + + public List> findLadders(String start, String end, List dict) { + results = new ArrayList<>(); + if (dict.size() == 0) { + return results; + } + + int min = Integer.MAX_VALUE; + + Queue queue = new ArrayDeque<>(); + queue.add(start); + + map = new HashMap<>(); + + Map ladder = new HashMap<>(); + for (String string : dict) { + ladder.put(string, Integer.MAX_VALUE); + } + ladder.put(start, 0); + + dict.add(end); + // BFS: Dijisktra search + while (!queue.isEmpty()) { + + String word = queue.poll(); + + int step = + ladder.get(word) + + 1; // 'step' indicates how many steps are needed to travel to one + // word. + + if (step > min) { + break; + } + + for (int i = 0; i < word.length(); i++) { + StringBuilder builder = new StringBuilder(word); + for (char ch = 'a'; ch <= 'z'; ch++) { + builder.setCharAt(i, ch); + String newWord = builder.toString(); + if (ladder.containsKey(newWord)) { + + if (step > ladder.get(newWord)) { + // Check if it is the shortest path to one word. + continue; + } else if (step < ladder.get(newWord)) { + queue.add(newWord); + ladder.put(newWord, step); + } else { + // It is a KEY line. If one word already appeared in one ladder, + // Do not insert the same word inside the queue twice. Otherwise it + // gets TLE. + } + if (map.containsKey(newWord)) { + // Build adjacent Graph + map.get(newWord).add(word); + } else { + List list = new LinkedList(); + list.add(word); + map.put(newWord, list); + // It is possible to write three lines in one: + // map.put(new_word,new LinkedList(Arrays.asList(new + // String[]{word}))); + // Which one is better? + } + + if (newWord.equals(end)) { + min = step; + } + } + // End if dict contains new_word + } + // End:Iteration from 'a' to 'z' + } + // End:Iteration from the first to the last + } + // End While + + // BackTracking + LinkedList result = new LinkedList<>(); + backTrace(end, start, result); + + return results; + } + + private void backTrace(String word, String start, List list) { + if (word.equals(start)) { + list.add(0, start); + results.add(new ArrayList<>(list)); + list.remove(0); + return; + } + list.add(0, word); + if (map.get(word) != null) { + for (String s : map.get(word)) { + backTrace(s, start, list); + } + } + list.remove(0); + } + } +} + + +================================================== +File: _106.java +Line count: 128 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import com.fishercoder.common.classes.TreeNode; +import java.util.HashMap; +import java.util.Map; + +public class _106 { + public static class Solution1 { + + /* + * https://discuss.leetcode.com/topic/3296/my-recursive-java-code-with-o-n-time-and-o-n-space + * Note: the last element of postorder array is the root! + * The idea is to take the last element in postorder as the root; find the position of the root + * in the inorder array; then locate the range for left sub-tree and right sub-tree and do + * recursion, use a hashmap to record the index of root in the inorder array. + */ + public TreeNode buildTree(int[] inorder, int[] postorder) { + if (inorder == null || postorder == null || inorder.length != postorder.length) { + return null; + } + HashMap inorderMap = new HashMap<>(); + for (int i = 0; i < inorder.length; i++) { + inorderMap.put(inorder[i], i); + } + /*At the beginning, both start from 0 to nums.length-1*/ + return buildTreeRecursively( + inorderMap, 0, inorder.length - 1, postorder, 0, postorder.length - 1); + } + + private TreeNode buildTreeRecursively( + Map inorderMap, + int inorderStart, + int inorderEnd, + int[] postorder, + int postorderStart, + int postorderEnd) { + if (postorderStart > postorderEnd || inorderStart > inorderEnd) { + return null; + } + TreeNode root = new TreeNode(postorder[postorderEnd]); + int inRoot = inorderMap.get(postorder[postorderEnd]); + int numsLeft = inRoot - inorderStart; + + /*It's easy to understand and remember: + * for the indices of inorder array: + * inStart and inRoot-1 as new start and end indices + * inRoot+1 and inEnd as new start and end indices + * + * this is easy to understand and remember: since inRoot is already been used in this recursion call, so we're going to use inRoot-1 and inRoot+1 for next recursion call + * + * for the indices of postorder array: + * postorderStart and postorderStart+numsLeft-1 should be the new start and end indices + * postorderStart+numsLeft and postorderEnd-1 should be the new start and end indices + * + * this is also easy to understand and remember: + * since the last one in postorder is the root and we have used it in this recursion call already, so the end is definitely postorderEnd-1; + * then the postorderEnd for root.left is contiguous to the postorderStart of root.right, :)*/ + root.left = + buildTreeRecursively( + inorderMap, + inorderStart, + inRoot - 1, + postorder, + postorderStart, + postorderStart + numsLeft - 1); + root.right = + buildTreeRecursively( + inorderMap, + inRoot + 1, + inorderEnd, + postorder, + postorderStart + numsLeft, + postorderEnd - 1); + return root; + } + } + + public static class Solution2 { + /* + * My own solution after inspiration from LeetCode 105. + * I go from the right to the left for the postorder array, also, I build the tree by forming the right subtree first and then the left subtree. + * A bit different from using numsLeft from LeetCode 106, I use numsRight, meaning the number of nodes needed to form the right subtree in the inorder array. + */ + public TreeNode buildTree(int[] inorder, int[] postorder) { + Map inMap = new HashMap<>(); + for (int i = 0; i < inorder.length; i++) { + inMap.put(inorder[i], i); + } + return helper( + postorder, inorder, inMap, postorder.length - 1, 0, 0, inorder.length - 1); + } + + private TreeNode helper( + int[] postorder, + int[] inorder, + Map inMap, + int postStart, + int postEnd, + int inStart, + int inEnd) { + if (postStart < postEnd) { + return null; + } + int inRoot = inMap.get(postorder[postStart]); + int numsRight = inEnd - inRoot; + TreeNode node = new TreeNode(postorder[postStart]); + node.right = + helper( + postorder, + inorder, + inMap, + postStart - 1, + postStart - numsRight, + inRoot + 1, + inEnd); + node.left = + helper( + postorder, + inorder, + inMap, + postStart - numsRight - 1, + postEnd, + inStart, + inRoot - 1); + return node; + } + } +} + + +================================================== +File: _385.java +Line count: 105 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import com.fishercoder.common.classes.NestedInteger; +import java.util.Stack; + +public class _385 { + + public static class Solution1 { + // Lessons: ask the interviewer to clarify the input, for this question, the input could be + // "324", "[324]", they are different + // the former should return a nested integer with one single integer, the latter should + // return a nested integer with a list + + // Idea: + // if it's '[', we just construct a new nested integer and push it onto the stack + // if it's a number, we parse the whole number and add to the previous nested integer object + // if it's ',', we'll just continue; + // if it's ']', we'll just pop one nested integer from the working stack and assign it to + // the result + + public NestedInteger deserialize(String s) { + if (s == null || s.isEmpty() || s.length() == 0) { + return new NestedInteger(); + } + Stack workStack = new Stack<>(); + NestedInteger result = null; + StringBuilder sb = new StringBuilder(); + int i = 0; + // if it's just a single number, then we'll just return a nested integer with one + // integer + if (s.charAt(i) != '[') { + sb.setLength(0); + while (i < s.length() + && ((Character.getNumericValue(s.charAt(i)) < 10 + && Character.getNumericValue(s.charAt(i)) >= 0) + || s.charAt(i) == '-')) { + sb.append(s.charAt(i)); + i++; + } + int num = Integer.parseInt(sb.toString()); + return new NestedInteger(num); + } else { + // all other cases, we'll return a nested integer with a list + while (i < s.length()) { + if (s.charAt(i) == '[') { + NestedInteger ni = new NestedInteger(); + // we'll put this one into its last one if there's one on the workStack + if (!workStack.isEmpty()) { + NestedInteger lastNi = workStack.pop(); + lastNi.add(ni); + workStack.push(lastNi); // then push it back + } + workStack.push(ni); + i++; + } else if (s.charAt(i) == ',') { + i++; + } else if (s.charAt(i) == ']') { + NestedInteger completedNi = workStack.pop(); + result = completedNi; + i++; + } else { + // then it must be a number + sb.setLength(0); + while (i < s.length() + && ((Character.getNumericValue(s.charAt(i)) < 10 + && Character.getNumericValue(s.charAt(i)) >= 0) + || s.charAt(i) == '-')) { + sb.append(s.charAt(i)); + i++; + } + int num = Integer.parseInt(sb.toString()); + NestedInteger ni = null; + if (!workStack.isEmpty()) { + ni = workStack.pop(); + } else { + ni = new NestedInteger(); + } + // case 1: if this one contains one integer + if (ni.isInteger()) { + // we'll add it to this ni + ni.add(new NestedInteger(num)); + } else if (ni.getList() != null && ni.getList().size() != 0) { + // case 2: if this one contains a nested integer + // we'll get the last nested integer and add this one to it + ni.add(new NestedInteger(num)); + } else { + // case 3: if this is an empty nested integer + if (i > 0) { + ni.add(new NestedInteger(num)); + } else { + ni.setInteger(num); + } + } + workStack.push(ni); + if (i == s.length()) { + return ni; // this is for test cases like this: "324", there's no '[' or + // ']' + } + } + } + } + return result; + } + } +} + + +================================================== +File: _151.java +Line count: 105 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.ArrayDeque; +import java.util.Deque; + +public class _151 { + public static class Solution1 { + public String reverseWords(String s) { + s.trim(); + if (s == null || s.length() == 0) { + return ""; + } + String[] words = s.split(" "); + if (words == null || words.length == 0) { + return ""; + } + Deque stack = new ArrayDeque<>(); + for (String word : words) { + if (!word.equals("")) { + stack.offer(word); + } + } + StringBuilder stringBuilder = new StringBuilder(); + while (!stack.isEmpty()) { + stringBuilder.append(stack.pollLast()).append(" "); + } + return stringBuilder.substring(0, stringBuilder.length() - 1); + } + } + + public static class Solution2 { + public String reverseWords(String s) { + int len = s.length(); + int i = 0; + int j = 0; + String result = ""; + while (i < len) { + + // index i keeps track of the spaces and ignores them if found + while (i < len && s.charAt(i) == ' ') { + i++; + } + if (i == len) { + break; + } + j = i + 1; + + // index j keeps track of non-space characters and gives index of the first + // occurrence of space after a non-space character + while (j < len && s.charAt(j) != ' ') { + j++; + } + // word found + String word = s.substring(i, j); + if (result.length() == 0) { + result = word; + } else { + result = word + " " + result; + } + i = j + 1; + } + return result; + } + } + + public static class Solution3 { + public String reverseWords(String s) { + s = s.trim(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < s.length(); i++) { + if (s.charAt(i) == ' ' && sb.length() > 0 && sb.charAt(sb.length() - 1) == ' ') { + continue; + } else { + sb.append(s.charAt(i)); + } + } + int left = 0; + int right = sb.length() - 1; + while (left < right) { + char tmp = sb.charAt(left); + sb.setCharAt(left, sb.charAt(right)); + sb.setCharAt(right, tmp); + left++; + right--; + } + int boundary = 0; + while (boundary < sb.length()) { + left = boundary; + while (boundary < sb.length() && sb.charAt(boundary) != ' ') { + boundary++; + } + right = boundary - 1; + while (left < right) { + char tmp = sb.charAt(left); + sb.setCharAt(left, sb.charAt(right)); + sb.setCharAt(right, tmp); + left++; + right--; + } + boundary++; + } + return sb.toString(); + } + } +} + + +================================================== +File: _716.java +Line count: 252 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.ArrayList; +import java.util.Deque; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Stack; +import java.util.TreeMap; + +public class _716 { + public static class Solution1 { + /* + * This is O(n) for popMax() and pop() while O(1) for the other three operations which is UN-acceptable during an interview! + * We need to do better than O(n) time complexity in order to ace the interview! + * But O(1) is impossible, so let's aim for O(logn). + */ + public static class MaxStack { + + private int max; + private Stack stack; + + /* + * initialize your data structure here. + */ + public MaxStack() { + max = Integer.MIN_VALUE; + stack = new Stack<>(); + } + + public void push(int x) { + if (x > max) { + max = x; + } + stack.push(x); + } + + public int pop() { + if (stack.peek() == max) { + int result = stack.pop(); + max = findMax(); + return result; + } else { + return stack.pop(); + } + } + + private int findMax() { + if (!stack.isEmpty()) { + Iterator iterator = stack.iterator(); + int max = stack.peek(); + while (iterator.hasNext()) { + max = Math.max(max, iterator.next()); + } + return max; + } else { + max = Integer.MIN_VALUE; + return max; + } + } + + public int top() { + return stack.peek(); + } + + public int peekMax() { + return max; + } + + public int popMax() { + Stack tmp = new Stack<>(); + int result = 0; + while (!stack.isEmpty()) { + if (stack.peek() != max) { + tmp.push(stack.pop()); + } else { + result = stack.pop(); + break; + } + } + while (!tmp.isEmpty()) { + stack.push(tmp.pop()); + } + max = findMax(); + return result; + } + } + } + + public static class Solution2 { + /* + * Use a treemap and a doubly linked list to achieve O(logn) time complexity. + */ + + static class Node { + int val; + Node prev; + Node next; + + public Node(int val) { + this.val = val; + } + } + + static class DoublyLinkedList { + Node head; + Node tail; + + public DoublyLinkedList() { + head = new Node(0); + tail = new Node(0); + head.next = tail; + tail.prev = head; + } + + public Node add(int val) { + /*For this doubly linked list, we always add it to the end of the list*/ + Node x = new Node(val); + x.next = tail; + x.prev = tail.prev; + tail.prev.next = x; + tail.prev = tail.prev.next; + return x; + } + + public int pop() { + /*for pop(), we always pop one from the tail of the doubly linked list*/ + return unlink(tail.prev).val; + } + + public Node unlink(Node node) { + node.prev.next = node.next; + node.next.prev = node.prev; + return node; + } + + public int peek() { + return tail.prev.val; + } + } + + public static class MaxStack { + TreeMap> treeMap; + /* + * the reason we have a list of nodes as treemap's value is because one value could be pushed + * multiple times into this MaxStack and we want to keep track of all of them. + */ + DoublyLinkedList doublyLinkedList; + + /* + * initialize your data structure here. + */ + public MaxStack() { + treeMap = new TreeMap(); + doublyLinkedList = new DoublyLinkedList(); + } + + public void push(int x) { + Node node = doublyLinkedList.add(x); + if (!treeMap.containsKey(x)) { + treeMap.put(x, new ArrayList<>()); + } + treeMap.get(x).add(node); + } + + public int pop() { + int val = doublyLinkedList.pop(); + List nodes = treeMap.get(val); + nodes.remove(nodes.size() - 1); + if (nodes.isEmpty()) { + treeMap.remove(val); + } + return val; + } + + public int top() { + return doublyLinkedList.peek(); + } + + public int peekMax() { + return treeMap.lastKey(); + } + + public int popMax() { + int max = treeMap.lastKey(); + List nodes = treeMap.get(max); + Node node = nodes.remove(nodes.size() - 1); + doublyLinkedList.unlink(node); + if (nodes.isEmpty()) { + treeMap.remove(max); + } + return max; + } + } + } + + public static class Solution3 { + /* + * My completely original solution on 10/25/2021. + * popMax() takes O(n) time, all other operations take O(1) time. + */ + + public static class MaxStack { + + Deque stack; + Deque tmp; + + public MaxStack() { + stack = new LinkedList<>(); + tmp = new LinkedList<>(); + } + + public void push(int x) { + if (stack.isEmpty()) { + stack.addLast(new int[] {x, x}); + } else { + int[] last = stack.peekLast(); + stack.addLast(new int[] {x, Math.max(last[1], x)}); + } + } + + public int pop() { + return stack.pollLast()[0]; + } + + public int top() { + return stack.peekLast()[0]; + } + + public int peekMax() { + return stack.peekLast()[1]; + } + + public int popMax() { + tmp.clear(); + while (stack.peekLast()[0] != stack.peekLast()[1]) { + tmp.addLast(stack.pollLast()); + } + int[] max = stack.pollLast(); + while (!tmp.isEmpty()) { + int[] curr = tmp.pollLast(); + if (!stack.isEmpty()) { + stack.addLast(new int[] {curr[0], Math.max(curr[0], stack.peekLast()[1])}); + } else { + stack.addLast(new int[] {curr[0], curr[0]}); + } + } + return max[0]; + } + } + } +} + + +================================================== +File: _468.java +Line count: 111 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class _468 { + + public static class Solution1 { + + static final String NEITHER = "Neither"; + + public String validIPAddress(String IP) { + if (IP.contains(".")) { + return isValidIPv4(IP); + } else if (IP.contains(":")) { + return isValidIPv6(IP); + } else { + return NEITHER; + } + } + + private String isValidIPv6(String IP) { + if (getDelimiterCount(IP, ':') != 7) { + return NEITHER; + } + String[] bytes = IP.split("\\:"); + if (bytes.length != 8) { + return NEITHER; + } + for (int i = 0; i < 8; i++) { + if (hasInvalidIPV6Char(bytes[i])) { + return NEITHER; + } + try { + if (bytes[i].length() > 4) { + return NEITHER; + } + int intNum = Integer.parseInt(bytes[i], 16); + if (intNum < 0) { + return NEITHER; + } + if (i == 0 && intNum != 0 && bytes[i].charAt(0) == '0') { + return NEITHER; + } + } catch (Exception e) { + return NEITHER; + } + } + return "IPv6"; + } + + private String isValidIPv4(String IP) { + if (getDelimiterCount(IP, '.') != 3) { + return NEITHER; + } + String[] bytes = IP.split("\\."); + if (bytes.length != 4) { + return NEITHER; + } + for (String num : bytes) { + try { + int intNum = Integer.parseInt(num); + if (intNum > 255 || intNum < 0) { + return NEITHER; + } + if (intNum != 0) { + for (int i = 0; i < num.length(); i++) { + if (num.charAt(i) == '0') { + return NEITHER; + } else { + break; + } + } + } else if (intNum == 0) { + if (num.length() != 1) { + return NEITHER; + } + } + } catch (Exception e) { + return NEITHER; + } + } + return "IPv4"; + } + + private boolean hasInvalidIPV6Char(String str) { + Set set = + new HashSet<>( + Arrays.asList( + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', + 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F')); + for (char c : str.toCharArray()) { + if (!set.contains(c)) { + return true; + } + } + return false; + } + + private int getDelimiterCount(String ip, char delimiter) { + int count = 0; + for (char c : ip.toCharArray()) { + if (c == delimiter) { + count++; + } + } + return count; + } + } +} + + +================================================== +File: _377.java +Line count: 124 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class _377 { + + public static class Solution1 { + /* + * this normal backtracking recursive solution will end up in MLE by this testcase: [4,2,1], 32 + */ + public int combinationSum4(int[] nums, int target) { + List> result = new ArrayList(); + Arrays.sort(nums); + backtracking(nums, target, new ArrayList(), result); + return result.size(); + } + + private void backtracking( + int[] nums, int target, List list, List> result) { + if (target == 0) { + result.add(new ArrayList(list)); + } else if (target > 0) { + for (int i = 0; i < nums.length; i++) { + list.add(nums[i]); + backtracking(nums, target - nums[i], list, result); + list.remove(list.size() - 1); + } + } + } + } + + public static class Solution2 { + /* + * Since we don't need to get all of the combinations, instead, + * we only need to get the possible count, I can use only a count instead of "List> result" + * However, it also ended up in TLE by this testcase: [1,2,3], 32 + */ + public static int count = 0; + + public int combinationSum4(int[] nums, int target) { + Arrays.sort(nums); + backtracking(nums, target, new ArrayList()); + return count; + } + + private void backtracking(int[] nums, int target, List list) { + if (target == 0) { + count++; + } else if (target > 0) { + for (int i = 0; i < nums.length; i++) { + list.add(nums[i]); + backtracking(nums, target - nums[i], list); + list.remove(list.size() - 1); + } + } + } + } + + public static class Solution3 { + /* + * Time: O(n^2) + * Space: O(n) + *

+ * Since this question doesn't require to return all the combination result, instead, it just wants one number, we could use DP + * the idea is similar to Climbing Stairs. + *

+ * The idea is very clear as the code speaks for itself: + * It's easy to find the routine + * dp[0] = 0; + * dp[1] = 1; + * ... + *

+ * Reference: https://discuss.leetcode.com/topic/52186/my-3ms-java-dp-solution + */ + public int combinationSum4(int[] nums, int target) { + Arrays.sort(nums); + int[] dp = new int[target + 1]; + for (int i = 1; i < dp.length; i++) { + for (int num : nums) { + if (num > i) { + break; + } else if (num == i) { + dp[i]++; + } else { + dp[i] += dp[i - num]; + } + } + } + return dp[target]; + } + } + + public static class Solution4 { + /* + * Time: O(n) + * Space: O(n) + *

+ * Reference: https://discuss.leetcode.com/topic/52255/java-recursion-solution-using-hashmap-as-memory + */ + Map map = new HashMap<>(); + + public int combinationSum4(int[] nums, int target) { + if (nums == null || nums.length == 0 || target < 0) { + return 0; + } + if (target == 0) { + return 1; + } + if (map.containsKey(target)) { + return map.get(target); + } + int count = 0; + for (int num : nums) { + count += combinationSum4(nums, target - num); + } + map.put(target, count); + return count; + } + } +} + + +================================================== +File: _224.java +Line count: 163 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.Deque; +import java.util.LinkedList; + +public class _224 { + + public static class Solution1 { + /* + * My complete original solution on 12/23/2021 + */ + public int calculate(String s) { + Deque stack = new LinkedList<>(); + for (int i = 0; i < s.length(); i++) { + if (s.charAt(i) == ' ') { + continue; + } else { + if (s.charAt(i) == '(' || s.charAt(i) == '+' || s.charAt(i) == '-') { + stack.addLast(s.charAt(i) + ""); + } else if (Character.isDigit(s.charAt(i))) { + int start = i; + while (i < s.length() && Character.isDigit(s.charAt(i))) { + i++; + } + stack.addLast(s.substring(start, i)); + i--; + } else if (s.charAt(i) == ')') { + int result = 0; + while (!stack.isEmpty() && !stack.peekLast().equals("(")) { + String numStr = stack.pollLast(); + int numInt = Integer.parseInt(numStr); + if (!stack.isEmpty() + && (stack.peekLast().equals("-") + || stack.peekLast().equals("+"))) { + String operator = stack.pollLast(); + if (operator.equals("+")) { + result += numInt; + } else if (operator.equals("-")) { + result -= numInt; + } + } else { + result += numInt; + if (!stack.isEmpty() && stack.peekLast().equals("(")) { + stack.pollLast(); + break; + } + } + } + if (!stack.isEmpty() && stack.peekLast().equals("(")) { + stack.pollLast(); + } + stack.addLast(result + ""); + } + } + } + int result = 0; + while (!stack.isEmpty() && stack.peekLast() != "(") { + String numStr = stack.pollLast(); + int numInt = Integer.parseInt(numStr); + if (!stack.isEmpty()) { + String operator = stack.pollLast(); + if (operator.equals("+")) { + result += numInt; + } else if (operator.equals("-")) { + result -= numInt; + } + } else { + result += numInt; + } + } + return !stack.isEmpty() ? Integer.parseInt(stack.peekLast()) + result : result; + } + } + + public static class Solution2 { + /* + * Simple and clean recursion solution, credit: https://leetcode.com/problems/basic-calculator/solutions/2344042/java-2ms-100-recursion-easy-to-understand/ + * Key points: + * 1. it uses a global variable called index to control which char to iterate on; + * 2. it passes the entire string s into recursive functions. + */ + int index; + + public int calculate(String s) { + index = 0; + return cal(s); + } + + private int cal(String s) { + int result = 0; + int num = 0; + int sign = 1; + while (index < s.length()) { + char c = s.charAt(index++); + if (c >= '0' && c <= '9') { + num = num * 10 + c - '0'; + } else if (c == '(') { + // this is the beginning of a new sub-problem, we let recursion do its job + num = cal(s); + } else if (c == ')') { + // this is the end of a problem/sub-problem, so we return + return result + sign * num; + } else if (c == '+' || c == '-') { + // now we know we finished reading one number and a new number has begun + result += sign * num; + num = 0; + sign = c == '-' ? -1 : 1; + } + } + return result + sign * num; + } + } + + public static class Solution3 { + /* + * A more elegant solution using stack and iterative approach, credit: https://leetcode.com/problems/basic-calculator/solutions/62361/iterative-java-solution-with-stack/ + * Key points: + * 1. use an integer to represent sign: 1 or -1, so it can be pushed onto a stack that's of Integer type; + */ + public int calculate(String s) { + Deque stack = new LinkedList<>(); + int result = 0; + int sign = 1; + int num = 0; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (Character.isDigit(c)) { + num = num * 10 + c - '0'; + } else if (c == '(') { + // we push the result onto the stack first, then sign + stack.addLast(result); + stack.addLast(sign); + + // reset them + sign = 1; + num = 0; + } else if (c == ')') { + // this means we reached the end of one parenthesis, so we compute result and + // reset num + result += num * sign; + num = 0; + + result *= stack.pollLast(); // this is the last sign we pushed onto the stack + result += stack.pollLast(); // this is the last number on the stack + } else if (c == '+') { + result += num * sign; + // reset below two variables + num = 0; + sign = 1; + } else if (c == '-') { + result -= num * sign; + // reset below two variables + num = 0; + sign = 1; + } + } + if (num != 0) { + result += num * sign; + } + return result; + } + } +} + + +================================================== +File: _212.java +Line count: 116 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +public class _212 { + + public static class Solution1 { + public List findWords(char[][] board, String[] words) { + TrieNode root = buildTrie(words); + List result = new ArrayList(); + for (int i = 0; i < board.length; i++) { + for (int j = 0; j < board[0].length; j++) { + dfs(root, board, i, j, result); + } + } + return result; + } + + private void dfs(TrieNode root, char[][] board, int i, int j, List result) { + char tmp = board[i][j]; + + if (tmp == '#' || root.children[tmp - 'a'] == null) { + return; + } + + if (root.children[tmp - 'a'].word != null) { + result.add(root.children[tmp - 'a'].word); + root.children[tmp - 'a'].word = null; // de-duplicate + } + board[i][j] = '#'; // mark it as visited to avoid cycles + if (i > 0) { + dfs(root.children[tmp - 'a'], board, i - 1, j, result); + } + if (j > 0) { + dfs(root.children[tmp - 'a'], board, i, j - 1, result); + } + if (i + 1 < board.length) { + dfs(root.children[tmp - 'a'], board, i + 1, j, result); + } + if (j + 1 < board[0].length) { + dfs(root.children[tmp - 'a'], board, i, j + 1, result); + } + + // backtracking + board[i][j] = tmp; + } + + private TrieNode root; + + class TrieNode { + String word; + TrieNode[] children = new TrieNode[26]; + } + + private TrieNode buildTrie(String[] words) { + TrieNode root = new TrieNode(); + for (String word : words) { + char[] chars = word.toCharArray(); + TrieNode temp = root; + for (char c : chars) { + if (temp.children[c - 'a'] == null) { + temp.children[c - 'a'] = new TrieNode(); + } + temp = temp.children[c - 'a']; + } + temp.word = word; + } + return root; + } + } + + public static class Solution2 { + public List findWords(char[][] board, String[] words) { + List result; + HashSet set = new HashSet(); + for (String word : words) { + for (int i = 0; i < board.length; i++) { + for (int j = 0; j < board[0].length; j++) { + if (board[i][j] == word.charAt(0) && search(board, i, j, 0, word)) { + set.add(word); + } + } + } + } + result = new ArrayList<>(set); + return result; + } + + private boolean search(char[][] board, int i, int j, int index, String word) { + if (index == word.length()) { + return true; + } + + if (i < 0 + || i >= board.length + || j < 0 + || j >= board[0].length + || board[i][j] != word.charAt(index)) { + return false; + } + + char temp = board[i][j]; + board[i][j] = ' '; + + boolean foundWord = + search(board, i + 1, j, index + 1, word) + || search(board, i - 1, j, index + 1, word) + || search(board, i, j + 1, index + 1, word) + || search(board, i, j - 1, index + 1, word); + board[i][j] = temp; + return foundWord; + } + } +} + + +================================================== +File: _300.java +Line count: 124 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.Arrays; + +public class _300 { + + public static class Solution1 { + /* + * brute force: + * Time: O(2^n), size of recursion tree will be: 2^n + * Space: O(n^2) + * will result in Time Limit Exceeded exception. + *

+ * The idea is straightforward: we'll iterate through each number, check to see if its next neighbor is smaller or bigger than itself, + * if bigger, then we'll take it, if not, we'll not take it. + */ + public int lengthOfLIS(int[] nums) { + return recursion(nums, Integer.MIN_VALUE, 0); + } + + private int recursion(int[] nums, int prev, int curr) { + if (curr == nums.length) { + return 0; + } + int taken = 0; + if (nums[curr] > prev) { + taken = 1 + recursion(nums, nums[curr], curr + 1); + } + int notTaken = recursion(nums, prev, curr + 1); + return Math.max(taken, notTaken); + } + } + + public static class Solution2 { + /* + * This is an iteration on the previous solution, we use a 2-d array to memoize the previously calculated result + * Time: O(n^2) + * Space: O(n^2) + */ + public int lengthOfLIS(int[] nums) { + int len = nums.length; + int[][] memo = new int[len + 1][len]; + for (int[] m : memo) { + Arrays.fill(m, -1); + } + return recusionWithMemo(nums, -1, 0, memo); + } + + private int recusionWithMemo(int[] nums, int prevIndex, int currIndex, int[][] memo) { + if (currIndex == nums.length) { + return 0; + } + if (memo[prevIndex + 1][currIndex] >= 0) { + // because we initialize all elements in memo to be -1, + // so if it's not -1, then it means we have computed this value before, + // we'll just return it and this way it avoid duplicate recursion + return memo[prevIndex + 1][currIndex]; + } + int taken = 0; + if (prevIndex < 0 || nums[currIndex] > nums[prevIndex]) { + taken = 1 + recusionWithMemo(nums, currIndex, currIndex + 1, memo); + } + int notTaken = recusionWithMemo(nums, prevIndex, currIndex + 1, memo); + memo[prevIndex + 1][currIndex] = Math.max(taken, notTaken); + return memo[prevIndex + 1][currIndex]; + } + } + + public static class Solution3 { + /* + * DP solution, credit: https://leetcode.com/problems/longest-increasing-subsequence/editorial/ + * Time: O(n^2) + * Space: O(n) + */ + public int lengthOfLIS(int[] nums) { + if (nums.length == 0) { + return 0; + } + int[] dp = new int[nums.length]; + Arrays.fill(dp, 1); + for (int i = 1; i < nums.length; i++) { + for (int j = 0; j < i; j++) { + if (nums[i] > nums[j]) { + dp[i] = Math.max(dp[i], dp[j] + 1); + } + } + } + int ans = 1; + for (int val : dp) { + ans = Math.max(ans, val); + } + return ans; + } + } + + public static class Solution4 { + /* + * use binary search. + * Time: O(nlogn) + * Space: O(n) + *

+ * The reason we can use binary search here is all numbers we put into dp array are sorted. + * Arrays.binarySearch() method returns index of the search key, + * if it is contained in the array, else it returns (-(insertion point) - 1). + * The insertion point is the point at which the key would be inserted into the array: + * the index of the first element greater than the key, or a.length if all elements in the array are less than the specified key. + */ + public int lengthOfLIS(int[] nums) { + int[] dp = new int[nums.length]; + int len = 0; + for (int num : nums) { + int index = Arrays.binarySearch(dp, 0, len, num); + if (index < 0) { + index = -(index + 1); + } + dp[index] = num; + if (index == len) { + len++; + } + } + return len; + } + } +} + + +================================================== +File: _697.java +Line count: 105 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.*; + +public class _697 { + public static class Solution1 { + public int findShortestSubArray(int[] nums) { + Map map = new HashMap<>(); + for (int i = 0; i < nums.length; i++) { + if (map.containsKey(nums[i])) { + map.put(nums[i], map.get(nums[i]) + 1); + } else { + map.put(nums[i], 1); + } + } + int degree = -1; + for (int key : map.keySet()) { + degree = Math.max(degree, map.get(key)); + } + List candidateNums = new ArrayList(); + for (int key : map.keySet()) { + if (map.get(key) == degree) { + candidateNums.add(key); + } + } + int shortest = Integer.MAX_VALUE; + for (int candidate : candidateNums) { + shortest = Math.min(shortest, findLength(nums, candidate)); + } + return shortest; + } + + int findLength(int[] arr, int candidate) { + int firstAppearance = Integer.MAX_VALUE; + for (int i = 0; i < arr.length; i++) { + if (arr[i] == candidate) { + firstAppearance = i; + break; + } + } + int lastAppearance = arr.length - 1; + for (int i = arr.length - 1; i > firstAppearance; i--) { + if (arr[i] == candidate) { + lastAppearance = i; + break; + } + } + return (lastAppearance - firstAppearance) + 1; + } + } + + public static class Solution2 { + public int findShortestSubArray(int[] nums) { + Map count = new HashMap<>(); + Map left = new HashMap<>(); + Map right = new HashMap<>(); + + for (int i = 0; i < nums.length; i++) { + count.put(nums[i], count.getOrDefault(nums[i], 0) + 1); + if (!left.containsKey(nums[i])) { + left.put(nums[i], i); + } + right.put(nums[i], i); + } + + int result = nums.length; + int degree = Collections.max(count.values()); + for (int num : count.keySet()) { + if (count.get(num) == degree) { + result = Math.min(result, right.get(num) - left.get(num) + 1); + } + } + return result; + } + } + + public static class Solution3 { + public int findShortestSubArray(int[] nums) { + Map frequencyMap = new HashMap<>(); + Map> numberToIndicesMap = new HashMap<>(); + for (int i = 0; i < nums.length; i++) { + frequencyMap.put(nums[i], frequencyMap.getOrDefault(nums[i], 0) + 1); + List indices = numberToIndicesMap.getOrDefault(nums[i], new ArrayList<>()); + indices.add(i); + numberToIndicesMap.put(nums[i], indices); + } + int degree = 0; + Set numbersThatOccurTheMost = new HashSet<>(); + for (Map.Entry entry : frequencyMap.entrySet()) { + degree = Math.max(degree, entry.getValue()); + } + for (Map.Entry entry : frequencyMap.entrySet()) { + if (entry.getValue() == degree) { + numbersThatOccurTheMost.add(entry.getKey()); + } + } + int result = nums.length; + for (int num : numbersThatOccurTheMost) { + List indices = numberToIndicesMap.get(num); + result = Math.min(result, indices.get(indices.size() - 1) - indices.get(0) + 1); + } + return result; + } + } +} + + +================================================== +File: _395.java +Line count: 102 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +public class _395 { + public static class Solution1 { + /* + * Reference: https://discuss.leetcode.com/topic/57372/java-divide-and-conquer-recursion-solution + */ + public int longestSubstring(String s, int k) { + return findLongestSubstring(s.toCharArray(), 0, s.length(), k); + } + + int findLongestSubstring(char[] chars, int start, int end, int k) { + /*Base case 1 of 2*/ + if (end - start < k) { + return 0; + } + int[] count = new int[26]; + for (int i = start; i < end; i++) { + int index = chars[i] - 'a'; + count[index]++; + } + + /*For every character in the above frequency table*/ + for (int i = 0; i < 26; i++) { + if (count[i] < k && count[i] > 0) { + for (int j = start; j < end; j++) { + if (chars[j] == i + 'a') { + int left = findLongestSubstring(chars, start, j, k); + int right = findLongestSubstring(chars, j + 1, end, k); + return Math.max(left, right); + } + } + } + } + /*Base case 2 of 2: + * when any characters in this substring has repeated at least k times, then this entire substring is a valid answer*/ + return end - start; + } + } + + public static class Solution2 { + /* + * classic sliding window approach. + * credit: https://leetcode.com/problems/longest-substring-with-at-least-k-repeating-characters/discuss/170010/Java-O(n)-Solution-with-Detailed-Explanation-Sliding-Window/774350 + */ + public int longestSubstring(String s, int k) { + int res = 0; + for (int numUniqueTarget = 1; numUniqueTarget <= 26; numUniqueTarget++) { + res = Math.max(res, slidingWindowHelper(s, k, numUniqueTarget)); + } + return res; + } + + // sliding window template + private int slidingWindowHelper(String s, int k, int numUniqueTarget) { + int[] map = new int[26]; + int start = 0; + int end = 0; + int res = 0; + int uniqueLetterCount = 0; + int numNoLessThanK = 0; + while (end < s.length()) { + char c1 = s.charAt(end); + if (map[c1 - 'a'] == 0) { + // we increment this when we include a new letter into our sliding window + uniqueLetterCount++; + } + map[c1 - 'a']++; + if (map[c1 - 'a'] == k) { + // we increment this number when we find a letter's frequency reaches k + numNoLessThanK++; + } + end++; + + while (uniqueLetterCount > numUniqueTarget) { + // as long as the counter (the number of qualified letters) is greater than our + // target number, + // we can move the left pointer to the right, + // this keeps the interval within our sliding window always valid + char c2 = s.charAt(start); + if (map[c2 - 'a'] == k) { + // we decrement this numNoLessThanK when we find this letter's frequency + // equals + // to k because we'll move past this letter, i.e. our sliding window no + // longer includes it + numNoLessThanK--; + } + map[c2 - 'a']--; + if (map[c2 - 'a'] == 0) { + uniqueLetterCount--; + } + start++; + } + + if (uniqueLetterCount == numNoLessThanK) { + res = Math.max(res, end - start); + } + } + return res; + } + } +} + + +================================================== +File: _706.java +Line count: 195 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.Arrays; + +public class _706 { + public static class Solution1 { + /* + * credit: https://leetcode.com/problems/design-hashmap/discuss/152746/Java-Solution + */ + class MyHashMap { + + final ListNode[] nodes = new ListNode[10000]; + + public void put(int key, int value) { + int i = idx(key); + if (nodes[i] == null) { + nodes[i] = new ListNode(-1, -1); + } + ListNode prev = find(nodes[i], key); + if (prev.next == null) { + prev.next = new ListNode(key, value); + } else { + prev.next.val = value; + } + } + + public int get(int key) { + int i = idx(key); + if (nodes[i] == null) { + return -1; + } + ListNode node = find(nodes[i], key); + return node.next == null ? -1 : node.next.val; + } + + public void remove(int key) { + int i = idx(key); + if (nodes[i] == null) { + return; + } + ListNode prev = find(nodes[i], key); + if (prev.next == null) { + return; + } + prev.next = prev.next.next; + } + + int idx(int key) { + return Integer.hashCode(key) % nodes.length; + } + + ListNode find(ListNode bucket, int key) { + ListNode node = bucket; + ListNode prev = null; + while (node != null && node.key != key) { + prev = node; + node = node.next; + } + return prev; + } + + class ListNode { + int key; + int val; + ListNode next; + + ListNode(int key, int val) { + this.key = key; + this.val = val; + } + } + } + } + + public static class Solution2 { + + public static class MyHashMap { + /* + * Considering the given constraints for this problem on LeetCode, load factors and resizing/rehashing are not considered. Thus an EASY problem. + *

+ * inspired by: https://leetcode.com/problems/design-hashmap/discuss/225312/hashmaparraylinkedlistcollision + */ + class Node { + /* + * We need to have both key and val in this ListNode because all values input key are hashed to the same bucket, so we need its original key + * to be a differentiator within this bucket. + */ + int val; + int key; + Node next; + + public Node(int key, int val) { + this.key = key; + this.val = val; + } + } + + Node[] nodes; + int size = 1000000; + + /* + * Initialize your data structure here. + */ + public MyHashMap() { + nodes = new Node[size]; + } + + /* + * value will always be non-negative. + */ + public void put(int key, int value) { + int index = getHashedKey(key); + Node head = nodes[index]; + Node tmp = head; + while (tmp != null) { + if (tmp.key == key) { + tmp.val = value; + return; + } + tmp = tmp.next; + } + Node newHead = new Node(key, value); + newHead.next = head; + nodes[index] = newHead; + } + + private int getHashedKey(int key) { + return Integer.hashCode(key) % size; + } + + /* + * Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key + */ + public int get(int key) { + Node head = nodes[getHashedKey(key)]; + Node tmp = head; + while (tmp != null && tmp.key != key) { + tmp = tmp.next; + } + if (tmp == null) { + return -1; + } + if (tmp.key == key) { + return tmp.val; + } + return -1; + } + + /* + * Removes the mapping of the specified value key if this map contains a mapping for the key + */ + public void remove(int key) { + /*We can just set the value of this key to -1 since the constraint for this problem is that all values are >= 0*/ + Node node = nodes[getHashedKey(key)]; + Node tmp = node; + Node pre = new Node(-1, -1); + pre.next = tmp; + while (tmp != null) { + if (tmp.key == key) { + tmp.val = -1; + return; + } + tmp = tmp.next; + } + } + } + } + + public static class Solution3 { + /* + * My completely original, but hacky and cheaty solution to take full advantage of the problem constraints. + */ + public static class MyHashMap { + + int[] map; + + public MyHashMap() { + map = new int[1000001]; + Arrays.fill(map, -1); + } + + public void put(int key, int value) { + map[key] = value; + } + + public int get(int key) { + return map[key]; + } + + public void remove(int key) { + map[key] = -1; + } + } + } +} + + +================================================== +File: _767.java +Line count: 107 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.HashMap; +import java.util.Map; +import java.util.PriorityQueue; + +public class _767 { + public static class Solution1 { + public String reorganizeString(String S) { + Map map = new HashMap<>(); + for (char c : S.toCharArray()) { + map.put(c, map.getOrDefault(c, 0) + 1); + } + int len = S.length(); + for (char c : map.keySet()) { + if ((len % 2 == 0 && map.get(c) > len / 2) + || (len % 2 != 0 && map.get(c) >= len / 2 + 2)) { + return ""; + } + } + PriorityQueue queue = new PriorityQueue<>((a, b) -> b.count - a.count); + for (char c : map.keySet()) { + queue.offer(new CustChar(c, map.get(c))); + } + + StringBuilder sb = new StringBuilder(); + while (!queue.isEmpty()) { + CustChar curr = queue.poll(); + char c = curr.c; + if (sb.length() > 0 && sb.charAt(sb.length() - 1) != c) { + sb.append(c); + if (curr.count > 1) { + queue.offer(new CustChar(c, curr.count - 1)); + } + } else if (sb.length() == 0) { + sb.append(c); + if (curr.count > 1) { + queue.offer(new CustChar(c, curr.count - 1)); + } + } else if (sb.length() > 0 && sb.charAt(sb.length() - 1) == c && !queue.isEmpty()) { + CustChar next = queue.poll(); + sb.append(next.c); + if (next.count > 1) { + queue.offer(new CustChar(next.c, next.count - 1)); + } + queue.offer(curr); + } + } + return sb.toString(); + } + + class CustChar { + Character c; + int count; + + public CustChar(Character c, int count) { + this.c = c; + this.count = count; + } + } + } + + public static class Solution2 { + /* + * My completely original solution on 12/24/2021. + */ + public String reorganizeString(String s) { + Map map = new HashMap<>(); + for (char c : s.toCharArray()) { + map.put(c, map.getOrDefault(c, 0) + 1); + } + PriorityQueue maxHeap = new PriorityQueue<>((a, b) -> b.count - a.count); + for (char c : map.keySet()) { + maxHeap.add(new Tuple(c, map.get(c))); + } + StringBuilder sb = new StringBuilder("1"); + while (!maxHeap.isEmpty()) { + PriorityQueue tmp = new PriorityQueue<>((a, b) -> b.count - a.count); + Tuple curr = maxHeap.poll(); + while (sb.length() != 0 + && sb.charAt(sb.length() - 1) == curr.c + && !maxHeap.isEmpty()) { + tmp.offer(curr); + curr = maxHeap.poll(); + } + if (curr.c != sb.charAt(sb.length() - 1)) { + sb.append(curr.c); + } + maxHeap.addAll(tmp); + if (curr.count > 1) { + maxHeap.offer(new Tuple(curr.c, curr.count - 1)); + } + } + return sb.substring(1).length() != s.length() ? "" : sb.substring(1); + } + + class Tuple { + char c; + int count; + + public Tuple(char c, int count) { + this.c = c; + this.count = count; + } + } + } +} + + +================================================== +File: _726.java +Line count: 110 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class _726 { + public static class Solution1 { + /* + * My completely original solution: + * 1. use a stack; + * 2. whenever we encounter the open paren, we push it into the top of the stack; + * 3. whenever we encounter an uppercase, we check to get its full atom letters, + * then check to get the number after it if there's any, then form a pair objet and push onto the stack; + * 4. whenever we encounter the closed paren, we check if there's any number after it, + * then poll all items on top of the stack off onto a new/temp stack until we encounter the corresponding open paren, + * then add these items from the temp stack back into the original/main stack; + */ + public String countOfAtoms(String formula) { + Deque stack = new LinkedList<>(); + for (int i = 0; i < formula.length(); i++) { + char curr = formula.charAt(i); + if (curr == '(') { + stack.addLast(new Pair("(", 1)); + } else if (Character.isUpperCase(curr)) { + StringBuilder sb = new StringBuilder(curr + ""); + i++; + while (i < formula.length() && Character.isLowerCase(formula.charAt(i))) { + sb.append(formula.charAt(i++)); + } + if (i < formula.length()) { + if (Character.isUpperCase(formula.charAt(i)) + || formula.charAt(i) == '(' + || formula.charAt(i) == ')') { + // no numbers + stack.addLast(new Pair(sb.toString(), 1)); + i--; + } else { + // there are numbers + StringBuilder numberSb = new StringBuilder(); + while (i < formula.length() && Character.isDigit(formula.charAt(i))) { + numberSb.append(formula.charAt(i++)); + } + i--; + stack.addLast( + new Pair(sb.toString(), Integer.parseInt(numberSb.toString()))); + } + } else { + stack.addLast(new Pair(sb.toString(), 1)); + } + } else if (curr == ')') { + i++; + StringBuilder sb = new StringBuilder(); + while (i < formula.length() && Character.isDigit(formula.charAt(i))) { + sb.append(formula.charAt(i)); + i++; + } + i--; + int number = 1; + if (sb.length() > 0) { + number = Integer.parseInt(sb.toString()); + } + Deque stack2 = new LinkedList<>(); + while (!stack.isEmpty() && !stack.peekLast().atom.equals("(")) { + Pair pair = stack.pollLast(); + stack2.addLast(new Pair(pair.atom, pair.count * number)); + } + stack.pollLast(); // poll "(" off of the stack + while (!stack2.isEmpty()) { + stack.addLast(stack2.pollLast()); + } + } + } + List list = new ArrayList<>(); + while (!stack.isEmpty()) { + list.add(stack.pollLast()); + } + // now merge the same atoms + Map map = new HashMap<>(); + for (Pair pair : list) { + map.put(pair.atom, map.getOrDefault(pair.atom, 0) + pair.count); + } + // now add the merged atoms into the list again before sorting them + list.clear(); + for (Map.Entry entry : map.entrySet()) { + list.add(new Pair(entry.getKey(), entry.getValue())); + } + Collections.sort(list, (a, b) -> a.atom.compareToIgnoreCase(b.atom)); + StringBuilder sb = new StringBuilder(); + for (Pair pair : list) { + sb.append(pair.atom + (pair.count == 1 ? "" : pair.count)); + } + return sb.toString(); + } + + class Pair { + String atom; + int count; + + public Pair(String atom, int count) { + this.atom = atom; + this.count = count; + } + } + } +} + + +================================================== +File: _248.java +Line count: 107 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.HashMap; +import java.util.Map; + +public class _248 { + + public static class Solution1 { + /*Credit: https://discuss.leetcode.com/topic/31386/concise-java-solution + * + Construct char arrays from low.length() to high.length() + Add stro pairs from outside + When left > right, add eligible count + */ + + private static final char[][] pairs = { + {'0', '0'}, {'1', '1'}, {'6', '9'}, {'8', '8'}, {'9', '6'} + }; + + public int strobogrammaticInRange(String low, String high) { + int[] count = {0}; + for (int len = low.length(); len <= high.length(); len++) { + char[] c = new char[len]; + dfs(low, high, c, 0, len - 1, count); + } + return count[0]; + } + + public void dfs(String low, String high, char[] c, int left, int right, int[] count) { + if (left > right) { + String s = new String(c); + if ((s.length() == low.length() && s.compareTo(low) < 0) + || (s.length() == high.length() && s.compareTo(high) > 0)) { + return; + } + count[0]++; + return; + } + for (char[] p : pairs) { + c[left] = p[0]; + c[right] = p[1]; + if (c.length != 1 && c[0] == '0') { + continue; + } + if (left == right && p[0] != p[1]) { + continue; + } + dfs(low, high, c, left + 1, right - 1, count); + } + } + } + + public static class Solution2 { + Map map = new HashMap<>(); + + { + map.put('1', '1'); + map.put('8', '8'); + map.put('6', '9'); + map.put('9', '6'); + map.put('0', '0'); + } + + String low = ""; + String high = ""; + + public int strobogrammaticInRange(String low, String high) { + this.low = low; + this.high = high; + int result = 0; + for (int n = low.length(); n <= high.length(); n++) { + int[] count = new int[1]; + strobogrammaticInRange(new char[n], count, 0, n - 1); + result += count[0]; + } + return result; + } + + private void strobogrammaticInRange(char[] arr, int[] count, int lo, int hi) { + if (lo > hi) { + String s = new String(arr); + if ((arr[0] != '0' || arr.length == 1) && compare(low, s) && compare(s, high)) { + count[0]++; + } + return; + } + for (Character c : map.keySet()) { + arr[lo] = c; + arr[hi] = map.get(c); + if ((lo == hi && c == map.get(c)) || lo < hi) { + strobogrammaticInRange(arr, count, lo + 1, hi - 1); + } + } + } + + private boolean compare(String a, String b) { + if (a.length() != b.length()) { + return a.length() < b.length(); + } + int i = 0; + while (i < a.length() && a.charAt(i) == b.charAt(i)) { + i++; + } + return i == a.length() ? true : a.charAt(i) <= b.charAt(i); + } + } +} + + +================================================== +File: _764.java +Line count: 139 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.HashSet; +import java.util.Set; + +public class _764 { + public static class Solution1 { + /* + * Dp + *

+ * Time: O(N^2) + * Space: O(N^2) + * Credit: https://leetcode.com/articles/largest-plus-sign/ + */ + public int orderOfLargestPlusSign(int N, int[][] mines) { + Set banned = new HashSet<>(); + for (int[] mine : mines) { + banned.add(mine[0] * N + mine[1]); + } + + int[][] dp = new int[N][N]; + + for (int row = 0; row < N; row++) { + int count = 0; + for (int col = 0; col < N; col++) { + count = banned.contains(row * N + col) ? 0 : count + 1; + dp[row][col] = count; + } + + count = 0; + for (int col = N - 1; col >= 0; col--) { + count = banned.contains(row * N + col) ? 0 : count + 1; + dp[row][col] = Math.min(dp[row][col], count); + } + } + + int result = 0; + for (int col = 0; col < N; col++) { + int count = 0; + for (int row = 0; row < N; row++) { + count = banned.contains(row * N + col) ? 0 : count + 1; + dp[row][col] = Math.min(dp[row][col], count); + } + + count = 0; + for (int row = N - 1; row >= 0; row--) { + count = banned.contains(row * N + col) ? 0 : count + 1; + dp[row][col] = Math.min(dp[row][col], count); + result = Math.max(result, dp[row][col]); + } + } + return result; + } + } + + public static class Solution2 { + /* + * credit: https://leetcode.com/problems/largest-plus-sign/discuss/113314/JavaC%2B%2BPython-O(N2)-solution-using-only-one-grid-matrix + */ + public int orderOfLargestPlusSign(int n, int[][] mines) { + int[][] grid = new int[n][n]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + grid[i][j] = n; + } + } + for (int i = 0; i < mines.length; i++) { + grid[mines[i][0]][mines[i][1]] = 0; + } + for (int i = 0; i < n; i++) { + for (int j = 0, k = n - 1, l = 0, r = 0, u = 0, d = 0; j < n; j++, k--) { + grid[i][j] = + Math.min( + grid[i][j], + l = (grid[i][j] == 0 ? 0 : l + 1)); // left direction + grid[i][k] = + Math.min( + grid[i][k], + r = (grid[i][k] == 0 ? 0 : r + 1)); // right direction + grid[j][i] = Math.min(grid[j][i], u = (grid[j][i] == 0 ? 0 : u + 1)); // upwards + grid[k][i] = + Math.min(grid[k][i], d = (grid[k][i] == 0 ? 0 : d + 1)); // downwards + } + } + int result = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + result = Math.max(result, grid[i][j]); + } + } + return result; + } + + /* + * break the above into FOUR separate loops to go over four directions for easier understanding + */ + public int orderOfLargestPlusSign_initialVersion(int n, int[][] mines) { + int[][] grid = new int[n][n]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + grid[i][j] = n; + } + } + for (int i = 0; i < mines.length; i++) { + grid[mines[i][0]][mines[i][1]] = 0; + } + for (int i = 0; i < n; i++) { + for (int j = 0, l = 0; j < n; j++) { + grid[i][j] = Math.min(grid[i][j], l = (grid[i][j] == 0 ? 0 : l + 1)); + } + } + + for (int i = 0; i < n; i++) { + for (int j = 0, k = n - 1, r = 0; j < n; j++, k--) { + grid[i][k] = Math.min(grid[i][k], r = (grid[i][k] == 0 ? 0 : r + 1)); + } + } + + for (int i = 0; i < n; i++) { + for (int j = 0, k = n - 1, u = 0; j < n; j++, k--) { + grid[j][i] = Math.min(grid[j][i], u = (grid[j][i] == 0 ? 0 : u + 1)); + } + } + + for (int i = 0; i < n; i++) { + for (int j = 0, k = n - 1, d = 0; j < n; j++, k--) { + grid[k][i] = Math.min(grid[k][i], d = (grid[k][i] == 0 ? 0 : d + 1)); + } + } + int result = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + result = Math.max(result, grid[i][j]); + } + } + return result; + } + } +} + + +================================================== +File: _508.java +Line count: 154 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import com.fishercoder.common.classes.TreeNode; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class _508 { + + public static class Solution1 { + // my purely original but verbose solution + public int[] findFrequentTreeSum(TreeNode root) { + if (root == null) { + return new int[] {}; + } + + Map map = new HashMap(); + postOrder(root, map); + + Map frequencyMap = new HashMap<>(); + for (Map.Entry entry : map.entrySet()) { + frequencyMap.put( + (Integer) entry.getValue(), + frequencyMap.getOrDefault(entry.getValue(), 0) + 1); + } + + List> list = new LinkedList<>(frequencyMap.entrySet()); + Collections.sort(list, (o1, o2) -> (o2.getValue()).compareTo(o1.getValue())); + + int mostFrequency = list.get(0).getValue(); + List topFrequencyList = new ArrayList<>(); + topFrequencyList.add(list.get(0).getKey()); + int i = 1; + while (i < list.size() && list.get(i).getValue() == mostFrequency) { + topFrequencyList.add(list.get(i).getKey()); + i++; + } + + int[] result = new int[topFrequencyList.size()]; + for (int j = 0; j < topFrequencyList.size(); j++) { + result[j] = topFrequencyList.get(j); + } + + return result; + } + + private int postOrder(TreeNode root, Map map) { + int left = 0; + int right = 0; + if (root.left != null) { + left = postOrder(root.left, map); + } + if (root.right != null) { + right = postOrder(root.right, map); + } + if (root.left == null && root.right == null) { + map.put(root, root.val); + return root.val; + } + int sum = left + right + root.val; + map.put(root, sum); + return sum; + } + } + + public static class Solution2 { + // my 2nd purely original but verbose solution + public int[] findFrequentTreeSum(TreeNode root) { + Map map = new HashMap<>(); + dfs(root, map); + List> entryList = new ArrayList<>(map.entrySet()); + Collections.sort(entryList, (a, b) -> b.getValue() - a.getValue()); + List list = new ArrayList<>(); + for (int i = 0; i < entryList.size(); i++) { + if (list.size() == 0) { + list.add(entryList.get(i).getKey()); + } else { + if (map.get(list.get(0)) == entryList.get(i).getValue()) { + list.add(entryList.get(i).getKey()); + } else { + break; + } + } + } + int[] result = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + result[i] = list.get(i); + } + return result; + } + + private int dfs(TreeNode root, Map map) { + if (root == null) { + return 0; + } + if (root.left == null && root.right == null) { + map.put(root.val, map.getOrDefault(root.val, 0) + 1); + return root.val; + } + int leftVal = 0; + if (root.left != null) { + leftVal = dfs(root.left, map); + } + int rightVal = 0; + if (root.right != null) { + rightVal = dfs(root.right, map); + } + int subtreeSum = leftVal + rightVal + root.val; + map.put(subtreeSum, map.getOrDefault(subtreeSum, 0) + 1); + return subtreeSum; + } + } + + public static class Solution3 { + /* + * Use post-order traversal for this problem as it needs to process subtree first before processing the root. + */ + Map map = new HashMap<>(); + + public int[] findFrequentTreeSum(TreeNode root) { + postOrder(root); + int mostFreq = -1; + for (Map.Entry entry : map.entrySet()) { + mostFreq = Math.max(mostFreq, entry.getValue()); + } + List list = new ArrayList<>(); + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == mostFreq) { + list.add(entry.getKey()); + } + } + return list.stream().mapToInt(integer -> integer).toArray(); + } + + private int postOrder(TreeNode root) { + if (root == null) { + return 0; + } + int leftSum = postOrder(root.left); + int rightSum = postOrder(root.right); + int subtreeSum = leftSum + rightSum + root.val; + map.put(subtreeSum, map.getOrDefault(subtreeSum, 0) + 1); + return subtreeSum; + } + } + + // a more concise and space-efficient solution: + // https://discuss.leetcode.com/topic/77775/verbose-java-solution-postorder-traverse-hashmap-18ms + // the key difference between the above post and my original solution is that it's using + // Frequency as the key of the HashMap +} + + +================================================== +File: _3.java +Line count: 164 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class _3 { + + public static class Solution1 { + public int lengthOfLongestSubstring(String s) { + int result = 0; + Map map = new HashMap(); + for (int i = 0, j = i; j < s.length(); ) { + if (!map.containsKey(s.charAt(j)) + || (map.containsKey(s.charAt(j)) && map.get(s.charAt(j)) == 0)) { + map.put(s.charAt(j), 1); + result = Math.max(j - i + 1, result); + j++; + } else { + map.put(s.charAt(i), map.get(s.charAt(i)) - 1); + i++; + } + } + return result; + } + } + + public static class Solution2 { + /* + * Sliding Window + * O(n) time + * O(min(m,n)) or O(k) space + */ + public int lengthOfLongestSubstring(String s) { + int n = s.length(); + Set set = new HashSet<>(); + int result = 0; + int i = 0; + int j = 0; + while (i < n && j < n) { + /*Try to extend the range i, j*/ + if (!set.contains(s.charAt(j))) { + set.add(s.charAt(j++)); + result = Math.max(result, j - i); + } else { + set.remove(s.charAt(i++)); + } + } + return result; + } + } + + public static class Solution3 { + /* + * Sliding Window + * O(n) time + * O(n) space + */ + public int lengthOfLongestSubstring(String s) { + if (s.length() == 0) { + return 0; + } + int max = 0; + Map map = new HashMap<>(); + /*Try to extend the range (i, j)*/ + for (int i = 0, j = 0; i < s.length(); i++) { + if (map.containsKey(s.charAt(i))) { + j = Math.max(j, map.get(s.charAt(i)) + 1); + } + map.put(s.charAt(i), i); + max = Math.max(max, i + 1 - j); + } + return max; + } + } + + public static class Solution4 { + /* + * Sliding Window Optimized + * O(n) time + * O(n) space + */ + public int lengthOfLongestSubstring(String s) { + if (s.length() == 0) { + return 0; + } + int max = 0; + int[] index = new int[128]; + /*Try to extend the range (i, j)*/ + for (int i = 0, j = 0; j < s.length(); j++) { + i = Math.max(index[s.charAt(j)], i); + max = Math.max(max, j - i + 1); + index[s.charAt(j)] = j + 1; + } + return max; + } + } + + public static class Solution5 { + /* + * Sliding Window, my completely original idea on 9/17/2021. + * Basically, keep moving the left boundary towards the right and keep updating the result along the way. + * O(n) time + * O(n) space + */ + public int lengthOfLongestSubstring(String s) { + int startIndex = 0; + int longest = 0; + Map map = new HashMap<>(); + for (int i = 0; i < s.length(); i++) { + if (map.containsKey(s.charAt(i))) { + Integer removedIndex = map.get(s.charAt(i)); + if (removedIndex >= startIndex) { + startIndex = removedIndex + 1; + } + } + map.put(s.charAt(i), i); + longest = Math.max(longest, i - startIndex + 1); + } + return longest; + } + } + + public static class Solution6 { + /* + * Sliding Window, my completely original idea on 10/20/2021. Although less efficient then Solution5, it follows a generic template without any manipulation. + * Basically, keep moving the left boundary towards the right and keep updating the result along the way. + * O(n) time + * O(n) space + */ + + public int lengthOfLongestSubstring(String s) { + int left = 0; + int right = 0; + int ans = 0; + Map map = new HashMap<>(); + while (right < s.length()) { + map.put(s.charAt(right), map.getOrDefault(s.charAt(right), 0) + 1); + right++; + if (allUnique(map)) { + ans = Math.max(ans, right - left); + } + while (!allUnique(map)) { + map.put(s.charAt(left), map.get(s.charAt(left)) - 1); + if (map.get(s.charAt(left)) == 0) { + map.remove(s.charAt(left)); + } + left++; + } + } + return ans; + } + + private boolean allUnique(Map map) { + for (char key : map.keySet()) { + if (map.get(key) > 1) { + return false; + } + } + return true; + } + } +} + + +================================================== +File: _73.java +Line count: 164 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +public class _73 { + + public static class Solution1 { + /* + * Space: O(m*n) + */ + public void setZeroes(int[][] matrix) { + if (matrix == null || matrix.length == 0) { + return; + } + int height = matrix.length; + int width = matrix[0].length; + boolean[][] zero = new boolean[height][width]; + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + if (matrix[i][j] == 0) { + zero[i][j] = true; + } + } + } + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + if (zero[i][j]) { + for (int k = 0; k < height; k++) { + matrix[k][j] = 0; + } + for (int k = 0; k < width; k++) { + matrix[i][k] = 0; + } + } + } + } + } + } + + public static class Solution2 { + /* + * Space: O(m+n) + */ + public void setZeroes(int[][] matrix) { + if (matrix == null || matrix.length == 0) { + return; + } + int m = matrix.length; + int n = matrix[0].length; + boolean[] row = new boolean[m]; + boolean[] col = new boolean[n]; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (matrix[i][j] == 0) { + row[i] = true; + col[j] = true; + } + } + } + + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (row[i] && col[j]) { + for (int k = 0; k < m; k++) { + matrix[k][j] = 0; + } + for (int k = 0; k < n; k++) { + matrix[i][k] = 0; + } + } + } + } + } + } + + public static class Solution3 { + /* + * Space: O(1) + */ + public void setZeroes(int[][] matrix) { + if (matrix == null || matrix.length == 0) { + return; + } + int m = matrix.length; + int n = matrix[0].length; + boolean firstRow = false; + boolean firstCol = false; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (matrix[i][j] == 0) { + if (i == 0) { + firstRow = true; + } + if (j == 0) { + firstCol = true; + } + matrix[i][0] = 0; + matrix[0][j] = 0; + } + } + } + + for (int i = 1; i < m; i++) { + for (int j = 1; j < n; j++) { + if (matrix[i][0] == 0 || matrix[0][j] == 0) { + matrix[i][j] = 0; + } + } + } + + if (firstRow) { + for (int j = 0; j < n; j++) { + matrix[0][j] = 0; + } + } + + if (firstCol) { + for (int i = 0; i < m; i++) { + matrix[i][0] = 0; + } + } + } + } + + public static class Solution4 { + /* + * Space: O(1) + * credit: https://leetcode.com/problems/set-matrix-zeroes/discuss/26014/Any-shorter-O(1)-space-solution + */ + public void setZeroes(int[][] matrix) { + int col0 = 1; + int m = matrix.length; + int n = matrix[0].length; + /*the first iteration (first nested for loop) is to check from top row to bottom row: + * keep the first column state into variable col0; + * then starting from the second column, check all the rest of the columns and mark its top cell and its most-left cell if it + * s a zero.*/ + for (int i = 0; i < m; i++) { + if (matrix[i][0] == 0) { + col0 = 0; + } + + for (int j = 1; j < n; j++) { + if (matrix[i][j] == 0) { + matrix[i][0] = 0; + matrix[0][j] = 0; + } + } + } + + /*the second iteration (second nested for loop) is to check from bottom row to the top row + * from the right-most column to the second left-most column: as long as its left-most column cell or its top row cell is zero, then set that cell to be zero + * at last, check col0 variable, if it's zero, mark that row cell as zero*/ + for (int i = m - 1; i >= 0; i--) { + for (int j = n - 1; j >= 1; j--) { + if (matrix[i][0] == 0 || matrix[0][j] == 0) { + matrix[i][j] = 0; + } + } + if (col0 == 0) { + matrix[i][0] = 0; + } + } + } + } +} + + +================================================== +File: _752.java +Line count: 103 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.*; + +public class _752 { + public static class Solution1 { + public int openLock(String[] deadends, String target) { + // Map the next slot digit for each current slot digit. + Map nextSlot = + new HashMap() { + { + put('0', '1'); + put('1', '2'); + put('2', '3'); + put('3', '4'); + put('4', '5'); + put('5', '6'); + put('6', '7'); + put('7', '8'); + put('8', '9'); + put('9', '0'); + } + }; + // Map the previous slot digit for each current slot digit. + Map prevSlot = + new HashMap() { + { + put('0', '9'); + put('1', '0'); + put('2', '1'); + put('3', '2'); + put('4', '3'); + put('5', '4'); + put('6', '5'); + put('7', '6'); + put('8', '7'); + put('9', '8'); + } + }; + + // Set to store visited and dead-end combinations. + Set visited = new HashSet<>(Arrays.asList(deadends)); + // Queue to store combinations generated after each turn. + Queue q = new LinkedList<>(); + + // Count the number of wheel turns made. + int turns = 0; + + // If the starting combination is also a dead-end, + // then we can't move from the starting combination. + if (visited.contains("0000")) { + return -1; + } + + // Start with the initial combination '0000'. + q.add("0000"); + visited.add("0000"); + + while (!q.isEmpty()) { + // Explore all the combinations of the current level. + int currLevelNodesCount = q.size(); + for (int i = 0; i < currLevelNodesCount; i++) { + // Get the current combination from the front of the queue. + String curr = q.poll(); + + // If the current combination matches the target, + // return the number of turns/level. + if (curr.equals(target)) { + return turns; + } + + // Explore all possible new combinations by turning each wheel in both + // directions. + for (int j = 0; j < curr.length(); j += 1) { + // Generate the new combination by turning the wheel to the next digit. + StringBuilder newCombination = new StringBuilder(curr); + newCombination.setCharAt(j, nextSlot.get(newCombination.charAt(j))); + // If the new combination is not a dead-end and was never visited, + // add it to the queue and mark it as visited. + if (!visited.contains(newCombination.toString())) { + q.add(newCombination.toString()); + visited.add(newCombination.toString()); + } + + // Generate the new combination by turning the wheel to the previous digit. + newCombination = new StringBuilder(curr); + newCombination.setCharAt(j, prevSlot.get(newCombination.charAt(j))); + // If the new combination is not a dead-end and is never visited, + // add it to the queue and mark it as visited. + if (!visited.contains(newCombination.toString())) { + q.add(newCombination.toString()); + visited.add(newCombination.toString()); + } + } + } + // We will visit next-level combinations. + turns++; + } + // We never reached the target combination. + return -1; + } + } +} + + +================================================== +File: _355.java +Line count: 281 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; +import java.util.Set; + +public class _355 { + + public static class Solution1 { + /* + * reference: https://discuss.leetcode.com/topic/48100/java-oo-design-with-most-efficient-function-getnewsfeed + */ + public static class Twitter { + + private static int timestamp = 0; + private Map map; + + class Tweet { + public int time; + public int id; + public Tweet next; + + /* + * have a pointer, + * so we could be more memory efficient when retrieving tweets, + * think about merging k sorted lists + */ + + public Tweet(int id) { + this.id = id; + time = timestamp++; + next = null; + } + } + + /* + * the meat part of this OO design problem, + * have a User object itself, + * have follow() and unfollow() method embedded inside it + */ + class User { + public int id; + public Set followed; + public Tweet tweetHead; + + public User(int id) { + this.id = id; + followed = new HashSet<>(); + followed.add(id); // follow oneself first + this.tweetHead = null; + } + + public void follow(int followeeId) { + followed.add(followeeId); + } + + public void unfollow(int followeeId) { + followed.remove(followeeId); + } + + public void postTweet(int tweetId) { + // every time we post, we prepend it to the head of the tweet + Tweet head = new Tweet(tweetId); + head.next = tweetHead; + tweetHead = head; // don't forget to overwrite tweetHead with the new head + } + } + + /* + * Initialize your data structure here. + */ + public Twitter() { + map = new HashMap(); + } + + /* + * Compose a new tweet. + */ + public void postTweet(int userId, int tweetId) { + /*update oneself newsFeed first and also all of his followers' newsFeed*/ + if (!map.containsKey(userId)) { + User user = new User(userId); + map.put(userId, user); + } + map.get(userId).postTweet(tweetId); + } + + /* + * Retrieve the 10 most recent tweet ids in the user's news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent. + */ + public List getNewsFeed(int userId) { + List newsFeed = new LinkedList<>(); + if (!map.containsKey(userId)) { + return newsFeed; + } + Set users = map.get(userId).followed; + PriorityQueue heap = + new PriorityQueue<>(users.size(), (a, b) -> b.time - a.time); + for (int user : users) { + Tweet tweet = map.get(user).tweetHead; + // it's super important to check null before putting into the heap + if (tweet != null) { + heap.offer(tweet); + } + } + + int count = 0; + while (!heap.isEmpty() && count < 10) { + Tweet tweet = heap.poll(); + newsFeed.add(tweet.id); + count++; + if (tweet.next != null) { + heap.offer(tweet.next); + } + } + + return newsFeed; + } + + /* + * Follower follows a followee. If the operation is invalid, it should be a no-op. + */ + public void follow(int followerId, int followeeId) { + if (!map.containsKey(followeeId)) { + User user = new User(followeeId); + map.put(followeeId, user); + } + + if (!map.containsKey(followerId)) { + User user = new User(followerId); + map.put(followerId, user); + } + + map.get(followerId).follow(followeeId); + } + + /* + * Follower unfollows a followee. If the operation is invalid, it should be a no-op. + */ + public void unfollow(int followerId, int followeeId) { + if (!map.containsKey(followerId) || followeeId == followerId) { + return; + } + map.get(followerId).unfollow(followeeId); + } + /* + * Your Twitter object will be instantiated and called as such: + * Twitter obj = new Twitter(); + * obj.postTweet(userId,tweetId); + * List param_2 = obj.getNewsFeed(userId); + * obj.follow(followerId,followeeId); + * obj.unfollow(followerId,followeeId); + */ + } + } + + public static class Solution2 { + public static class Twitter { + + Map map; + private int timestamp; + + private class User { + private int userId; + private Set followed; + private Tweet tweetHead; + + public User(int userId) { + this.userId = userId; + this.followed = new HashSet<>(); + this.followed.add(userId); + this.tweetHead = null; + } + + public void postTweet(int tweetId) { + Tweet tweet = new Tweet(tweetId); + tweet.next = tweetHead; + tweetHead = tweet; + } + + public void follow(int followeeId) { + followed.add(followeeId); + } + + public void unfollow(int followeeId) { + followed.remove(followeeId); + } + } + + private class Tweet { + int time; + int id; + Tweet next; + + public Tweet(int id) { + this.id = id; + time = timestamp++; + next = null; + } + } + + /* + * Initialize your data structure here. + */ + public Twitter() { + map = new HashMap<>(); + timestamp = 0; + } + + /* + * Compose a new tweet. + */ + public void postTweet(int userId, int tweetId) { + if (!map.containsKey(userId)) { + User user = new User(userId); + map.put(userId, user); + } + map.get(userId).postTweet(tweetId); + } + + /* + * Retrieve the 10 most recent tweet ids in the user's news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent. + */ + public List getNewsFeed(int userId) { + List result = new LinkedList<>(); + if (!map.containsKey(userId)) { + return result; + } + Set followeeSet = map.get(userId).followed; + PriorityQueue maxHeap = new PriorityQueue<>((a, b) -> b.time - a.time); + for (int followeeId : followeeSet) { + if (map.containsKey(followeeId)) { + Tweet tweet = map.get(followeeId).tweetHead; + if (tweet != null) { + maxHeap.offer(tweet); + } + } + } + + int count = 0; + while (!maxHeap.isEmpty() && count++ < 10) { + Tweet tweet = maxHeap.poll(); + if (tweet != null) { + result.add(tweet.id); + if (tweet.next != null) { + maxHeap.offer(tweet.next); + } + } + } + return result; + } + + /* + * Follower follows a followee. If the operation is invalid, it should be a no-op. + */ + public void follow(int followerId, int followeeId) { + if (!map.containsKey(followerId)) { + map.put(followerId, new User(followerId)); + } + if (!map.containsKey(followeeId)) { + map.put(followeeId, new User(followeeId)); + } + map.get(followerId).follow(followeeId); + } + + /* + * Follower unfollows a followee. If the operation is invalid, it should be a no-op. + */ + public void unfollow(int followerId, int followeeId) { + if (!map.containsKey(followerId) || followeeId == followerId) { + return; + } + map.get(followerId).unfollow(followeeId); + } + } + } +} + + +================================================== +File: _314.java +Line count: 143 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import com.fishercoder.common.classes.TreeNode; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.TreeMap; + +public class _314 { + public static class Solution1 { + public List> verticalOrder(TreeNode root) { + List> result = new ArrayList(); + if (root == null) { + return result; + } + Queue bfsQ = new LinkedList(); + Queue indexQ = new LinkedList(); + TreeMap> map = new TreeMap(); + bfsQ.offer(root); + indexQ.offer( + 0); // we set the root as index 0, left will be negative, right will be positive + while (!bfsQ.isEmpty()) { + int qSize = bfsQ.size(); + for (int i = 0; i < qSize; i++) { + TreeNode curr = bfsQ.poll(); + int index = indexQ.poll(); + if (map.containsKey(index)) { + map.get(index).add(curr.val); + } else if (!map.containsKey(index)) { + List list = new ArrayList(); + list.add(curr.val); + map.put(index, list); + } + if (curr.left != null) { + bfsQ.offer(curr.left); + indexQ.offer(index - 1); + } + if (curr.right != null) { + bfsQ.offer(curr.right); + indexQ.offer(index + 1); + } + } + } + for (int i : map.keySet()) { + result.add(map.get(i)); + } + return result; + } + } + + public static class Solution2 { + public List> verticalOrder(TreeNode root) { + List> result = new ArrayList(); + if (root == null) { + return result; + } + Queue bfsQ = new LinkedList(); + Queue indexQ = new LinkedList(); + HashMap> map = new HashMap(); + bfsQ.offer(root); + indexQ.offer( + 0); // we set the root as index 0, left will be negative, right will be positive + int min = 0; + int max = 0; + while (!bfsQ.isEmpty()) { + int qSize = bfsQ.size(); + for (int i = 0; i < qSize; i++) { + TreeNode curr = bfsQ.poll(); + int index = indexQ.poll(); + if (map.containsKey(index)) { + map.get(index).add(curr.val); + } else if (!map.containsKey(index)) { + List list = new ArrayList(); + list.add(curr.val); + map.put(index, list); + } + if (curr.left != null) { + bfsQ.offer(curr.left); + indexQ.offer(index - 1); + min = Math.min(min, index - 1); + } + if (curr.right != null) { + bfsQ.offer(curr.right); + indexQ.offer(index + 1); + max = Math.max(max, index + 1); + } + } + } + for (int i = min; i <= max; i++) { + result.add(map.get(i)); + } + return result; + } + } + + public static class Solution3 { + public List> verticalOrder(TreeNode root) { + if (root == null) { + return new ArrayList<>(); + } + TreeMap> map = new TreeMap<>(); + Queue queue = new LinkedList<>(); + queue.offer(new NodeWithIndex(root, 0)); + while (!queue.isEmpty()) { + int size = queue.size(); + for (int i = 0; i < size; i++) { + NodeWithIndex nodeWithIndex = queue.poll(); + List thisList = + map.getOrDefault(nodeWithIndex.index, new ArrayList<>()); + thisList.add(nodeWithIndex.node.val); + map.put(nodeWithIndex.index, thisList); + if (nodeWithIndex.node.left != null) { + queue.offer( + new NodeWithIndex( + nodeWithIndex.node.left, nodeWithIndex.index - 1)); + } + if (nodeWithIndex.node.right != null) { + queue.offer( + new NodeWithIndex( + nodeWithIndex.node.right, nodeWithIndex.index + 1)); + } + } + } + List> result = new ArrayList<>(); + for (int index : map.keySet()) { + result.add(map.get(index)); + } + return result; + } + + class NodeWithIndex { + TreeNode node; + int index; + + public NodeWithIndex(TreeNode node, int index) { + this.node = node; + this.index = index; + } + } + } +} + + +================================================== +File: _207.java +Line count: 212 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.Set; + +/* + * 207. Course Schedule + *

+ * There are a total of numCourses courses you have to take, labeled from 0 to numCourses - 1. + * You are given an array prerequisites where prerequisites[i] = [ai, bi] indicates that you must take course bi first if you want to take course ai. + * For example, the pair [0, 1], indicates that to take course 0 you have to first take course 1. + * Return true if you can finish all courses. Otherwise, return false. + *

+ * Example 1: + * Input: numCourses = 2, prerequisites = [[1,0]] + * Output: true + * Explanation: There are a total of 2 courses to take. + * To take course 1 you should have finished course 0. So it is possible. + *

+ * Example 2: + * Input: numCourses = 2, prerequisites = [[1,0],[0,1]] + * Output: false + * Explanation: There are a total of 2 courses to take. + * To take course 1 you should have finished course 0, and to take course 0 you should also have finished course 1. So it is impossible. + *

+ * Constraints: + * 1 <= numCourses <= 2000 + * 0 <= prerequisites.length <= 5000 + * prerequisites[i].length == 2 + * 0 <= ai, bi < numCourses + * All the pairs prerequisites[i] are unique. + */ +public class _207 { + + public static class Solution1 { + /* + * Kahn's algorithm for topological sorting + */ + public boolean canFinish(int numCourses, int[][] prerequisites) { + int[] indegree = new int[numCourses]; + for (int[] prereq : prerequisites) { + indegree[prereq[0]]++; + } + Set zeroDegree = new HashSet(); + for (int i = 0; i < numCourses; i++) { + if (indegree[i] == 0) { + zeroDegree.add(i); + } + } + if (zeroDegree.isEmpty()) { + return false; + } + + while (!zeroDegree.isEmpty()) { + Iterator it = zeroDegree.iterator(); + int course = it.next(); + zeroDegree.remove(course); + for (int[] pre : prerequisites) { + if (pre[1] == course) { + indegree[pre[0]]--; + if (indegree[pre[0]] == 0) { + zeroDegree.add(pre[0]); + } + } + } + } + + for (int i : indegree) { + if (i != 0) { + return false; + } + } + return true; + } + } + + public static class Solution2 { + /* + * BFS + */ + public boolean canFinish(int numCourses, int[][] prerequisites) { + int[] indegree = new int[numCourses]; + for (int[] pre : prerequisites) { + indegree[pre[0]]++; + } + Queue queue = new LinkedList(); + for (int i = 0; i < numCourses; i++) { + if (indegree[i] == 0) { + queue.offer(i); + } + } + if (queue.isEmpty()) { + return false; + } + while (!queue.isEmpty()) { + int course = queue.poll(); + for (int[] pre : prerequisites) { + if (pre[1] == course) { + indegree[pre[0]]--; + if (indegree[pre[0]] == 0) { + queue.offer(pre[0]); + } + } + } + } + for (int degree : indegree) { + if (degree != 0) { + return false; + } + } + return true; + } + } + + public static class Solution3 { + /* + * DFS, the fastest method in all, with the help of a cache and also converted edges into adjacency list, + * although theoretically, all these three methods' time complexity are: O(V+E) + */ + public boolean canFinish(int numCourses, int[][] prerequisites) { + List> courseList = new ArrayList<>(); + for (int i = 0; i < numCourses; i++) { + courseList.add(new ArrayList<>()); + } + for (int[] pre : prerequisites) { + courseList.get(pre[1]).add(pre[0]); + } + int[] visited = new int[numCourses]; + // visit each course using DFS + for (int i = 0; i < numCourses; i++) { + if (!dfs(i, courseList, visited)) { + return false; + } + } + return true; + } + + private boolean dfs(int course, List> courseList, int[] visited) { + visited[course] = 1; // mark as temporarily visited + List coursesCanBeTaken = courseList.get(course); + for (int i = 0; i < coursesCanBeTaken.size(); i++) { + int courseToTake = coursesCanBeTaken.get(i); + if (visited[courseToTake] == 1) { + return false; + } + if (visited[courseToTake] == 0) { + if (!dfs(courseToTake, courseList, visited)) { + return false; + } + } + } + visited[course] = 2; // mark it as completely done. + return true; + } + } + + public static class Solution4 { + /* + * This is also Kahn's algorithm, but builds an adjacency list (unncessary for this problem) + * which is often times very helpful in other graph problems, doing it here as a practice: + * it's a very practical template: + * 1. an array of list type to hold all adjacency lists; + * 2. an array of integers to hold indegree for each node; + * 3. several for loops: + * first for-loop: initialize the adjacency list; + * second for-loop: go through all prerequisites to build both adjacency list and indegree array; + * third for-loop: find all nodes that have indegree == zero and add them into the queue; + * 4. start a while loop as long as q is not empty: + * 4.1: poll a node from the q, this node has indegree == zero; + * 4.2: go through all adjacent nodes of this node, decrement ecah of their indegree by one; + * 4.3: if any of their indegree == zero, add that adjacent node into the q + * 5. in the end, all nodes' indegree should be zero, otherwise, it's not a valid topological sortably graph. + */ + public boolean canFinish(int numCourses, int[][] prerequisites) { + List[] adjList = new ArrayList[numCourses]; + for (int i = 0; i < numCourses; i++) { + adjList[i] = new ArrayList<>(); + } + int[] indegree = new int[numCourses]; + for (int[] pre : prerequisites) { + indegree[pre[1]]++; + adjList[pre[0]].add(pre[1]); + } + Queue q = new LinkedList<>(); + for (int i = 0; i < numCourses; i++) { + if (indegree[i] == 0) { + q.offer(i); + } + } + while (!q.isEmpty()) { + Integer curr = q.poll(); + for (int v : adjList[curr]) { + indegree[v]--; + if (indegree[v] == 0) { + q.offer(v); + } + } + } + for (int i : indegree) { + if (i != 0) { + return false; + } + } + return true; + } + } +} + + +================================================== +File: _987.java +Line count: 107 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import com.fishercoder.common.classes.TreeNode; +import java.util.*; + +public class _987 { + public static class Solution1 { + /* + * credit: https://leetcode.com/problems/vertical-order-traversal-of-a-binary-tree/discuss/231148/Java-TreeMap-Solution + */ + public List> verticalTraversal(TreeNode root) { + TreeMap>> map = new TreeMap<>(); + dfs(root, 0, 0, map); + List> list = new ArrayList<>(); + for (TreeMap> yMap : map.values()) { + list.add(new ArrayList<>()); + for (PriorityQueue nodes : yMap.values()) { + while (!nodes.isEmpty()) { + list.get(list.size() - 1).add(nodes.poll()); + } + } + } + return list; + } + + private void dfs( + TreeNode root, + int x, + int y, + TreeMap>> map) { + if (root == null) { + return; + } + if (!map.containsKey(x)) { + map.put(x, new TreeMap<>()); + } + if (!map.get(x).containsKey(y)) { + map.get(x).put(y, new PriorityQueue<>()); + } + map.get(x).get(y).offer(root.val); + dfs(root.left, x - 1, y + 1, map); + dfs(root.right, x + 1, y + 1, map); + } + } + + public static class Solution2 { + + /* + * My completely original solution on 6/13/2024. + */ + public List> verticalTraversal(TreeNode root) { + TreeMap> map = new TreeMap<>(); + Queue q = new LinkedList<>(); + q.offer(new NodeWithCoords(root, 0, 0)); + while (!q.isEmpty()) { + int size = q.size(); + for (int i = 0; i < size; i++) { + NodeWithCoords curr = q.poll(); + int col = curr.col; + int row = curr.row; + List list = map.getOrDefault(col, new ArrayList<>()); + list.add(curr); + map.put(col, list); + if (curr.node.left != null) { + q.offer(new NodeWithCoords(curr.node.left, row + 1, col - 1)); + } + if (curr.node.right != null) { + q.offer(new NodeWithCoords(curr.node.right, row + 1, col + 1)); + } + } + } + List> result = new ArrayList<>(); + for (Integer key : map.keySet()) { + List list = map.get(key); + Collections.sort( + list, + (a, b) -> { + if (a.row != b.row) { + return a.row - b.row; + } else if (a.col != b.col) { + return a.col - b.col; + } else { + return a.node.val - b.node.val; + } + }); + List intList = new ArrayList<>(); + for (NodeWithCoords nodeWithCoords : list) { + intList.add(nodeWithCoords.node.val); + } + result.add(intList); + } + return result; + } + + class NodeWithCoords { + TreeNode node; + int row; + int col; + + public NodeWithCoords(TreeNode node, int row, int col) { + this.node = node; + this.row = row; + this.col = col; + } + } + } +} + + +================================================== +File: _417.java +Line count: 134 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +public class _417 { + public static class Solution1 { + /* + * Credit: looked at this post: https://discuss.leetcode.com/topic/62379/java-bfs-dfs-from-ocean + *

+ * One typical trick to work on 2d grid problems is to go through the border and put proper ones into a queue if using BFS. + */ + public List pacificAtlantic(int[][] matrix) { + + List result = new ArrayList(); + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { + return result; + } + + int m = matrix.length; + int n = matrix[0].length; + boolean[][] pacific = new boolean[m][n]; + boolean[][] atlantic = new boolean[m][n]; + + for (int i = 0; i < m; i++) { + dfs(matrix, pacific, Integer.MIN_VALUE, i, 0); + dfs(matrix, atlantic, Integer.MIN_VALUE, i, n - 1); + } + + for (int i = 0; i < n; i++) { + dfs(matrix, pacific, Integer.MIN_VALUE, 0, i); + dfs(matrix, atlantic, Integer.MIN_VALUE, m - 1, i); + } + + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (pacific[i][j] && atlantic[i][j]) { + result.add(new int[] {i, j}); + } + } + } + + return result; + } + + void dfs(int[][] matrix, boolean[][] visited, int height, int x, int y) { + int m = matrix.length; + int n = matrix[0].length; + if (x < 0 || y < 0 || x >= m || y >= n || matrix[x][y] < height || visited[x][y]) { + return; + } + visited[x][y] = true; + dfs(matrix, visited, matrix[x][y], x + 1, y); + dfs(matrix, visited, matrix[x][y], x, y + 1); + dfs(matrix, visited, matrix[x][y], x - 1, y); + dfs(matrix, visited, matrix[x][y], x, y - 1); + } + } + + public static class Solution2 { + /*This is my original solution on 3/25/2021, although brute force, it works.*/ + public List> pacificAtlantic(int[][] matrix) { + List> result = new ArrayList<>(); + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { + return result; + } + int m = matrix.length; + int n = matrix[0].length; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + boolean[] touchesPacificAndAtlantic = new boolean[2]; + update(i, j, m, n, touchesPacificAndAtlantic); + Queue queue = new LinkedList<>(); + boolean[][] visited = new boolean[m][n]; + visited[i][j] = true; + queue.offer(new int[] {i, j}); + if (bfs(matrix, m, n, touchesPacificAndAtlantic, queue, visited)) { + result.add(new ArrayList<>(Arrays.asList(i, j))); + } + } + } + return result; + } + + private void update(int i, int j, int m, int n, boolean[] touchesPacificAndAtlantic) { + if (i == 0 || j == 0) { + touchesPacificAndAtlantic[0] = true; + } + if (i == m - 1 || j == n - 1) { + touchesPacificAndAtlantic[1] = true; + } + } + + private boolean bfs( + int[][] matrix, + int m, + int n, + boolean[] touchesPacificAndAtlantic, + Queue queue, + boolean[][] visited) { + if (touchesPacificAndAtlantic[0] && touchesPacificAndAtlantic[1]) { + return true; + } + int[] directions = new int[] {0, 1, 0, -1, 0}; + while (!queue.isEmpty()) { + int size = queue.size(); + for (int k = 0; k < size; k++) { + int[] curr = queue.poll(); + for (int p = 0; p < directions.length - 1; p++) { + int newx = curr[0] + directions[p]; + int newy = curr[1] + directions[p + 1]; + if (newx >= 0 + && newx < m + && newy >= 0 + && newy < n + && matrix[newx][newy] <= matrix[curr[0]][curr[1]] + && !visited[newx][newy]) { + visited[newx][newy] = true; + queue.offer(new int[] {newx, newy}); + update(newx, newy, m, n, touchesPacificAndAtlantic); + if (touchesPacificAndAtlantic[0] && touchesPacificAndAtlantic[1]) { + return true; + } + } + } + } + } + return false; + } + } +} + + +================================================== +File: _460.java +Line count: 101 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.HashMap; +import java.util.LinkedHashSet; + +public class _460 { + public static class Solution1 { + /* + * Wikipedia: The simplest method to employ an LFU algorithm is to assign a counter to every + * block that is loaded into the cache. Each time a reference is made to that block the counter + * is increased by one. When the cache reaches capacity and has a new block waiting to be + * inserted the system will search for the block with the lowest counter and remove it from the + * cache. + * Policy to handle frequency ties: based on timestamp, the entries that get set into cache earlier will be evicted first. + */ + + public static class LFUCache { + /* + * credit: https://discuss.leetcode.com/topic/69737/java-o-1-very-easy-solution-using-3-hashmaps-and-linkedhashset/2 + */ + + HashMap keyToValue; + /* + * key is the key, value is the value + */ + HashMap keyToCount; + /* + * key is the key, value if the count of the key/value pair + */ + HashMap> countToLRUKeys; + /* + * key is count, value is a set of keys that have the same key, but keeps insertion order + */ + int cap; + int minimumCount; + + public LFUCache(int capacity) { + this.minimumCount = -1; + this.cap = capacity; + this.keyToValue = new HashMap<>(); + this.keyToCount = new HashMap<>(); + this.countToLRUKeys = new HashMap<>(); + this.countToLRUKeys.put(1, new LinkedHashSet<>()); + } + + public int get(int key) { + if (!keyToValue.containsKey(key)) { + return -1; + } + int count = keyToCount.get(key); + keyToCount.put(key, count + 1); + countToLRUKeys.get(count).remove(key); + + if (count == minimumCount && countToLRUKeys.get(count).size() == 0) { + /*This means this key's count equals to current minimumCount + * AND + * this count doesn't have any entries in the cache. + * So, we'll increment minimumCount by 1 to get the next LFU cache entry + * when we need to evict.*/ + minimumCount++; + } + + if (!countToLRUKeys.containsKey(count + 1)) { + countToLRUKeys.put(count + 1, new LinkedHashSet<>()); + } + countToLRUKeys.get(count + 1).add(key); + + return keyToValue.get(key); + } + + public void put(int key, int value) { + if (cap <= 0) { + return; + } + + if (keyToValue.containsKey(key)) { + /*If the key is already in the cache, we can simply overwrite this entry; + * then call get(key) which will do the update work.*/ + keyToValue.put(key, value); + get(key); + return; + } + + /*If the key is not in the cache, we'll check the size first, evict the LFU entry first, + * then insert this one into cache.*/ + if (keyToValue.size() >= cap) { + int evit = countToLRUKeys.get(minimumCount).iterator().next(); + countToLRUKeys.get(minimumCount).remove(evit); + keyToValue.remove(evit); + } + keyToValue.put(key, value); + keyToCount.put(key, 1); + countToLRUKeys + .get(1) + .add( + key); /*Because we put this key/value into cache for the first time, so its count is 1*/ + minimumCount = 1; /*For the same above reason, minimumCount is of course 1*/ + } + } + } +} + + +================================================== +File: _25.java +Line count: 141 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import com.fishercoder.common.classes.ListNode; + +public class _25 { + + /* + * We use recursion to go all the way until the end: when the number of nodes are smaller than k; + * then we start to reverse each group of k nodes from the end towards the start. + */ + public static class Solution1 { + public ListNode reverseKGroup(ListNode head, int k) { + ListNode curr = head; + int count = 0; + while (curr != null && count != k) { + // find the k+1 node + curr = curr.next; + count++; + } + + if (count == k) { + /*after this below recursive call finishes, it'll return head; + * then this returned "head" will become "curr", while the head + * in its previous callstack is the real head after this call. + * Setting up a break point will make all of this crystal clear.*/ + curr = reverseKGroup(curr, k); + + while (count-- > 0) { + ListNode temp = head.next; + head.next = curr; + curr = head; + head = temp; + } + head = curr; + } + return head; // we run out of nodes before we hit count == k, so we'll just directly + // return head in this case as well + } + } + + public static class Solution2 { + public ListNode reverseKGroup(ListNode head, int k) { + if (head == null || head.next == null || k == 1) { + return head; + } + + int n = 0; // number of nodes + + ListNode curr = head; + while (curr != null) { + n++; + curr = curr.next; + } + + ListNode prev = null; + ListNode next = null; + ListNode newHead = null; + ListNode tail1 = null; + ListNode tail2 = head; + + curr = head; + + while (n >= k) { + // reverse nodes in blocks of k + for (int i = 0; i < k; i++) { + // linked List reversal code + next = curr.next; + curr.next = prev; + prev = curr; + curr = next; + } + if (newHead == null) { + newHead = prev; + } + if (tail1 != null) { + tail1.next = prev; + } + tail2.next = curr; // when n is not multiple of k + tail1 = tail2; + tail2 = curr; + prev = null; + n -= k; + } + return newHead; + } + } + + public static class Solution3 { + /* + * My completely original solution on 10/25/2021. Beats 100% submissions on LeetCode in runtime. + * Again, using a pen and paper to visualize the process helps a lot! + * My helper function returns two nodes: reversed node head and the starting node for the next reversal. + */ + public ListNode reverseKGroup(ListNode head, int k) { + ListNode pre = new ListNode(-1); + pre.next = head; + ListNode tmp = pre; + ListNode curr = head; + ListNode[] result = new ListNode[] {null, curr}; + do { + result = reverseKGroupHelper(result[1], k); + if (result[0] == result[1]) { + tmp.next = result[0]; + return pre.next; + } else { + tmp.next = result[0]; + while (tmp.next != null) { + tmp = tmp.next; + } + } + } while (true); + } + + private ListNode[] reverseKGroupHelper(ListNode head, int k) { + int originalK = k; + ListNode tmp = head; + while (tmp != null) { + tmp = tmp.next; + k--; + } + if (k > 0) { + return new ListNode[] {head, head}; + } else { + tmp = head; + k = originalK; + ListNode prev = null; + while (tmp != null) { + ListNode next = tmp.next; + tmp.next = prev; + prev = tmp; + tmp = next; + k--; + if (k == 0) { + return new ListNode[] {prev, tmp}; + } + } + return null; + } + } + } +} + + +================================================== +File: _48.java +Line count: 123 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import com.fishercoder.common.utils.CommonUtils; + +public class _48 { + + /* + * Note: this is an n*n matrix, in other words, it's a square, this makes it easier as well. + */ + + public static class Solution1 { + // Time: O(n^2) + // Space: O(1) + public void rotate(int[][] matrix) { + /*First swap the elements on the diagonal, then reverse each row: + * 1, 2, 3 1, 4, 7 7, 4, 1 + * 4, 5, 6 becomes 2, 5, 8 becomes 8, 5, 2 + * 7, 8, 9 3, 6, 9 9, 6, 3 + **/ + int m = matrix.length; + for (int i = 0; i < m; i++) { + for (int j = i; j < m; j++) { + /*ATTN: j starts from i, so that the diagonal changes with itself, results in no change.*/ + int tmp = matrix[i][j]; + matrix[i][j] = matrix[j][i]; + matrix[j][i] = tmp; + } + } + CommonUtils.print2DIntArray(matrix); + + /*then reverse*/ + for (int i = 0; i < m; i++) { + int left = 0; + int right = m - 1; + while (left < right) { + int tmp = matrix[i][left]; + matrix[i][left] = matrix[i][right]; + matrix[i][right] = tmp; + left++; + right--; + } + } + } + } + + public static class Solution2 { + /* + * First swap the rows bottom up, then swap the element on the diagonal: + *

+ * 1, 2, 3 7, 8, 9 7, 4, 1 + * 4, 5, 6 becomes 4, 5, 6 becomes 8, 5, 2 + * 7, 8, 9 1, 2, 3 9, 6, 3 + *

+ * Time: O(n^2) + * Space: O(1) + */ + public void rotate(int[][] matrix) { + int m = matrix.length; + int n = matrix[0].length; + int top = 0; + int bottom = n - 1; + while (top < bottom) { + int[] tmp = matrix[top]; + matrix[top] = matrix[bottom]; + matrix[bottom] = tmp; + top++; + bottom--; + } + + for (int i = 0; i < m; i++) { + for (int j = i + 1; j < n; j++) { + int tmp = matrix[i][j]; + matrix[i][j] = matrix[j][i]; + matrix[j][i] = tmp; + } + } + } + } + + public static class Solution3 { + /* + * You only need to rotate the top right quarter, + * with this example: + * {1, 2, 3, 4}, + * {5, 6, 7, 8}, + * {9, 10, 11, 12}, + * {13, 14, 15, 16} + * + * top will only be: + * 1, 2, 3, + * 6 + * + * then this entire matrix is rotated. As they'll drive to ratate the corresponding three elements in the matrix. + * + * Another cool trick that this solution takes advantage is that because it's a square matrix, + * meaning number of rows equals to the number of columns, we could do swap like this, + * if it's a rectangular, below method won't work and will throw ArrayIndexOutOfBoundsException. + * + */ + public void rotate(int[][] matrix) { + int n = matrix.length; + for (int i = 0; i < n / 2; i++) { + for (int j = i; j < n - i - 1; j++) { + // save the top left + int top = matrix[i][j]; + System.out.println("top = " + top); + + // move bottom left to top left + matrix[i][j] = matrix[n - 1 - j][i]; + + // move bottom right to bottom left + matrix[n - 1 - j][i] = matrix[n - i - 1][n - 1 - j]; + + // move top right to bottom right + matrix[n - i - 1][n - 1 - j] = matrix[j][n - i - 1]; + + // move top left to top right + matrix[j][n - i - 1] = top; + } + } + } + } +} + + +================================================== +File: _289.java +Line count: 120 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +public class _289 { + public static class Solution1 { + /* + * Time: O(m*n) + * Space: O(m*n) + */ + public void gameOfLife(int[][] board) { + int height = board.length; + int width = board[0].length; + int[][] next = new int[height][width]; + int[][] directions = { + {-1, -1}, {-1, 0}, {-1, 1}, {0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1} + }; + + for (int i = 0; i < board.length; i++) { + for (int j = 0; j < board[0].length; j++) { + int liveCellsCount = 0; + // count all its live cells + + for (int[] dir : directions) { + int x = i + dir[0]; + int y = j + dir[1]; + if (x >= 0 && y >= 0 && x < height && y < width && board[x][y] == 1) { + liveCellsCount++; + } + } + + if (board[i][j] == 1) { + if (liveCellsCount <= 3 && liveCellsCount >= 2) { + next[i][j] = 1; + } + } else if (board[i][j] == 0) { + if (liveCellsCount == 3) { + next[i][j] = 1; + } + } + } + } + + for (int i = 0; i < board.length; i++) { + for (int j = 0; j < board[0].length; j++) { + board[i][j] = next[i][j]; + } + } + } + } + + public static class Solution2 { + /* + * Time: O(m*n) + * Space: O(1) + *

+ * we use different numbers to represent its previous state so that we can change in place without losing track of its previous state + * we define: + * live to dead is 1 -> -1 + * dead to live is 0 -> 2 + * live to live is 1 -> 1 + *

+ * Credit: Solution 2 from https://leetcode.com/problems/game-of-life/solution/ + */ + public void gameOfLife(int[][] board) { + // Neighbors array to find 8 neighboring cells for a given cell + int[] neighbors = {0, 1, -1}; + + int rows = board.length; + int cols = board[0].length; + + // Iterate through board cell by cell. + for (int row = 0; row < rows; row++) { + for (int col = 0; col < cols; col++) { + + // For each cell count the number of live neighbors. + int liveNeighbors = 0; + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + + if (!(neighbors[i] == 0 && neighbors[j] == 0)) { + int r = (row + neighbors[i]); + int c = (col + neighbors[j]); + + // Check the validity of the neighboring cell. + // and whether it was originally a live cell. + if ((r < rows && r >= 0) + && (c < cols && c >= 0) + && (Math.abs(board[r][c]) == 1)) { + liveNeighbors += 1; + } + } + } + } + + // Rule 1 or Rule 3 + if ((board[row][col] == 1) && (liveNeighbors < 2 || liveNeighbors > 3)) { + // -1 signifies the cell is now dead but originally was live. + board[row][col] = -1; + } + // Rule 4 + if (board[row][col] == 0 && liveNeighbors == 3) { + // 2 signifies the cell is now live but was originally dead. + board[row][col] = 2; + } + } + } + + // Get the final representation for the newly updated board. + for (int row = 0; row < rows; row++) { + for (int col = 0; col < cols; col++) { + if (board[row][col] > 0) { + board[row][col] = 1; + } else { + board[row][col] = 0; + } + } + } + } + } +} + + +================================================== +File: _698.java +Line count: 106 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.Arrays; + +public class _698 { + + public static class Solution1 { + public boolean canPartitionKSubsets(int[] nums, int k) { + long sum = 0; + for (int num : nums) { + sum += num; + } + if (sum % k != 0) { + return false; + } + int equalSum = (int) (sum / k); + boolean[] visited = new boolean[nums.length]; + return canPartition(nums, visited, 0, k, 0, 0, equalSum); + } + + private boolean canPartition( + int[] nums, + boolean[] visited, + int startIndex, + int k, + int currSum, + int currNum, + int target) { + if (k == 1) { + return true; + } + if (currSum == target && currNum > 0) { + /*Everytime when we get currSum == target, we'll start from index 0 and look up the numbers that are not used yet + * and try to find another sum that could equal to target*/ + return canPartition(nums, visited, 0, k - 1, 0, 0, target); + } + for (int i = startIndex; i < nums.length; i++) { + if (!visited[i]) { + visited[i] = true; + if (canPartition( + nums, visited, i + 1, k, currSum + nums[i], currNum++, target)) { + return true; + } + visited[i] = false; + } + } + return false; + } + } + + public static class Solution2 { + /* + * I'm glad that I figured out below solution completely on my own on 9/30/2021. + * Backtracking is so beautiful! + *

+ * Although not super concise, and I've added a sorting step, it's completely original. + */ + public boolean canPartitionKSubsets(int[] nums, int k) { + Arrays.sort(nums); + long sum = 0L; + for (int num : nums) { + sum += num; + } + int ave = (int) sum / k; + if (sum % k != 0) { + return false; + } + boolean[] used = new boolean[nums.length]; + int found = 0; + for (int i = nums.length - 1; i >= 0; i--) { + if (!used[i]) { + used[i] = true; + found += recursive(nums, used, ave, nums[i], i); + } + } + return k == found; + } + + private int recursive( + int[] nums, boolean[] used, int targetSum, int currSum, int currIndex) { + if (currSum == targetSum) { + return 1; + } else if (currSum > targetSum) { + used[currIndex] = false; + return 0; + } else { + if (currIndex > 0) { + for (int i = currIndex; i > 0; i--) { + if (!used[i - 1]) { + used[i - 1] = true; + int found = + recursive(nums, used, targetSum, currSum + nums[i - 1], i - 1); + if (found == 1) { + return found; + } + used[i - 1] = + false; // this is the backtracking step: reset this number to be + // available if not found + } + } + } + return 0; + } + } + } +} + + +================================================== +File: _55.java +Line count: 103 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +public class _55 { + + public static class Solution1 { + /* + * My very original but lengthy solution. + */ + public boolean canJump(int[] nums) { + int furthestReach = nums[0]; + if (furthestReach >= nums.length - 1) { + return true; + } + int i = 1; + for (; i < nums.length; ) { + int newFurthestReach = -1; + while (i <= furthestReach) { + newFurthestReach = Math.max(newFurthestReach, nums[i] + i); + if (newFurthestReach >= nums.length) { + return true; + } + i++; + } + if (newFurthestReach <= furthestReach) { + return false; + } else if (newFurthestReach >= nums.length - 1) { + return true; + } else { + furthestReach = newFurthestReach; + } + } + return false; + } + } + + public static class Solution2 { + /* + * The same idea as mine above, but much more concise. + * Credit: https://leetcode.com/problems/jump-game/discuss/20917/Linear-and-simple-solution-in-C%2B%2B + */ + public boolean canJump(int[] nums) { + int i = 0; + for (int reach = 0; i < nums.length && i <= reach; i++) { + reach = Math.max(reach, nums[i] + i); + } + return i >= nums.length; + } + } + + public static class Solution3 { + /* + * Top-down DP. + * Credit: https://leetcode.com/problems/jump-game/solution/ approach 2 + *

+ * Specifically, for this problem, my very own Solution1 and the above Solution2 run much faster than this DP solution, likely due to this is top-down, there's stack overhead. + * But just use this problem to practice DP. + *

+ * The reason it's called top-down is that it's filling the dp array from the right to the left if you set break points and step through this. + */ + public boolean canJump(int[] nums) { + int[] dp = new int[nums.length]; + // 0 means unknown, 1 means reachable, 2 means unreachable + dp[nums.length - 1] = 1; + return canJumpFrom(0, nums, dp); + } + + private boolean canJumpFrom(int index, int[] nums, int[] dp) { + if (dp[index] != 0) { + return dp[index] == 1; + } + int furthestReach = Math.min(index + nums[index], nums.length - 1); + for (int i = index + 1; i <= furthestReach; i++) { + if (canJumpFrom(i, nums, dp)) { + dp[i] = 1; + return true; + } + } + dp[index] = 2; + return false; + } + } + + public static class Solution4 { + /* + * This is bottom-up DP. + */ + public boolean canJump(int[] nums) { + int[] dp = new int[nums.length]; + // 0 means unknown, 1 means reachable, 2 means unreachable + dp[nums.length - 1] = 1; + for (int i = nums.length - 2; i >= 0; i--) { + int furthestReach = Math.min(nums[i] + i, nums.length - 1); + for (int j = i + 1; j <= furthestReach; j++) { + if (dp[j] == 1) { + dp[i] = 1; + break; + } + } + } + return dp[0] == 1; + } + } +} + + +================================================== +File: _43.java +Line count: 125 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +public class _43 { + + public static class Solution1 { + /* + * Inspired by https://discuss.leetcode.com/topic/30508/easiest-java-solution-with-graph-explanation + * Basically, the rule we can find is that products of each two digits will land in this position in the final product: + * i+j and i+j+1 + */ + public String multiply(String num1, String num2) { + if (isZero(num1) || isZero(num2)) { + return "0"; + } + int[] a1 = new int[num1.length()]; + int[] a2 = new int[num2.length()]; + int[] product = new int[num1.length() + num2.length()]; + + for (int i = a1.length - 1; i >= 0; i--) { + for (int j = a2.length - 1; j >= 0; j--) { + int thisProduct = + Character.getNumericValue(num1.charAt(i)) + * Character.getNumericValue(num2.charAt(j)); + product[i + j + 1] += thisProduct % 10; + if (product[i + j + 1] >= 10) { + product[i + j + 1] %= 10; + product[i + j]++; + } + product[i + j] += thisProduct / 10; + if (product[i + j] >= 10) { + product[i + j] %= 10; + product[i + j - 1]++; + } + } + } + + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < product.length; i++) { + if (i == 0 && product[i] == 0) { + continue; + } + stringBuilder.append(product[i]); + } + return stringBuilder.toString(); + } + + private boolean isZero(String num) { + for (char c : num.toCharArray()) { + if (c != '0') { + return false; + } + } + return true; + } + } + + public static class Solution2 { + /* + * My completely original solution on 10/14/2021. + * + * Gist: just use string instead of integers for times variable, otherwise guaranteed to overflow/underflow! + * Also: using a pen and paper to visualize how this works out helps a great deal! + */ + public String multiply(String num1, String num2) { + String previous = ""; + String j = ""; + for (int i = num2.length() - 1; i >= 0; i--, j += "0") { + String intermediate = + multiplyBySingleDigit(num1, Character.getNumericValue(num2.charAt(i)), j); + String result = add(intermediate, previous); + previous = result; + } + int i = 0; + for (; i < previous.length(); i++) { + if (previous.charAt(i) != '0') { + break; + } + } + return i == previous.length() ? "0" : previous.substring(i); + } + + private String add(String num1, String num2) { + int i = num1.length() - 1; + int j = num2.length() - 1; + int carry = 0; + StringBuilder sb = new StringBuilder(); + while (i >= 0 || j >= 0) { + int sum = carry; + if (i >= 0) { + sum += Character.getNumericValue(num1.charAt(i)); + } + if (j >= 0) { + sum += Character.getNumericValue(num2.charAt(j)); + } + sb.append(sum % 10); + carry = sum / 10; + i--; + j--; + } + if (carry > 0) { + sb.append(carry); + } + return sb.reverse().toString(); + } + + private String multiplyBySingleDigit(String num, int multiplier, String times) { + if (multiplier == 0) { + return "0"; + } + StringBuilder sb = new StringBuilder(); + int carry = 0; + for (int i = num.length() - 1; i >= 0; i--) { + int val = Character.getNumericValue(num.charAt(i)); + int product = val * multiplier; + product += carry; + sb.append(product % 10); + carry = product / 10; + } + if (carry > 0) { + sb.append(carry); + } + return sb.reverse() + times; + } + } +} + + +================================================== +File: _685.java +Line count: 196 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class _685 { + public static class Solution1 { + /* + * My original solution, failed by _685Test.test3 + */ + class UnionFind { + int[] ids; + Set nodes; + int[][] edges; + List visitedLinkedNodes; + Set visitedValues; + int[] redundantConn; + int m; + int n; + + class LinkedNode { + List parents; // at most, there's one node that has two parents + int val; + + public LinkedNode(int val) { + this.val = val; + } + + public LinkedNode(int val, LinkedNode parent) { + if (parents == null) { + parents = new ArrayList<>(); + } + this.parents.add(parent); + this.val = val; + } + + public void addParent(LinkedNode parent) { + if (parents == null) { + parents = new ArrayList<>(); + } + this.parents.add(parent); + } + } + + public UnionFind(int[][] edges) { + this.edges = edges; + m = edges.length; + n = edges[0].length; + nodes = new HashSet<>(); + visitedLinkedNodes = new ArrayList<>(); + visitedValues = new HashSet<>(); + redundantConn = new int[] {}; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + nodes.add(edges[i][j]); + } + } + ids = new int[nodes.size()]; + for (int i = 0; i < ids.length; i++) { + ids[i] = i + 1; + } + } + + public void union(int[] edge) { + if (!visitedValues.contains(edge[1])) { + LinkedNode parent = new LinkedNode(edge[0]); + LinkedNode node = new LinkedNode(edge[1], parent); + visitedLinkedNodes.add(node); + visitedValues.add(edge[1]); + } else { + for (int i = 0; i < visitedLinkedNodes.size(); i++) { + LinkedNode node = visitedLinkedNodes.get(i); + if (node.val == edge[1]) { + node.addParent(new LinkedNode(edge[0])); + } + } + } + } + + public int[] findRedundantDirectedConnection() { + int index = hasTwoParents(); + if (index != -1) { + List parents = + visitedLinkedNodes.get(index).parents; // parents size is fixed, only 2 + int[] result = new int[2]; + for (int i = 0; i < parents.size(); i++) { + if (hasCycle(visitedLinkedNodes.get(index), parents.get(i))) { + result = + new int[] { + parents.get(i).val, visitedLinkedNodes.get(index).val + }; + break; + } + } + return result; + } else { + return edges[m - 1]; + } + } + + private boolean hasCycle(LinkedNode linkedNode, LinkedNode parent) { + Set visited = new HashSet<>(); + visited.add(linkedNode.val); + while (parent != null) { + if (visited.contains(parent.val)) { + return true; + } + visited.add(parent.val); + parent = findParent(parent.val); + } + return false; + } + + private LinkedNode findParent(int val) { + for (int i = 0; i < visitedLinkedNodes.size(); i++) { + if (visitedLinkedNodes.get(i).val == val) { + return visitedLinkedNodes.get(i).parents.get(0); + } + } + return null; + } + + private int hasTwoParents() { + for (int i = 0; i < visitedLinkedNodes.size(); i++) { + if (visitedLinkedNodes.get(i).parents.size() > 1) { + return i; + } + } + return -1; + } + } + + public int[] findRedundantDirectedConnection(int[][] edges) { + UnionFind unionFind = new UnionFind(edges); + /*two cases: + * 1. the entire edges are just one directed loop, in this case, just return the last edge, see test2 in _685Test.java + * 2. there's one directed loop, but one node of the loop has two parents, in this case, what we'll need to do + * is just to return the edge in this loop that points to the child that has two parents, see test1 in _685Test.java + * also, in case 2, use the id of the node that has two parents as the id for all nodes in this loop, this way, I could know which of its + * two parents is in the loop and should be the redundant one to return. + * */ + for (int[] edge : edges) { + unionFind.union(edge); + } + return unionFind.findRedundantDirectedConnection(); + } + } + + public static class Solution2 { + /* + * credit: https://discuss.leetcode.com/topic/105108/c-java-union-find-with-explanation-o-n + */ + public int[] findRedundantDirectedConnection(int[][] edges) { + int[] can1 = {-1, -1}; + int[] can2 = {-1, -1}; + int[] parent = new int[edges.length + 1]; + for (int i = 0; i < edges.length; i++) { + if (parent[edges[i][1]] == 0) { + parent[edges[i][1]] = edges[i][0]; + } else { + can2 = new int[] {edges[i][0], edges[i][1]}; + can1 = new int[] {parent[edges[i][1]], edges[i][1]}; + edges[i][1] = 0; + } + } + for (int i = 0; i < edges.length; i++) { + parent[i] = i; + } + for (int i = 0; i < edges.length; i++) { + if (edges[i][1] == 0) { + continue; + } + int child = edges[i][1]; + int father = edges[i][0]; + if (root(parent, father) == child) { + if (can1[0] == -1) { + return edges[i]; + } + return can1; + } + parent[child] = father; + } + return can2; + } + + int root(int[] parent, int i) { + while (i != parent[i]) { + parent[i] = parent[parent[i]]; + i = parent[i]; + } + return i; + } + } +} + + +================================================== +File: _353.java +Line count: 106 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.Deque; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; + +public class _353 { + public class SnakeGame { + private Set + set; // Use a set to hold all occupied points for the snake body, this is for easy + // access for the case of eating its own body + private Deque + body; // use a queue to hold all occupied points for the snake body as well, this is + // for easy access to update the tail + int[][] food; + int score; + int foodIndex; + int width; + int height; + + /* + * Initialize your data structure here. + * + * @param width - screen width + * @param height - screen height + * @param food - A list of food positions + * E.g food = [[1,1], [1,0]] means the first food is positioned at [1,1], the second is at [1,0]. + */ + public SnakeGame(int width, int height, int[][] food) { + this.set = new HashSet(); + set.add(0); // initially at [0][0] + this.body = new LinkedList(); + body.offerLast(0); + this.food = food; + this.width = width; + this.height = height; + } + + /* + * Moves the snake. + * + * @param direction - 'U' = Up, 'L' = Left, 'R' = Right, 'D' = Down + * @return The game's score after the move. Return -1 if game over. + * Game over when snake crosses the screen boundary or bites its body. + */ + public int move(String direction) { + if (score == -1) { + return -1; + } + + // compute head + int rowHead = body.peekFirst() / width; + int colHead = body.peekFirst() % width; + switch (direction) { + case "U": + rowHead--; + break; + case "D": + rowHead++; + break; + case "L": + colHead--; + break; + default: + colHead++; + } + int newHead = rowHead * width + colHead; + + set.remove(body.peekLast()); // we'll remove the tail from set for now to see if it + // hits its tail + // if it hits the boundary + if (set.contains(newHead) + || rowHead < 0 + || colHead < 0 + || rowHead >= height + || colHead >= width) { + return score = -1; + } + + // add head for the following two normal cases: + set.add(newHead); + body.offerFirst(newHead); + + // normal eat case: keep tail, add head + if (foodIndex < food.length + && rowHead == food[foodIndex][0] + && colHead == food[foodIndex][1]) { + set.add(body.peekLast()); // old tail does not change, so add it back to set + // since we removed it earlier + foodIndex++; + return ++score; + } + + // normal move case without eating: move head and remove tail + body.pollLast(); + return score; + } + } + + /* + * Your SnakeGame object will be instantiated and called as such: + * SnakeGame obj = new SnakeGame(width, height, food); + * int param_1 = obj.move(direction); + */ +} + + +================================================== +File: _200.java +Line count: 114 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +public class _200 { + + public static class Solution1 { + + /* + * DFS solution, note: this modifies the input. + */ + public int numIslands(char[][] grid) { + if (grid == null || grid.length == 0) { + return 0; + } + int count = 0; + int m = grid.length; + int n = grid[0].length; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (grid[i][j] == '1') { + count++; + dfs(grid, i, j, m, n); + } + } + } + return count; + } + + void dfs(char[][] grid, int i, int j, int m, int n) { + if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == '0') { + return; + } + grid[i][j] = '0'; + dfs(grid, i + 1, j, m, n); + dfs(grid, i, j + 1, m, n); + dfs(grid, i - 1, j, m, n); + dfs(grid, i, j - 1, m, n); + } + } + + public static class Solution2 { + + class UnionFind { + int count; + int m; + int n; + int[] ids; + + public UnionFind(char[][] grid) { + m = grid.length; + n = grid[0].length; + ids = new int[m * n]; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (grid[i][j] == '1') { + // at initialization, we count each '1' as one island, later, during + // traversal, we'll union them during which we'll dedup the number of + // islands. + count++; + } + ids[i * n + j] = i * n + j; + } + } + } + + public void union(int i, int j) { + int x = find(i); + int y = find(j); + if (x != y) { + /* + * This means when these two nodes should be unioned, however, so far, + * they have not, i.e. they have different ids, + * so we'll have to unify them by assigning one's ids to the other, or vice versa. + * */ + ids[x] = y; // ids[y] = x; //also works + count--; // since now these two islands are unified/merged, we'll decrement the + // count by one + } + } + + public int find(int i) { + if (i != ids[i]) { + ids[i] = find((ids[i])); + } + return ids[i]; + } + } + + public int numIslands(char[][] grid) { + if (grid == null || grid.length == 0 || grid[0].length == 0) { + return 0; + } + int[] dirs = new int[] {0, 1, 0, -1, 0}; + UnionFind uf = new UnionFind(grid); + int m = grid.length; + int n = grid[0].length; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (grid[i][j] == '1') { + for (int k = 0; k < 4; k++) { + int x = i + dirs[k]; + int y = j + dirs[k + 1]; + if (x >= 0 && x < m && y >= 0 && y < n && grid[x][y] == '1') { + int id1 = i * n + j; + int id2 = x * n + y; + uf.union(id1, id2); + } + } + } + } + } + return uf.count; + } + } +} + + +================================================== +File: _79.java +Line count: 158 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +public class _79 { + + public static class Solution1 { + // credit: https://discuss.leetcode.com/topic/21142/my-java-solution + + boolean[][] visited; + + public boolean exist(char[][] board, String word) { + int m = board.length; + int n = board[0].length; + visited = new boolean[m][n]; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (word.charAt(0) == board[i][j] && search(board, word, i, j, 0)) { + return true; + } + } + } + return false; + } + + boolean search(char[][] board, String word, int i, int j, int pos) { + if (pos == word.length()) { + return true; + } + if (i < 0 + || j < 0 + || i >= board.length + || j >= board[0].length + || word.charAt(pos) != board[i][j] + || visited[i][j]) { + return false; + } + visited[i][j] = true; + if (search(board, word, i + 1, j, pos + 1) + || search(board, word, i - 1, j, pos + 1) + || search(board, word, i, j + 1, pos + 1) + || search(board, word, i, j - 1, pos + 1)) { + return true; + } + + visited[i][j] = false; + return false; + } + } + + // O(1) space solution + public static class Solution2 { + public boolean exist(char[][] board, String word) { + // do DFS traversal + int row = board.length; + int col = board[0].length; + + for (int i = 0; i < row; i++) { + for (int j = 0; j < col; j++) { + if (board[i][j] == word.charAt(0) && search(board, i, j, word, 0) == true) { + return true; + } + } + } + return false; + } + + private boolean search(char[][] board, int i, int j, String word, int index) { + if (index == word.length() - 1) { + return true; + } + + // store the visited char in a temp variable + char temp = board[i][j]; + board[i][j] = ' '; + if (i > 0 + && board[i - 1][j] == word.charAt(index + 1) + && search(board, i - 1, j, word, index + 1) == true) { + return true; + } + if (i < board.length - 1 + && board[i + 1][j] == word.charAt(index + 1) + && search(board, i + 1, j, word, index + 1) == true) { + return true; + } + + if (j > 0 + && board[i][j - 1] == word.charAt(index + 1) + && search(board, i, j - 1, word, index + 1) == true) { + return true; + } + + if (j < board[0].length - 1 + && board[i][j + 1] == word.charAt(index + 1) + && search(board, i, j + 1, word, index + 1) == true) { + return true; + } + + board[i][j] = temp; + return false; + } + } + + public static class Solution3 { + /* + * I came up with below solution completely independently on 10/7/2021, although space complexity is O(m*n) instead of constant. + */ + public boolean exist(char[][] board, String word) { + int m = board.length; + int n = board[0].length; + boolean[][] visited = new boolean[m][n]; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (board[i][j] == word.charAt(0)) { + visited[i][j] = true; + if (existByDfs(board, i, j, word.substring(1), visited, m, n)) { + return true; + } + // backtracking + visited[i][j] = false; + } + } + } + return false; + } + + int[] directions = new int[] {0, 1, 0, -1, 0}; + + private boolean existByDfs( + char[][] board, + int startI, + int startJ, + String word, + boolean[][] visited, + int m, + int n) { + if (word.equals("")) { + return true; + } + for (int i = 0; i < directions.length - 1; i++) { + int nextX = startI + directions[i]; + int nextY = startJ + directions[i + 1]; + if (nextX >= 0 + && nextX < m + && nextY >= 0 + && nextY < n + && !visited[nextX][nextY] + && board[nextX][nextY] == word.charAt(0)) { + visited[nextX][nextY] = true; + if (existByDfs(board, nextX, nextY, word.substring(1), visited, m, n)) { + return true; + } + // backtracking + visited[nextX][nextY] = false; + } + } + return false; + } + } +} + + +================================================== +File: _735.java +Line count: 163 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.Deque; +import java.util.LinkedList; + +public class _735 { + public static class Solution1 { + public int[] asteroidCollision(int[] asteroids) { + Deque stack = new LinkedList<>(); + for (int i = 0; i < asteroids.length; i++) { + if (!stack.isEmpty() && stack.peek() > 0 && asteroids[i] < 0) { + if (Math.abs(stack.peek()) < Math.abs(asteroids[i])) { + stack.pop(); + stack.push(asteroids[i]); + collide(stack); + } else if (Math.abs(stack.peek()) == Math.abs(asteroids[i])) { + stack.pop(); + } + } else { + stack.push(asteroids[i]); + } + } + int[] result = new int[stack.size()]; + int i = stack.size(); + while (!stack.isEmpty()) { + result[--i] = stack.pop(); + } + return result; + } + + private void collide(Deque stack) { + do { + Integer top = stack.pop(); + if (!stack.isEmpty() && stack.peek() * top < 0) { + if (stack.peek() < Math.abs(top)) { + stack.pop(); + stack.push(top); + } else if (stack.peek() == Math.abs(top)) { + stack.pop(); + break; + } else { + break; + } + } else if (stack.isEmpty() || stack.peek() * top > 0) { + stack.push(top); + break; + } + } while (!stack.isEmpty()); + } + } + + public static class Solution2 { + /* + * My completely original solution on 11/5/2021. + */ + public int[] asteroidCollision(int[] asteroids) { + Deque stack = new LinkedList<>(); + for (int a : asteroids) { + if (a > 0) { + stack.addLast(a); + } else { + if (!stack.isEmpty() && stack.peekLast() > 0) { + if (stack.peekLast() > Math.abs(a)) { + continue; + } else if (stack.peekLast() == Math.abs(a)) { + stack.pollLast(); + } else { + while (!stack.isEmpty() + && stack.peekLast() > 0 + && stack.peekLast() < Math.abs(a)) { + stack.pollLast(); + } + if (!stack.isEmpty() + && stack.peekLast() > 0 + && stack.peekLast() == Math.abs(a)) { + stack.pollLast(); + continue; + } else if (stack.isEmpty() || stack.peekLast() < 0) { + stack.addLast(a); + } + } + } else { + stack.addLast(a); + } + } + } + int[] ans = new int[stack.size()]; + for (int i = stack.size() - 1; i >= 0; i--) { + ans[i] = stack.pollLast(); + } + return ans; + } + } + + public static class Solution3 { + /* + * My completely original solution on 1/14/2022. + */ + public int[] asteroidCollision(int[] asteroids) { + Deque stack = new LinkedList<>(); + for (int i = 0; i < asteroids.length; i++) { + int a = asteroids[i]; + if (a > 0) { + stack.addLast(a); + } else { + if (!stack.isEmpty() && stack.peekLast() > 0) { + if (stack.peekLast() > Math.abs(a)) { + continue; + } else if (stack.peekLast() == Math.abs(a)) { + stack.pollLast(); + } else { + stack.pollLast(); + i--; + } + } else { + stack.addLast(a); + } + } + } + int[] ans = new int[stack.size()]; + for (int i = ans.length - 1; i >= 0; i--) { + ans[i] = stack.pollLast(); + } + return ans; + } + } + + public static class Solution4 { + /* + * My completely original solution on 7/19/2024. + */ + public int[] asteroidCollision(int[] asteroids) { + Deque stack = new LinkedList<>(); + for (int asteroid : asteroids) { + if (asteroid < 0 && !stack.isEmpty() && stack.peekLast() > 0) { + boolean bothRemoved = false; + while (!stack.isEmpty() + && stack.peekLast() > 0 + && stack.peekLast() <= -asteroid) { + if (stack.peekLast() == -asteroid) { + bothRemoved = true; + stack.pollLast(); + break; + } else if (stack.peekLast() < -asteroid) { + stack.pollLast(); + } + } + if ((stack.isEmpty() || stack.peekLast() < 0) && !bothRemoved) { + stack.addLast(asteroid); + } + } else { + stack.addLast(asteroid); + } + } + int[] result = new int[stack.size()]; + int i = 0; + while (!stack.isEmpty()) { + result[i++] = stack.pollFirst(); + } + return result; + } + } +} + + +================================================== +File: _59.java +Line count: 106 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +public class _59 { + + public static class Solution1 { + /* + * credit: https://leetcode.com/problems/spiral-matrix-ii/discuss/22289/My-Super-Simple-Solution.-Can-be-used-for-both-Spiral-Matrix-I-and-II/21907 + */ + public int[][] generateMatrix(int n) { + int[][] matrix = new int[n][n]; + if (n == 0) { + return matrix; + } + + int value = 1; + int top = 0; + int bottom = n - 1; + int left = 0; + int right = n - 1; + while (left <= right && top <= bottom) { + for (int j = left; j <= right; j++) { + matrix[top][j] = value++; + } + top++; + for (int i = top; i <= bottom; i++) { + matrix[i][right] = value++; + } + right--; + for (int j = right; j >= left; j--) { + matrix[bottom][j] = value++; + } + bottom--; + for (int i = bottom; i >= top; i--) { + matrix[i][left] = value++; + } + left++; + } + return matrix; + } + } + + public static class Solution2 { + /* + * My completely original solution on 10/12/2021. + */ + public int[][] generateMatrix(int n) { + int direction = 0; + int[][] matrix = new int[n][n]; + int num = 1; + int i = 0; + int j = 0; + int eastBoundary = n; + int southBoundary = n; + int westBoundary = 0; + int northBoundary = 0; + int limit = n * n; + while (num <= limit) { + if (direction % 4 == 0) { + // 0 means going east + for (; j < eastBoundary && num <= limit; j++) { + matrix[i][j] = num; + num++; + } + j--; + direction++; + eastBoundary--; + i++; + } + if (direction % 4 == 1) { + // 1 means going south + for (; i < southBoundary && num <= limit; i++) { + matrix[i][j] = num; + num++; + } + i--; + direction++; + southBoundary--; + j--; + } + if (direction % 4 == 2) { + // 2 means going west + for (; j >= westBoundary && num <= limit; j--) { + matrix[i][j] = num; + num++; + } + j++; + direction++; + westBoundary++; + i--; + } + if (direction % 4 == 3) { + // 3 means going north + for (; i > northBoundary && num <= limit; i--) { + matrix[i][j] = num; + num++; + } + i++; + direction++; + northBoundary++; + j++; + } + } + return matrix; + } + } +} + + +================================================== +File: _631.java +Line count: 117 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.HashMap; +import java.util.Stack; + +public class _631 { + + public static class Solution1 { + /* + * Credit: https://leetcode.com/articles/design-excel-sum-formula/#approach-1-using-topological-sortaccepted + */ + public static class Excel { + + Formula[][] formulas; + + class Formula { + Formula(HashMap c, int v) { + val = v; + cells = c; + } + + HashMap cells; + int val; + } + + Stack stack = new Stack<>(); + + public Excel(int H, char W) { + formulas = new Formula[H][(W - 'A') + 1]; + } + + public int get(int r, char c) { + if (formulas[r - 1][c - 'A'] == null) { + return 0; + } + return formulas[r - 1][c - 'A'].val; + } + + public void set(int r, char c, int v) { + formulas[r - 1][c - 'A'] = new Formula(new HashMap(), v); + topologicalSort(r - 1, c - 'A'); + execute_stack(); + } + + public int sum(int r, char c, String[] strs) { + HashMap cells = convert(strs); + int summ = calculate_sum(r - 1, c - 'A', cells); + set(r, c, summ); + formulas[r - 1][c - 'A'] = new Formula(cells, summ); + return summ; + } + + public void topologicalSort(int r, int c) { + for (int i = 0; i < formulas.length; i++) { + for (int j = 0; j < formulas[0].length; j++) { + if (formulas[i][j] != null + && formulas[i][j].cells.containsKey( + "" + (char) ('A' + c) + (r + 1))) { + topologicalSort(i, j); + } + } + } + stack.push(new int[] {r, c}); + } + + public void execute_stack() { + while (!stack.isEmpty()) { + int[] top = stack.pop(); + if (formulas[top[0]][top[1]].cells.size() > 0) { + calculate_sum(top[0], top[1], formulas[top[0]][top[1]].cells); + } + } + } + + public HashMap convert(String[] strs) { + HashMap res = new HashMap<>(); + for (String st : strs) { + if (st.indexOf(":") < 0) { + res.put(st, res.getOrDefault(st, 0) + 1); + } else { + String[] cells = st.split(":"); + int si = Integer.parseInt(cells[0].substring(1)); + int ei = Integer.parseInt(cells[1].substring(1)); + char sj = cells[0].charAt(0); + char ej = cells[1].charAt(0); + for (int i = si; i <= ei; i++) { + for (char j = sj; j <= ej; j++) { + res.put("" + j + i, res.getOrDefault("" + j + i, 0) + 1); + } + } + } + } + return res; + } + + public int calculate_sum(int r, int c, HashMap cells) { + int sum = 0; + for (String s : cells.keySet()) { + int x = Integer.parseInt(s.substring(1)) - 1; + int y = s.charAt(0) - 'A'; + sum += (formulas[x][y] != null ? formulas[x][y].val : 0) * cells.get(s); + } + formulas[r][c] = new Formula(cells, sum); + return sum; + } + } + } + + /* + * Your Excel object will be instantiated and called as such: + * Excel obj = new Excel(H, W); + * obj.set(r,c,v); + * int param_2 = obj.get(r,c); + * int param_3 = obj.sum(r,c,strs); + */ + +} + + +================================================== +File: _148.java +Line count: 225 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import com.fishercoder.common.classes.ListNode; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class _148 { + + public static class Solution1 { + /* + * Credit: https://discuss.leetcode.com/topic/18100/java-merge-sort-solution + * But this is not using constant space. + */ + public ListNode sortList(ListNode head) { + if (head == null || head.next == null) { + return head; + } + + // Step 1: split the list into halves + ListNode prev = null; + ListNode slow = head; + ListNode fast = head; + while (fast != null && fast.next != null) { + prev = slow; + fast = fast.next.next; + slow = slow.next; + } + prev.next = null; + + // step 2: sort each half + ListNode l1 = sortList(head); + ListNode l2 = sortList(slow); + + // step 3: merge the two halves + return merge(l1, l2); + } + + private ListNode merge(ListNode l1, ListNode l2) { + ListNode result = new ListNode(0); + ListNode tmp = result; + + while (l1 != null && l2 != null) { + if (l1.val < l2.val) { + tmp.next = l1; + l1 = l1.next; + } else { + tmp.next = l2; + l2 = l2.next; + } + tmp = tmp.next; + } + + if (l1 != null) { + tmp.next = l1; + } + if (l2 != null) { + tmp.next = l2; + } + return result.next; + } + } + + public static class Solution2 { + ListNode tail = new ListNode(0); + ListNode nextSubList = new ListNode(0); + + public ListNode sortList(ListNode head) { + if (head == null || head.next == null) { + return head; + } + int n = getCount(head); + ListNode start = head; + ListNode dummyHead = new ListNode(0); + for (int size = 1; size < n; size = size * 2) { + tail = dummyHead; + while (start != null) { + if (start.next == null) { + tail.next = start; + break; + } + ListNode mid = split(start, size); + merge(start, mid); + start = nextSubList; + } + start = dummyHead.next; + } + return dummyHead.next; + } + + ListNode split(ListNode start, int size) { + ListNode midPrev = start; + ListNode end = start.next; + // use fast and slow approach to find middle and end of second linked list + for (int index = 1; + index < size && (midPrev.next != null || end.next != null); + index++) { + if (end.next != null) { + end = (end.next.next != null) ? end.next.next : end.next; + } + if (midPrev.next != null) { + midPrev = midPrev.next; + } + } + ListNode mid = midPrev.next; + midPrev.next = null; + nextSubList = end.next; + end.next = null; + // return the start of second linked list + return mid; + } + + void merge(ListNode list1, ListNode list2) { + ListNode dummyHead = new ListNode(0); + ListNode newTail = dummyHead; + while (list1 != null && list2 != null) { + if (list1.val < list2.val) { + newTail.next = list1; + list1 = list1.next; + newTail = newTail.next; + } else { + newTail.next = list2; + list2 = list2.next; + newTail = newTail.next; + } + } + newTail.next = (list1 != null) ? list1 : list2; + // traverse till the end of merged list to get the newTail + while (newTail.next != null) { + newTail = newTail.next; + } + // link the old tail with the head of merged list + tail.next = dummyHead.next; + // update the old tail to the new tail of merged list + tail = newTail; + } + + int getCount(ListNode head) { + int cnt = 0; + ListNode ptr = head; + while (ptr != null) { + ptr = ptr.next; + cnt++; + } + return cnt; + } + } + + public static class Solution3 { + /* + * Credit: https://leetcode.com/problems/sort-list/solution/ top down approach. + */ + public ListNode sortList(ListNode head) { + if (head == null || head.next == null) { + return head; + } + ListNode mid = getMid(head); + ListNode left = sortList(head); + ListNode right = sortList(mid); + return mergeList(left, right); + } + + private ListNode mergeList(ListNode left, ListNode right) { + ListNode pre = new ListNode(-1); + ListNode tmp = pre; + while (left != null && right != null) { + if (left.val < right.val) { + tmp.next = left; + left = left.next; + } else { + tmp.next = right; + right = right.next; + } + tmp = tmp.next; + } + if (left != null) { + tmp.next = left; + } else if (right != null) { + tmp.next = right; + } + return pre.next; + } + + private ListNode getMid(ListNode head) { + /*The key/trick is in this method: + * it directly uses this head to iterate, so that we could use this top down recursive approach. + * If we assign head to slow and fast pointers, then this algorithm will run into StackOverflow exception. + * + * This is an absolutely amazing method!*/ + ListNode midPrev = null; + while (head != null && head.next != null) { + midPrev = (midPrev == null) ? head : midPrev.next; + head = head.next.next; + } + ListNode mid = midPrev.next; + midPrev.next = null; // this is the key, otherwise, StackOverflow exception will occur. + return mid; + } + } + + public static class Solution4 { + /*This is the most naive, using O(n) extra memory, O(nlogn) time.*/ + public ListNode sortList(ListNode head) { + if (head == null) { + return head; + } + List list = new ArrayList<>(); + ListNode tmp = head; + while (tmp != null) { + list.add(tmp.val); + tmp = tmp.next; + } + Collections.sort(list); + ListNode pre = new ListNode(-1); + ListNode newHead = new ListNode(list.get(0)); + pre.next = newHead; + for (int i = 1; i < list.size(); i++) { + ListNode next = new ListNode(list.get(i)); + newHead.next = next; + newHead = newHead.next; + } + return pre.next; + } + } +} + + +================================================== +File: _666.java +Line count: 168 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import com.fishercoder.common.classes.TreeNode; +import java.util.HashMap; +import java.util.Map; + +public class _666 { + public static class Solution1 { + /* + * OMG, since it's no larger than depth 5, I've got a hardcoded solution here.... + * By "harcoded", I mean the constructTree() method. + */ + public int totalSum = 0; + + public int pathSum(int[] nums) { + TreeNode root = constructTree(nums); + if (root == null) { + return 0; + } + computePathSum(root, 0); + return totalSum; + } + + private void computePathSum(TreeNode root, int pathSum) { + pathSum += root.val; + if (root.left != null) { + computePathSum(root.left, pathSum); + } + if (root.right != null) { + computePathSum(root.right, pathSum); + } + if (root.left == null && root.right == null) { + totalSum += pathSum; + } + // pathSum -= root.val; + /*this line is not necessary as I'm passing pathSum as a local variable around, so it's always updated + it's AC'ed with or without this line*/ + } + + private TreeNode constructTree(int[] nums) { + if (nums == null || nums.length == 0) { + return null; + } + TreeNode root = + new TreeNode(Integer.parseInt(Integer.toString(nums[0]).substring(2, 3))); + // depth 2 + for (int i = 1; i < nums.length; i++) { + if (Integer.parseInt(Integer.toString(nums[i]).substring(0, 1)) == 2 + && Integer.parseInt(Integer.toString(nums[i]).substring(1, 2)) == 1) { + root.left = + new TreeNode( + Integer.parseInt(Integer.toString(nums[i]).substring(2, 3))); + } else if (Integer.parseInt(Integer.toString(nums[i]).substring(0, 1)) == 2 + && Integer.parseInt(Integer.toString(nums[i]).substring(1, 2)) == 2) { + root.right = + new TreeNode( + Integer.parseInt(Integer.toString(nums[i]).substring(2, 3))); + } + } + + // depth 3 + for (int i = 2; i < nums.length; i++) { + if (Integer.parseInt(Integer.toString(nums[i]).substring(0, 1)) == 3 + && Integer.parseInt(Integer.toString(nums[i]).substring(1, 2)) == 1) { + root.left.left = + new TreeNode( + Integer.parseInt(Integer.toString(nums[i]).substring(2, 3))); + } else if (Integer.parseInt(Integer.toString(nums[i]).substring(0, 1)) == 3 + && Integer.parseInt(Integer.toString(nums[i]).substring(1, 2)) == 2) { + root.left.right = + new TreeNode( + Integer.parseInt(Integer.toString(nums[i]).substring(2, 3))); + } else if (Integer.parseInt(Integer.toString(nums[i]).substring(0, 1)) == 3 + && Integer.parseInt(Integer.toString(nums[i]).substring(1, 2)) == 3) { + root.right.left = + new TreeNode( + Integer.parseInt(Integer.toString(nums[i]).substring(2, 3))); + } else if (Integer.parseInt(Integer.toString(nums[i]).substring(0, 1)) == 3 + && Integer.parseInt(Integer.toString(nums[i]).substring(1, 2)) == 4) { + root.right.right = + new TreeNode( + Integer.parseInt(Integer.toString(nums[i]).substring(2, 3))); + } + } + + // depth 4 + for (int i = 3; i < nums.length; i++) { + if (Integer.parseInt(Integer.toString(nums[i]).substring(0, 1)) == 4 + && Integer.parseInt(Integer.toString(nums[i]).substring(1, 2)) == 1) { + root.left.left.left = + new TreeNode( + Integer.parseInt(Integer.toString(nums[i]).substring(2, 3))); + } else if (Integer.parseInt(Integer.toString(nums[i]).substring(0, 1)) == 4 + && Integer.parseInt(Integer.toString(nums[i]).substring(1, 2)) == 2) { + root.left.left.right = + new TreeNode( + Integer.parseInt(Integer.toString(nums[i]).substring(2, 3))); + } else if (Integer.parseInt(Integer.toString(nums[i]).substring(0, 1)) == 4 + && Integer.parseInt(Integer.toString(nums[i]).substring(1, 2)) == 3) { + root.left.right.left = + new TreeNode( + Integer.parseInt(Integer.toString(nums[i]).substring(2, 3))); + } else if (Integer.parseInt(Integer.toString(nums[i]).substring(0, 1)) == 4 + && Integer.parseInt(Integer.toString(nums[i]).substring(1, 2)) == 4) { + root.left.right.right = + new TreeNode( + Integer.parseInt(Integer.toString(nums[i]).substring(2, 3))); + } else if (Integer.parseInt(Integer.toString(nums[i]).substring(0, 1)) == 4 + && Integer.parseInt(Integer.toString(nums[i]).substring(1, 2)) == 5) { + root.right.left.left = + new TreeNode( + Integer.parseInt(Integer.toString(nums[i]).substring(2, 3))); + } else if (Integer.parseInt(Integer.toString(nums[i]).substring(0, 1)) == 4 + && Integer.parseInt(Integer.toString(nums[i]).substring(1, 2)) == 6) { + root.right.left.right = + new TreeNode( + Integer.parseInt(Integer.toString(nums[i]).substring(2, 3))); + } else if (Integer.parseInt(Integer.toString(nums[i]).substring(0, 1)) == 4 + && Integer.parseInt(Integer.toString(nums[i]).substring(1, 2)) == 7) { + root.right.right.left = + new TreeNode( + Integer.parseInt(Integer.toString(nums[i]).substring(2, 3))); + } else if (Integer.parseInt(Integer.toString(nums[i]).substring(0, 1)) == 4 + && Integer.parseInt(Integer.toString(nums[i]).substring(1, 2)) == 8) { + root.right.right.right = + new TreeNode( + Integer.parseInt(Integer.toString(nums[i]).substring(2, 3))); + } + } + + return root; + } + } + + public static class Solution2 { + public int totalSum = 0; + + public int pathSum(int[] nums) { + Map map = new HashMap<>(); + for (int i = 0; i < nums.length; i++) { + int key = nums[i] / 10; + int value = nums[i] % 10; + map.put(key, value); + } + dfs(nums[0] / 10, 0, map); + return totalSum; + } + + private void dfs(int node, int preSum, Map map) { + int level = node / 10; + int pos = node % 10; + int leftChild = (level + 1) * 10 + pos * 2 - 1; + int rightChild = (level + 1) * 10 + pos * 2; + int currSum = preSum + map.get(node); + if (!map.containsKey(leftChild) && !map.containsKey(rightChild)) { + totalSum += currSum; + return; + } + + if (map.containsKey(leftChild)) { + dfs(leftChild, currSum, map); + } + if (map.containsKey(rightChild)) { + dfs(rightChild, currSum, map); + } + } + } +} + + +================================================== +File: _34.java +Line count: 141 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +public class _34 { + + public static class Solution1 { + public int[] searchRange(int[] nums, int target) { + int[] range = new int[2]; + range[0] = -1; + range[1] = -1; + if (nums == null || nums.length == 0) { + return range; + } + int start = 0; + int end = nums.length - 1; + while (start + 1 < end) { + int mid = start + (end - start) / 2; + if (nums[mid] == target) { + int left = mid; + while (left - 1 >= 0 && nums[left] == nums[left - 1]) { + left--; + } + range[0] = left; + int right = mid; + while (right + 1 < nums.length && nums[right] == nums[right + 1]) { + right++; + } + range[1] = right; + break; + } else if (nums[mid] < target) { + start = mid; + } else { + end = mid; + } + } + + if (nums[start] == target) { + range[0] = start; + while (start + 1 < nums.length && nums[start] == nums[start + 1]) { + start++; + } + range[1] = start; + } else if (nums[end] == target) { + range[1] = end; + while (end - 1 >= 0 && nums[end] == nums[end - 1]) { + end--; + } + range[0] = end; + } + return range; + } + } + + public static class Solution2 { + public int[] searchRange(int[] nums, int target) { + int[] result = new int[] {-1, -1}; + if (nums == null || nums.length == 0) { + return result; + } + if (nums[0] > target) { + return result; + } + if (nums[nums.length - 1] < target) { + return result; + } + int left = 0; + int right = nums.length - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + if (nums[mid] == target) { + while (mid - 1 >= 0 && nums[mid] == nums[mid - 1]) { + mid--; + } + result[0] = mid; + while (mid + 1 < nums.length && nums[mid] == nums[mid + 1]) { + mid++; + } + result[1] = mid; + return result; + } else if (nums[mid] > target) { + right = mid - 1; + } else { + left = mid + 1; + } + } + return result; + } + } + + public static class Solution3 { + /* + * My completely original solution on 1/15/2022. A great practice to solidify binary search basics. + */ + public int[] searchRange(int[] nums, int target) { + int left = 0; + int right = nums.length - 1; + int[] ans = new int[] {-1, -1}; + while (left < right) { + int mid = left + (right - left) / 2; + if (nums[mid] > target) { + right = mid - 1; + } else if (nums[mid] < target) { + left = mid + 1; + } else { + right = mid - 1; + ans[0] = mid; + ans[1] = mid; + } + } + if (left < nums.length + && nums[left] != target + && right > 0 + && nums[right] != target + && right + 1 < nums.length + && nums[right + 1] != target) { + return ans; + } + if (left < nums.length && nums[left] == target) { + ans[0] = left; + ans[1] = left; + } + right = nums.length - 1; + while (left < right) { + int mid = left + (right - left) / 2; + if (nums[mid] < target) { + left = mid + 1; + } else if (nums[mid] > target) { + right = mid - 1; + } else { + ans[1] = mid; + left = mid + 1; + } + } + if (right >= 0 && nums[right] == target) { + ans[1] = right; + } else if (left < nums.length && nums[left] == target) { + ans[1] = left; + } + return ans; + } + } +} + + +================================================== +File: _173.java +Line count: 106 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import com.fishercoder.common.classes.TreeNode; +import java.util.Deque; +import java.util.LinkedList; +import java.util.Queue; +import java.util.Stack; + +public class _173 { + + public static class Solution1 { + + public static class BSTIterator { + private Queue queue; + + public BSTIterator(TreeNode root) { + queue = new LinkedList<>(); + if (root != null) { + dfs(root, queue); + } + } + + private void dfs(TreeNode root, Queue q) { + if (root.left != null) { + dfs(root.left, q); + } + q.offer(root.val); + if (root.right != null) { + dfs(root.right, q); + } + } + + public boolean hasNext() { + return !queue.isEmpty(); + } + + public int next() { + return queue.poll(); + } + } + } + + public static class Solution2 { + public static class BSTIterator { + /* + * This is a super cool/clever idea: use a stack to store all the current left nodes of the BST, when pop(), we + * push all its right nodes into the stack if there are any. + * This way, we use only O(h) memory for this iterator, this is a huge saving when the tree is huge + * since h could be much smaller than n. Cheers! + */ + + private Stack stack; + + public BSTIterator(TreeNode root) { + stack = new Stack(); + pushToStack(root, stack); + } + + private void pushToStack(TreeNode root, Stack stack) { + while (root != null) { + stack.push(root); + root = root.left; + } + } + + public boolean hasNext() { + return !stack.isEmpty(); + } + + public int next() { + TreeNode curr = stack.pop(); + pushToStack(curr.right, stack); + return curr.val; + } + } + } + + public static class Solution3 { + /* + * credit: https://leetcode.com/problems/binary-search-tree-iterator/discuss/52647/Nice-Comparison-(and-short-Solution + */ + public static class BSTIterator { + Deque stack; + TreeNode visit; + + public BSTIterator(TreeNode root) { + stack = new LinkedList<>(); + visit = root; + } + + public int next() { + while (visit != null) { + stack.addLast(visit); + visit = visit.left; + } + TreeNode next = stack.pollLast(); + visit = next.right; + return next.val; + } + + public boolean hasNext() { + return visit != null && !stack.isEmpty(); + } + } + } +} + + +================================================== +File: _4.java +Line count: 112 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import static java.lang.Math.max; +import static java.lang.Math.min; + +public class _4 { + + public static class Solution1 { + /*credit: https://discuss.leetcode.com/topic/28602/concise-java-solution-based-on-binary-search + * + * The key point of this problem is to ignore half part of A and B each step recursively by comparing the median of remaining A and B: + + if (aMid < bMid) Keep [aRight + bLeft] + else Keep [bRight + aLeft] + + As the following: time=O(log(m + n)) + */ + public double findMedianSortedArrays(int[] A, int[] B) { + int m = A.length; + int n = B.length; + int l = (m + n + 1) / 2; + int r = (m + n + 2) / 2; + return (getkth(A, 0, B, 0, l) + getkth(A, 0, B, 0, r)) / 2.0; + } + + public double getkth(int[] A, int aStart, int[] B, int bStart, int k) { + if (aStart > A.length - 1) { + return B[bStart + k - 1]; + } + if (bStart > B.length - 1) { + return A[aStart + k - 1]; + } + if (k == 1) { + return Math.min(A[aStart], B[bStart]); + } + + int aMid = Integer.MAX_VALUE; + int bMid = Integer.MAX_VALUE; + if (aStart + k / 2 - 1 < A.length) { + aMid = A[aStart + k / 2 - 1]; + } + if (bStart + k / 2 - 1 < B.length) { + bMid = B[bStart + k / 2 - 1]; + } + + if (aMid < bMid) { + return getkth(A, aStart + k / 2, B, bStart, k - k / 2); // Check: aRight + bLeft + } else { + return getkth(A, aStart, B, bStart + k / 2, k - k / 2); // Check: bRight + aLeft + } + } + } + + public static class Solution2 { + /* + * Reference: https://leetcode.com/discuss/28843/my-accepted-java-solution: + * Basic Idea is very similar to K-selection. it's easier to understand if you imagine this to be chopping off the last K elements from a total of len(A) + len(B) elements, + * where K = (len(A) + len(B))/2. + * we want to remove K, but each time we can remove only at most K/2 elements, + * because we can only be sure that these elements are not within the first (len(A) + len(B)) -K elements. + */ + public double findMedianSortedArrays(int[] nums1, int[] nums2) { + int K = nums1.length + nums2.length; + if (K % 2 == 0) { + return (findMedianSortedArrays(nums1, nums2, (K - K / 2)) + + findMedianSortedArrays(nums1, nums2, (K - (K / 2 + 1)))) + / 2; + } else { + return findMedianSortedArrays(nums1, nums2, K - (K / 2 + 1)); + } + } + + // k is the number of elements to REMOVE, or "Chop off" + public double findMedianSortedArrays(int[] A, int[] B, int K) { + + int lowA = 0; + int lowB = 0; + int highA = A.length; + int highB = B.length; + int midA; + int midB; + while (K > 0 && highA > 0 && highB > 0) { + int chopA = max(1, min(K / 2, (highA) / 2)); + int chopB = max(1, min(K / 2, (highB) / 2)); + + midA = highA - chopA; + midB = highB - chopB; + if (A[midA] < B[midB]) { // here A[0 .. midA] < B[midB], and we know that B[0 .. + // midB-1] < B[midB], so B[midB..highB] can not possibly be + // within the first (len(A) + len(B) - K) elements, and can + // be safely removed. + highB = midB; + K = K - chopB; + } else { + highA = midA; + K = K - chopA; + } + } + + if (highA == 0 && highB == 0) { + return 0; + } + if (highA == 0) { + return B[highB - 1 - K]; + } + if (highB == 0) { + return A[highA - 1 - K]; + } + return max(A[highA - 1], B[highB - 1]); + } + } +} + + +================================================== +File: _427.java +Line count: 111 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +public class _427 { + static class Node { + public boolean val; + public boolean isLeaf; + public Node topLeft; + public Node topRight; + public Node bottomLeft; + public Node bottomRight; + + public Node() { + this.val = false; + this.isLeaf = false; + this.topLeft = null; + this.topRight = null; + this.bottomLeft = null; + this.bottomRight = null; + } + + public Node(boolean val, boolean isLeaf) { + this.val = val; + this.isLeaf = isLeaf; + this.topLeft = null; + this.topRight = null; + this.bottomLeft = null; + this.bottomRight = null; + } + + public Node( + boolean val, + boolean isLeaf, + Node topLeft, + Node topRight, + Node bottomLeft, + Node bottomRight) { + this.val = val; + this.isLeaf = isLeaf; + this.topLeft = topLeft; + this.topRight = topRight; + this.bottomLeft = bottomLeft; + this.bottomRight = bottomRight; + } + } + + public static class Solution1 { + public Node construct(int[][] grid) { + return recurse(grid, 0, 0, grid.length); + } + + private Node recurse(int[][] grid, int row, int col, int limit) { + if (allTheSameValue(grid, row, col, limit)) { + return new Node(grid[row][col] == 1, true); + } else { + Node root = new Node(false, false); + // top left + root.topLeft = recurse(grid, row, col, limit / 2); + // top right + root.topRight = recurse(grid, row, col + limit / 2, limit / 2); + // bottom left + root.bottomLeft = recurse(grid, row + limit / 2, col, limit / 2); + // bottom right + root.bottomRight = recurse(grid, row + limit / 2, col + limit / 2, limit / 2); + return root; + } + } + + private boolean allTheSameValue(int[][] grid, int row, int col, int limit) { + int val = grid[row][col]; + for (int i = row; i < row + limit; i++) { + for (int j = col; j < col + limit; j++) { + if (val != grid[i][j]) { + return false; + } + } + } + return true; + } + } + + public static class Solution2 { + public Node construct(int[][] grid) { + return recurse(grid, 0, 0, grid.length); + } + + private Node recurse(int[][] grid, int row, int col, int limit) { + if (limit == 1) { + return new Node(grid[row][col] == 1, true); + } + Node topLeft = recurse(grid, row, col, limit / 2); + Node topRgith = recurse(grid, row, col + limit / 2, limit / 2); + Node bottomLeft = recurse(grid, row + limit / 2, col, limit / 2); + Node bottomRight = recurse(grid, row + limit / 2, col + limit / 2, limit / 2); + if (topLeft.isLeaf + && topRgith.isLeaf + && bottomLeft.isLeaf + && bottomRight.isLeaf + && topLeft.val == topRgith.val + && topLeft.val == bottomLeft.val + && topLeft.val == bottomRight.val) { + return new Node(topLeft.val, true); + } + Node root = new Node(grid[row][col] == 1, false); + root.topLeft = topLeft; + root.topRight = topRgith; + root.bottomLeft = bottomLeft; + root.bottomRight = bottomRight; + return root; + } + } +} + + +================================================== +File: _535.java +Line count: 125 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +public class _535 { + + public static class Solution1 { + /* + * Simple counter approach + * Analysis: + * The range of URLs that can be decoded is limited by the range of Integer. + * If excessively large number of URLs have to be encoded, after the range of Integer is exceeded, + * integer overflow could lead to overwriting previous URL's encodings. + * The length of the URL isn't necessary shorter than incoming URL. + * One potential security issue with this problem is that it's very easy to predict the next code generated, + * since this pattern is very easy to be detected. + */ + public class Codec { + int i = 0; + Map map = new HashMap(); + public static final String PREFIX = "http://tinyurl/"; + + public String encode(String longUrl) { + map.put(i, longUrl); + return PREFIX + i++; + } + + public String decode(String shortUrl) { + return map.get(Integer.parseInt(shortUrl.substring(PREFIX.length()))); + } + } + } + + public static class Solution2 { + /* + * Use Java built-in HashCode + * Analysis: + * hashCode() does NOT generate unique codes for different strings, collision might happen. + * As the number of URLs increase, the probability of getting collision increases which leads to failure. + */ + public class Codec { + Map map = new HashMap<>(); + public static final String PREFIX = "http://tinyurl/"; + + // Encodes a URL to a shortened URL. + public String encode(String longUrl) { + /*I don't need to create a local variable to cache longUrl.hashCode() + * since Java's String cache it already. :) Look at its source code.*/ + map.put(longUrl.hashCode(), longUrl); + return PREFIX + longUrl.hashCode(); + } + + // Decodes a shortened URL to its original URL. + public String decode(String shortUrl) { + return map.get(Integer.parseInt(shortUrl.substring(PREFIX.length()))); + } + } + } + + public static class Solution3 { + /* + * Use a random number + */ + Map map = new HashMap<>(); + Random random = new Random(); + public static final String PREFIX = "http://tinyurl/"; + + // Encodes a URL to a shortened URL. + public String encode(String longUrl) { + int num = random.nextInt(Integer.MAX_VALUE); + while (map.containsKey(num)) { + num = random.nextInt(Integer.MAX_VALUE); + } + map.put(num, longUrl); + return PREFIX + num; + } + + // Decodes a shortened URL to its original URL. + public String decode(String shortUrl) { + return map.get(Integer.parseInt(shortUrl.substring(PREFIX.length()))); + } + } + + public static class Solution4 { + /* + * Use a random but fixed length encoding + * Analysis: + * 1. This is the most optimal solution so far. + * 2. The number of URLs that can be encoded can be as big as Math.pow((10 + 26*2), FIXED_LENGTH), in below code, FIXED_LENGTH = 7 + * 3. The length of the shortened URL is fixed at a certain length, which could be a significant reduce for large URLs + * 4. The performance of this scheme is pretty good, due to much smaller probability of encountering collision + * 5. Predicting pattern/encoding isn't possible in this case since random numbers are used. + */ + Map map = new HashMap<>(); + public static final String PREFIX = "http://tinyurl/"; + public static final int FIXED_LENGTH = 7; + Random random = new Random(); + String alphabet = "0123456789abcdefghijklmnopgrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + // Encodes a URL to a shortened URL. + public String encode(String longUrl) { + String shortKey = getRandomFixedLengthKey(); + while (map.containsKey(shortKey)) { + shortKey = getRandomFixedLengthKey(); + } + map.put(shortKey, longUrl); + return PREFIX + shortKey; + } + + private String getRandomFixedLengthKey() { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < FIXED_LENGTH; i++) { + stringBuilder.append(alphabet.charAt(random.nextInt(alphabet.length()))); + } + return stringBuilder.toString(); + } + + // Decodes a shortened URL to its original URL. + public String decode(String shortUrl) { + return map.get(shortUrl.substring(PREFIX.length())); + } + } +} + + +================================================== +File: _309.java +Line count: 135 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +public class _309 { + public static class Solution1 { + /* + * The series of problems are typical dp. The key for dp is to find the variables to + * represent the states and deduce the transition function. + * + * Of course one may come up with a O(1) space solution directly, but I think it is better + * to be generous when you think and be greedy when you implement. + * + * The natural states for this problem is the 3 possible transactions : buy, sell, rest. + * Here rest means no transaction on that day (aka cooldown). + * + * Then the transaction sequences can end with any of these three states. + * + * For each of them we make an array, buy[n], sell[n] and rest[n]. + * + * buy[i] means before day i what is the maxProfit for any sequence end with buy. + * + * sell[i] means before day i what is the maxProfit for any sequence end with sell. + * + * rest[i] means before day i what is the maxProfit for any sequence end with rest. + * + * Then we want to deduce the transition functions for buy sell and rest. By definition we + * have: + * + * buy[i] = max(rest[i-1]-price, buy[i-1]) + * sell[i] = max(buy[i-1]+price, sell[i-1]) + * rest[i] = max(sell[i-1], buy[i-1], rest[i-1]) + * + * Where price is the price of day i. All of these are very straightforward. They simply represents : + * + * (1) We have to `rest` before we `buy` and + * (2) we have to `buy` before we `sell` + * One tricky point is how do you make sure you sell before you buy, since from the equations it seems that [buy, rest, buy] is entirely possible. + * + * Well, the answer lies within the fact that buy[i] <= rest[i] which means rest[i] = + * max(sell[i-1], rest[i-1]). That made sure [buy, rest, buy] is never occurred. + * + * A further observation is that and rest[i] <= sell[i] is also true therefore + * + * rest[i] = sell[i-1] Substitute this in to buy[i] we now have 2 functions instead of 3: + * + * buy[i] = max(sell[i-2]-price, buy[i-1]) sell[i] = max(buy[i-1]+price, sell[i-1]) This is + * better than 3, but + * + * we can do even better + * + * Since states of day i relies only on i-1 and i-2 we can reduce the O(n) space to O(1). + * And here we are at our final solution: + */ + public int maxProfit(int[] prices) { + int sell = 0; + int prevSell = 0; + int buy = Integer.MIN_VALUE; + int prevBuy; + for (int price : prices) { + prevBuy = buy; + buy = Math.max(prevSell - price, prevBuy); + prevSell = sell; + sell = Math.max(prevBuy + price, prevSell); + } + return sell; + } + } + + public static class Solution2 { + /*Surprisingly, this solution is even much faster than the one above provided by the author.*/ + /* + * Here I share my no brainer weapon when it comes to this kind of problems. + * + * 1. Define States + * + * To represent the decision at index i: + * + * buy[i]: Max profit till index i. The series of transaction is ending with a buy. + * sell[i]: Max profit till index i. The series of transaction is ending with a sell. + * + * 2. Define Recursion + * + * buy[i]: To make a decision whether to buy at i, we either take a rest, by just using the + * old decision at i - 1, or sell at/before i - 2, then buy at i, We cannot sell at i - 1, + * then buy at i, because of cooldown. + * sell[i]: To make a decision whether to sell at i, we either take a rest, by just using the old decision at i - 1, + * or buy at/before i - 1, then sell at i. + * + * So we get the following formula: + * + * buy[i] = Math.max(buy[i - 1], sell[i - 2] - prices[i]); + * sell[i] = Math.max(sell[i - 1], buy[i - 1] + prices[i]); + * + * 3. Optimize to O(1) Space + * + * DP solution only depending on i - 1 and i - 2 can be optimized using O(1) space. + * + * Let b2, b1, b0 represent buy[i - 2], buy[i - 1], buy[i] + * Let s2, s1, s0 represent sell[i - 2], sell[i - 1], sell[i] + * + * Then arrays turn into Fibonacci like recursion: + * + * b0 = Math.max(b1, s2 - prices[i]); + * s0 = Math.max(s1, b1 + prices[i]); + * + * 4. Write Code in 5 Minutes + * + * First we define the initial states at i = 0: + * + * We can buy. The max profit at i = 0 ending with a buy is -prices[0]. + * We cannot sell. The max profit at i = 0 ending with a sell is 0. + */ + public int maxProfit(int[] prices) { + if (prices == null || prices.length <= 1) { + return 0; + } + + int b0 = -prices[0]; + int b1 = b0; + int b2 = b0; + int s0 = 0; + int s1 = s0; + int s2 = s0; + + for (int i = 1; i < prices.length; i++) { + b0 = Math.max(b1, s2 - prices[i]); + s0 = Math.max(s1, b1 + prices[i]); + b2 = b1; + b1 = b0; + s2 = s1; + s1 = s0; + } + return s0; + } + } +} + + +================================================== +File: _19.java +Line count: 103 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import com.fishercoder.common.classes.ListNode; + +public class _19 { + + public static class Solution1 { + /* + * Naive/most straightforward approach: + * go through the list, find its total length, then go through the list a second time: + * this time, pause at the delta point, then assign its next.next pointer to next. + * This approach has to traverse the list twice, not one-pass. + */ + public ListNode removeNthFromEnd(ListNode head, int n) { + ListNode temp = head; + int len = 0; + while (temp != null) { + temp = temp.next; + len++; + } + if (n == len) { + return head.next; + } + + temp = head; + int cut = len - n; + while (cut-- > 1) { + temp = temp.next; + } + if (temp.next != null) { + temp.next = temp.next.next; + return head; + } + return null; + } + } + + public static class Solution2 { + public ListNode removeNthFromEnd(ListNode head, int n) { + // this approach uses two pointers, fast moves first for n nodes, when fast reaches n, + // then we start to move slow + // then, when fast reaches null, slow reaches the point where the node should be + // deleted. + ListNode dummy = new ListNode(-1); + dummy.next = head; + ListNode slow = head; + ListNode fast = head; + int tempN = n; + while (tempN-- > 0) { + fast = fast.next; + } + + if (fast == null) { + if (n > 0) { + // this is for cases like this: [1,2] 2 or [1,2,3,4] 4, namely, remove the head + // of + // the list and return the second node from the original list + dummy.next = dummy.next.next; + } + return dummy.next; + } + + fast = fast.next; // we'll have to move fast pointer one node forward before moving the + // two together, this way, + // when fast reaches null, slow will be at the previous node to the node that should be + // deleted, thus, we can change the next pointer easily + + while (fast != null) { + fast = fast.next; + slow = slow.next; + } + + if (slow.next != null) { + slow.next = slow.next.next; + } + return dummy.next; + } + } + + public static class Solution3 { + // a more concise version using the same idea + // i.e. sliding window + // Time: O(n) + // Space: O(1) + public ListNode removeNthFromEnd(ListNode head, int n) { + ListNode pre = new ListNode(-1); + pre.next = head; + ListNode slow = pre; + ListNode fast = pre; + while (fast.next != null) { + if (n <= 0) { + slow = slow.next; + } + fast = fast.next; + n--; + } + if (slow.next != null) { + slow.next = slow.next.next; + } + return pre.next; + } + } +} + + +================================================== +File: _128.java +Line count: 171 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +public class _128 { + public static class Solution1 { + public int longestConsecutive(int[] nums) { + Map map = new HashMap(); + // + UnionFind uf = new UnionFind(nums); + for (int i = 0; i < nums.length; i++) { + if (map.containsKey(nums[i])) { + continue; + } + map.put(nums[i], i); + if (map.containsKey(nums[i] - 1)) { + uf.union(i, map.get(nums[i] - 1)); + // note: we want to union this index and nums[i]-1's root index which we can get + // from the map + } + if (map.containsKey(nums[i] + 1)) { + uf.union(i, map.get(nums[i] + 1)); + } + } + return uf.maxUnion(); + } + + class UnionFind { + int[] ids; + + public UnionFind(int[] nums) { + ids = new int[nums.length]; + for (int i = 0; i < nums.length; i++) { + ids[i] = i; + } + } + + public void union(int i, int j) { + int x = find(ids, i); + int y = find(ids, j); + ids[x] = y; + } + + public int find(int[] ids, int i) { + while (i != ids[i]) { + ids[i] = ids[ids[i]]; + i = ids[i]; + } + return i; + } + + public boolean connected(int i, int j) { + return find(ids, i) == find(ids, j); + } + + public int maxUnion() { + // this is O(n) + int max = 0; + int[] count = new int[ids.length]; + for (int i = 0; i < ids.length; i++) { + count[find(ids, i)]++; + max = max < count[find(ids, i)] ? count[find(ids, i)] : max; + } + return max; + } + } + } + + public static class Solution2 { + // inspired by this solution: + // https://discuss.leetcode.com/topic/25493/simple-fast-java-solution-using-set + public int longestConsecutive(int[] nums) { + if (nums == null || nums.length == 0) { + return 0; + } + + Set set = new HashSet(); + for (int i : nums) { + set.add(i); + } + int max = 1; + + for (int num : nums) { + if (set.remove(num)) { + int val = num; + int count = 1; + while (set.remove(val - 1)) { + val--; // we find all numbers that are smaller than num and remove them from + // the set + } + count += num - val; + + val = num; + while (set.remove(val + 1)) { + val++; // then we find all numbers that are bigger than num and also remove + // them from the set + } + count += val - num; + + max = Math.max(max, count); + } + } + return max; + } + } + + public static class Solution3 { + /* + * O(n) time complexity. + */ + public int longestConsecutive(int[] nums) { + Set set = new HashSet<>(); + for (int num : nums) { + set.add(num); + } + + int longestStreak = 0; + for (int num : set) { + // we'll go through this set instead of nums, this makes a big difference in time + // complexity, esp. based on LeetCode test cases + if (!set.contains(num - 1)) { + int currentNum = num; + int currentStreak = 1; + + while (set.contains(currentNum + 1)) { + currentNum += 1; + currentStreak += 1; + } + longestStreak = Math.max(longestStreak, currentStreak); + } + } + return longestStreak; + } + } + + public static class Solution4 { + /* + * O(nlogn) time complexity + */ + public int longestConsecutive(int[] nums) { + if (nums.length == 0) { + return 0; + } + TreeSet treeSet = new TreeSet<>(); + for (int i : nums) { + treeSet.add(i); // O(logn) time complexity for each add() call + } + int ans = 1; + Iterator it = treeSet.iterator(); + Integer curr = it.next(); + int len = 1; + while (it.hasNext()) { + Integer next = it.next(); + if (curr + 1 == next) { + len++; + } else { + len = 1; + } + curr = next; + ans = Math.max(ans, len); + } + ans = Math.max(ans, len); + return ans; + } + } +} + + +================================================== +File: _684.java +Line count: 133 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class _684 { + + public static class Solution1 { + /* + * This is my original solution. A little verbose. + */ + class UnionFind { + int[] ids; + Set nodes; + Set visitedNodes; + int[] redundantConn; + int m; + int n; + + public UnionFind(int[][] edges) { + m = edges.length; + n = edges[0].length; + nodes = new HashSet<>(); + visitedNodes = new HashSet<>(); + redundantConn = new int[] {}; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + nodes.add(edges[i][j]); + } + } + ids = new int[nodes.size()]; + for (int i = 0; i < ids.length; i++) { + ids[i] = i + 1; + } + } + + public int[] union(int[] edge) { + int x = find(edge[0] - 1); + int y = find(edge[1] - 1); + + if (x == y && visitedNodes.contains(edge[0]) && visitedNodes.contains(edge[1])) { + redundantConn = edge; + } + + if (x != y) { + if (x < y) { + ids[y] = x + 1; + } else { + ids[x] = y + 1; + } + } + + visitedNodes.add(edge[0]); + visitedNodes.add(edge[1]); + return redundantConn; + } + + private int find(int id) { + if (isTree()) { + return ids[id]; + } + if ((id + 1) != ids[id]) { + return find(ids[id] - 1); + } + return id; + } + + private boolean isTree() { + Set set = new HashSet<>(); + for (int i : ids) { + set.add(i); + } + return set.size() == 1; + } + } + + public int[] findRedundantConnection(int[][] edges) { + UnionFind unionFind = new UnionFind(edges); + int[] result = new int[] {}; + for (int[] edge : edges) { + result = unionFind.union(edge); + } + return result; + } + } + + public static class Solution2 { + /* + * DFS, credit: https://leetcode.com/problems/redundant-connection/editorial/ + * 1. we build the graph one edge at a time, each time, we add both edge[0] to the neighbors of edge[1] and vice versa since this is an un-directed graph; + * 2. as soon as we encounter an edge that can connect to each other, it must be the redundant one. + * In other words, we first check if this new edge is needed or not based on the current existing graph: + * if the two nodes connected by this edge is already connected, then this edge is redundant. + */ + private static final int MAX_VERTICES = 1000; + + public int[] findRedundantConnection(int[][] edges) { + List[] graph = new ArrayList[MAX_VERTICES + 1]; + for (int i = 0; i < graph.length; i++) { + graph[i] = new ArrayList<>(); + } + Set visited = new HashSet<>(); + for (int[] edge : edges) { + visited.clear(); + if (!graph[edge[0]].isEmpty() + && !graph[edge[1]].isEmpty() + && canConnect(edge[0], edge[1], graph, visited)) { + return edge; + } + graph[edge[0]].add(edge[1]); + graph[edge[1]].add(edge[0]); + } + return null; + } + + private boolean canConnect( + int source, int target, List[] graph, Set visited) { + if (visited.add(source)) { + if (source == target) { + return true; + } + for (int v : graph[target]) { + if (canConnect(source, v, graph, visited)) { + return true; + } + } + } + return false; + } + } +} + + +================================================== +File: _450.java +Line count: 154 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import com.fishercoder.common.classes.TreeNode; +import java.util.ArrayList; +import java.util.List; + +public class _450 { + public static class Solution1 { + /* + * credit: https://discuss.leetcode.com/topic/65792/recursive-easy-to-understand-java-solution + * Steps: + * 1. Recursively find the node that has the same value as the key, while setting the left/right nodes equal to the returned subtree + * 2. Once the node is found, have to handle the below 4 cases + * a. node doesn't have left or right - return null + * b. node only has left subtree- return the left subtree + * c. node only has right subtree- return the right subtree + * d. node has both left and right - find the minimum value in the right subtree, set that value to the currently found node, then recursively delete the minimum value in the right subtree + */ + public TreeNode deleteNode(TreeNode root, int key) { + if (root == null) { + return root; + } + if (root.val > key) { + root.left = deleteNode(root.left, key); + } else if (root.val < key) { + root.right = deleteNode(root.right, key); + } else { + if (root.left == null) { + return root.right; + } else if (root.right == null) { + return root.left; + } + + TreeNode minNode = getMin(root.right); + root.val = minNode.val; + root.right = deleteNode(root.right, root.val); + } + return root; + } + + private TreeNode getMin(TreeNode node) { + while (node.left != null) { + node = node.left; + } + return node; + } + } + + public static class Solution2 { + /* + * My original, but brute force solution, time complexity: O(n) instead of O(h) + */ + public TreeNode deleteNode(TreeNode root, int key) { + List list = new ArrayList<>(); + dfs(root, key, list); + return formBst(list, 0, list.size() - 1); + } + + private TreeNode formBst(List list, int left, int right) { + if (left > right) { + return null; + } + int mid = left + (right - left) / 2; + TreeNode root = new TreeNode(list.get(mid)); + root.left = formBst(list, left, mid - 1); + root.right = formBst(list, mid + 1, right); + return root; + } + + private List dfs(TreeNode root, int key, List list) { + if (root == null) { + return list; + } + dfs(root.left, key, list); + if (root.val != key) { + list.add(root.val); + } + dfs(root.right, key, list); + return list; + } + } + + public static class Solution3 { + /* + * credit: https://leetcode.com/problems/delete-node-in-a-bst/solution/ + * + * Again, using a pen and paper to visualize helps a lot. + * Putting a BST into an inorder traversal array helps a lot to understand: + * + * The successor of a node is always: go the right once, and then go to the left as many times as possible, + * because the successor is the next smallest element that is larger than the current one: so going to the right one time + * makes sure that we are finding the larger one, and then keeping going to the left makes sure that we'll find the smallest one. + * + * The predecessor of a node is always: go the left once, and then go the right as many times as possible, + * because the predecessor is the largest element that is smaller than the current one: so going to the left once makes it smaller than the current node, + * then keeping going to the right makes sure that we are getting the largest element. + */ + public TreeNode deleteNode(TreeNode root, int key) { + if (root == null) { + return null; + } + if (root.val < key) { + // delete from the right subtree + root.right = deleteNode(root.right, key); + } else if (root.val > key) { + // delete from the left subtree + root.left = deleteNode(root.left, key); + } else { + // delete this current node, three cases: + if (root.left == null && root.right == null) { + // case 1: if this is a leaf + root = null; + } else if (root.right != null) { + // case 2: has a right child, regardless whether it has left children or not, + // this is because we want to traverse the tree only once, so we'll want to keep + // going down the tree + root.val = + findSuccessor( + root); // we find the value of the successor and assign it to + // current root.val + root.right = + deleteNode( + root.right, + root.val); // and then we delete this successor's value in the + // right subtree as it's been moved up + } else if (root.left != null) { + // case 3: this node is not a leaf and no right child, but has a left child + // That means that its successor is somewhere upper in the tree but we don't + // want to go back. + // Let's use the predecessor here which is somewhere lower in the left subtree. + root.val = findPredecessor(root); + root.left = deleteNode(root.left, root.val); + } + } + return root; + } + + private int findPredecessor(TreeNode root) { + root = root.left; + while (root.right != null) { + root = root.right; + } + return root.val; + } + + private int findSuccessor(TreeNode root) { + root = root.right; + while (root.left != null) { + root = root.left; + } + return root.val; + } + } +} + + +================================================== +File: _295.java +Line count: 123 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.Collections; +import java.util.PriorityQueue; +import java.util.Queue; + +public class _295 { + /* + * A few key points for both following solutions: + *

+ * 1. always keep one queue one element more than the other if the number is odd, offer into that one + * first, then poll from that queue and offer into the other queue, then check whether that queue is smaller + * in size than the other, if so, poll one from the other queue and offer it into this queue + *

+ * 2. only need to check whether this bigger queue size is greater than the other queue when returning. + */ + + public static class Solution1 { + public static class MedianFinder { + private Queue large; + private Queue small; + + public MedianFinder() { + large = new PriorityQueue<>(); + small = new PriorityQueue<>(Collections.reverseOrder()); + } + + // Adds a number into the data structure. + public void addNum(int num) { + large.offer((long) num); + small.offer(large.poll()); + if (large.size() < small.size()) { + large.offer(small.poll()); + } + } + + // Returns the median of current data stream + public double findMedian() { + if (large.size() > small.size()) { + return large.peek(); + } + return (large.peek() + small.peek()) / 2.0; + } + } + } + + public static class Solution2 { + public static class MedianFinder { + /* + * credit: https://discuss.leetcode.com/topic/27521/short-simple-java-c-python-o-log-n-o-1 + * The idea is for sure to use two heaps, one is max heap, one is min heap, we always let the max heap have one more element + * than min heap if the total number of elements is not even. + * we could always get the median in O(1) time. + * 1. use Long type to avoid overflow + * 2. negate the numbers for small heap to save the effort for writing a reverse comparator, brilliant! + */ + + private Queue large; + private Queue small; + + /* + * initialize your data structure here. + */ + public MedianFinder() { + large = new PriorityQueue<>(); + small = new PriorityQueue<>(); + } + + // Adds a number into the data structure. + public void addNum(int num) { + large.offer((long) num); + small.offer(-large.poll()); + if (large.size() < small.size()) { + large.offer(-small.poll()); + } + } + + // Returns the median of current data stream + public double findMedian() { + if (large.size() > small.size()) { + return large.peek(); + } + return (large.peek() - small.peek()) / 2.0; + } + } + } + + public static class Solution3 { + public static class MedianFinder { + /* + * The same as Solution2, but not using negation for minHeap. + */ + + private Queue maxHeap; + private Queue minHeap; + + /* + * initialize your data structure here. + */ + public MedianFinder() { + maxHeap = new PriorityQueue<>(); + minHeap = new PriorityQueue<>((a, b) -> (int) (b - a)); + } + + // Adds a number into the data structure. + public void addNum(int num) { + maxHeap.offer((long) num); + minHeap.offer(maxHeap.poll()); + if (maxHeap.size() < minHeap.size()) { + maxHeap.offer(minHeap.poll()); + } + } + + // Returns the median of current data stream + public double findMedian() { + if (maxHeap.size() > minHeap.size()) { + return maxHeap.peek(); + } + return (maxHeap.peek() + minHeap.peek()) / 2.0; + } + } + } +} + + +================================================== +File: _411.java +Line count: 102 +================================================== +Content: +package com.fishercoder.solutions.firstthousand; + +import java.util.ArrayList; +import java.util.List; + +public class _411 { + public static class Solution1 { + /* + * Credit: https://discuss.leetcode.com/topic/61346/trie-bruteforce + */ + class Trie { + Trie[] children = new Trie[26]; + boolean isWord = false; + } + + Trie root = new Trie(); + List abbrs; + + public String minAbbreviation(String target, String[] dictionary) { + for (String s : dictionary) { + addTrie(s); + } + + for (int i = 0; i < target.length(); i++) { + abbrs = new ArrayList<>(); + abbrGenerator(target, 0, "", 0, i + 1); + for (String s : abbrs) { + if (search(s, root, 0, 0) == false) { + return s; + } + } + } + return ""; + } + + public void addTrie(String s) { + Trie cur = root; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (cur.children[c - 'a'] == null) { + cur.children[c - 'a'] = new Trie(); + } + cur = cur.children[c - 'a']; + } + cur.isWord = true; + } + + public boolean search(String target, Trie root, int i, int loop) { + if (root == null) { + return false; + } + + if (loop != 0) { + for (int a = 0; a < 26; a++) { + if (search(target, root.children[a], i, loop - 1)) { + return true; + } + } + return false; + } + if (i == target.length()) { + if (root.isWord) { + return true; + } + return false; + } + if (Character.isDigit(target.charAt(i))) { + int tmp = 0; + while (i < target.length() && Character.isDigit(target.charAt(i))) { + tmp = tmp * 10 + target.charAt(i) - '0'; + i++; + } + return search(target, root, i, tmp); + } else { + return search(target, root.children[target.charAt(i) - 'a'], i + 1, 0); + } + } + + public void abbrGenerator(String target, int i, String tmp, int abbr, int num) { + if (i == target.length()) { + if (num == 0 && abbr == 0) { + abbrs.add(tmp); + } + if (num == 1 && abbr != 0) { + abbrs.add(tmp + abbr); + } + return; + } + if (num <= 0) { + return; + } + char cur = target.charAt(i); + abbrGenerator( + target, + i + 1, + abbr == 0 ? tmp + cur : tmp + abbr + cur, + 0, + abbr == 0 ? num - 1 : num - 2); + abbrGenerator(target, i + 1, tmp, abbr + 1, num); + } + } +} + + diff --git a/src/main/java/com/fishercoder/file_count/fourththousand.txt b/src/main/java/com/fishercoder/file_count/fourththousand.txt new file mode 100644 index 0000000000..a493703203 --- /dev/null +++ b/src/main/java/com/fishercoder/file_count/fourththousand.txt @@ -0,0 +1,121 @@ +Total files count: 1 +================================================== +File: _3249.java +Line count: 113 +================================================== +Content: +package com.fishercoder.solutions.fourththousand; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class _3249 { + public static class Solution1 { + /* + * My completely original solution during the contest. + */ + class TreeNode { + int val; + List children; + int totalChildrenCount; + + public TreeNode(int val) { + this.val = val; + this.children = new ArrayList<>(); + this.totalChildrenCount = 1; // count itself as its own child + } + } + + int goodNodes = 0; + + public int countGoodNodes(int[][] edges) { + if (edges == null || edges.length == 0 || edges[0].length == 0) { + return 0; + } + TreeNode root = buildTree(edges); + postOrder(root); + dfs(root); + return goodNodes; + } + + private void dfs(TreeNode root) { + if (root == null || root.children.isEmpty()) { + return; + } + int size = root.children.get(0).totalChildrenCount; + if (size == 0) { + return; + } + boolean possiblyGoodNode = true; + for (TreeNode child : root.children) { + if (child.totalChildrenCount != size) { + possiblyGoodNode = false; + break; + } + } + if (possiblyGoodNode) { + goodNodes++; + } + for (TreeNode child : root.children) { + dfs(child); + } + } + + private int postOrder(TreeNode root) { + if (root == null) { + return 0; + } + if (root.children.isEmpty()) { + goodNodes++; + return 1; + } + int totalChildrenCount = 1; + for (TreeNode child : root.children) { + int count = postOrder(child); + totalChildrenCount += count; + } + root.totalChildrenCount = totalChildrenCount; + return totalChildrenCount; + } + + private TreeNode buildTree(int[][] edges) { + Map map = new HashMap<>(); + for (int i = 0; i < edges.length; i++) { + if (edges[i][0] == 0 || edges[i][1] == 0) { + if (edges[i][0] == 0) { + TreeNode parent = map.getOrDefault(edges[i][0], new TreeNode(edges[i][0])); + TreeNode child = map.getOrDefault(edges[i][1], new TreeNode(edges[i][1])); + parent.children.add(child); + map.put(edges[i][0], parent); + map.put(edges[i][1], child); + } else { + TreeNode parent = map.getOrDefault(edges[i][1], new TreeNode(edges[i][1])); + TreeNode child = map.getOrDefault(edges[i][0], new TreeNode(edges[i][0])); + parent.children.add(child); + map.put(edges[i][1], parent); + map.put(edges[i][0], child); + } + } else { + if (map.containsKey(edges[i][0])) { + TreeNode parent = map.get(edges[i][0]); + TreeNode child = map.getOrDefault(edges[i][1], new TreeNode(edges[i][1])); + parent.children.add(child); + map.put(edges[i][0], parent); + map.put(edges[i][1], child); + } else if (map.containsKey(edges[i][1])) { + TreeNode parent = map.get(edges[i][1]); + TreeNode child = map.getOrDefault(edges[i][0], new TreeNode(edges[i][0])); + parent.children.add(child); + map.put(edges[i][1], parent); + map.put(edges[i][0], child); + } + } + } + return map.get(0); + } + } +} + + diff --git a/src/main/java/com/fishercoder/file_count/secondthousand.txt b/src/main/java/com/fishercoder/file_count/secondthousand.txt new file mode 100644 index 0000000000..481222e412 --- /dev/null +++ b/src/main/java/com/fishercoder/file_count/secondthousand.txt @@ -0,0 +1,1604 @@ +Total files count: 12 +================================================== +File: _1209.java +Line count: 155 +================================================== +Content: +package com.fishercoder.solutions.secondthousand; + +import java.util.Deque; +import java.util.LinkedList; +import java.util.Stack; + +public class _1209 { + public static class Solution1 { + public String removeDuplicates(String s, int k) { + Stack stack = new Stack<>(); + char c = s.charAt(0); + stack.push(c); + int count = 1; + for (int i = 1; i < s.length(); i++) { + if (s.charAt(i) == c) { + count++; + stack.push(s.charAt(i)); + if (count == k) { + while (!stack.isEmpty() && stack.peek() == c) { + stack.pop(); + } + count = 0; + if (!stack.isEmpty()) { + c = stack.peek(); + while (!stack.isEmpty() && c == stack.peek()) { + count++; + stack.pop(); + } + int tmp = count; + while (tmp-- > 0) { + stack.push(c); + } + } + } + } else { + c = s.charAt(i); + stack.push(s.charAt(i)); + count = 1; + } + } + StringBuilder sb = new StringBuilder(); + while (!stack.isEmpty()) { + sb.append(stack.pop()); + } + return sb.reverse().toString(); + } + } + + public static class Solution2 { + public String removeDuplicates(String s, int k) { + StringBuilder sb = new StringBuilder(); + int dupCount = 0; + for (int i = 0; i < s.length(); i++) { + if (sb.length() != 0 && sb.charAt(sb.length() - 1) == s.charAt(i)) { + dupCount++; + } else { + dupCount = 1; + } + sb.append(s.charAt(i)); + if (dupCount == k) { + sb.setLength(sb.length() - k); + if (i + 1 < s.length()) { + dupCount = 0; + for (int j = sb.length() - 1; j >= 0; j--) { + if (sb.charAt(j) == s.charAt(i + 1)) { + dupCount++; + } else { + break; + } + } + } + } + } + return sb.toString(); + } + } + + public static class Solution3 { + /* + * My completely original solution on 1/6/2021. + */ + class CharCount { + char c; + int count; + + public CharCount(char c, int count) { + this.c = c; + this.count = count; + } + } + + public String removeDuplicates(String s, int k) { + Deque stack = new LinkedList<>(); + for (char c : s.toCharArray()) { + if (stack.isEmpty()) { + stack.addLast(new CharCount(c, 1)); + } else { + if (stack.peekLast().c == c && stack.peekLast().count + 1 == k) { + stack.pollLast(); + } else if (stack.peekLast().c == c) { + stack.addLast(new CharCount(c, stack.pollLast().count + 1)); + } else { + stack.addLast(new CharCount(c, 1)); + } + } + } + StringBuilder sb = new StringBuilder(); + while (!stack.isEmpty()) { + CharCount pair = stack.pollLast(); + int count = pair.count; + while (count-- > 0) { + sb.append(pair.c); + } + } + return sb.reverse().toString(); + } + } + + public static class Solution4 { + // my completely original solution on 6/19/2024 + public String removeDuplicates(String s, int k) { + Deque stack = new LinkedList<>(); + for (char c : s.toCharArray()) { + if (!stack.isEmpty() && stack.peekLast().c == c) { + Pair pair = stack.pollLast(); + pair.count = pair.count + 1; + if (pair.count < k) { + stack.addLast(pair); + } + } else { + stack.addLast(new Pair(c, 1)); + } + } + StringBuilder sb = new StringBuilder(); + while (!stack.isEmpty()) { + Pair pair = stack.pollLast(); + int count = pair.count; + while (count-- > 0) { + sb.append(pair.c); + } + } + return sb.reverse().toString(); + } + + class Pair { + char c; + int count; + + public Pair(char c, int count) { + this.c = c; + this.count = count; + } + } + } +} + + +================================================== +File: _1993.java +Line count: 121 +================================================== +Content: +package com.fishercoder.solutions.secondthousand; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class _1993 { + public static class Solution1 { + /* + * My completely original solution: + * 1. use hashmap1 to store num to node mapping; + * 2. use hashmap2 to store num to user lock mapping; + * 3. use hashmap3 to store child to parent mapping; + * 4. build the tree: make sure to retrieve the node from map if it exists, otherwise, the tree might be disconnected, i.e. leading to wrong ansers. + */ + public static class LockingTree { + class TreeNode { + List children; + int val; + + public TreeNode(int val) { + this.val = val; + this.children = new ArrayList<>(); + } + } + + Map map; + Map childToParentMap; + Map lockMap; + TreeNode root; + + public LockingTree(int[] parent) { + this.map = new HashMap<>(); + this.root = new TreeNode(0); + this.map.put(0, root); + this.childToParentMap = new HashMap<>(); + constructTree(parent, map, childToParentMap); + this.lockMap = new HashMap<>(); + } + + private void constructTree( + int[] parent, + Map map, + Map childToParentMap) { + for (int i = 1; i < parent.length; i++) { + TreeNode parentNode = map.getOrDefault(parent[i], new TreeNode(parent[i])); + TreeNode childNode = map.getOrDefault(i, new TreeNode(i)); + parentNode.children.add(childNode); + map.put(parent[i], parentNode); + map.put(i, childNode); + childToParentMap.put(childNode, parentNode); + } + } + + public boolean lock(int num, int user) { + if (lockMap.containsKey(num)) { + return false; + } else { + lockMap.put(num, user); + return true; + } + } + + public boolean unlock(int num, int user) { + if (!lockMap.containsKey(num)) { + return false; + } else if (lockMap.get(num) == user || user == -1) { + lockMap.remove(num); + return true; + } else { + return false; + } + } + + public boolean upgrade(int num, int user) { + if (hasLockedAncestor(num) || !hasOneLockedChild(num) || lockMap.containsKey(num)) { + return false; + } + lock(num, user); + List children = map.get(num).children; + for (TreeNode child : children) { + unlockRegardlessUser(child); + } + return true; + } + + private boolean hasOneLockedChild(int num) { + if (lockMap.containsKey(num)) { + return true; + } + TreeNode node = map.get(num); + for (TreeNode child : node.children) { + if (hasOneLockedChild(child.val)) { + return true; + } + } + return false; + } + + private boolean hasLockedAncestor(int num) { + TreeNode node = map.get(num); + while (childToParentMap.containsKey(node)) { + TreeNode parent = childToParentMap.get(node); + if (lockMap.containsKey(parent.val)) { + return true; + } + node = parent; + } + return false; + } + + private void unlockRegardlessUser(TreeNode treeNode) { + unlock(treeNode.val, -1); + for (TreeNode child : treeNode.children) { + unlockRegardlessUser(child); + } + } + } + } +} + + +================================================== +File: _1268.java +Line count: 162 +================================================== +Content: +package com.fishercoder.solutions.secondthousand; + +import java.util.ArrayList; +import java.util.List; + +public class _1268 { + public static class Solution1 { + public List> suggestedProducts(String[] products, String searchWord) { + TrieNode root = buildTrie(products); + List> result = new ArrayList<>(); + for (int i = 1; i <= searchWord.length(); i++) { + result.add(findTopThreeMatches(root, searchWord.substring(0, i))); + } + return result; + } + + private List findTopThreeMatches(TrieNode root, String searchTerm) { + List result = new ArrayList<>(); + TrieNode node = root; + for (char c : searchTerm.toCharArray()) { + if (node.children[c - 'a'] == null) { + return result; + } else { + node = node.children[c - 'a']; + } + } + if (node.isWord) { + result.add(searchTerm); + } + for (TrieNode child : node.children) { + if (child != null) { + List thisResult = dfs(child, searchTerm, new ArrayList<>()); + result.addAll(thisResult); + if (result.size() >= 3) { + return result.subList(0, 3); + } + } + } + return result; + } + + private List dfs(TrieNode node, String substring, List result) { + if (node.isWord) { + result.add(substring + node.c); + if (result.size() >= 3) { + return result; + } + } + for (TrieNode child : node.children) { + if (child != null) { + dfs(child, substring + node.c, result); + } + } + return result; + } + + private TrieNode buildTrie(String[] products) { + TrieNode root = new TrieNode(' '); + for (String pro : products) { + insert(pro, root); + } + return root; + } + + private void insert(String word, TrieNode root) { + TrieNode node = root; + for (int i = 0; i < word.length(); i++) { + char c = word.charAt(i); + if (node.children[c - 'a'] == null) { + node.children[c - 'a'] = new TrieNode(c); + } + node = node.children[c - 'a']; + } + node.isWord = true; + } + + class TrieNode { + TrieNode[] children; + boolean isWord; + char c; + + public TrieNode(char c) { + this.c = c; + this.children = new TrieNode[26]; + } + } + } + + public static class Solution2 { + public List> suggestedProducts(String[] products, String searchWord) { + TrieNode root = buildTrie(products); + List> result = new ArrayList<>(); + for (int i = 1; i <= searchWord.length(); i++) { + String searchTerm = searchWord.substring(0, i); + TrieNode tmp = root; + List searchResult = new ArrayList<>(); + for (int j = 0; j < searchTerm.length(); j++) { + char c = searchTerm.charAt(j); + if (tmp.children[c - 'a'] == null) { + break; + } else { + tmp = tmp.children[c - 'a']; + } + if (j == searchTerm.length() - 1) { + searchResult.addAll(findAllWords(tmp, searchTerm)); + } + } + result.add(searchResult.size() > 3 ? searchResult.subList(0, 3) : searchResult); + } + return result; + } + + private List findAllWords(TrieNode trieNode, String prefix) { + List result = new ArrayList<>(); + if (trieNode.isWord) { + result.add(prefix); + if (result.size() > 3) { + return result; + } + } + for (TrieNode node : trieNode.children) { + if (node != null) { + result.addAll(findAllWords(node, prefix + node.val)); + if (result.size() > 3) { + return result; + } + } + } + return result; + } + + private TrieNode buildTrie(String[] words) { + TrieNode root = new TrieNode(' '); + for (String word : words) { + TrieNode tmp = root; + for (int i = 0; i < word.length(); i++) { + char c = word.charAt(i); + if (tmp.children[c - 'a'] == null) { + tmp.children[c - 'a'] = new TrieNode(c); + } + tmp = tmp.children[c - 'a']; + if (i == word.length() - 1) { + tmp.isWord = true; + } + } + } + return root; + } + + public class TrieNode { + TrieNode[] children; + char val; + boolean isWord; + + public TrieNode(char val) { + this.children = new TrieNode[26]; + this.val = val; + this.isWord = false; + } + } + } +} + + +================================================== +File: _1087.java +Line count: 101 +================================================== +Content: +package com.fishercoder.solutions.secondthousand; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class _1087 { + public static class Solution1 { + public String[] expand(String s) { + List letters = parse(s); + List result = backtracking(letters, 0, new StringBuilder(), new ArrayList<>()); + String[] r = result.stream().toArray(String[]::new); + Arrays.sort(r); + return r; + } + + private List backtracking( + List letters, int start, StringBuilder sb, List result) { + if (start >= letters.size()) { + result.add(sb.toString()); + return result; + } + char[] chars = letters.get(start); + for (int i = 0; i < chars.length; i++) { + sb.append(chars[i]); + backtracking(letters, start + 1, sb, result); + sb.setLength(sb.length() - 1); + } + return result; + } + + private List parse(String s) { + List result = new ArrayList<>(); + for (int i = 0; i < s.length(); i++) { + if (s.charAt(i) == '{') { + int start = ++i; + while (i < s.length() && s.charAt(i) != '}') { + i++; + } + String[] strings = s.substring(start, i).split(","); + char[] chars = new char[strings.length]; + for (int j = 0; j < strings.length; j++) { + chars[j] = strings[j].charAt(0); + } + result.add(chars); + } else { + char[] chars = new char[1]; + chars[0] = s.charAt(i); + result.add(chars); + } + } + return result; + } + } + + public static class Solution2 { + /* + * My completely original solution on 1/17/2022. + */ + public String[] expand(String s) { + List list = new ArrayList<>(); + list.add(""); + for (int i = 0; i < s.length(); i++) { + List newList = new ArrayList<>(); + if (s.charAt(i) == '{') { + int j = i + 1; + while (s.charAt(j) != '}') { + j++; + } + String s2 = s.substring(i + 1, j); + String[] chars = s2.split("\\,"); + for (String c : chars) { + for (String sb : list) { + sb += c; + newList.add(sb); + } + } + i = j; + } else { + for (String sb : list) { + sb += s.charAt(i); + newList.add(sb); + } + } + list.clear(); + list.addAll(newList); + } + List res = new ArrayList<>(); + for (String sb : list) { + res.add(sb); + } + Collections.sort(res); + String[] ans = new String[res.size()]; + for (int i = 0; i < res.size(); i++) { + ans[i] = res.get(i); + } + return ans; + } + } +} + + +================================================== +File: _1110.java +Line count: 173 +================================================== +Content: +package com.fishercoder.solutions.secondthousand; + +import com.fishercoder.common.classes.TreeNode; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.Set; + +public class _1110 { + public static class Solution1 { + public List delNodes(TreeNode root, int[] toDelete) { + Queue queue = new LinkedList<>(); + queue.offer(root); + for (int d : toDelete) { + delete(d, queue); + } + List result = new ArrayList<>(); + while (!queue.isEmpty()) { + result.add(queue.poll()); + } + return result; + } + + private void delete(int toDelete, Queue queue) { + int size = queue.size(); + for (int i = 0; i < size; i++) { + TreeNode curr = queue.poll(); + if (delete(curr, toDelete, queue)) { + if (toDelete != curr.val) { + queue.offer(curr); + } + break; + } else { + queue.offer(curr); + } + } + } + + private boolean delete(TreeNode curr, int toDelete, Queue queue) { + if (curr == null) { + return false; + } else { + if (curr.val == toDelete) { + if (curr.left != null) { + queue.offer(curr.left); + } + if (curr.right != null) { + queue.offer(curr.right); + } + return true; + } else if (curr.left != null && curr.left.val == toDelete) { + if (curr.left.left != null) { + queue.offer(curr.left.left); + } + if (curr.left.right != null) { + queue.offer(curr.left.right); + } + curr.left = null; + return true; + } else if (curr.right != null && curr.right.val == toDelete) { + if (curr.right.left != null) { + queue.offer(curr.right.left); + } + if (curr.right.right != null) { + queue.offer(curr.right.right); + } + curr.right = null; + return true; + } + return delete(curr.left, toDelete, queue) || delete(curr.right, toDelete, queue); + } + } + } + + public static class Solution2 { + // use BFS + public List delNodes(TreeNode root, int[] toDelete) { + Set deleteSet = new HashSet<>(); + for (int d : toDelete) { + deleteSet.add(d); + } + Queue q = new LinkedList<>(); + q.offer(root); + List forest = new ArrayList<>(); + while (!q.isEmpty()) { + TreeNode curr = q.poll(); + + // process left child if any + if (curr.left != null) { + // add it into the q first because we need to process it any ways as it might + // have children that might not need to be deleted + q.offer(curr.left); + if (deleteSet.contains(curr.left.val)) { + curr.left = null; + } + } + + // process right child if any + if (curr.right != null) { + q.offer(curr.right); + if (deleteSet.contains(curr.right.val)) { + curr.right = null; + } + } + + // process this curr node: if it needs to be deleted, then add its non-null children + // into forest as we checked its children + // and we know they do not need to be deleted at this point + if (deleteSet.contains(curr.val)) { + if (curr.left != null) { + forest.add(curr.left); + } + if (curr.right != null) { + forest.add(curr.right); + } + } + // we don't add curr into forest here, otherwise there might be duplicate as we + // might have added them as their parent's child already + } + // at this point, only root might be missing, so we check root + if (!deleteSet.contains(root.val)) { + forest.add(root); + } + return forest; + } + } + + public static class Solution3 { + // use DFS/Post-order traversal + // key to recognize to apply post-order traversal: we need to handle subtree/children first + // before handling the root. + // it is in this case, handle children first in case children do not need to be removed and + // the parent needs to be removed, + // so we avoid the case of prematurely removing the parent before handling its children + // credit: https://leetcode.com/problems/delete-nodes-and-return-forest/editorial/ + public List delNodes(TreeNode root, int[] toDelete) { + List forest = new ArrayList<>(); + if (root == null) { + return forest; + } + Set deleteSet = new HashSet<>(); + for (int d : toDelete) { + deleteSet.add(d); + } + root = postOrder(root, deleteSet, forest); + if (root != null) { + forest.add(root); + } + return forest; + } + + private TreeNode postOrder(TreeNode root, Set deleteSet, List forest) { + if (root == null) { + return null; + } + root.left = postOrder(root.left, deleteSet, forest); + root.right = postOrder(root.right, deleteSet, forest); + if (deleteSet.contains(root.val)) { + if (root.left != null) { + forest.add(root.left); + } + if (root.right != null) { + forest.add(root.right); + } + // return null to its parent to delete the current node + return null; + } + return root; + } + } +} + + +================================================== +File: _1171.java +Line count: 108 +================================================== +Content: +package com.fishercoder.solutions.secondthousand; + +import com.fishercoder.common.classes.ListNode; +import java.util.*; + +public class _1171 { + public static class Solution1 { + /* + * I keep shrinking the array whenever I found there's a range of sum that equals to zero + * until the size of the list doesn't change any more. + * This is probably not super efficient, but accepted on LeetCode. + */ + public ListNode removeZeroSumSublists(ListNode head) { + List list = convertToList(head); + int size; + do { + size = list.size(); + list = shrinkList(list); + } while (list.size() != size); + return recoverLinkedList(list); + } + + private ListNode recoverLinkedList(List list) { + ListNode pre = new ListNode(-1); + ListNode tmp = pre; + for (int i = 0; i < list.size(); i++) { + tmp.next = new ListNode(list.get(i)); + tmp = tmp.next; + } + return pre.next; + } + + private List convertToList(ListNode head) { + List list = new ArrayList<>(); + while (head != null) { + if (head.val != 0) { + // if it's zero, we'll just ignore it, this can help us take care of the zero + // values + list.add(head.val); + } + head = head.next; + } + return list; + } + + private List shrinkList(List list) { + for (int i = 0; i < list.size(); i++) { + int start = i; + List preSumList = new ArrayList<>(); + for (int k = 0; k < start; k++) { + preSumList.add(0); + } + preSumList.add(list.get(i)); + for (int k = i; k < list.size(); k++) { + if (k > start) { + Integer sum = preSumList.get(k - 1) + list.get(k); + if (sum == 0) { + List shrinkedList = new ArrayList<>(); + for (int j = 0; j < start; j++) { + shrinkedList.add(list.get(j)); + } + for (int j = k + 1; j < list.size(); j++) { + shrinkedList.add(list.get(j)); + } + return shrinkedList; + } else { + preSumList.add(sum); + } + } + } + } + return list; + } + } + + public static class Solution2 { + /* + * credit: https://leetcode.com/problems/remove-zero-sum-consecutive-nodes-from-linked-list/discuss/366337/Java-Iterative-and-Recursive-solution + * this post explains it all + * key of the hashmap is the prefix sum of all the nodes we've gone so far + * value of the hashmap is the corresponding linked list node + */ + public ListNode removeZeroSumSublists(ListNode head) { + ListNode pre = new ListNode(-1); + ListNode curr = pre; + pre.next = head; + Map map = new HashMap<>(); + int preSum = 0; + while (curr != null) { + preSum += curr.val; + if (map.containsKey(preSum)) { + curr = map.get(preSum).next; + int key = preSum + curr.val; + while (key != preSum) { + map.remove(key); + curr = curr.next; + key += curr.val; + } + map.get(preSum).next = curr.next; + } else { + map.put(preSum, curr); + } + curr = curr.next; + } + return pre.next; + } + } +} + + +================================================== +File: _1721.java +Line count: 108 +================================================== +Content: +package com.fishercoder.solutions.secondthousand; + +import com.fishercoder.common.classes.ListNode; +import java.util.ArrayList; +import java.util.List; + +public class _1721 { + public static class Solution1 { + public ListNode swapNodes(ListNode head, int k) { + List list = new ArrayList<>(); + ListNode tmp = head; + while (tmp != null) { + list.add(tmp.val); + tmp = tmp.next; + } + int first = list.get(k - 1); + int size = list.size(); + int second = list.get(size - k); + list.remove(k - 1); + list.add(k - 1, second); + list.remove(size - k); + list.add(size - k, first); + ListNode pre = new ListNode(-1); + tmp = pre; + for (int i = 0; i < list.size(); i++) { + pre.next = new ListNode(list.get(i)); + pre = pre.next; + } + return tmp.next; + } + } + + public static class Solution2 { + public ListNode swapNodes(ListNode head, int k) { + if (head == null || head.next == null) { + return head; + } + + // find length of list + int n = 0; + ListNode current = head; + while (current != null) { + current = current.next; + n++; + } + + int[] nums = new int[n]; + current = head; + int i = 0; + while (current != null) { + nums[i++] = current.val; + current = current.next; + } + int firstIndex; + int secondIndex; + firstIndex = k; + secondIndex = n - k; + int temp = nums[firstIndex - 1]; + nums[firstIndex - 1] = nums[secondIndex]; + nums[secondIndex] = temp; + ListNode dummy = new ListNode(-1); + current = dummy; + for (i = 0; i < n; i++) { + ListNode node = new ListNode(nums[i]); + current.next = node; + current = current.next; + } + return dummy.next; + } + } + + public static class Solution3 { + public ListNode swapNodes(ListNode head, int k) { + // O(n) linear time + /* + 1. Calculate length of linked list + 2. Initialize 3 ptrs, temp1 and temp2 used for pointing to nodes at k, (len - k + 1) + and temp3 used to iterate over the linked list + */ + int length = 0; + int secondIndex; + + ListNode temp1 = null; + ListNode temp2 = null; + ListNode temp3 = head; + while (temp3 != null) { + length++; + temp3 = temp3.next; + } + + secondIndex = length - k + 1; + temp3 = head; + for (int i = 1; i <= length; i++) { + if (i == k) { + temp1 = temp3; + } + if (i == secondIndex) { + temp2 = temp3; + } + temp3 = temp3.next; + } + int value = temp1.val; + temp1.val = temp2.val; + temp2.val = value; + return head; + } + } +} + + +================================================== +File: _1170.java +Line count: 113 +================================================== +Content: +package com.fishercoder.solutions.secondthousand; + +import java.util.Arrays; + +public class _1170 { + public static class Solution1 { + /* + * Use simple iteration when finding counts + * Time: O(n^m) where m is the size of queries and n is the size of words + * Space: O(max(m, n) where m is the size of queries and n is the size of words) + */ + public int[] numSmallerByFrequency(String[] queries, String[] words) { + int[] queriesMinFrequecies = new int[queries.length]; + for (int i = 0; i < queries.length; i++) { + queriesMinFrequecies[i] = computeLowestFrequency(queries[i]); + } + + int[] wordsMinFrequecies = new int[words.length]; + for (int i = 0; i < words.length; i++) { + wordsMinFrequecies[i] = computeLowestFrequency(words[i]); + } + Arrays.sort(wordsMinFrequecies); + + int[] result = new int[queries.length]; + for (int i = 0; i < result.length; i++) { + result[i] = search(wordsMinFrequecies, queriesMinFrequecies[i]); + } + return result; + } + + private int search(int[] nums, int target) { + int count = 0; + for (int i = nums.length - 1; i >= 0; i--) { + if (nums[i] > target) { + count++; + } else { + break; + } + } + return count; + } + + private int computeLowestFrequency(String string) { + char[] str = string.toCharArray(); + Arrays.sort(str); + String sortedString = new String(str); + int frequency = 1; + for (int i = 1; i < sortedString.length(); i++) { + if (sortedString.charAt(i) == sortedString.charAt(0)) { + frequency++; + } else { + break; + } + } + return frequency; + } + } + + public static class Solution2 { + /* + * Use binary search when finding counts + * Time: O(n^logn) where m is the size of queries and n is the size of words + * Space: O(max(m, n) where m is the size of queries and n is the size of words) + */ + public int[] numSmallerByFrequency(String[] queries, String[] words) { + int[] queriesMinFrequecies = new int[queries.length]; + for (int i = 0; i < queries.length; i++) { + queriesMinFrequecies[i] = computeLowestFrequency(queries[i]); + } + + int[] wordsMinFrequecies = new int[words.length]; + for (int i = 0; i < words.length; i++) { + wordsMinFrequecies[i] = computeLowestFrequency(words[i]); + } + Arrays.sort(wordsMinFrequecies); + + int[] result = new int[queries.length]; + for (int i = 0; i < result.length; i++) { + result[i] = binarySearch(wordsMinFrequecies, queriesMinFrequecies[i]); + } + return result; + } + + private int binarySearch(int[] nums, int target) { + int left = 0; + int right = nums.length - 1; + while (left <= right) { + int mid = (left + right) / 2; + if (nums[mid] <= target) { + left = mid + 1; + } else { + right = mid - 1; + } + } + return nums.length - left; + } + + private int computeLowestFrequency(String string) { + char[] str = string.toCharArray(); + Arrays.sort(str); + String sortedString = new String(str); + int frequency = 1; + for (int i = 1; i < sortedString.length(); i++) { + if (sortedString.charAt(i) == sortedString.charAt(0)) { + frequency++; + } else { + break; + } + } + return frequency; + } + } +} + + +================================================== +File: _1644.java +Line count: 107 +================================================== +Content: +package com.fishercoder.solutions.secondthousand; + +import com.fishercoder.common.classes.TreeNode; + +public class _1644 { + public static class Solution1 { + /* + * This is my not so elegant but original solution to get it accepted. + */ + boolean[] exists = new boolean[2]; + + public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { + exists(p, root, 0); + exists(q, root, 1); + if (!exists[0] || !exists[1]) { + return null; + } + return dfs(root, p, q); + } + + private void exists(TreeNode target, TreeNode root, int index) { + if (root == null) { + return; + } + if (target == root) { + exists[index] = true; + return; + } + if (!exists[index]) { + exists(target, root.left, index); + } + if (!exists[index]) { + exists(target, root.right, index); + } + } + + private TreeNode dfs(TreeNode root, TreeNode p, TreeNode q) { + if (root == null || p == root || q == root) { + return root; + } + TreeNode left = lowestCommonAncestor(root.left, p, q); + TreeNode right = lowestCommonAncestor(root.right, p, q); + if (left != null && right != null) { + return root; + } + return left != null ? left : right; + } + } + + public static class Solution2 { + /* + * This still checks nodes existence. + */ + int found = 0; + + public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { + TreeNode lca = lca(root, p, q); + return found == 2 ? lca : null; + } + + private TreeNode lca(TreeNode root, TreeNode p, TreeNode q) { + if (root == null) { + return null; + } + TreeNode left = lca(root.left, p, q); + TreeNode right = lca(root.right, p, q); + if (root == p || root == q) { + found++; + return root; + } + return (left != null && right != null) ? root : left != null ? left : right; + } + } + + public static class Solution3 { + /* + * Credit: https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree-ii/solutions/944963/beat-96-recursion-without-count-easy-understanding/ + */ + public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { + if (root == null || p == null || q == null) { + return null; + } + TreeNode result = findLCA(root, p, q); + if (result == p) { + // if p equals result, we'll check the existence of q in the subtree of p + return findLCA(p, q, q) != null ? result : null; + } else if (result == q) { + // if q equals result, we'll check the existence of p in the subtree of q + return findLCA(q, p, p) != null ? result : null; + } + // otherwise, it's this case: (p != result && q != result) || result == null + return result; + } + + private TreeNode findLCA(TreeNode root, TreeNode p, TreeNode q) { + if (root == null || p == root || q == root) { + return root; + } + TreeNode left = findLCA(root.left, p, q); + TreeNode right = findLCA(root.right, p, q); + if (left != null && right != null) { + return root; + } + return left != null ? left : right; + } + } +} + + +================================================== +File: _1104.java +Line count: 135 +================================================== +Content: +package com.fishercoder.solutions.secondthousand; + +import com.fishercoder.common.classes.TreeNode; +import com.fishercoder.common.utils.CommonUtils; +import com.fishercoder.common.utils.TreeUtils; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +public class _1104 { + public static class Solution1 { + /* + * This brute force solution is correct but results in TLE on LeetCode. + */ + public List pathInZigZagTree(int label) { + Deque deque = buildZigZagOrderList(label); + CommonUtils.printDeque(deque); + TreeNode root = buildZigZagOrderTree(deque); + TreeUtils.printBinaryTree(root); + return dfs(root, label, new ArrayList<>()); + } + + private List dfs(TreeNode root, int label, List list) { + if (root == null) { + return list; + } + list.add(root.val); + if (root.val == label) { + return list; + } + dfs(root.left, label, list); + dfs(root.right, label, list); + if (list.get(list.size() - 1) == label) { + return list; + } + list.remove(list.size() - 1); + return list; + } + + private TreeNode buildZigZagOrderTree(Deque deque) { + TreeNode root = new TreeNode(deque.pollFirst()); + Queue queue = new LinkedList<>(); + queue.offer(root); + while (!deque.isEmpty()) { + int size = queue.size(); + for (int i = 0; i < size; i++) { + TreeNode curr = queue.poll(); + curr.left = new TreeNode(deque.pollFirst()); + curr.right = new TreeNode(deque.pollFirst()); + queue.offer(curr.left); + queue.offer(curr.right); + } + } + return root; + } + + private Deque buildZigZagOrderList(int label) { + Deque deque = new LinkedList<>(); + int num = 1; + int level = 2; + deque.add(num); + do { + num++; + List newLevel = new ArrayList<>(); + for (; num < Math.pow(2, level); num++) { + newLevel.add(num); + } + num--; + if (level % 2 == 0) { + Collections.reverse(newLevel); + } + deque.addAll(newLevel); + newLevel.clear(); + level++; + } while (deque.getLast() < label); + return deque; + } + } + + public static class Solution2 { + /* + * We'll directly compute the index of its parent, it'll be much faster this way. + */ + public List pathInZigZagTree(int label) { + List> lists = buildZigZagOrderList(label); + List result = new ArrayList<>(); + int index = findIndex(lists.get(lists.size() - 1), label); + result.add(label); + for (int i = lists.size() - 2; i >= 0; i--) { + index /= 2; + result.add(lists.get(i).get(index)); + } + Collections.sort(result); + return result; + } + + private int findIndex(List level, int label) { + for (int i = 0; i < level.size(); i++) { + if (level.get(i) == label) { + return i; + } + } + return -1; + } + + private List> buildZigZagOrderList(int label) { + List> lists = new ArrayList<>(); + int num = 1; + int level = 2; + lists.add(Arrays.asList(num)); + if (label == 1) { + return lists; + } + List newLevel = new ArrayList<>(); + do { + newLevel.clear(); + num++; + for (; num < Math.pow(2, level); num++) { + newLevel.add(num); + } + num--; + if (level % 2 == 0) { + Collections.reverse(newLevel); + } + lists.add(new ArrayList<>(newLevel)); + level++; + } while (newLevel.get(0) < label && newLevel.get(newLevel.size() - 1) < label); + return lists; + } + } +} + + +================================================== +File: _1325.java +Line count: 129 +================================================== +Content: +package com.fishercoder.solutions.secondthousand; + +import com.fishercoder.common.classes.TreeNode; + +/* + * 1325. Delete Leaves With a Given Value + * + * Given a binary tree root and an integer target, delete all the leaf nodes with value target. + * Note that once you delete a leaf node with value target, if it's parent node becomes a leaf node and has the value target, + * it should also be deleted (you need to continue doing that until you can't). + * + * Example 1: + * 1 1 1 + * / \ / \ \ + * 2 3 -> 2 3 -> 3 + * / / \ \ \ + * 2 2 4 4 4 + * + * Input: root = [1,2,3,2,null,2,4], target = 2 + * Output: [1,null,3,null,4] + * Explanation: Leaf nodes in green with value (target = 2) are removed (Picture in left). + * After removing, new nodes become leaf nodes with value (target = 2) (Picture in center). + * + * Example 2: + * 1 1 + * / \ / + * 3 3 -> 3 + * / \ \ + * 3 2 2 + * + * Input: root = [1,3,3,3,2], target = 3 + * Output: [1,3,null,null,2] + * + * Example 3: + * 1 1 1 1 + * / / / + * 2 2 2 + * / -> / -> -> + * 2 2 + * / + * 2 + * + * Input: root = [1,2,null,2,null,2], target = 2 + * Output: [1] + * Explanation: Leaf nodes in green with value (target = 2) are removed at each step. + * + * Example 4: + * 1 + * / \ -> + * 1 1 + * Input: root = [1,1,1], target = 1 + * Output: [] + * + * Example 5: + * 1 1 + * / \ -> / \ + * 2 3 2 3 + * + * Input: root = [1,2,3], target = 1 + * Output: [1,2,3] + * + * Constraints: + * 1 <= target <= 1000 + * Each tree has at most 3000 nodes. + * Each node's value is between [1, 1000]. + * */ +public class _1325 { + public static class Solution1 { + /* + * my original but verbose solution + */ + public TreeNode removeLeafNodes(TreeNode root, int target) { + while (hasTargetLeafNodes(root, target)) { + root = removeLeafNodes(target, root); + } + return root; + } + + private TreeNode removeLeafNodes(int target, TreeNode root) { + if (root == null) { + return root; + } + if (root.val == target && root.left == null && root.right == null) { + root = null; + return root; + } + if (root.left != null + && root.left.val == target + && root.left.left == null + && root.left.right == null) { + root.left = null; + } + if (root.right != null + && root.right.val == target + && root.right.left == null + && root.right.right == null) { + root.right = null; + } + removeLeafNodes(target, root.left); + removeLeafNodes(target, root.right); + return root; + } + + private boolean hasTargetLeafNodes(TreeNode root, int target) { + if (root == null) { + return false; + } + if (root.left == null && root.right == null && root.val == target) { + return true; + } + return hasTargetLeafNodes(root.left, target) || hasTargetLeafNodes(root.right, target); + } + } + + public static class Solution2 { + /*A much more concise and efficient solution.*/ + public TreeNode removeLeafNodes(TreeNode root, int target) { + if (root == null) { + return root; + } + root.left = removeLeafNodes(root.left, target); + root.right = removeLeafNodes(root.right, target); + if (root.left == null && root.right == null && root.val == target) { + return null; + } + return root; + } + } +} + + +================================================== +File: _1466.java +Line count: 107 +================================================== +Content: +package com.fishercoder.solutions.secondthousand; + +import java.util.*; + +public class _1466 { + public static class Solution1 { + public int minReorder(int n, int[][] connections) { + // key is entering city, value is departure city + Map> map = new HashMap<>(); + Queue queue = new LinkedList<>(); + int minReorder = 0; + Set visited = new HashSet<>(); + for (int i = 0; i < n; i++) { + visited.add(i); + } + + // key is departure city, value is entering city + Map> reverseMap = new HashMap<>(); + for (int[] con : connections) { + if (!map.containsKey(con[1])) { + map.put(con[1], new HashSet<>()); + } + map.get(con[1]).add(con[0]); + + if (!reverseMap.containsKey(con[0])) { + reverseMap.put(con[0], new HashSet<>()); + } + reverseMap.get(con[0]).add(con[1]); + + // for all those directly connected to city 0, must be reordered if not yet + // and they are the start nodes of BFS + if (con[0] == 0) { + minReorder++; + queue.offer(con[1]); + visited.remove(con[1]); + visited.remove(0); + } + if (con[1] == 0) { + queue.offer(con[0]); + visited.remove(0); + } + } + while (!queue.isEmpty() || !visited.isEmpty()) { + int curr = queue.poll(); + visited.remove(curr); + if (map.containsKey(curr)) { + Set departureCityList = map.get(curr); + for (int city : departureCityList) { + if (visited.contains(city)) { + queue.offer(city); + } + } + } + if (reverseMap.containsKey(curr)) { + Set enteringCityList = reverseMap.get(curr); + for (int city : enteringCityList) { + if (visited.contains(city)) { + queue.offer(city); + minReorder++; + } + } + } + } + return minReorder; + } + } + + public static class Solution2 { + /* + * build an adjacency list and BFS + */ + public int minReorder(int n, int[][] connections) { + // int[] in the below map holds two integers, the first one means the node, the second + // one means the direction: + // 0 means it's pointing to the key, i.e. doesn't need to be flipped, + // 1 means it's the opposite direction, i.e. needs to be flipped + Map> adjList = new HashMap<>(); + for (int[] conn : connections) { + adjList.computeIfAbsent(conn[0], k -> new ArrayList<>()) + .add(new int[] {conn[1], 1}); + adjList.computeIfAbsent(conn[1], k -> new ArrayList<>()) + .add(new int[] {conn[0], 0}); + } + int count = 0; + Queue queue = new LinkedList<>(); + queue.offer(0); + boolean[] visited = new boolean[n]; + visited[0] = true; + while (!queue.isEmpty()) { + Integer curr = queue.poll(); + if (!adjList.containsKey(curr)) { + continue; + } + for (int[] next : adjList.get(curr)) { + int neighbor = next[0]; + int flip = next[1]; + if (!visited[neighbor]) { + count += flip; + visited[neighbor] = true; + queue.offer(neighbor); + } + } + } + return count; + } + } +} + + diff --git a/src/main/java/com/fishercoder/file_count/thirdthousand.txt b/src/main/java/com/fishercoder/file_count/thirdthousand.txt new file mode 100644 index 0000000000..b5c82849ca --- /dev/null +++ b/src/main/java/com/fishercoder/file_count/thirdthousand.txt @@ -0,0 +1,359 @@ +Total files count: 3 +================================================== +File: _2673.java +Line count: 110 +================================================== +Content: +package com.fishercoder.solutions.thirdthousand; + +public class _2673 { + public static class Solution1 { + /* + * My completely original solution, although verbose and could be further optimized. + * Practice makes perfect! + */ + class TreeNodeWithCost { + int val; + int cost; + int costSumFromRootToThisNode; + int maxCostFromThisNodeToAllPossibleLeafNodes; + TreeNodeWithCost left; + TreeNodeWithCost right; + + public TreeNodeWithCost(int val, int cost) { + this.val = val; + this.cost = cost; + this.costSumFromRootToThisNode = cost; + } + } + + int maxCostFromRootToLeaf = 0; + int minIncs = 0; + + public int minIncrements(int n, int[] cost) { + TreeNodeWithCost root = new TreeNodeWithCost(1, cost[0]); + preOrderBuildTree(root, n, cost, 1); + inOrderFindMaxCostPath(root); + // in order to do the minimum increments, we want to increment as many times as possible + // on the nodes as close to the root as possible + // but to how many? + // then we need to know the maximum cost of all paths from each node to all of its + // possible leaf nodes + // the difference is the number of increments we can do on this node + postOrderFindMaxCostForEachNode(root); + preOrderToIncrementCost(root); + return minIncs; + } + + private void preOrderToIncrementCost(TreeNodeWithCost root) { + if (root == null) { + return; + } + int incsNeeded = maxCostFromRootToLeaf - root.maxCostFromThisNodeToAllPossibleLeafNodes; + minIncs += incsNeeded; + if (incsNeeded > 0) { + root.costSumFromRootToThisNode += incsNeeded; + preOrderToUpdateCostSums(root, incsNeeded); + } + preOrderToIncrementCost(root.left); + preOrderToIncrementCost(root.right); + } + + private void preOrderToUpdateCostSums(TreeNodeWithCost root, int incsNeeded) { + if (root == null) { + return; + } + root.costSumFromRootToThisNode += incsNeeded; + root.maxCostFromThisNodeToAllPossibleLeafNodes += incsNeeded; + preOrderToUpdateCostSums(root.left, incsNeeded); + preOrderToUpdateCostSums(root.right, incsNeeded); + } + + private int postOrderFindMaxCostForEachNode(TreeNodeWithCost node) { + if (node == null) { + return 0; + } + int leftMaxCost = postOrderFindMaxCostForEachNode(node.left); + int rightMaxCost = postOrderFindMaxCostForEachNode(node.right); + if (leftMaxCost == 0 && rightMaxCost == 0) { + // this means this node is a leaf node + node.maxCostFromThisNodeToAllPossibleLeafNodes = node.costSumFromRootToThisNode; + } else { + // if it's not leaf node, then we take the bigger one from left and right + node.maxCostFromThisNodeToAllPossibleLeafNodes = + Math.max(leftMaxCost, rightMaxCost); + } + return node.maxCostFromThisNodeToAllPossibleLeafNodes; + } + + private void inOrderFindMaxCostPath(TreeNodeWithCost root) { + if (root == null) { + return; + } + inOrderFindMaxCostPath(root.left); + if (root.left == null && root.right == null) { + maxCostFromRootToLeaf = + Math.max(maxCostFromRootToLeaf, root.costSumFromRootToThisNode); + } + inOrderFindMaxCostPath(root.right); + } + + private int preOrderBuildTree(TreeNodeWithCost root, int n, int[] cost, int base) { + if (root == null || base * 2 >= n) { + return 0; + } + + root.left = new TreeNodeWithCost(base * 2, cost[base * 2 - 1]); + root.left.costSumFromRootToThisNode = root.left.cost + root.costSumFromRootToThisNode; + root.right = new TreeNodeWithCost(base * 2 + 1, cost[base * 2]); + root.right.costSumFromRootToThisNode = root.right.cost + root.costSumFromRootToThisNode; + + preOrderBuildTree(root.left, n, cost, base * 2); + preOrderBuildTree(root.right, n, cost, base * 2 + 1); + return root.costSumFromRootToThisNode; + } + } +} + + +================================================== +File: _2812.java +Line count: 126 +================================================== +Content: +package com.fishercoder.solutions.thirdthousand; + +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +public class _2812 { + public static class Solution1 { + /* + * A great problem, credit: https://leetcode.com/problems/find-the-safest-path-in-a-grid/editorial/ + *

+ * BFS twice: + * 1. once: to build the safeness factor for each cell; + * 2. second time: check if there's a valid path from that cell; + */ + final int[] dirs = new int[] {0, 1, 0, -1, 0}; + + public int maximumSafenessFactor(List> grid) { + int n = grid.size(); + int[][] mat = new int[n][n]; + Queue multiSourceQueue = new LinkedList<>(); + + // To make modifications and navigation easier, the grid is converted into a 2-d array. + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + if (grid.get(i).get(j) == 1) { + // Push thief coordinates to the queue + multiSourceQueue.add(new int[] {i, j}); + // Mark thief cell with 0 + mat[i][j] = 0; + } else { + // Mark empty cell with -1 + mat[i][j] = -1; + } + } + } + + // Calculate safeness factor for each cell using BFS + while (!multiSourceQueue.isEmpty()) { + int size = multiSourceQueue.size(); + while (size-- > 0) { + int[] curr = multiSourceQueue.poll(); + // Check neighboring cells + for (int k = 0; k < dirs.length - 1; k++) { + int di = curr[0] + dirs[k]; + int dj = curr[1] + dirs[k + 1]; + int val = mat[curr[0]][curr[1]]; + // Check if the neighboring cell is valid and unvisited + if (isValidCell(mat, di, dj) && mat[di][dj] == -1) { + // Update safeness factor and push to the queue + mat[di][dj] = val + 1; + multiSourceQueue.add(new int[] {di, dj}); + } + } + } + } + + // Binary search for maximum safeness factor + int start = 0; + int end = 0; + int res = -1; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + // Set end as the maximum safeness factor possible + end = Math.max(end, mat[i][j]); + } + } + + while (start <= end) { + int mid = start + (end - start) / 2; + if (isValidSafeness(mat, mid)) { + // Store valid safeness and search for larger ones + res = mid; + start = mid + 1; + } else { + end = mid - 1; + } + } + return res; + } + + // Check if a path exists with given minimum safeness value + private boolean isValidSafeness(int[][] grid, int minSafeness) { + int n = grid.length; + + // Check if the source and destination cells satisfy minimum safeness + if (grid[0][0] < minSafeness || grid[n - 1][n - 1] < minSafeness) { + return false; + } + + Queue traversalQueue = new LinkedList<>(); + traversalQueue.add(new int[] {0, 0}); + boolean[][] visited = new boolean[n][n]; + visited[0][0] = true; + + // BFS to find a valid path + while (!traversalQueue.isEmpty()) { + int[] curr = traversalQueue.poll(); + if (curr[0] == n - 1 && curr[1] == n - 1) { + return true; // Valid path found + } + // Check neighboring cells + for (int k = 0; k < dirs.length - 1; k++) { + int di = curr[0] + dirs[k]; + int dj = curr[1] + dirs[k + 1]; + // Check if the neighboring cell is valid, unvisited and satisfying minimum + // safeness + if (isValidCell(grid, di, dj) + && !visited[di][dj] + && grid[di][dj] >= minSafeness) { + visited[di][dj] = true; + traversalQueue.add(new int[] {di, dj}); + } + } + } + + return false; // No valid path found + } + + // Check if a given cell lies within the grid + private boolean isValidCell(int[][] mat, int i, int j) { + int n = mat.length; + return i >= 0 && j >= 0 && i < n && j < n; + } + } +} + + +================================================== +File: _2049.java +Line count: 101 +================================================== +Content: +package com.fishercoder.solutions.thirdthousand; + +import com.fishercoder.common.classes.TreeNode; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class _2049 { + public static class Solution1 { + /* + * My completely original solution. + * Practice makes perfect! + */ + public int countHighestScoreNodes(int[] parents) { + Map valToNodeMap = new HashMap<>(); + TreeNode root = buildBinaryTree(parents, valToNodeMap); + + // it'll be handy if we can cache the number of children each node has as we'll do this + // many times, so we can quickly calculate the score for each node + // key is the node since each node's value is unique, value if the number of children + // this node has + Map nodeCountMap = new HashMap<>(); + // naturally we should use post-order traversal since we need to count the children for + // each child first, then we can roll up to add one to get the number of children for + // the root node + long allNodeCount = postOrder(root, nodeCountMap); + nodeCountMap.put(root.val, allNodeCount); + + // now calculate the score of each node + List scoreList = new ArrayList<>(); + long highestScore = 0; + for (int i = 0; i < parents.length; i++) { + long score = computeScore(i, nodeCountMap, valToNodeMap); + highestScore = Math.max(score, highestScore); + scoreList.add(score); + } + int count = 0; + for (long score : scoreList) { + if (score == highestScore) { + count++; + } + } + return count; + } + + private Long computeScore( + int nodeVal, Map nodeCountMap, Map nodeValueMap) { + // since this is a binary tree, so, at most, removing a node, it'll split the original + // tree into three disjoint trees + TreeNode node = nodeValueMap.get(nodeVal); + Long leftSubtree = 1L; + Long rightSubtree = 1L; + Long parentSubtree = 1L; + if (node.left != null) { + if (nodeCountMap.get(node.left.val) > 0) { + leftSubtree = nodeCountMap.get(node.left.val); + } + } + if (node.right != null) { + if (nodeCountMap.get(node.right.val) > 0) { + rightSubtree = nodeCountMap.get(node.right.val); + } + } + if (nodeVal != 0) { + long diff = nodeCountMap.get(0) - nodeCountMap.get(nodeVal); + if (diff > 0) { + parentSubtree = diff; + } + } + return leftSubtree * rightSubtree * parentSubtree; + } + + private long postOrder(TreeNode root, Map map) { + if (root == null) { + return 0; + } + long leftCount = postOrder(root.left, map); + long rightCount = postOrder(root.right, map); + long sum = leftCount + rightCount + 1; + map.put(root.val, sum); + return sum; + } + + private TreeNode buildBinaryTree(int[] parents, Map map) { + map.put(0, new TreeNode(0)); + for (int i = 1; i < parents.length; i++) { + TreeNode childNode = map.getOrDefault(i, new TreeNode(i)); + TreeNode parentNode = map.getOrDefault(parents[i], new TreeNode(parents[i])); + if (parentNode.left == null) { + parentNode.left = childNode; + } else { + parentNode.right = childNode; + } + map.put(parents[i], parentNode); + map.put(i, childNode); + } + return map.get(0); + } + } +} + +