diff --git a/articles/accounts-merge.md b/articles/accounts-merge.md new file mode 100644 index 000000000..665334798 --- /dev/null +++ b/articles/accounts-merge.md @@ -0,0 +1,932 @@ +## 1. Depth First Search + +::tabs-start + +```python +class Solution: + def accountsMerge(self, accounts: List[List[str]]) -> List[List[str]]: + n = len(accounts) + emailIdx = {} # email -> id + emails = [] # set of emails of all accounts + emailToAcc = {} # email_index -> account_Id + + m = 0 + for accId, a in enumerate(accounts): + for i in range(1, len(a)): + email = a[i] + if email in emailIdx: + continue + emails.append(email) + emailIdx[email] = m + emailToAcc[m] = accId + m += 1 + + adj = [[] for _ in range(m)] + for a in accounts: + for i in range(2, len(a)): + id1 = emailIdx[a[i]] + id2 = emailIdx[a[i - 1]] + adj[id1].append(id2) + adj[id2].append(id1) + + emailGroup = defaultdict(list) # index of acc -> list of emails + visited = [False] * m + def dfs(node, accId): + visited[node] = True + emailGroup[accId].append(emails[node]) + for nei in adj[node]: + if not visited[nei]: + dfs(nei, accId) + + for i in range(m): + if not visited[i]: + dfs(i, emailToAcc[i]) + + res = [] + for accId in emailGroup: + name = accounts[accId][0] + res.append([name] + sorted(emailGroup[accId])) + + return res +``` + +```java +public class Solution { + private Map emailIdx = new HashMap<>(); // email -> id + private List emails = new ArrayList<>(); // set of emails of all accounts + private Map emailToAcc = new HashMap<>(); // email_index -> account_Id + private List> adj; + private Map> emailGroup = new HashMap<>(); // index of acc -> list of emails + private boolean[] visited; + + public List> accountsMerge(List> accounts) { + int n = accounts.size(); + int m = 0; + + // Build email index and mappings + for (int accId = 0; accId < n; accId++) { + List account = accounts.get(accId); + for (int i = 1; i < account.size(); i++) { + String email = account.get(i); + if (!emailIdx.containsKey(email)) { + emails.add(email); + emailIdx.put(email, m); + emailToAcc.put(m, accId); + m++; + } + } + } + + // Build adjacency list + adj = new ArrayList<>(); + for (int i = 0; i < m; i++) { + adj.add(new ArrayList<>()); + } + for (List account : accounts) { + for (int i = 2; i < account.size(); i++) { + int id1 = emailIdx.get(account.get(i)); + int id2 = emailIdx.get(account.get(i - 1)); + adj.get(id1).add(id2); + adj.get(id2).add(id1); + } + } + + // Initialize visited array + visited = new boolean[m]; + + // DFS traversal + for (int i = 0; i < m; i++) { + if (!visited[i]) { + int accId = emailToAcc.get(i); + emailGroup.putIfAbsent(accId, new ArrayList<>()); + dfs(i, accId); + } + } + + // Build result + List> res = new ArrayList<>(); + for (int accId : emailGroup.keySet()) { + List group = emailGroup.get(accId); + Collections.sort(group); + List merged = new ArrayList<>(); + merged.add(accounts.get(accId).get(0)); + merged.addAll(group); + res.add(merged); + } + + return res; + } + + private void dfs(int node, int accId) { + visited[node] = true; + emailGroup.get(accId).add(emails.get(node)); + for (int neighbor : adj.get(node)) { + if (!visited[neighbor]) { + dfs(neighbor, accId); + } + } + } +} +``` + +```cpp +class Solution { + unordered_map emailIdx; // email -> id + vector emails; // set of emails of all accounts + unordered_map emailToAcc; // email_index -> account_Id + vector> adj; + unordered_map> emailGroup; // index of acc -> list of emails + vector visited; + +public: + vector> accountsMerge(vector>& accounts) { + int n = accounts.size(); + int m = 0; + + // Build email index and mappings + for (int accId = 0; accId < n; accId++) { + vector& account = accounts[accId]; + for (int i = 1; i < account.size(); i++) { + string& email = account[i]; + if (emailIdx.find(email) == emailIdx.end()) { + emails.push_back(email); + emailIdx[email] = m; + emailToAcc[m] = accId; + m++; + } + } + } + + // Build adjacency list + adj.resize(m); + for (auto& account : accounts) { + for (int i = 2; i < account.size(); i++) { + int id1 = emailIdx[account[i]]; + int id2 = emailIdx[account[i - 1]]; + adj[id1].push_back(id2); + adj[id2].push_back(id1); + } + } + + visited.resize(m, false); + // DFS traversal + for (int i = 0; i < m; i++) { + if (!visited[i]) { + int accId = emailToAcc[i]; + dfs(i, accId); + } + } + + // Build result + vector> res; + for (auto& [accId, group] : emailGroup) { + sort(group.begin(), group.end()); + vector merged; + merged.push_back(accounts[accId][0]); + merged.insert(merged.end(), group.begin(), group.end()); + res.push_back(merged); + } + + return res; + } + +private: + void dfs(int node, int& accId) { + visited[node] = true; + emailGroup[accId].push_back(emails[node]); + for (int& neighbor : adj[node]) { + if (!visited[neighbor]) { + dfs(neighbor, accId); + } + } + } +}; +``` + +```javascript +class Solution { + /** + * @param {string[][]} accounts + * @return {string[][]} + */ + accountsMerge(accounts) { + const emailIdx = new Map(); // email -> id + const emails = []; // set of emails of all accounts + const emailToAcc = new Map(); // email_index -> account_Id + const adj = []; + const emailGroup = new Map(); // index of acc -> list of emails + let visited = []; + + const n = accounts.length; + let m = 0; + + // Build email index and mappings + for (let accId = 0; accId < n; accId++) { + const account = accounts[accId]; + for (let i = 1; i < account.length; i++) { + const email = account[i]; + if (!emailIdx.has(email)) { + emails.push(email); + emailIdx.set(email, m); + emailToAcc.set(m, accId); + m++; + } + } + } + + // Build adjacency list + for (let i = 0; i < m; i++) { + adj.push([]); + } + for (const account of accounts) { + for (let i = 2; i < account.length; i++) { + const id1 = emailIdx.get(account[i]); + const id2 = emailIdx.get(account[i - 1]); + adj[id1].push(id2); + adj[id2].push(id1); + } + } + + // Initialize visited array + visited = Array(m).fill(false); + + // DFS traversal + const dfs = (node, accId) => { + visited[node] = true; + emailGroup.get(accId).push(emails[node]); + for (const neighbor of adj[node]) { + if (!visited[neighbor]) { + dfs(neighbor, accId); + } + } + }; + + for (let i = 0; i < m; i++) { + if (!visited[i]) { + const accId = emailToAcc.get(i); + if (!emailGroup.has(accId)) { + emailGroup.set(accId, []); + } + dfs(i, accId); + } + } + + // Build result + const res = []; + for (const [accId, group] of emailGroup.entries()) { + group.sort(); + const merged = [accounts[accId][0], ...group]; + res.push(merged); + } + + return res; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O((n * m)\log (n * m))$ +* Space complexity: $O(n * m)$ + +> Where $n$ is the number of accounts and $m$ is the number of emails. + +--- + +## 2. Breadth First Search + +::tabs-start + +```python +class Solution: + def accountsMerge(self, accounts: List[List[str]]) -> List[List[str]]: + n = len(accounts) + emailIdx = {} # email -> id + emails = [] # set of emails of all accounts + emailToAcc = {} # email_index -> account_Id + + m = 0 + for accId, a in enumerate(accounts): + for i in range(1, len(a)): + email = a[i] + if email in emailIdx: + continue + emails.append(email) + emailIdx[email] = m + emailToAcc[m] = accId + m += 1 + + adj = [[] for _ in range(m)] + for a in accounts: + for i in range(2, len(a)): + id1 = emailIdx[a[i]] + id2 = emailIdx[a[i - 1]] + adj[id1].append(id2) + adj[id2].append(id1) + + emailGroup = defaultdict(list) # index of acc -> list of emails + visited = [False] * m + + def bfs(start, accId): + queue = deque([start]) + visited[start] = True + while queue: + node = queue.popleft() + emailGroup[accId].append(emails[node]) + for nei in adj[node]: + if not visited[nei]: + visited[nei] = True + queue.append(nei) + + for i in range(m): + if not visited[i]: + bfs(i, emailToAcc[i]) + + res = [] + for accId in emailGroup: + name = accounts[accId][0] + res.append([name] + sorted(emailGroup[accId])) + + return res +``` + +```java +public class Solution { + private Map emailIdx = new HashMap<>(); // email -> id + private List emails = new ArrayList<>(); // set of emails of all accounts + private Map emailToAcc = new HashMap<>(); // email_index -> account_Id + private List> adj; + private Map> emailGroup = new HashMap<>(); // index of acc -> list of emails + private boolean[] visited; + + public List> accountsMerge(List> accounts) { + int n = accounts.size(); + int m = 0; + + // Build email index and mappings + for (int accId = 0; accId < n; accId++) { + List account = accounts.get(accId); + for (int i = 1; i < account.size(); i++) { + String email = account.get(i); + if (!emailIdx.containsKey(email)) { + emails.add(email); + emailIdx.put(email, m); + emailToAcc.put(m, accId); + m++; + } + } + } + + // Build adjacency list + adj = new ArrayList<>(); + for (int i = 0; i < m; i++) { + adj.add(new ArrayList<>()); + } + for (List account : accounts) { + for (int i = 2; i < account.size(); i++) { + int id1 = emailIdx.get(account.get(i)); + int id2 = emailIdx.get(account.get(i - 1)); + adj.get(id1).add(id2); + adj.get(id2).add(id1); + } + } + + // Initialize visited array + visited = new boolean[m]; + + // BFS traversal + for (int i = 0; i < m; i++) { + if (!visited[i]) { + int accId = emailToAcc.get(i); + emailGroup.putIfAbsent(accId, new ArrayList<>()); + bfs(i, accId); + } + } + + // Build result + List> res = new ArrayList<>(); + for (int accId : emailGroup.keySet()) { + List group = emailGroup.get(accId); + Collections.sort(group); + List merged = new ArrayList<>(); + merged.add(accounts.get(accId).get(0)); + merged.addAll(group); + res.add(merged); + } + + return res; + } + + private void bfs(int start, int accId) { + Queue queue = new LinkedList<>(); + queue.offer(start); + visited[start] = true; + + while (!queue.isEmpty()) { + int node = queue.poll(); + emailGroup.get(accId).add(emails.get(node)); + for (int neighbor : adj.get(node)) { + if (!visited[neighbor]) { + visited[neighbor] = true; + queue.offer(neighbor); + } + } + } + } +} +``` + +```cpp +class Solution { + unordered_map emailIdx; // email -> id + vector emails; // set of emails of all accounts + unordered_map emailToAcc; // email_index -> account_Id + vector> adj; + unordered_map> emailGroup; // index of acc -> list of emails + vector visited; + +public: + vector> accountsMerge(vector>& accounts) { + int n = accounts.size(); + int m = 0; + + // Build email index and mappings + for (int accId = 0; accId < n; accId++) { + vector& account = accounts[accId]; + for (int i = 1; i < account.size(); i++) { + string& email = account[i]; + if (emailIdx.find(email) == emailIdx.end()) { + emails.push_back(email); + emailIdx[email] = m; + emailToAcc[m] = accId; + m++; + } + } + } + + // Build adjacency list + adj.resize(m); + for (auto& account : accounts) { + for (int i = 2; i < account.size(); i++) { + int id1 = emailIdx[account[i]]; + int id2 = emailIdx[account[i - 1]]; + adj[id1].push_back(id2); + adj[id2].push_back(id1); + } + } + + visited.resize(m, false); + // BFS traversal + for (int i = 0; i < m; i++) { + if (!visited[i]) { + int accId = emailToAcc[i]; + bfs(i, accId); + } + } + + // Build result + vector> res; + for (auto& [accId, group] : emailGroup) { + sort(group.begin(), group.end()); + vector merged; + merged.push_back(accounts[accId][0]); + merged.insert(merged.end(), group.begin(), group.end()); + res.push_back(merged); + } + + return res; + } + +private: + void bfs(int start, int accId) { + queue q; + q.push(start); + visited[start] = true; + + while (!q.empty()) { + int node = q.front(); + q.pop(); + emailGroup[accId].push_back(emails[node]); + for (int& neighbor : adj[node]) { + if (!visited[neighbor]) { + visited[neighbor] = true; + q.push(neighbor); + } + } + } + } +}; +``` + +```javascript +class Solution { + /** + * @param {string[][]} accounts + * @return {string[][]} + */ + accountsMerge(accounts) { + const emailIdx = new Map(); // email -> id + const emails = []; // set of emails of all accounts + const emailToAcc = new Map(); // email_index -> account_Id + const adj = []; + const emailGroup = new Map(); // index of acc -> list of emails + let visited = []; + + const n = accounts.length; + let m = 0; + + // Build email index and mappings + for (let accId = 0; accId < n; accId++) { + const account = accounts[accId]; + for (let i = 1; i < account.length; i++) { + const email = account[i]; + if (!emailIdx.has(email)) { + emails.push(email); + emailIdx.set(email, m); + emailToAcc.set(m, accId); + m++; + } + } + } + + // Build adjacency list + for (let i = 0; i < m; i++) { + adj.push([]); + } + for (const account of accounts) { + for (let i = 2; i < account.length; i++) { + const id1 = emailIdx.get(account[i]); + const id2 = emailIdx.get(account[i - 1]); + adj[id1].push(id2); + adj[id2].push(id1); + } + } + + // Initialize visited array + visited = Array(m).fill(false); + + // BFS traversal + const bfs = (start, accId) => { + const queue = new Queue([start]); + visited[start] = true; + + while (!queue.isEmpty()) { + const node = queue.pop(); + emailGroup.get(accId).push(emails[node]); + for (const neighbor of adj[node]) { + if (!visited[neighbor]) { + visited[neighbor] = true; + queue.push(neighbor); + } + } + } + }; + + for (let i = 0; i < m; i++) { + if (!visited[i]) { + const accId = emailToAcc.get(i); + if (!emailGroup.has(accId)) { + emailGroup.set(accId, []); + } + bfs(i, accId); + } + } + + // Build result + const res = []; + for (const [accId, group] of emailGroup.entries()) { + group.sort(); + const merged = [accounts[accId][0], ...group]; + res.push(merged); + } + + return res; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O((n * m)\log (n * m))$ +* Space complexity: $O(n * m)$ + +> Where $n$ is the number of accounts and $m$ is the number of emails. + +--- + +## 3. Disjoint Set Union + +::tabs-start + +```python +class UnionFind: + def __init__(self, n): + self.par = [i for i in range(n)] + self.rank = [1] * n + + def find(self, x): + while x != self.par[x]: + self.par[x] = self.par[self.par[x]] + x = self.par[x] + return x + + def union(self, x1, x2): + p1, p2 = self.find(x1), self.find(x2) + if p1 == p2: + return False + if self.rank[p1] > self.rank[p2]: + self.par[p2] = p1 + self.rank[p1] += self.rank[p2] + else: + self.par[p1] = p2 + self.rank[p2] += self.rank[p1] + return True + +class Solution: + def accountsMerge(self, accounts: List[List[str]]) -> List[List[str]]: + uf = UnionFind(len(accounts)) + emailToAcc = {} # email -> index of acc + + for i, a in enumerate(accounts): + for e in a[1:]: + if e in emailToAcc: + uf.union(i, emailToAcc[e]) + else: + emailToAcc[e] = i + + emailGroup = defaultdict(list) # index of acc -> list of emails + for e, i in emailToAcc.items(): + leader = uf.find(i) + emailGroup[leader].append(e) + + res = [] + for i, emails in emailGroup.items(): + name = accounts[i][0] + res.append([name] + sorted(emailGroup[i])) + return res +``` + +```java +class UnionFind { + private int[] parent; + private int[] rank; + + public UnionFind(int n) { + parent = new int[n]; + rank = new int[n]; + for (int i = 0; i < n; i++) { + parent[i] = i; + rank[i] = 1; + } + } + + public int find(int x) { + if (x != parent[x]) { + parent[x] = find(parent[x]); + } + return parent[x]; + } + + public boolean union(int x1, int x2) { + int p1 = find(x1); + int p2 = find(x2); + if (p1 == p2) { + return false; + } + if (rank[p1] > rank[p2]) { + parent[p2] = p1; + rank[p1] += rank[p2]; + } else { + parent[p1] = p2; + rank[p2] += rank[p1]; + } + return true; + } +} + +public class Solution { + public List> accountsMerge(List> accounts) { + int n = accounts.size(); + UnionFind uf = new UnionFind(n); + Map emailToAcc = new HashMap<>(); // email -> index of acc + + // Build union-find structure + for (int i = 0; i < n; i++) { + List account = accounts.get(i); + for (int j = 1; j < account.size(); j++) { + String email = account.get(j); + if (emailToAcc.containsKey(email)) { + uf.union(i, emailToAcc.get(email)); + } else { + emailToAcc.put(email, i); + } + } + } + + // Group emails by leader account + Map> emailGroup = new HashMap<>(); // index of acc -> list of emails + for (Map.Entry entry : emailToAcc.entrySet()) { + String email = entry.getKey(); + int accId = entry.getValue(); + int leader = uf.find(accId); + emailGroup.putIfAbsent(leader, new ArrayList<>()); + emailGroup.get(leader).add(email); + } + + // Build result + List> res = new ArrayList<>(); + for (Map.Entry> entry : emailGroup.entrySet()) { + int accId = entry.getKey(); + List emails = entry.getValue(); + Collections.sort(emails); + List merged = new ArrayList<>(); + merged.add(accounts.get(accId).get(0)); // Add account name + merged.addAll(emails); + res.add(merged); + } + + return res; + } +} +``` + +```cpp +class UnionFind { + vector parent; + vector rank; + +public: + UnionFind(int n) { + parent.resize(n); + rank.resize(n, 1); + for (int i = 0; i < n; i++) { + parent[i] = i; + } + } + + int find(int x) { + if (x != parent[x]) { + parent[x] = find(parent[x]); + } + return parent[x]; + } + + bool unionSets(int x1, int x2) { + int p1 = find(x1); + int p2 = find(x2); + if (p1 == p2) { + return false; + } + if (rank[p1] > rank[p2]) { + parent[p2] = p1; + rank[p1] += rank[p2]; + } else { + parent[p1] = p2; + rank[p2] += rank[p1]; + } + return true; + } +}; + +class Solution { +public: + vector> accountsMerge(vector>& accounts) { + int n = accounts.size(); + UnionFind uf(n); + unordered_map emailToAcc; // email -> index of acc + + // Build union-find structure + for (int i = 0; i < n; i++) { + for (int j = 1; j < accounts[i].size(); j++) { + const string& email = accounts[i][j]; + if (emailToAcc.count(email)) { + uf.unionSets(i, emailToAcc[email]); + } else { + emailToAcc[email] = i; + } + } + } + + // Group emails by leader account + map> emailGroup; // index of acc -> list of emails + for (const auto& [email, accId] : emailToAcc) { + int leader = uf.find(accId); + emailGroup[leader].push_back(email); + } + + // Build result + vector> res; + for (auto& [accId, emails] : emailGroup) { + sort(emails.begin(), emails.end()); + vector merged; + merged.push_back(accounts[accId][0]); + merged.insert(merged.end(), emails.begin(), emails.end()); + res.push_back(merged); + } + + return res; + } +}; +``` + +```javascript +class UnionFind { + /** + * @constructor + * @param {number} n + */ + constructor(n) { + this.parent = Array.from({ length: n }, (_, i) => i); + this.rank = Array(n).fill(1); + } + + /** + * @param {number} x + * @return {number} + */ + find(x) { + if (x !== this.parent[x]) { + this.parent[x] = this.find(this.parent[x]); + } + return this.parent[x]; + } + + /** + * @param {number} x1 + * @param {number} x2 + * @return {boolean} + */ + union(x1, x2) { + const p1 = this.find(x1); + const p2 = this.find(x2); + if (p1 === p2) { + return false; + } + if (this.rank[p1] > this.rank[p2]) { + this.parent[p2] = p1; + this.rank[p1] += this.rank[p2]; + } else { + this.parent[p1] = p2; + this.rank[p2] += this.rank[p1]; + } + return true; + } +} + +class Solution { + /** + * @param {string[][]} accounts + * @return {string[][]} + */ + accountsMerge(accounts) { + const n = accounts.length; + const uf = new UnionFind(n); + const emailToAcc = new Map(); // email -> index of acc + + // Build union-find structure + for (let i = 0; i < n; i++) { + for (let j = 1; j < accounts[i].length; j++) { + const email = accounts[i][j]; + if (emailToAcc.has(email)) { + uf.union(i, emailToAcc.get(email)); + } else { + emailToAcc.set(email, i); + } + } + } + + // Group emails by leader account + const emailGroup = new Map(); // index of acc -> list of emails + for (const [email, accId] of emailToAcc.entries()) { + const leader = uf.find(accId); + if (!emailGroup.has(leader)) { + emailGroup.set(leader, []); + } + emailGroup.get(leader).push(email); + } + + // Build result + const res = []; + for (const [accId, emails] of emailGroup.entries()) { + emails.sort(); + const merged = [accounts[accId][0], ...emails]; + res.push(merged); + } + + return res; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O((n * m)\log (n * m))$ +* Space complexity: $O(n * m)$ + +> Where $n$ is the number of accounts and $m$ is the number of emails. \ No newline at end of file diff --git a/articles/add-binary.md b/articles/add-binary.md new file mode 100644 index 000000000..424adb990 --- /dev/null +++ b/articles/add-binary.md @@ -0,0 +1,240 @@ +## 1. Iteration + +::tabs-start + +```python +class Solution: + def addBinary(self, a: str, b: str) -> str: + res = "" + carry = 0 + + a, b = a[::-1], b[::-1] + for i in range(max(len(a), len(b))): + digitA = ord(a[i]) - ord("0") if i < len(a) else 0 + digitB = ord(b[i]) - ord("0") if i < len(b) else 0 + + total = digitA + digitB + carry + char = str(total % 2) + res = char + res + carry = total // 2 + + if carry: + res = "1" + res + + return res +``` + +```java +public class Solution { + public String addBinary(String a, String b) { + StringBuilder res = new StringBuilder(); + int carry = 0; + + StringBuilder sa = new StringBuilder(a).reverse(); + StringBuilder sb = new StringBuilder(b).reverse(); + + for (int i = 0; i < Math.max(sa.length(), sb.length()); i++) { + int digitA = i < sa.length() ? sa.charAt(i) - '0' : 0; + int digitB = i < sb.length() ? sb.charAt(i) - '0' : 0; + + int total = digitA + digitB + carry; + char c = (char)((total % 2) + '0'); + res.append(c); + carry = total / 2; + } + + if (carry > 0) { + res.append('1'); + } + + return res.reverse().toString(); + } +} +``` + +```cpp +class Solution { +public: + string addBinary(string a, string b) { + string res = ""; + int carry = 0; + + reverse(a.begin(), a.end()); + reverse(b.begin(), b.end()); + + for (int i = 0; i < max(a.length(), b.length()); i++) { + int digitA = i < a.length() ? a[i] - '0' : 0; + int digitB = i < b.length() ? b[i] - '0' : 0; + + int total = digitA + digitB + carry; + char c = (total % 2) + '0'; + res += c; + carry = total / 2; + } + + if (carry) { + res += '1'; + } + reverse(res.begin(), res.end()); + return res; + } +}; +``` + +```javascript +class Solution { + /** + * @param {string} a + * @param {string} b + * @return {string} + */ + addBinary(a, b) { + let res = []; + let carry = 0; + + a = a.split("").reverse().join(""); + b = b.split("").reverse().join(""); + + for (let i = 0; i < Math.max(a.length, b.length); i++) { + const digitA = i < a.length ? a[i] - '0' : 0; + const digitB = i < b.length ? b[i] - '0' : 0; + + const total = digitA + digitB + carry; + const char = (total % 2).toString(); + res.push(char) + carry = Math.floor(total / 2); + } + + if (carry) { + res.push('1'); + } + res.reverse() + return res.join(''); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(max(m, n))$ +* Space complexity: $O(m + n)$ + +> Where $m$ and $n$ are the lengths of the strings $a$ and $b$ respectively. + +--- + +## 2. Iteration (Optimal) + +::tabs-start + +```python +class Solution: + def addBinary(self, a: str, b: str) -> str: + res = [] + carry = 0 + + i, j = len(a) - 1, len(b) - 1 + while i >= 0 or j >= 0 or carry > 0: + digitA = int(a[i]) if i >= 0 else 0 + digitB = int(b[j]) if j >= 0 else 0 + + total = digitA + digitB + carry + res.append(total % 2) + carry = total // 2 + + i -= 1 + j -= 1 + + res.reverse() + return ''.join(map(str, res)) +``` + +```java +public class Solution { + public String addBinary(String a, String b) { + StringBuilder res = new StringBuilder(); + int carry = 0; + + int i = a.length() - 1, j = b.length() - 1; + while (i >= 0 || j >= 0 || carry > 0) { + int digitA = i >= 0 ? a.charAt(i) - '0' : 0; + int digitB = j >= 0 ? b.charAt(j) - '0' : 0; + + int total = digitA + digitB + carry; + res.append(total % 2); + carry = total / 2; + + i--; + j--; + } + + return res.reverse().toString(); + } +} +``` + +```cpp +class Solution { +public: + string addBinary(string a, string b) { + string res = ""; + int carry = 0; + + int i = a.size() - 1, j = b.size() - 1; + while (i >= 0 || j >= 0 || carry > 0) { + int digitA = i >= 0 ? a[i] - '0' : 0; + int digitB = j >= 0 ? b[j] - '0' : 0; + + int total = digitA + digitB + carry; + res += (total % 2) + '0'; + carry = total / 2; + + i--; + j--; + } + + reverse(res.begin(), res.end()); + return res; + } +}; +``` + +```javascript +class Solution { + /** + * @param {string} a + * @param {string} b + * @return {string} + */ + addBinary(a, b) { + let res = []; + let carry = 0; + + let i = a.length - 1, j = b.length - 1; + while (i >= 0 || j >= 0 || carry > 0) { + const digitA = i >= 0 ? a[i] - "0" : 0; + const digitB = j >= 0 ? b[j] - "0" : 0; + + const total = digitA + digitB + carry; + res.push(total % 2); + carry = Math.floor(total / 2); + + i--; + j--; + } + res.reverse() + return res.join(''); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(max(m, n))$ +* Space complexity: $O(max(m, n))$ + +> Where $m$ and $n$ are the lengths of the strings $a$ and $b$ respectively. \ No newline at end of file diff --git a/articles/bitwise-and-of-numbers-range.md b/articles/bitwise-and-of-numbers-range.md new file mode 100644 index 000000000..6c5ab4521 --- /dev/null +++ b/articles/bitwise-and-of-numbers-range.md @@ -0,0 +1,292 @@ +## 1. Brute Force + +::tabs-start + +```python +class Solution: + def rangeBitwiseAnd(self, left: int, right: int) -> int: + res = left + for i in range(left + 1, right + 1): + res &= i + return res +``` + +```java +public class Solution { + public int rangeBitwiseAnd(int left, int right) { + int res = left; + for (int i = left + 1; i <= right; i++) { + res &= i; + } + return res; + } +} +``` + +```cpp +class Solution { +public: + int rangeBitwiseAnd(int left, int right) { + int res = left; + for (int i = left + 1; i <= right; i++) { + res &= i; + } + return res; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number} left + * @param {number} right + * @return {number} + */ + rangeBitwiseAnd(left, right) { + let res = left; + for (let i = left + 1; i <= right; i++) { + res &= i; + } + return res; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n)$ +* Space complexity: $O(1)$ + +--- + +## 2. Bit Manipulation - I + +::tabs-start + +```python +class Solution: + def rangeBitwiseAnd(self, left: int, right: int) -> int: + res = 0 + for i in range(32): + bit = (left >> i) & 1 + if not bit: + continue + + remain = left % (1 << (i + 1)) + diff = (1 << (i + 1)) - remain + if right - left < diff: + res |= (1 << i) + + return res +``` + +```java +public class Solution { + public int rangeBitwiseAnd(int left, int right) { + int res = 0; + for (int i = 0; i < 32; i++) { + int bit = (left >> i) & 1; + if (bit == 0) { + continue; + } + + int remain = left % (1 << (i + 1)); + int diff = (1 << (i + 1)) - remain; + if (right - left < diff) { + res |= (1 << i); + } + } + return res; + } +} +``` + +```cpp +class Solution { +public: + int rangeBitwiseAnd(int left, int right) { + int res = 0; + for (int i = 0; i < 32; i++) { + int bit = (left >> i) & 1; + if (!bit) { + continue; + } + + int remain = left % (1 << (i + 1)); + uint diff = (1ul << (i + 1)) - remain; + if (right - left < diff) { + res |= (1 << i); + } + } + return res; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number} left + * @param {number} right + * @return {number} + */ + rangeBitwiseAnd(left, right) { + let res = 0; + for (let i = 0; i < 32; i++) { + const bit = (left >> i) & 1; + if (!bit) { + continue; + } + const next = Math.pow(2, i + 1); + const remain = left % next; + const diff = next - remain; + if (right - left < diff) { + res |= (1 << i); + } + } + return res; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(1)$ since we iterate $32$ times. +* Space complexity: $O(1)$ + +--- + +## 3. Bit Manipulation - II + +::tabs-start + +```python +class Solution: + def rangeBitwiseAnd(self, left: int, right: int) -> int: + i = 0 + while left != right: + left >>= 1 + right >>= 1 + i += 1 + return left << i +``` + +```java +public class Solution { + public int rangeBitwiseAnd(int left, int right) { + int i = 0; + while (left != right) { + left >>= 1; + right >>= 1; + i++; + } + return left << i; + } +} +``` + +```cpp +class Solution { +public: + int rangeBitwiseAnd(int left, int right) { + int i = 0; + while (left != right) { + left >>= 1; + right >>= 1; + i++; + } + return left << i; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number} left + * @param {number} right + * @return {number} + */ + rangeBitwiseAnd(left, right) { + let i = 0; + while (left !== right) { + left >>= 1; + right >>= 1; + i++; + } + return left << i; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(1)$ +* Space complexity: $O(1)$ + +--- + +## 4. Bit Manipulation - III + +::tabs-start + +```python +class Solution: + def rangeBitwiseAnd(self, left: int, right: int) -> int: + while left < right: + right &= right - 1 + return right +``` + +```java +public class Solution { + public int rangeBitwiseAnd(int left, int right) { + while (left < right) { + right &= (right - 1); + } + return right; + } +} +``` + +```cpp +class Solution { +public: + int rangeBitwiseAnd(int left, int right) { + while (left < right) { + right &= (right - 1); + } + return right; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number} left + * @param {number} right + * @return {number} + */ + rangeBitwiseAnd(left, right) { + while (left < right) { + right &= (right - 1); + } + return right; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(1)$ +* Space complexity: $O(1)$ \ No newline at end of file diff --git a/articles/candy.md b/articles/candy.md new file mode 100644 index 000000000..adeba0a66 --- /dev/null +++ b/articles/candy.md @@ -0,0 +1,399 @@ +## 1. Brute Force + +::tabs-start + +```python +class Solution: + def candy(self, ratings: List[int]) -> int: + n = len(ratings) + arr = [1] * n + + for i in range(n - 1): + if ratings[i] == ratings[i + 1]: + continue + if ratings[i + 1] > ratings[i]: + arr[i + 1] = arr[i] + 1 + elif arr[i] == arr[i + 1]: + arr[i + 1] = arr[i] + arr[i] += 1 + for j in range(i - 1, -1, -1): + if ratings[j] > ratings[j + 1]: + if arr[j + 1] < arr[j]: + break + arr[j] += 1 + + return sum(arr) +``` + +```java +public class Solution { + public int candy(int[] ratings) { + int n = ratings.length; + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = 1; + } + + for (int i = 0; i < n - 1; i++) { + if (ratings[i] == ratings[i + 1]) { + continue; + } + if (ratings[i + 1] > ratings[i]) { + arr[i + 1] = arr[i] + 1; + } else if (arr[i] == arr[i + 1]) { + arr[i + 1] = arr[i]; + arr[i]++; + for (int j = i - 1; j >= 0; j--) { + if (ratings[j] > ratings[j + 1]) { + if (arr[j + 1] < arr[j]) { + break; + } + arr[j]++; + } + } + } + } + + int sum = 0; + for (int num : arr) { + sum += num; + } + return sum; + } +} +``` + +```cpp +class Solution { +public: + int candy(vector& ratings) { + int n = ratings.size(); + vector arr(n, 1); + + for (int i = 0; i < n - 1; i++) { + if (ratings[i] == ratings[i + 1]) { + continue; + } + if (ratings[i + 1] > ratings[i]) { + arr[i + 1] = arr[i] + 1; + } else if (arr[i] == arr[i + 1]) { + arr[i + 1] = arr[i]; + arr[i]++; + for (int j = i - 1; j >= 0; j--) { + if (ratings[j] > ratings[j + 1]) { + if (arr[j + 1] < arr[j]) { + break; + } + arr[j]++; + } + } + } + } + + return accumulate(arr.begin(), arr.end(), 0); + } +}; +``` + +```javascript +class Solution { + /** + * @param {number[]} ratings + * @return {number} + */ + candy(ratings) { + const n = ratings.length; + const arr = new Array(n).fill(1); + + for (let i = 0; i < n - 1; i++) { + if (ratings[i] === ratings[i + 1]) { + continue; + } + if (ratings[i + 1] > ratings[i]) { + arr[i + 1] = arr[i] + 1; + } else if (arr[i] === arr[i + 1]) { + arr[i + 1] = arr[i]; + arr[i]++; + for (let j = i - 1; j >= 0; j--) { + if (ratings[j] > ratings[j + 1]) { + if (arr[j + 1] < arr[j]) { + break; + } + arr[j]++; + } + } + } + } + + return arr.reduce((sum, num) => sum + num, 0); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n ^ 2)$ +* Space complexity: $O(n)$ + +--- + +## 2. Greedy (Two Pass) + +::tabs-start + +```python +class Solution: + def candy(self, ratings: List[int]) -> int: + n = len(ratings) + arr = [1] * n + + for i in range(1, n): + if ratings[i - 1] < ratings[i]: + arr[i] = arr[i - 1] + 1 + + for i in range(n - 2, -1, -1): + if ratings[i] > ratings[i + 1]: + arr[i] = max(arr[i], arr[i + 1] + 1) + + return sum(arr) +``` + +```java +public class Solution { + public int candy(int[] ratings) { + int n = ratings.length; + int[] arr = new int[n]; + Arrays.fill(arr, 1); + + for (int i = 1; i < n; i++) { + if (ratings[i - 1] < ratings[i]) { + arr[i] = arr[i - 1] + 1; + } + } + + for (int i = n - 2; i >= 0; i--) { + if (ratings[i] > ratings[i + 1]) { + arr[i] = Math.max(arr[i], arr[i + 1] + 1); + } + } + + int sum = 0; + for (int num : arr) { + sum += num; + } + return sum; + } +} +``` + +```cpp +class Solution { +public: + int candy(vector& ratings) { + int n = ratings.size(); + vector arr(n, 1); + + for (int i = 1; i < n; i++) { + if (ratings[i - 1] < ratings[i]) { + arr[i] = arr[i - 1] + 1; + } + } + + for (int i = n - 2; i >= 0; i--) { + if (ratings[i] > ratings[i + 1]) { + arr[i] = max(arr[i], arr[i + 1] + 1); + } + } + + return accumulate(arr.begin(), arr.end(), 0); + } +}; +``` + +```javascript +class Solution { + /** + * @param {number[]} ratings + * @return {number} + */ + candy(ratings) { + const n = ratings.length; + const arr = new Array(n).fill(1); + + for (let i = 1; i < n; i++) { + if (ratings[i - 1] < ratings[i]) { + arr[i] = arr[i - 1] + 1; + } + } + + for (let i = n - 2; i >= 0; i--) { + if (ratings[i] > ratings[i + 1]) { + arr[i] = Math.max(arr[i], arr[i + 1] + 1); + } + } + + return arr.reduce((sum, num) => sum + num, 0); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n)$ +* Space complexity: $O(n)$ + +--- + +## 3. Greedy (One Pass) + +::tabs-start + +```python +class Solution: + def candy(self, ratings: List[int]) -> int: + n = len(ratings) + res = n + + i = 1 + while i < n: + if ratings[i] == ratings[i - 1]: + i += 1 + continue + + inc = 0 + while i < n and ratings[i] > ratings[i - 1]: + inc += 1 + res += inc + i += 1 + + dec = 0 + while i < n and ratings[i] < ratings[i - 1]: + dec += 1 + res += dec + i += 1 + + res -= min(inc, dec) + + return res +``` + +```java +public class Solution { + public int candy(int[] ratings) { + int n = ratings.length; + int res = n; + + int i = 1; + while (i < n) { + if (ratings[i] == ratings[i - 1]) { + i++; + continue; + } + + int inc = 0; + while (i < n && ratings[i] > ratings[i - 1]) { + inc++; + res += inc; + i++; + } + + int dec = 0; + while (i < n && ratings[i] < ratings[i - 1]) { + dec++; + res += dec; + i++; + } + + res -= Math.min(inc, dec); + } + + return res; + } +} +``` + +```cpp +class Solution { +public: + int candy(vector& ratings) { + int n = ratings.size(); + int res = n; + + int i = 1; + while (i < n) { + if (ratings[i] == ratings[i - 1]) { + i++; + continue; + } + + int inc = 0; + while (i < n && ratings[i] > ratings[i - 1]) { + inc++; + res += inc; + i++; + } + + int dec = 0; + while (i < n && ratings[i] < ratings[i - 1]) { + dec++; + res += dec; + i++; + } + + res -= min(inc, dec); + } + + return res; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number[]} ratings + * @return {number} + */ + candy(ratings) { + const n = ratings.length; + let res = n; + + let i = 1; + while (i < n) { + if (ratings[i] === ratings[i - 1]) { + i++; + continue; + } + + let inc = 0; + while (i < n && ratings[i] > ratings[i - 1]) { + inc++; + res += inc; + i++; + } + + let dec = 0; + while (i < n && ratings[i] < ratings[i - 1]) { + dec++; + res += dec; + i++; + } + + res -= Math.min(inc, dec); + } + + return res; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n)$ +* Space complexity: $O(1)$ extra space. \ No newline at end of file diff --git a/articles/dota2-senate.md b/articles/dota2-senate.md new file mode 100644 index 000000000..4e30f1d55 --- /dev/null +++ b/articles/dota2-senate.md @@ -0,0 +1,423 @@ +## 1. Brute Force + +::tabs-start + +```python +class Solution: + def predictPartyVictory(self, senate: str) -> str: + s = list(senate) + while True: + if 'R' not in s: + return "Dire" + if 'D' not in s: + return "Radiant" + + i = 0 + while i < len(s): + if s[i] == 'R': + j = (i + 1) % len(s) + while s[j] == 'R': + j = (j + 1) % len(s) + s.pop(j) + if j < i: + i -= 1 + else: + j = (i + 1) % len(s) + while s[j] == 'D': + j = (j + 1) % len(s) + s.pop(j) + if j < i: + i -= 1 + i += 1 +``` + +```java +public class Solution { + public String predictPartyVictory(String senate) { + List s = new ArrayList<>(); + for (char c : senate.toCharArray()) { + s.add(c); + } + + while (true) { + if (!s.contains('R')) { + return "Dire"; + } + if (!s.contains('D')) { + return "Radiant"; + } + + int i = 0; + while (i < s.size()) { + if (s.get(i) == 'R') { + int j = (i + 1) % s.size(); + while (s.get(j) == 'R') { + j = (j + 1) % s.size(); + } + s.remove(j); + if (j < i) { + i--; + } + } else { + int j = (i + 1) % s.size(); + while (s.get(j) == 'D') { + j = (j + 1) % s.size(); + } + s.remove(j); + if (j < i) { + i--; + } + } + i++; + } + } + } +} +``` + +```cpp +class Solution { +public: + string predictPartyVictory(string senate) { + vector s(senate.begin(), senate.end()); + + while (true) { + if (find(s.begin(), s.end(), 'R') == s.end()) { + return "Dire"; + } + if (find(s.begin(), s.end(), 'D') == s.end()) { + return "Radiant"; + } + + int i = 0; + while (i < s.size()) { + if (s[i] == 'R') { + int j = (i + 1) % s.size(); + while (s[j] == 'R') { + j = (j + 1) % s.size(); + } + s.erase(s.begin() + j); + if (j < i) { + i--; + } + } else { + int j = (i + 1) % s.size(); + while (s[j] == 'D') { + j = (j + 1) % s.size(); + } + s.erase(s.begin() + j); + if (j < i) { + i--; + } + } + i++; + } + } + } +}; +``` + +```javascript +class Solution { + /** + * @param {string} senate + * @return {string} + */ + predictPartyVictory(senate) { + const s = senate.split(""); + + while (true) { + if (!s.includes("R")) { + return "Dire"; + } + if (!s.includes("D")) { + return "Radiant"; + } + + let i = 0; + while (i < s.length) { + if (s[i] === "R") { + let j = (i + 1) % s.length; + while (s[j] === "R") { + j = (j + 1) % s.length; + } + s.splice(j, 1); + if (j < i) { + i--; + } + } else { + let j = (i + 1) % s.length; + while (s[j] === "D") { + j = (j + 1) % s.length; + } + s.splice(j, 1); + if (j < i) { + i--; + } + } + i++; + } + } + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n ^ 2)$ +* Space complexity: $O(n)$ + +--- + +## 2. Greedy (Two Queues) + +::tabs-start + +```python +class Solution: + def predictPartyVictory(self, senate: str) -> str: + D, R = deque(), deque() + n = len(senate) + + for i, c in enumerate(senate): + if c == 'R': + R.append(i) + else: + D.append(i) + + while D and R: + dTurn = D.popleft() + rTurn = R.popleft() + + if rTurn < dTurn: + R.append(rTurn + n) + else: + D.append(dTurn + n) + + return "Radiant" if R else "Dire" +``` + +```java +public class Solution { + public String predictPartyVictory(String senate) { + Queue R = new LinkedList<>(); + Queue D = new LinkedList<>(); + int n = senate.length(); + + for (int i = 0; i < n; i++) { + if (senate.charAt(i) == 'R') { + R.add(i); + } else { + D.add(i); + } + } + + while (!R.isEmpty() && !D.isEmpty()) { + int rTurn = R.poll(); + int dTurn = D.poll(); + + if (rTurn < dTurn) { + R.add(rTurn + n); + } else { + D.add(dTurn + n); + } + } + + return R.isEmpty() ? "Dire" : "Radiant"; + } +} +``` + +```cpp +class Solution { +public: + string predictPartyVictory(string senate) { + queue R, D; + int n = senate.size(); + + for (int i = 0; i < n; i++) { + if (senate[i] == 'R') { + R.push(i); + } else { + D.push(i); + } + } + + while (!R.empty() && !D.empty()) { + int rTurn = R.front(); R.pop(); + int dTurn = D.front(); D.pop(); + + if (rTurn < dTurn) { + R.push(rTurn + n); + } else { + D.push(dTurn + n); + } + } + + return R.empty() ? "Dire" : "Radiant"; + } +}; +``` + +```javascript +class Solution { + /** + * @param {string} senate + * @return {string} + */ + predictPartyVictory(senate) { + const R = new Queue(); + const D = new Queue(); + const n = senate.length; + + for (let i = 0; i < n; i++) { + if (senate[i] === "R") { + R.push(i); + } else { + D.push(i); + } + } + + while (!R.isEmpty() && !D.isEmpty()) { + const rTurn = R.pop(); + const dTurn = D.pop(); + + if (rTurn < dTurn) { + R.push(rTurn + n); + } else { + D.push(dTurn + n); + } + } + + return !R.isEmpty() ? "Radiant" : "Dire"; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n)$ +* Space complexity: $O(n)$ + +--- + +## 3. Greedy + +::tabs-start + +```python +class Solution: + def predictPartyVictory(self, senate: str) -> str: + senate = list(senate) + cnt = i = 0 + + while i < len(senate): + c = senate[i] + if c == 'R': + if cnt < 0: + senate.append('D') + cnt += 1 + else: + if cnt > 0: + senate.append('R') + cnt -= 1 + i += 1 + + return "Radiant" if cnt > 0 else "Dire" +``` + +```java +public class Solution { + public String predictPartyVictory(String senate) { + StringBuilder sb = new StringBuilder(senate); + int cnt = 0, i = 0; + + while (i < sb.length()) { + char c = sb.charAt(i); + if (c == 'R') { + if (cnt < 0) { + sb.append('D'); + } + cnt++; + } else { + if (cnt > 0) { + sb.append('R'); + } + cnt--; + } + i++; + } + + return cnt > 0 ? "Radiant" : "Dire"; + } +} +``` + +```cpp +class Solution { +public: + string predictPartyVictory(string senate) { + int cnt = 0, i = 0; + + while (i < senate.size()) { + char c = senate[i]; + if (c == 'R') { + if (cnt < 0) { + senate.push_back('D'); + } + cnt++; + } else { + if (cnt > 0) { + senate.push_back('R'); + } + cnt--; + } + i++; + } + + return cnt > 0 ? "Radiant" : "Dire"; + } +}; +``` + +```javascript +class Solution { + /** + * @param {string} senate + * @return {string} + */ + predictPartyVictory(senate) { + let s = senate.split(''); + let cnt = 0, i = 0; + + while (i < s.length) { + const c = s[i]; + if (c === 'R') { + if (cnt < 0) { + s.push('D'); + } + cnt++; + } else { + if (cnt > 0) { + s.push('R'); + } + cnt--; + } + i++; + } + + return cnt > 0 ? "Radiant" : "Dire"; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n)$ +* Space complexity: $O(n)$ \ No newline at end of file diff --git a/articles/evaluate-division.md b/articles/evaluate-division.md new file mode 100644 index 000000000..647a8002b --- /dev/null +++ b/articles/evaluate-division.md @@ -0,0 +1,836 @@ +## 1. Breadth First Search + +::tabs-start + +```python +class Solution: + def calcEquation(self, equations: List[List[str]], values: List[float], queries: List[List[str]]) -> List[float]: + adj = collections.defaultdict(list) # Map a -> list of [b, a/b] + + for i, eq in enumerate(equations): + a, b = eq + adj[a].append((b, values[i])) + adj[b].append((a, 1 / values[i])) + + def bfs(src, target): + if src not in adj or target not in adj: + return -1 + q, visit = deque([(src, 1)]), set() + visit.add(src) + + while q: + node, w = q.popleft() + if node == target: + return w + for nei, weight in adj[node]: + if nei not in visit: + q.append((nei, w * weight)) + visit.add(nei) + return -1 + + return [bfs(q[0], q[1]) for q in queries] +``` + +```java +public class Solution { + public double[] calcEquation(List> equations, double[] values, List> queries) { + Map> adj = new HashMap<>(); // Map a -> list of [b, a/b] + + for (int i = 0; i < equations.size(); i++) { + String a = equations.get(i).get(0); + String b = equations.get(i).get(1); + adj.putIfAbsent(a, new ArrayList<>()); + adj.putIfAbsent(b, new ArrayList<>()); + adj.get(a).add(new Pair(b, values[i])); + adj.get(b).add(new Pair(a, 1 / values[i])); + } + + double[] res = new double[queries.size()]; + for (int i = 0; i < queries.size(); i++) { + String src = queries.get(i).get(0); + String target = queries.get(i).get(1); + res[i] = bfs(src, target, adj); + } + + return res; + } + + private double bfs(String src, String target, Map> adj) { + if (!adj.containsKey(src) || !adj.containsKey(target)) { + return -1.0; + } + + Queue q = new LinkedList<>(); + Set visited = new HashSet<>(); + q.offer(new Pair(src, 1.0)); + visited.add(src); + + while (!q.isEmpty()) { + Pair current = q.poll(); + String node = current.node; + double weight = current.weight; + + if (node.equals(target)) { + return weight; + } + + for (Pair neighbor : adj.get(node)) { + if (!visited.contains(neighbor.node)) { + visited.add(neighbor.node); + q.offer(new Pair(neighbor.node, weight * neighbor.weight)); + } + } + } + + return -1.0; + } + + class Pair { + String node; + double weight; + + Pair(String node, double weight) { + this.node = node; + this.weight = weight; + } + } +} +``` + +```cpp +class Solution { +public: + vector calcEquation(vector>& equations, vector& values, vector>& queries) { + unordered_map>> adj; // Map a -> list of [b, a/b] + + for (int i = 0; i < equations.size(); i++) { + string a = equations[i][0]; + string b = equations[i][1]; + adj[a].emplace_back(b, values[i]); + adj[b].emplace_back(a, 1.0 / values[i]); + } + + vector res; + for (const auto& query : queries) { + string src = query[0]; + string target = query[1]; + res.push_back(bfs(src, target, adj)); + } + + return res; + } + +private: + double bfs(const string& src, const string& target, unordered_map>>& adj) { + if (!adj.count(src) || !adj.count(target)) { + return -1.0; + } + + queue> q; + unordered_set visited; + q.emplace(src, 1.0); + visited.insert(src); + + while (!q.empty()) { + auto [node, weight] = q.front(); + q.pop(); + + if (node == target) { + return weight; + } + + for (const auto& [nei, neiWeight] : adj[node]) { + if (!visited.count(nei)) { + visited.insert(nei); + q.emplace(nei, weight * neiWeight); + } + } + } + + return -1.0; + } +}; +``` + +```javascript +class Solution { + /** + * @param {string[][]} equations + * @param {number[]} values + * @param {string[][]} queries + * @return {number[]} + */ + calcEquation(equations, values, queries) { + const adj = new Map(); // Map a -> list of [b, a/b] + + for (let i = 0; i < equations.length; i++) { + const [a, b] = equations[i]; + if (!adj.has(a)) adj.set(a, []); + if (!adj.has(b)) adj.set(b, []); + adj.get(a).push([b, values[i]]); + adj.get(b).push([a, 1 / values[i]]); + } + + const bfs = (src, target) => { + if (!adj.has(src) || !adj.has(target)) return -1; + + const queue = new Queue([[src, 1.0]]); + const visited = new Set(); + visited.add(src); + + while (!queue.isEmpty()) { + const [node, weight] = queue.pop(); + + if (node === target) return weight; + + for (const [nei, neiWeight] of adj.get(node)) { + if (!visited.has(nei)) { + visited.add(nei); + queue.push([nei, weight * neiWeight]); + } + } + } + + return -1; + }; + + return queries.map(([src, target]) => bfs(src, target)); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(m * n)$ +* Space complexity: $O(n + m)$ + +> Where $n$ is the number of unique strings and $m$ is the number of queries. + +--- + +## 2. Depth First Search + +::tabs-start + +```python +class Solution: + def calcEquation(self, equations: List[List[str]], values: List[float], queries: List[List[str]]) -> List[float]: + adj = collections.defaultdict(list) # Map a -> list of [b, a/b] + + for i, eq in enumerate(equations): + a, b = eq + adj[a].append((b, values[i])) + adj[b].append((a, 1 / values[i])) + + def dfs(src, target, visited): + if src not in adj or target not in adj: + return -1 + if src == target: + return 1 + + visited.add(src) + + for nei, weight in adj[src]: + if nei not in visited: + result = dfs(nei, target, visited) + if result != -1: + return weight * result + + return -1 + + return [dfs(q[0], q[1], set()) for q in queries] +``` + +```java +public class Solution { + public double[] calcEquation(List> equations, double[] values, List> queries) { + Map> adj = new HashMap<>(); // Map a -> list of [b, a/b] + + for (int i = 0; i < equations.size(); i++) { + String a = equations.get(i).get(0); + String b = equations.get(i).get(1); + adj.putIfAbsent(a, new ArrayList<>()); + adj.putIfAbsent(b, new ArrayList<>()); + adj.get(a).add(new Pair(b, values[i])); + adj.get(b).add(new Pair(a, 1 / values[i])); + } + + double[] res = new double[queries.size()]; + for (int i = 0; i < queries.size(); i++) { + String src = queries.get(i).get(0); + String target = queries.get(i).get(1); + res[i] = dfs(src, target, adj, new HashSet<>()); + } + + return res; + } + + private double dfs(String src, String target, Map> adj, Set visited) { + if (!adj.containsKey(src) || !adj.containsKey(target)) { + return -1.0; + } + if (src.equals(target)) { + return 1.0; + } + + visited.add(src); + + for (Pair neighbor : adj.get(src)) { + if (!visited.contains(neighbor.node)) { + double result = dfs(neighbor.node, target, adj, visited); + if (result != -1.0) { + return neighbor.weight * result; + } + } + } + + return -1.0; + } + + class Pair { + String node; + double weight; + + Pair(String node, double weight) { + this.node = node; + this.weight = weight; + } + } +} +``` + +```cpp +class Solution { +public: + vector calcEquation(vector>& equations, vector& values, vector>& queries) { + unordered_map>> adj; // Map a -> list of [b, a/b] + + for (int i = 0; i < equations.size(); i++) { + string a = equations[i][0]; + string b = equations[i][1]; + adj[a].emplace_back(b, values[i]); + adj[b].emplace_back(a, 1.0 / values[i]); + } + + vector res; + for (const auto& query : queries) { + string src = query[0]; + string target = query[1]; + res.push_back(dfs(src, target, adj, unordered_set())); + } + + return res; + } + +private: + double dfs(const string& src, const string& target, unordered_map>>& adj, unordered_set visited) { + if (!adj.count(src) || !adj.count(target)) { + return -1.0; + } + if (src == target) { + return 1.0; + } + + visited.insert(src); + + for (const auto& [nei, weight] : adj[src]) { + if (!visited.count(nei)) { + double result = dfs(nei, target, adj, visited); + if (result != -1.0) { + return weight * result; + } + } + } + + return -1.0; + } +}; +``` + +```javascript +class Solution { + /** + * @param {string[][]} equations + * @param {number[]} values + * @param {string[][]} queries + * @return {number[]} + */ + calcEquation(equations, values, queries) { + const adj = new Map(); // Map a -> list of [b, a/b] + + for (let i = 0; i < equations.length; i++) { + const [a, b] = equations[i]; + if (!adj.has(a)) adj.set(a, []); + if (!adj.has(b)) adj.set(b, []); + adj.get(a).push([b, values[i]]); + adj.get(b).push([a, 1 / values[i]]); + } + + const dfs = (src, target, visited) => { + if (!adj.has(src) || !adj.has(target)) return -1; + if (src === target) return 1; + + visited.add(src); + + for (const [nei, weight] of adj.get(src)) { + if (!visited.has(nei)) { + const result = dfs(nei, target, visited); + if (result !== -1) { + return weight * result; + } + } + } + + return -1; + }; + + return queries.map(([src, target]) => dfs(src, target, new Set())); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(m * n)$ +* Space complexity: $O(n + m)$ + +> Where $n$ is the number of unique strings and $m$ is the number of queries. + +--- + +## 3. Disjoint Set Union + +::tabs-start + +```python +class UnionFind: + def __init__(self): + self.parent = {} + self.weight = {} + + def add(self, x): + if x not in self.parent: + self.parent[x] = x + self.weight[x] = 1.0 + + def find(self, x): + if x != self.parent[x]: + orig_parent = self.parent[x] + self.parent[x] = self.find(self.parent[x]) + self.weight[x] *= self.weight[orig_parent] + return self.parent[x] + + def union(self, x, y, value): + self.add(x) + self.add(y) + root_x = self.find(x) + root_y = self.find(y) + + if root_x != root_y: + self.parent[root_x] = root_y + self.weight[root_x] = value * self.weight[y] / self.weight[x] + + def get_ratio(self, x, y): + if x not in self.parent or y not in self.parent or self.find(x) != self.find(y): + return -1.0 + return self.weight[x] / self.weight[y] + +class Solution: + def calcEquation(self, equations: List[List[str]], values: List[float], queries: List[List[str]]) -> List[float]: + uf = UnionFind() + + for (a, b), value in zip(equations, values): + uf.union(a, b, value) + + return [uf.get_ratio(a, b) for a, b in queries] +``` + +```java +class UnionFind { + private final Map parent; + private final Map weight; + + public UnionFind() { + parent = new HashMap<>(); + weight = new HashMap<>(); + } + + public void add(String x) { + if (!parent.containsKey(x)) { + parent.put(x, x); + weight.put(x, 1.0); + } + } + + public String find(String x) { + if (!x.equals(parent.get(x))) { + String origParent = parent.get(x); + parent.put(x, find(origParent)); + weight.put(x, weight.get(x) * weight.get(origParent)); + } + return parent.get(x); + } + + public void union(String x, String y, double value) { + add(x); + add(y); + String rootX = find(x); + String rootY = find(y); + + if (!rootX.equals(rootY)) { + parent.put(rootX, rootY); + weight.put(rootX, value * weight.get(y) / weight.get(x)); + } + } + + public double getRatio(String x, String y) { + if (!parent.containsKey(x) || !parent.containsKey(y) || !find(x).equals(find(y))) { + return -1.0; + } + return weight.get(x) / weight.get(y); + } +} + +public class Solution { + public double[] calcEquation(List> equations, double[] values, List> queries) { + UnionFind uf = new UnionFind(); + + for (int i = 0; i < equations.size(); i++) { + List equation = equations.get(i); + String a = equation.get(0); + String b = equation.get(1); + uf.union(a, b, values[i]); + } + + double[] result = new double[queries.size()]; + for (int i = 0; i < queries.size(); i++) { + List query = queries.get(i); + String a = query.get(0); + String b = query.get(1); + result[i] = uf.getRatio(a, b); + } + + return result; + } +} +``` + +```cpp +class UnionFind { + unordered_map parent; + unordered_map weight; + +public: + void add(const string& x) { + if (parent.find(x) == parent.end()) { + parent[x] = x; + weight[x] = 1.0; + } + } + + string find(const string& x) { + if (x != parent[x]) { + string origParent = parent[x]; + parent[x] = find(parent[x]); + weight[x] *= weight[origParent]; + } + return parent[x]; + } + + void unionSets(const string& x, const string& y, double value) { + add(x); + add(y); + string rootX = find(x); + string rootY = find(y); + + if (rootX != rootY) { + parent[rootX] = rootY; + weight[rootX] = value * weight[y] / weight[x]; + } + } + + double getRatio(const string& x, const string& y) { + if (parent.find(x) == parent.end() || parent.find(y) == parent.end() || find(x) != find(y)) { + return -1.0; + } + return weight[x] / weight[y]; + } +}; + +class Solution { +public: + vector calcEquation(vector>& equations, vector& values, vector>& queries) { + UnionFind uf; + + for (int i = 0; i < equations.size(); i++) { + string a = equations[i][0]; + string b = equations[i][1]; + uf.unionSets(a, b, values[i]); + } + + vector result; + for (const auto& query : queries) { + string a = query[0]; + string b = query[1]; + result.push_back(uf.getRatio(a, b)); + } + + return result; + } +}; +``` + +```javascript +class UnionFind { + constructor() { + this.parent = new Map(); + this.weight = new Map(); + } + + /** + * @param {string} x + * @return {void} + */ + add(x) { + if (!this.parent.has(x)) { + this.parent.set(x, x); + this.weight.set(x, 1.0); + } + } + + /** + * @param {string} x + * @return {string} + */ + find(x) { + if (x !== this.parent.get(x)) { + const origParent = this.parent.get(x); + this.parent.set(x, this.find(origParent)); + this.weight.set(x, this.weight.get(x) * this.weight.get(origParent)); + } + return this.parent.get(x); + } + + /** + * @param {string} x + * @param {string} y + * @param {number} value + * @return {number} + */ + union(x, y, value) { + this.add(x); + this.add(y); + const rootX = this.find(x); + const rootY = this.find(y); + + if (rootX !== rootY) { + this.parent.set(rootX, rootY); + this.weight.set(rootX, (value * this.weight.get(y)) / this.weight.get(x)); + } + } + + /** + * @param {string} x + * @param {string} y + * @return {number} + */ + getRatio(x, y) { + if (!this.parent.has(x) || !this.parent.has(y) || this.find(x) !== this.find(y)) { + return -1.0; + } + return this.weight.get(x) / this.weight.get(y); + } +} + +class Solution { + /** + * @param {string[][]} equations + * @param {number[]} values + * @param {string[][]} queries + * @return {number[]} + */ + calcEquation(equations, values, queries) { + const uf = new UnionFind(); + + for (let i = 0; i < equations.length; i++) { + const [a, b] = equations[i]; + uf.union(a, b, values[i]); + } + + return queries.map(([a, b]) => uf.getRatio(a, b)); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O((m + n)\log n)$ +* Space complexity: $O(n + m)$ + +> Where $n$ is the number of unique strings and $m$ is the number of queries. + +--- + +## 4. Floyd Warshall + +::tabs-start + +```python +class Solution: + def calcEquation(self, equations, values, queries): + graph = collections.defaultdict(dict) + + for (a, b), value in zip(equations, values): + graph[a][b] = value + graph[b][a] = 1.0 / value + + for k in graph: + for i in graph[k]: + for j in graph[k]: + if j not in graph[i]: + graph[i][j] = graph[i][k] * graph[k][j] + + result = [] + for a, b in queries: + if a in graph and b in graph[a]: + result.append(graph[a][b]) + else: + result.append(-1.0) + return result +``` + +```java +public class Solution { + public double[] calcEquation(List> equations, double[] values, List> queries) { + Map> graph = new HashMap<>(); + + for (int i = 0; i < equations.size(); i++) { + String a = equations.get(i).get(0); + String b = equations.get(i).get(1); + double value = values[i]; + graph.computeIfAbsent(a, k -> new HashMap<>()).put(b, value); + graph.computeIfAbsent(b, k -> new HashMap<>()).put(a, 1.0 / value); + } + + for (String k : graph.keySet()) { + for (String i : graph.get(k).keySet()) { + for (String j : graph.get(k).keySet()) { + graph.computeIfAbsent(i, x -> new HashMap<>()).putIfAbsent(j, graph.get(i).get(k) * graph.get(k).get(j)); + } + } + } + + double[] result = new double[queries.size()]; + for (int i = 0; i < queries.size(); i++) { + String a = queries.get(i).get(0); + String b = queries.get(i).get(1); + if (graph.containsKey(a) && graph.get(a).containsKey(b)) { + result[i] = graph.get(a).get(b); + } else { + result[i] = -1.0; + } + } + + return result; + } +} +``` + +```cpp +class Solution { +public: + vector calcEquation(vector>& equations, vector& values, vector>& queries) { + unordered_map> graph; + + for (int i = 0; i < equations.size(); i++) { + string a = equations[i][0]; + string b = equations[i][1]; + double value = values[i]; + graph[a][b] = value; + graph[b][a] = 1.0 / value; + } + + for (const auto& pair : graph) { + const string& k = pair.first; + for (const auto& pair1 : graph[k]) { + const string& i = pair1.first; + for (const auto& pair2 : graph[k]) { + const string& j = pair2.first; + if (!graph[i].count(j)) { + graph[i][j] = graph[i][k] * graph[k][j]; + } + } + } + } + + vector result; + for (const auto& query : queries) { + const string& a = query[0]; + const string& b = query[1]; + if (!graph.count(a) || !graph[a].count(b)) { + result.push_back(-1.0); + } else { + result.push_back(graph[a][b]); + } + } + + return result; + } +}; +``` + +```javascript +class Solution { + /** + * @param {string[][]} equations + * @param {number[]} values + * @param {string[][]} queries + * @return {number[]} + */ + calcEquation(equations, values, queries) { + const graph = new Map(); + + for (let i = 0; i < equations.length; i++) { + const [a, b] = equations[i]; + const value = values[i]; + if (!graph.has(a)) graph.set(a, new Map()); + if (!graph.has(b)) graph.set(b, new Map()); + graph.get(a).set(b, value); + graph.get(b).set(a, 1 / value); + } + + for (const k of graph.keys()) { + for (const i of graph.get(k).keys()) { + for (const j of graph.get(k).keys()) { + if (!graph.get(i).has(j)) { + graph.get(i).set(j, graph.get(i).get(k) * graph.get(k).get(j)); + } + } + } + } + + return queries.map(([a, b]) => { + if (graph.has(a) && graph.get(a).has(b)) { + return graph.get(a).get(b); + } else { + return -1.0; + } + }); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(m + n ^ 3)$ +* Space complexity: $O(n ^ 2 + m)$ + +> Where $n$ is the number of unique strings and $m$ is the number of queries. \ No newline at end of file diff --git a/articles/excel-sheet-column-title.md b/articles/excel-sheet-column-title.md new file mode 100644 index 000000000..98ae4f249 --- /dev/null +++ b/articles/excel-sheet-column-title.md @@ -0,0 +1,142 @@ +## 1. Recursion + +::tabs-start + +```python +class Solution: + def convertToTitle(self, columnNumber: int) -> str: + if columnNumber == 0: + return "" + + n = columnNumber - 1 + return self.convertToTitle(n // 26) + chr(n % 26 + ord('A')) +``` + +```java +public class Solution { + public String convertToTitle(int columnNumber) { + if (columnNumber == 0) { + return ""; + } + int n = columnNumber - 1; + return convertToTitle(n / 26) + (char) ('A' + n % 26); + } +} +``` + +```cpp +class Solution { +public: + string convertToTitle(int columnNumber) { + if (columnNumber == 0) { + return ""; + } + int n = columnNumber - 1; + return convertToTitle(n / 26) + char('A' + n % 26); + } +}; +``` + +```javascript +class Solution { + /** + * @param {number} columnNumber + * @return {string} + */ + convertToTitle(columnNumber) { + if (columnNumber === 0) { + return ""; + } + const n = columnNumber - 1; + return this.convertToTitle(Math.floor(n / 26)) + String.fromCharCode('A'.charCodeAt(0) + n % 26); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(\log n)$ +* Space complexity: $O(\log n)$ for recursion stack. + +> Where $n$ is the given column number. + +--- + +## 2. Iteration + +::tabs-start + +```python +class Solution: + def convertToTitle(self, columnNumber: int) -> str: + res = [] + while columnNumber > 0: + columnNumber -= 1 + offset = columnNumber % 26 + res += chr(ord('A') + offset) + columnNumber //= 26 + + return ''.join(reversed(res)) +``` + +```java +public class Solution { + public String convertToTitle(int columnNumber) { + StringBuilder res = new StringBuilder(); + while (columnNumber > 0) { + columnNumber--; + int offset = columnNumber % 26; + res.append((char) ('A' + offset)); + columnNumber /= 26; + } + return res.reverse().toString(); + } +} +``` + +```cpp +class Solution { +public: + string convertToTitle(int columnNumber) { + string res; + while (columnNumber > 0) { + columnNumber--; + int offset = columnNumber % 26; + res += ('A' + offset); + columnNumber /= 26; + } + reverse(res.begin(), res.end()); + return res; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number} columnNumber + * @return {string} + */ + convertToTitle(columnNumber) { + let res = []; + while (columnNumber > 0) { + columnNumber--; + const offset = columnNumber % 26; + res.push(String.fromCharCode('A'.charCodeAt(0) + offset)); + columnNumber = Math.floor(columnNumber / 26); + } + return res.reverse().join(''); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(\log n)$ +* Space complexity: $O(1)$ extra space. + +> Where $n$ is the given column number. \ No newline at end of file diff --git a/articles/find-critical-and-pseudo-critical-edges-in-minimum-spanning-tree.md b/articles/find-critical-and-pseudo-critical-edges-in-minimum-spanning-tree.md new file mode 100644 index 000000000..dd184b948 --- /dev/null +++ b/articles/find-critical-and-pseudo-critical-edges-in-minimum-spanning-tree.md @@ -0,0 +1,1281 @@ +## 1. Kruskal's Algorithm - I + +::tabs-start + +```python +class UnionFind: + def __init__(self, n): + self.par = [i for i in range(n)] + self.rank = [1] * n + + def find(self, v1): + while v1 != self.par[v1]: + self.par[v1] = self.par[self.par[v1]] + v1 = self.par[v1] + return v1 + + def union(self, v1, v2): + p1, p2 = self.find(v1), self.find(v2) + if p1 == p2: + return False + if self.rank[p1] > self.rank[p2]: + self.par[p2] = p1 + self.rank[p1] += self.rank[p2] + else: + self.par[p1] = p2 + self.rank[p2] += self.rank[p1] + return True + + +class Solution: + def findCriticalAndPseudoCriticalEdges(self, n: int, edges: List[List[int]]) -> List[List[int]]: + for i, e in enumerate(edges): + e.append(i) # [v1, v2, weight, original_index] + + edges.sort(key=lambda e: e[2]) + + mst_weight = 0 + uf = UnionFind(n) + for v1, v2, w, i in edges: + if uf.union(v1, v2): + mst_weight += w + + critical, pseudo = [], [] + for n1, n2, e_weight, i in edges: + # Try without curr edge + weight = 0 + uf = UnionFind(n) + for v1, v2, w, j in edges: + if i != j and uf.union(v1, v2): + weight += w + if max(uf.rank) != n or weight > mst_weight: + critical.append(i) + continue + + # Try with curr edge + uf = UnionFind(n) + uf.union(n1, n2) + weight = e_weight + for v1, v2, w, j in edges: + if uf.union(v1, v2): + weight += w + if weight == mst_weight: + pseudo.append(i) + return [critical, pseudo] +``` + +```java +class UnionFind { + int[] par, rank; + + public UnionFind(int n) { + par = new int[n]; + rank = new int[n]; + for (int i = 0; i < n; i++) { + par[i] = i; + rank[i] = 1; + } + } + + public int find(int v) { + if (par[v] != v) { + par[v] = find(par[v]); + } + return par[v]; + } + + public boolean union(int v1, int v2) { + int p1 = find(v1), p2 = find(v2); + if (p1 == p2) return false; + if (rank[p1] > rank[p2]) { + par[p2] = p1; + rank[p1] += rank[p2]; + } else { + par[p1] = p2; + rank[p2] += rank[p1]; + } + return true; + } +} + +public class Solution { + public List> findCriticalAndPseudoCriticalEdges(int n, int[][] edges) { + List edgeList = new ArrayList<>(); + for (int i = 0; i < edges.length; i++) { + edgeList.add(new int[] { edges[i][0], edges[i][1], edges[i][2], i }); + } + + edgeList.sort(Comparator.comparingInt(a -> a[2])); + + int mstWeight = 0; + UnionFind uf = new UnionFind(n); + for (int[] edge : edgeList) { + if (uf.union(edge[0], edge[1])) { + mstWeight += edge[2]; + } + } + + List critical = new ArrayList<>(); + List pseudo = new ArrayList<>(); + + for (int[] edge : edgeList) { + // Try without current edge + UnionFind ufWithout = new UnionFind(n); + int weight = 0; + for (int[] other : edgeList) { + if (other[3] != edge[3] && ufWithout.union(other[0], other[1])) { + weight += other[2]; + } + } + if (Arrays.stream(ufWithout.rank).max().getAsInt() != n || weight > mstWeight) { + critical.add(edge[3]); + continue; + } + + // Try with current edge + UnionFind ufWith = new UnionFind(n); + ufWith.union(edge[0], edge[1]); + weight = edge[2]; + for (int[] other : edgeList) { + if (ufWith.union(other[0], other[1])) { + weight += other[2]; + } + } + if (weight == mstWeight) { + pseudo.add(edge[3]); + } + } + + return Arrays.asList(critical, pseudo); + } +} +``` + +```cpp +class UnionFind { +public: + vector par, rank; + + UnionFind(int n) : par(n), rank(n, 1) { + iota(par.begin(), par.end(), 0); + } + + int find(int v) { + if (v != par[v]) { + par[v] = find(par[v]); + } + return par[v]; + } + + bool unionSets(int v1, int v2) { + int p1 = find(v1), p2 = find(v2); + if (p1 == p2) return false; + if (rank[p1] > rank[p2]) { + par[p2] = p1; + rank[p1] += rank[p2]; + } else { + par[p1] = p2; + rank[p2] += rank[p1]; + } + return true; + } +}; + +class Solution { +public: + vector> findCriticalAndPseudoCriticalEdges(int n, vector>& edges) { + vector> edgeList; + for (int i = 0; i < edges.size(); ++i) { + edgeList.push_back({ edges[i][0], edges[i][1], edges[i][2], i }); + } + + sort(edgeList.begin(), edgeList.end(), [](auto& a, auto& b) { + return a[2] < b[2]; + }); + + int mstWeight = 0; + UnionFind uf(n); + for (auto& edge : edgeList) { + if (uf.unionSets(edge[0], edge[1])) { + mstWeight += edge[2]; + } + } + + vector critical, pseudo; + for (auto& edge : edgeList) { + // Try without current edge + UnionFind ufWithout(n); + int weight = 0; + for (auto& other : edgeList) { + if (other[3] != edge[3] && ufWithout.unionSets(other[0], other[1])) { + weight += other[2]; + } + } + if (*max_element(ufWithout.rank.begin(), ufWithout.rank.end()) != n || weight > mstWeight) { + critical.push_back(edge[3]); + continue; + } + + // Try with current edge + UnionFind ufWith(n); + ufWith.unionSets(edge[0], edge[1]); + weight = edge[2]; + for (auto& other : edgeList) { + if (ufWith.unionSets(other[0], other[1])) { + weight += other[2]; + } + } + if (weight == mstWeight) { + pseudo.push_back(edge[3]); + } + } + + return { critical, pseudo }; + } +}; +``` + +```javascript +class UnionFind { + /** + * @constructor + * @param {number} n + */ + constructor(n) { + this.par = Array.from({ length: n }, (_, i) => i); + this.rank = Array(n).fill(1); + } + + /** + * @param {number} v1 + * @return {number} + */ + find(v1) { + if (this.par[v1] !== v1) { + this.par[v1] = this.find(this.par[v1]); + } + return this.par[v1]; + } + + /** + * @param {number} v1 + * @param {number} v2 + * @return {boolean} + */ + union(v1, v2) { + const p1 = this.find(v1), p2 = this.find(v2); + if (p1 === p2) return false; + if (this.rank[p1] > this.rank[p2]) { + this.par[p2] = p1; + this.rank[p1] += this.rank[p2]; + } else { + this.par[p1] = p2; + this.rank[p2] += this.rank[p1]; + } + return true; + } +} + +class Solution { + /** + * @param {number} n + * @param {number[][]} edges + * @return {number[][]} + */ + findCriticalAndPseudoCriticalEdges(n, edges) { + edges = edges.map((edge, idx) => [...edge, idx]); + edges.sort((a, b) => a[2] - b[2]); + + const uf = new UnionFind(n); + let mstWeight = 0; + for (const [v1, v2, w] of edges) { + if (uf.union(v1, v2)) { + mstWeight += w; + } + } + + const critical = []; + const pseudo = []; + + for (const [n1, n2, weight, i] of edges) { + // Try without current edge + const ufWithout = new UnionFind(n); + let weightWithout = 0; + for (const [v1, v2, w, j] of edges) { + if (j !== i && ufWithout.union(v1, v2)) { + weightWithout += w; + } + } + if (Math.max(...ufWithout.rank) !== n || weightWithout > mstWeight) { + critical.push(i); + continue; + } + + // Try with current edge + const ufWith = new UnionFind(n); + ufWith.union(n1, n2); + let weightWith = weight; + for (const [v1, v2, w, j] of edges) { + if (ufWith.union(v1, v2)) { + weightWith += w; + } + } + if (weightWith === mstWeight) { + pseudo.push(i); + } + } + + return [critical, pseudo]; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(E ^ 2)$ +* Space complexity: $O(V + E)$ + +> Where $V$ is the number of vertices and $E$ is the number of edges. + +--- + +## 2. Kruskal's Algorithm - II + +::tabs-start + +```python +class UnionFind: + def __init__(self, n): + self.n = n + self.Parent = list(range(n + 1)) + self.Size = [1] * (n + 1) + + def find(self, node): + if self.Parent[node] != node: + self.Parent[node] = self.find(self.Parent[node]) + return self.Parent[node] + + def union(self, u, v): + pu = self.find(u) + pv = self.find(v) + if pu == pv: + return False + self.n -= 1 + if self.Size[pu] < self.Size[pv]: + pu, pv = pv, pu + self.Size[pu] += self.Size[pv] + self.Parent[pv] = pu + return True + + def isConnected(self): + return self.n == 1 + +class Solution: + def findCriticalAndPseudoCriticalEdges(self, n: int, edges: List[List[int]]) -> List[List[int]]: + for i, e in enumerate(edges): + e.append(i) + edges.sort(key = lambda e : e[2]) + + def findMST(index, include): + uf = UnionFind(n) + wgt = 0 + if include: + wgt += edges[index][2] + uf.union(edges[index][0], edges[index][1]) + + for i, e in enumerate(edges): + if i == index: + continue + if uf.union(e[0], e[1]): + wgt += e[2] + return wgt if uf.isConnected() else float("inf") + + mst_wgt = findMST(-1, False) + critical, pseudo = [], [] + for i, e in enumerate(edges): + if mst_wgt < findMST(i, False): + critical.append(e[3]) + elif mst_wgt == findMST(i, True): + pseudo.append(e[3]) + + return [critical, pseudo] +``` + +```java +class UnionFind { + private int n; + private int[] Parent, Size; + + public UnionFind(int n) { + this.n = n; + Parent = new int[n + 1]; + Size = new int[n + 1]; + for (int i = 0; i <= n; i++) { + Parent[i] = i; + Size[i] = 1; + } + } + + public int find(int node) { + if (Parent[node] != node) { + Parent[node] = find(Parent[node]); + } + return Parent[node]; + } + + public boolean union(int u, int v) { + int pu = find(u), pv = find(v); + if (pu == pv) { + return false; + } + n--; + if (Size[pu] < Size[pv]) { + int temp = pu; + pu = pv; + pv = temp; + } + Size[pu] += Size[pv]; + Parent[pv] = pu; + return true; + } + + public boolean isConnected() { + return n == 1; + } +} + +public class Solution { + public List> findCriticalAndPseudoCriticalEdges(int n, int[][] edges) { + for (int i = 0; i < edges.length; i++) { + edges[i] = Arrays.copyOf(edges[i], edges[i].length + 1); + edges[i][3] = i; + } + + Arrays.sort(edges, Comparator.comparingInt(a -> a[2])); + + + int mst_wgt = findMST(n, edges, -1, false); + List critical = new ArrayList<>(); + List pseudo = new ArrayList<>(); + + for (int i = 0; i < edges.length; i++) { + if (mst_wgt < findMST(n, edges, i, false)) { + critical.add(edges[i][3]); + } else if (mst_wgt == findMST(n, edges, i, true)) { + pseudo.add(edges[i][3]); + } + } + + return Arrays.asList(critical, pseudo); + } + + public int findMST(int n, int[][] edges, int index, boolean include) { + UnionFind uf = new UnionFind(n); + int wgt = 0; + if (include) { + wgt += edges[index][2]; + uf.union(edges[index][0], edges[index][1]); + } + for (int i = 0; i < edges.length; i++) { + if (i == index) { + continue; + } + if (uf.union(edges[i][0], edges[i][1])) { + wgt += edges[i][2]; + } + } + return uf.isConnected() ? wgt : Integer.MAX_VALUE; + } +} +``` + +```cpp +class UnionFind { +private: + int n; + vector Parent, Size; + +public: + UnionFind(int n) : n(n), Parent(n + 1), Size(n + 1, 1) { + for (int i = 0; i <= n; ++i) { + Parent[i] = i; + } + } + + int find(int node) { + if (Parent[node] != node) { + Parent[node] = find(Parent[node]); + } + return Parent[node]; + } + + bool unionSets(int u, int v) { + int pu = find(u), pv = find(v); + if (pu == pv) return false; + n--; + if (Size[pu] < Size[pv]) { + swap(pu, pv); + } + Size[pu] += Size[pv]; + Parent[pv] = pu; + return true; + } + + bool isConnected() { + return n == 1; + } +}; + +class Solution { +public: + vector> findCriticalAndPseudoCriticalEdges(int n, vector>& edges) { + for (int i = 0; i < edges.size(); ++i) { + edges[i].push_back(i); + } + + sort(edges.begin(), edges.end(), [](const vector& a, const vector& b) { + return a[2] < b[2]; + }); + + auto findMST = [&](int index, bool include) -> int { + UnionFind uf(n); + int wgt = 0; + if (include) { + wgt += edges[index][2]; + uf.unionSets(edges[index][0], edges[index][1]); + } + for (int i = 0; i < edges.size(); ++i) { + if (i == index) continue; + if (uf.unionSets(edges[i][0], edges[i][1])) { + wgt += edges[i][2]; + } + } + return uf.isConnected() ? wgt : INT_MAX; + }; + + int mst_wgt = findMST(-1, false); + vector critical, pseudo; + + for (int i = 0; i < edges.size(); ++i) { + if (mst_wgt < findMST(i, false)) { + critical.push_back(edges[i][3]); + } else if (mst_wgt == findMST(i, true)) { + pseudo.push_back(edges[i][3]); + } + } + + return { critical, pseudo }; + } +}; +``` + +```javascript +class UnionFind { + /** + * @constructor + * @param {number} n + */ + constructor(n) { + this.n = n; + this.Parent = Array.from({ length: n + 1 }, (_, i) => i); + this.Size = Array(n + 1).fill(1); + } + + /** + * @param {number} v1 + * @return {number} + */ + find(v1) { + if (this.Parent[node] !== node) { + this.Parent[node] = this.find(this.Parent[node]); + } + return this.Parent[node]; + } + + /** + * @param {number} v1 + * @param {number} v2 + * @return {boolean} + */ + union(v1, v2) { + let pu = this.find(u); + let pv = this.find(v); + if (pu === pv) return false; + this.n--; + if (this.Size[pu] < this.Size[pv]) { + [pu, pv] = [pv, pu]; + } + this.Size[pu] += this.Size[pv]; + this.Parent[pv] = pu; + return true; + } + + isConnected() { + return this.n === 1; + } +} + +class Solution { + /** + * @param {number} n + * @param {number[][]} edges + * @return {number[][]} + */ + findCriticalAndPseudoCriticalEdges(n, edges) { + edges.forEach((edge, i) => edge.push(i)); + edges.sort((a, b) => a[2] - b[2]); + + const findMST = (index, include) => { + const uf = new UnionFind(n); + let wgt = 0; + if (include) { + wgt += edges[index][2]; + uf.union(edges[index][0], edges[index][1]); + } + for (let i = 0; i < edges.length; i++) { + if (i === index) continue; + if (uf.union(edges[i][0], edges[i][1])) { + wgt += edges[i][2]; + } + } + return uf.isConnected() ? wgt : Infinity; + }; + + const mst_wgt = findMST(-1, false); + const critical = []; + const pseudo = []; + + edges.forEach((edge, i) => { + if (mst_wgt < findMST(i, false)) { + critical.push(edge[3]); + } else if (mst_wgt === findMST(i, true)) { + pseudo.push(edge[3]); + } + }); + + return [critical, pseudo]; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(E ^ 2)$ +* Space complexity: $O(V + E)$ + +> Where $V$ is the number of vertices and $E$ is the number of edges. + +--- + +## 3. Dijkstra's Algorithm + +::tabs-start + +```python +class Solution: + def findCriticalAndPseudoCriticalEdges(self, n: int, edges: List[List[int]]) -> List[List[int]]: + for i, edge in enumerate(edges): + edge.append(i) + + adj = defaultdict(list) + for u, v, w, idx in edges: + adj[u].append((v, w, idx)) + adj[v].append((u, w, idx)) + + def minimax(src, dst, exclude_idx): + dist = [float('inf')] * n + dist[src] = 0 + pq = [(0, src)] + + while pq: + max_w, u = heappop(pq) + if u == dst: + return max_w + + for v, weight, edge_idx in adj[u]: + if edge_idx == exclude_idx: + continue + new_w = max(max_w, weight) + if new_w < dist[v]: + dist[v] = new_w + heappush(pq, (new_w, v)) + + return float('inf') + + critical, pseudo = [], [] + for i, (u, v, w, idx) in enumerate(edges): + if w < minimax(u, v, idx): + critical.append(idx) + elif w == minimax(u, v, -1): + pseudo.append(idx) + + return [critical, pseudo] +``` + +```java +public class Solution { + private List[] adj; + + public List> findCriticalAndPseudoCriticalEdges(int n, int[][] edges) { + for (int i = 0; i < edges.length; i++) { + edges[i] = Arrays.copyOf(edges[i], edges[i].length + 1); + edges[i][3] = i; + } + + adj = new ArrayList[n]; + for (int i = 0; i < n; i++) { + adj[i] = new ArrayList<>(); + } + for (int[] edge : edges) { + adj[edge[0]].add(new int[]{edge[1], edge[2], edge[3]}); + adj[edge[1]].add(new int[]{edge[0], edge[2], edge[3]}); + } + + + List critical = new ArrayList<>(); + List pseudo = new ArrayList<>(); + + for (int[] edge : edges) { + int u = edge[0], v = edge[1], w = edge[2], idx = edge[3]; + if (w < minimax(n, u, v, idx)) { + critical.add(idx); + } else if (w == minimax(n, u, v, -1)) { + pseudo.add(idx); + } + } + + return Arrays.asList(critical, pseudo); + } + + public int minimax(int n, int src, int dst, int excludeIdx) { + int[] dist = new int[n]; + Arrays.fill(dist, Integer.MAX_VALUE); + dist[src] = 0; + + PriorityQueue pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[0])); + pq.offer(new int[]{0, src}); + + while (!pq.isEmpty()) { + int[] curr = pq.poll(); + int maxW = curr[0], u = curr[1]; + if (u == dst) return maxW; + + for (int[] neighbor : adj[u]) { + int v = neighbor[0], weight = neighbor[1], edgeIdx = neighbor[2]; + if (edgeIdx == excludeIdx) continue; + int newW = Math.max(maxW, weight); + if (newW < dist[v]) { + dist[v] = newW; + pq.offer(new int[]{newW, v}); + } + } + } + return Integer.MAX_VALUE; + } +} +``` + +```cpp +class Solution { +public: + vector> findCriticalAndPseudoCriticalEdges(int n, vector>& edges) { + for (int i = 0; i < edges.size(); ++i) { + edges[i].push_back(i); + } + + vector>> adj(n); + for (const auto& edge : edges) { + adj[edge[0]].push_back({edge[1], edge[2], edge[3]}); + adj[edge[1]].push_back({edge[0], edge[2], edge[3]}); + } + + auto minimax = [&](int src, int dst, int excludeIdx) -> int { + vector dist(n, INT_MAX); + dist[src] = 0; + + priority_queue, vector>, greater<>> pq; + pq.push({0, src}); + + while (!pq.empty()) { + auto [maxW, u] = pq.top(); + pq.pop(); + if (u == dst) return maxW; + + for (const auto& neighbor : adj[u]) { + int v = neighbor[0], weight = neighbor[1], edgeIdx = neighbor[2]; + if (edgeIdx == excludeIdx) continue; + int newW = max(maxW, weight); + if (newW < dist[v]) { + dist[v] = newW; + pq.push({newW, v}); + } + } + } + return INT_MAX; + }; + + vector critical, pseudo; + for (const auto& edge : edges) { + int u = edge[0], v = edge[1], w = edge[2], idx = edge[3]; + if (w < minimax(u, v, idx)) { + critical.push_back(idx); + } else if (w == minimax(u, v, -1)) { + pseudo.push_back(idx); + } + } + + return {critical, pseudo}; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number} n + * @param {number[][]} edges + * @return {number[][]} + */ + findCriticalAndPseudoCriticalEdges(n, edges) { + edges.forEach((edge, i) => edge.push(i)); + + const adj = Array.from({ length: n }, () => []); + for (const [u, v, w, idx] of edges) { + adj[u].push([v, w, idx]); + adj[v].push([u, w, idx]); + } + + const minimax = (src, dst, excludeIdx) => { + const dist = Array(n).fill(Infinity); + dist[src] = 0; + + const pq = new MinPriorityQueue({ compare: (a, b) => a[0] - b[0] }); + pq.enqueue([0, src]); + + while (!pq.isEmpty()) { + const [maxW, u] = pq.dequeue(); + if (u === dst) return maxW; + + for (const [v, weight, edgeIdx] of adj[u]) { + if (edgeIdx === excludeIdx) continue; + const newW = Math.max(maxW, weight); + if (newW < dist[v]) { + dist[v] = newW; + pq.enqueue([newW, v]); + } + } + } + return Infinity; + }; + + const critical = []; + const pseudo = []; + + for (const [u, v, w, idx] of edges) { + if (w < minimax(u, v, idx)) { + critical.push(idx); + } else if (w === minimax(u, v, -1)) { + pseudo.push(idx); + } + } + + return [critical, pseudo]; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(E ^ 2 \log V)$ +* Space complexity: $O(V + E)$ + +> Where $V$ is the number of vertices and $E$ is the number of edges. + +--- + +## 4. Kruskal's Algorithm + DFS + +::tabs-start + +```python +class UnionFind: + def __init__(self, n): + self.Parent = list(range(n + 1)) + self.Size = [1] * (n + 1) + + def find(self, node): + if self.Parent[node] != node: + self.Parent[node] = self.find(self.Parent[node]) + return self.Parent[node] + + def union(self, u, v): + pu = self.find(u) + pv = self.find(v) + if pu == pv: + return False + if self.Size[pu] < self.Size[pv]: + pu, pv = pv, pu + self.Size[pu] += self.Size[pv] + self.Parent[pv] = pu + return True + +class Solution: + def findCriticalAndPseudoCriticalEdges(self, n: int, edges: List[List[int]]) -> List[List[int]]: + mst = [[] for _ in range(n)] + mstEdge = [] + + edge_list = [(w, u, v, i) for i, (u, v, w) in enumerate(edges)] + edge_list.sort() + + uf = UnionFind(n) + for w, u, v, i in edge_list: + if uf.union(u, v): + mst[u].append((v, i)) + mst[v].append((u, i)) + mstEdge.append(i) + + def dfs(node): + for next, ind in mst[node]: + if path and ind == path[-1]: + continue + path.append(ind) + if next == dst or dfs(next): + return True + path.pop() + return False + + pseudo, mstEdge = set(), set(mstEdge) + for ind in range(len(edges)): + if ind in mstEdge: + continue + path, dst = [], edges[ind][1] + dfs(edges[ind][0]) + for i in path: + if edges[i][2] == edges[ind][2]: + pseudo.add(i) + pseudo.add(ind) + mstEdge.add(i) + mstEdge.add(ind) + + return [list(mstEdge - pseudo), list(pseudo)] +``` + +```java +class UnionFind { + private int[] parent; + private int[] size; + + public UnionFind(int n) { + parent = new int[n]; + size = new int[n]; + for (int i = 0; i < n; i++) { + parent[i] = i; + size[i] = 1; + } + } + + public int find(int node) { + if (parent[node] != node) { + parent[node] = find(parent[node]); + } + return parent[node]; + } + + public boolean union(int u, int v) { + int pu = find(u); + int pv = find(v); + if (pu == pv) { + return false; + } + if (size[pu] < size[pv]) { + int temp = pu; + pu = pv; + pv = temp; + } + size[pu] += size[pv]; + parent[pv] = pu; + return true; + } +} + +public class Solution { + private List> mst; + private Set mstEdges; + private Set pseudoCriticalEdges; + private int destination; + private List path; + + public List> findCriticalAndPseudoCriticalEdges(int n, int[][] edges) { + mst = new ArrayList<>(); + mstEdges = new HashSet<>(); + pseudoCriticalEdges = new HashSet<>(); + + for (int i = 0; i < n; i++) { + mst.add(new ArrayList<>()); + } + + List edgeList = new ArrayList<>(); + for (int i = 0; i < edges.length; i++) { + edgeList.add(new int[]{edges[i][2], edges[i][0], edges[i][1], i}); + } + edgeList.sort(Comparator.comparingInt(a -> a[0])); + + UnionFind uf = new UnionFind(n); + for (int[] edge : edgeList) { + int weight = edge[0], u = edge[1], v = edge[2], index = edge[3]; + if (uf.union(u, v)) { + mst.get(u).add(index); + mst.get(v).add(index); + mstEdges.add(index); + } + } + + for (int i = 0; i < edges.length; i++) { + if (mstEdges.contains(i)) { + continue; + } + path = new ArrayList<>(); + destination = edges[i][1]; + if (dfs(edges[i][0], -1, edges)) { + for (int p : path) { + if (edges[p][2] == edges[i][2]) { + pseudoCriticalEdges.add(i); + pseudoCriticalEdges.add(p); + } + } + } + } + + List critical = new ArrayList<>(); + for (int edge : mstEdges) { + if (!pseudoCriticalEdges.contains(edge)) { + critical.add(edge); + } + } + + return Arrays.asList(critical, new ArrayList<>(pseudoCriticalEdges)); + } + + private boolean dfs(int node, int parent, int[][] edges) { + if (node == destination) { + return true; + } + for (int edgeIndex : mst.get(node)) { + if (edgeIndex == parent) { + continue; + } + path.add(edgeIndex); + int nextNode = edges[edgeIndex][0] == node ? edges[edgeIndex][1] : edges[edgeIndex][0]; + if (dfs(nextNode, edgeIndex, edges)) { + return true; + } + path.remove(path.size() - 1); + } + return false; + } +} +``` + +```cpp +class UnionFind { + vector parent, size; + +public: + UnionFind(int n) { + parent.resize(n); + size.resize(n, 1); + for (int i = 0; i < n; ++i) { + parent[i] = i; + } + } + + int find(int node) { + if (parent[node] != node) { + parent[node] = find(parent[node]); + } + return parent[node]; + } + + bool unionSets(int u, int v) { + int pu = find(u), pv = find(v); + if (pu == pv) return false; + if (size[pu] < size[pv]) swap(pu, pv); + size[pu] += size[pv]; + parent[pv] = pu; + return true; + } +}; + +class Solution { + vector> mst; + set mstEdges, pseudoCriticalEdges; + vector path; + int destination; + +public: + vector> findCriticalAndPseudoCriticalEdges(int n, vector>& edges) { + mst.resize(n); + vector> edgeList; + for (int i = 0; i < edges.size(); ++i) { + edgeList.push_back({edges[i][2], edges[i][0], edges[i][1], i}); + } + sort(edgeList.begin(), edgeList.end()); + + UnionFind uf(n); + for (auto& [w, u, v, idx] : edgeList) { + if (uf.unionSets(u, v)) { + mst[u].push_back(idx); + mst[v].push_back(idx); + mstEdges.insert(idx); + } + } + + for (int i = 0; i < edges.size(); ++i) { + if (mstEdges.count(i)) continue; + path.clear(); + destination = edges[i][1]; + if (dfs(edges[i][0], -1, edges)) { + for (int p : path) { + if (edges[p][2] == edges[i][2]) { + pseudoCriticalEdges.insert(p); + pseudoCriticalEdges.insert(i); + } + } + } + } + + vector critical; + for (int e : mstEdges) { + if (!pseudoCriticalEdges.count(e)) critical.push_back(e); + } + + return {critical, vector(pseudoCriticalEdges.begin(), pseudoCriticalEdges.end())}; + } + + bool dfs(int node, int parent, vector>& edges) { + if (node == destination) return true; + for (int& edgeIdx : mst[node]) { + if (edgeIdx == parent) continue; + path.push_back(edgeIdx); + int next = edges[edgeIdx][0] == node ? edges[edgeIdx][1] : edges[edgeIdx][0]; + if (dfs(next, edgeIdx, edges)) return true; + path.pop_back(); + } + return false; + } +}; +``` + +```javascript +class UnionFind { + /** + * @constructor + * @param {number} n + */ + constructor(n) { + this.Parent = Array.from({ length: n + 1 }, (_, i) => i); + this.Size = Array(n + 1).fill(1); + } + + /** + * @param {number} node + * @return {number} + */ + find(node) { + if (this.Parent[node] !== node) { + this.Parent[node] = this.find(this.Parent[node]); + } + return this.Parent[node]; + } + + /** + * @param {number} u + * @param {number} v + * @return {boolean} + */ + union(u, v) { + let pu = this.find(u); + let pv = this.find(v); + if (pu === pv) return false; + if (this.Size[pu] < this.Size[pv]) { + [pu, pv] = [pv, pu]; + } + this.Size[pu] += this.Size[pv]; + this.Parent[pv] = pu; + return true; + } +} + +class Solution { + /** + * @param {number} n + * @param {number[][]} edges + * @return {number[][]} + */ + findCriticalAndPseudoCriticalEdges(n, edges) { + const mst = Array.from({ length: n }, () => []); + const mstEdges = new Set(); + const pseudoCriticalEdges = new Set(); + const edgeList = edges.map((e, i) => [...e, i]).sort((a, b) => a[2] - b[2]); + + const uf = new UnionFind(n); + + for (const [u, v, w, idx] of edgeList) { + if (uf.union(u, v)) { + mst[u].push([v, idx]); + mst[v].push([u, idx]); + mstEdges.add(idx); + } + } + + let path = []; + let destination = null; + + const dfs = (node, parent) => { + if (node === destination) return true; + for (const [next, edgeIdx] of mst[node]) { + if (edgeIdx === parent) continue; + path.push(edgeIdx); + if (dfs(next, edgeIdx)) return true; + path.pop(); + } + return false; + }; + + for (let i = 0; i < edges.length; i++) { + if (mstEdges.has(i)) continue; + + path = []; + destination = edges[i][1]; + if (dfs(edges[i][0], -1)) { + for (const edgeIdx of path) { + if (edges[edgeIdx][2] === edges[i][2]) { + pseudoCriticalEdges.add(i); + pseudoCriticalEdges.add(edgeIdx); + } + } + } + } + + const critical = [...mstEdges].filter(edgeIdx => !pseudoCriticalEdges.has(edgeIdx)); + const pseudo = [...pseudoCriticalEdges]; + + return [critical, pseudo]; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(E ^ 2)$ +* Space complexity: $O(V + E)$ + +> Where $V$ is the number of vertices and $E$ is the number of edges. \ No newline at end of file diff --git a/articles/greatest-common-divisor-of-strings.md b/articles/greatest-common-divisor-of-strings.md new file mode 100644 index 000000000..ae138e7d6 --- /dev/null +++ b/articles/greatest-common-divisor-of-strings.md @@ -0,0 +1,459 @@ +## 1. Iteration + +::tabs-start + +```python +class Solution: + def gcdOfStrings(self, str1: str, str2: str) -> str: + len1, len2 = len(str1), len(str2) + + def isDivisor(l): + if len1 % l != 0 or len2 % l != 0: + return False + f1, f2 = len1 // l, len2 // l + return str1[:l] * f1 == str1 and str1[:l] * f2 == str2 + + for l in range(min(len1, len2), 0, -1): + if isDivisor(l): + return str1[:l] + + return "" +``` + +```java +public class Solution { + public String gcdOfStrings(String str1, String str2) { + int len1 = str1.length(), len2 = str2.length(); + + for (int l = Math.min(len1, len2); l > 0; l--) { + if (isDivisor(l, len1, len2, str1, str2)) { + return str1.substring(0, l); + } + } + + return ""; + } + + public boolean isDivisor(int l, int len1, int len2, String str1, String str2) { + if (len1 % l != 0 || len2 % l != 0) { + return false; + } + String sub = str1.substring(0, l); + int f1 = len1 / l, f2 = len2 / l; + return sub.repeat(f1).equals(str1) && sub.repeat(f2).equals(str2); + } +} +``` + +```cpp +class Solution { +public: + string gcdOfStrings(string str1, string str2) { + int len1 = str1.size(), len2 = str2.size(); + + auto isDivisor = [&](int l) { + if (len1 % l != 0 || len2 % l != 0) { + return false; + } + string sub = str1.substr(0, l); + int f1 = len1 / l, f2 = len2 / l; + string repeated1 = "", repeated2 = ""; + for (int i = 0; i < f1; ++i) repeated1 += sub; + for (int i = 0; i < f2; ++i) repeated2 += sub; + return repeated1 == str1 && repeated2 == str2; + }; + + for (int l = min(len1, len2); l > 0; l--) { + if (isDivisor(l)) { + return str1.substr(0, l); + } + } + + return ""; + } +}; +``` + +```javascript +class Solution { + /** + * @param {string} str1 + * @param {string} str2 + * @return {string} + */ + gcdOfStrings(str1, str2) { + const len1 = str1.length, len2 = str2.length; + + const isDivisor = (l) => { + if (len1 % l !== 0 || len2 % l !== 0) { + return false; + } + const sub = str1.slice(0, l); + const f1 = len1 / l, f2 = len2 / l; + return sub.repeat(f1) === str1 && sub.repeat(f2) === str2; + }; + + for (let l = Math.min(len1, len2); l > 0; l--) { + if (isDivisor(l)) { + return str1.slice(0, l); + } + } + + return ""; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(min(m, n) * (m + n))$ +* Space complexity: $O(m + n)$ + +> Where $m$ and $n$ are the lengths of the strings $str1$ and $str2$ respectively. + +--- + +## 2. Iteration (Space Optimized) + +::tabs-start + +```python +class Solution: + def gcdOfStrings(self, str1: str, str2: str) -> str: + m, n = len(str1), len(str2) + if m < n: + m, n = n, m + str1, str2 = str2, str1 + + for l in range(n, 0, -1): + if m % l != 0 or n % l != 0: + continue + + valid = True + for i in range(m): + if str1[i] != str2[i % l]: + valid = False + break + if not valid: continue + + for i in range(l, n): + if str2[i] != str2[i % l]: + valid = False + break + if valid: return str2[:l] + + return "" +``` + +```java +public class Solution { + public String gcdOfStrings(String str1, String str2) { + int m = str1.length(), n = str2.length(); + if (m < n) { + String temp = str1; + str1 = str2; + str2 = temp; + int tempLen = m; + m = n; + n = tempLen; + } + + for (int l = n; l > 0; l--) { + if (m % l != 0 || n % l != 0) { + continue; + } + + boolean valid = true; + for (int i = 0; i < m; i++) { + if (str1.charAt(i) != str2.charAt(i % l)) { + valid = false; + break; + } + } + if (!valid) continue; + + for (int i = l; i < n; i++) { + if (str2.charAt(i) != str2.charAt(i % l)) { + valid = false; + break; + } + } + if (valid) { + return str2.substring(0, l); + } + } + + return ""; + } +} +``` + +```cpp +class Solution { +public: + string gcdOfStrings(string str1, string str2) { + int m = str1.size(), n = str2.size(); + if (m < n) { + swap(m, n); + swap(str1, str2); + } + + for (int l = n; l > 0; l--) { + if (m % l != 0 || n % l != 0) { + continue; + } + + bool valid = true; + for (int i = 0; i < m; i++) { + if (str1[i] != str2[i % l]) { + valid = false; + break; + } + } + if (!valid) continue; + + for (int i = l; i < n; i++) { + if (str2[i] != str2[i % l]) { + valid = false; + break; + } + } + if (valid) { + return str2.substr(0, l); + } + } + + return ""; + } +}; +``` + +```javascript +class Solution { + /** + * @param {string} str1 + * @param {string} str2 + * @return {string} + */ + gcdOfStrings(str1, str2) { + let m = str1.length, n = str2.length; + if (m < n) { + [m, n] = [n, m]; + [str1, str2] = [str2, str1]; + } + + for (let l = n; l > 0; l--) { + if (m % l !== 0 || n % l !== 0) { + continue; + } + + let valid = true; + for (let i = 0; i < m; i++) { + if (str1[i] !== str2[i % l]) { + valid = false; + break; + } + } + if (!valid) continue; + + for (let i = l; i < n; i++) { + if (str2[i] !== str2[i % l]) { + valid = false; + break; + } + } + if (valid) { + return str2.slice(0, l); + } + } + + return ""; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(min(m, n) * (m + n))$ +* Space complexity: $O(g)$ for the output string. + +> Where $m$ is the length of the string $str1$, $n$ is the length of the string $str2$, and $g$ is the length of the output string. + +--- + +## 3. Greatest Common Divisor + +::tabs-start + +```python +class Solution: + def gcdOfStrings(self, str1: str, str2: str) -> str: + if str1 + str2 != str2 + str1: + return "" + + g = gcd(len(str1), len(str2)) + return str1[:g] +``` + +```java +public class Solution { + public String gcdOfStrings(String str1, String str2) { + if (!(str1 + str2).equals(str2 + str1)) { + return ""; + } + int g = gcd(str1.length(), str2.length()); + return str1.substring(0, g); + } + + private int gcd(int a, int b) { + return b == 0 ? a : gcd(b, a % b); + } +} +``` + +```cpp +class Solution { +public: + string gcdOfStrings(string str1, string str2) { + if (str1 + str2 != str2 + str1) { + return ""; + } + int g = __gcd((int)str1.size(), (int)str2.size()); + return str1.substr(0, g); + } +}; +``` + +```javascript +class Solution { + /** + * @param {string} str1 + * @param {string} str2 + * @return {string} + */ + gcdOfStrings(str1, str2) { + if (str1 + str2 !== str2 + str1) { + return ""; + } + const gcd = (a, b) => (b === 0 ? a : gcd(b, a % b)); + const g = gcd(str1.length, str2.length); + return str1.slice(0, g); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(m + n)$ +* Space complexity: $O(m + n)$. + +> Where $m$ and $n$ are the lengths of the strings $str1$ and $str2$ respectively. + +--- + +## 4. Greatest Common Divisor (Space Optimized) + +::tabs-start + +```python +class Solution: + def gcdOfStrings(self, str1: str, str2: str) -> str: + g = gcd(len(str1), len(str2)) + + if all(str1[i] == str1[i % g] for i in range(len(str1))) and \ + all(str2[i] == str1[i % g] for i in range(len(str2))): + return str1[:g] + return "" +``` + +```java +public class Solution { + public String gcdOfStrings(String str1, String str2) { + int g = gcd(str1.length(), str2.length()); + + for (int i = 0; i < str1.length(); i++) { + if (str1.charAt(i) != str1.charAt(i % g)) { + return ""; + } + } + + for (int i = 0; i < str2.length(); i++) { + if (str2.charAt(i) != str1.charAt(i % g)) { + return ""; + } + } + + return str1.substring(0, g); + } + + private int gcd(int a, int b) { + return b == 0 ? a : gcd(b, a % b); + } +} +``` + +```cpp +class Solution { +public: + string gcdOfStrings(string str1, string str2) { + int g = __gcd((int)str1.size(), (int)str2.size()); + + for (int i = 0; i < str1.size(); i++) { + if (str1[i] != str1[i % g]) { + return ""; + } + } + + for (int i = 0; i < str2.size(); i++) { + if (str2[i] != str1[i % g]) { + return ""; + } + } + + return str1.substr(0, g); + } +}; +``` + +```javascript +class Solution { + /** + * @param {string} str1 + * @param {string} str2 + * @return {string} + */ + gcdOfStrings(str1, str2) { + const gcd = (a, b) => (b === 0 ? a : gcd(b, a % b)); + const g = gcd(str1.length, str2.length); + + for (let i = 0; i < str1.length; i++) { + if (str1[i] !== str1[i % g]) { + return ""; + } + } + + for (let i = 0; i < str2.length; i++) { + if (str2[i] !== str1[i % g]) { + return ""; + } + } + + return str1.slice(0, g); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(m + n)$ +* Space complexity: $O(g)$ for the output string. + +> Where $m$ is the length of the string $str1$, $n$ is the length of the string $str2$, and $g$ is the GCD of $m$ and $n$. \ No newline at end of file diff --git a/articles/jump-game-vii.md b/articles/jump-game-vii.md new file mode 100644 index 000000000..d2be241f9 --- /dev/null +++ b/articles/jump-game-vii.md @@ -0,0 +1,526 @@ +## 1. Brute Force (Memoization) + +::tabs-start + +```python +class Solution: + def canReach(self, s: str, minJump: int, maxJump: int) -> bool: + n = len(s) + dp = [None] * n + dp[n - 1] = True + + def dfs(i): + if dp[i] is not None: + return dp[i] + + dp[i] = False + for j in range(i + minJump, min(n, i + maxJump + 1)): + if s[j] == '0' and dfs(j): + dp[i] = True + break + + return dp[i] + + if s[-1] == '1': + return False + return dfs(0) +``` + +```java +public class Solution { + private Boolean[] dp; + private int n; + + public boolean canReach(String s, int minJump, int maxJump) { + this.n = s.length(); + this.dp = new Boolean[n]; + dp[n - 1] = true; + + if (s.charAt(n - 1) == '1') { + return false; + } + + return dfs(0, s, minJump, maxJump); + } + + private boolean dfs(int i, String s, int minJump, int maxJump) { + if (dp[i] != null) { + return dp[i]; + } + + dp[i] = false; + for (int j = i + minJump; j <= Math.min(n - 1, i + maxJump); j++) { + if (s.charAt(j) == '0' && dfs(j, s, minJump, maxJump)) { + dp[i] = true; + break; + } + } + return dp[i]; + } +} +``` + +```cpp +class Solution { +public: + int n; + vector> dp; + + bool canReach(string s, int minJump, int maxJump) { + this->n = s.size(); + dp.resize(n, nullopt); + dp[n - 1] = true; + + if (s[n - 1] == '1') { + return false; + } + + return dfs(0, s, minJump, maxJump); + } + +private: + bool dfs(int i, string& s, int& minJump, int& maxJump) { + if (dp[i].has_value()) { + return dp[i].value(); + } + + dp[i] = false; + for (int j = i + minJump; j <= min(n - 1, i + maxJump); ++j) { + if (s[j] == '0' && dfs(j, s, minJump, maxJump)) { + dp[i] = true; + break; + } + } + return dp[i].value(); + } +}; +``` + +```javascript +class Solution { + /** + * @param {string} s + * @param {number} minJump + * @param {number} maxJump + * @return {boolean} + */ + canReach(s, minJump, maxJump) { + const n = s.length; + const dp = new Array(n).fill(null); + dp[n - 1] = true; + + const dfs = (i) => { + if (dp[i] !== null) { + return dp[i]; + } + + dp[i] = false; + for (let j = i + minJump; j <= Math.min(n - 1, i + maxJump); j++) { + if (s[j] === '0' && dfs(j)) { + dp[i] = true; + break; + } + } + return dp[i]; + }; + + if (s[n - 1] === '1') { + return false; + } + return dfs(0); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n * m)$ +* Space complexity: $O(n)$ + +> Where $n$ is the length of the string $s$ and $m$ is the given range of the jump $(maxJump - minJump + 1)$. + +--- + +## 2. Breadth First Search + +::tabs-start + +```python +class Solution: + def canReach(self, s: str, minJump: int, maxJump: int) -> bool: + q = deque([0]) + farthest = 0 + + while q: + i = q.popleft() + start = max(i + minJump, farthest + 1) + for j in range(start, min(i + maxJump + 1, len(s))): + if s[j] == "0": + q.append(j) + if j == len(s) - 1: + return True + farthest = i + maxJump + + return False +``` + +```java +public class Solution { + public boolean canReach(String s, int minJump, int maxJump) { + Queue q = new LinkedList<>(); + q.add(0); + int farthest = 0; + int n = s.length(); + + while (!q.isEmpty()) { + int i = q.poll(); + int start = Math.max(i + minJump, farthest + 1); + + for (int j = start; j < Math.min(i + maxJump + 1, n); j++) { + if (s.charAt(j) == '0') { + q.add(j); + if (j == n - 1) { + return true; + } + } + } + farthest = i + maxJump; + } + + return false; + } +} +``` + +```cpp +class Solution { +public: + bool canReach(string s, int minJump, int maxJump) { + queue q; + q.push(0); + int farthest = 0; + int n = s.size(); + + while (!q.empty()) { + int i = q.front(); + q.pop(); + int start = max(i + minJump, farthest + 1); + + for (int j = start; j < min(i + maxJump + 1, n); ++j) { + if (s[j] == '0') { + q.push(j); + if (j == n - 1) { + return true; + } + } + } + farthest = i + maxJump; + } + + return false; + } +}; +``` + +```javascript +class Solution { + /** + * @param {string} s + * @param {number} minJump + * @param {number} maxJump + * @return {boolean} + */ + canReach(s, minJump, maxJump) { + const q = new Queue(); + q.push(0); + let farthest = 0; + + while (!q.isEmpty()) { + const i = q.pop(); + const start = Math.max(i + minJump, farthest + 1); + + for (let j = start; j < Math.min(i + maxJump + 1, s.length); j++) { + if (s[j] === '0') { + q.push(j); + if (j === s.length - 1) { + return true; + } + } + } + farthest = i + maxJump; + } + + return false; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n)$ +* Space complexity: $O(n)$ + +--- + +## 3. Dynamic Programming (Sliding Window) + +::tabs-start + +```python +class Solution: + def canReach(self, s: str, minJump: int, maxJump: int) -> bool: + n = len(s) + if s[n - 1] == '1': + return False + + dp = [False] * n + dp[0] = True + cnt = 0 + for i in range(1, n): + if i >= minJump and dp[i - minJump]: + cnt += 1 + if i > maxJump and dp[i - maxJump - 1]: + cnt -= 1 + if cnt > 0 and s[i] == '0': + dp[i] = True + + return dp[n - 1] +``` + +```java +public class Solution { + public boolean canReach(String s, int minJump, int maxJump) { + int n = s.length(); + if (s.charAt(n - 1) == '1') { + return false; + } + + boolean[] dp = new boolean[n]; + dp[0] = true; + int cnt = 0; + + for (int i = 1; i < n; i++) { + if (i >= minJump && dp[i - minJump]) { + cnt++; + } + if (i > maxJump && dp[i - maxJump - 1]) { + cnt--; + } + if (cnt > 0 && s.charAt(i) == '0') { + dp[i] = true; + } + } + + return dp[n - 1]; + } +} +``` + +```cpp +class Solution { +public: + bool canReach(string s, int minJump, int maxJump) { + int n = s.size(); + if (s[n - 1] == '1') { + return false; + } + + vector dp(n, false); + dp[0] = true; + int cnt = 0; + + for (int i = 1; i < n; i++) { + if (i >= minJump && dp[i - minJump]) { + cnt++; + } + if (i > maxJump && dp[i - maxJump - 1]) { + cnt--; + } + if (cnt > 0 && s[i] == '0') { + dp[i] = true; + } + } + + return dp[n - 1]; + } +}; +``` + +```javascript +class Solution { + /** + * @param {string} s + * @param {number} minJump + * @param {number} maxJump + * @return {boolean} + */ + canReach(s, minJump, maxJump) { + const n = s.length; + if (s[n - 1] === '1') { + return false; + } + + const dp = new Array(n).fill(false); + dp[0] = true; + let cnt = 0; + + for (let i = 1; i < n; i++) { + if (i >= minJump && dp[i - minJump]) { + cnt++; + } + if (i > maxJump && dp[i - maxJump - 1]) { + cnt--; + } + if (cnt > 0 && s[i] === '0') { + dp[i] = true; + } + } + + return dp[n - 1]; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n)$ +* Space complexity: $O(n)$ + +--- + +## 4. Dynamic Programming (Two Pointers) + +::tabs-start + +```python +class Solution: + def canReach(self, s: str, minJump: int, maxJump: int) -> bool: + n = len(s) + if s[n - 1] == '1': + return False + + dp = [False] * n + dp[0] = True + j = 0 + for i in range(n): + if dp[i] == False: + continue + + j = max(j, i + minJump) + while j < min(i + maxJump + 1, n): + if s[j] == '0': + dp[j] = True + j += 1 + + return dp[n - 1] +``` + +```java +public class Solution { + public boolean canReach(String s, int minJump, int maxJump) { + int n = s.length(); + if (s.charAt(n - 1) == '1') { + return false; + } + + boolean[] dp = new boolean[n]; + dp[0] = true; + int j = 0; + + for (int i = 0; i < n; i++) { + if (!dp[i]) { + continue; + } + j = Math.max(j, i + minJump); + while (j < Math.min(i + maxJump + 1, n)) { + if (s.charAt(j) == '0') { + dp[j] = true; + } + j++; + } + } + + return dp[n - 1]; + } +} +``` + +```cpp +class Solution { +public: + bool canReach(string s, int minJump, int maxJump) { + int n = s.size(); + if (s[n - 1] == '1') { + return false; + } + + vector dp(n, false); + dp[0] = true; + int j = 0; + + for (int i = 0; i < n; i++) { + if (!dp[i]) { + continue; + } + j = max(j, i + minJump); + while (j < min(i + maxJump + 1, n)) { + if (s[j] == '0') { + dp[j] = true; + } + j++; + } + } + + return dp[n - 1]; + } +}; +``` + +```javascript +class Solution { + /** + * @param {string} s + * @param {number} minJump + * @param {number} maxJump + * @return {boolean} + */ + canReach(s, minJump, maxJump) { + const n = s.length; + if (s[n - 1] === '1') { + return false; + } + + const dp = new Array(n).fill(false); + dp[0] = true; + let j = 0; + + for (let i = 0; i < n; i++) { + if (!dp[i]) { + continue; + } + j = Math.max(j, i + minJump); + while (j < Math.min(i + maxJump + 1, n)) { + if (s[j] === '0') { + dp[j] = true; + } + j++; + } + } + + return dp[n - 1]; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n)$ +* Space complexity: $O(n)$ \ No newline at end of file diff --git a/articles/minimum-array-end.md b/articles/minimum-array-end.md new file mode 100644 index 000000000..2aba4edba --- /dev/null +++ b/articles/minimum-array-end.md @@ -0,0 +1,320 @@ +## 1. Brute Force + +::tabs-start + +```python +class Solution: + def minEnd(self, n: int, x: int) -> int: + res = x + for i in range(n - 1): + res = (res + 1) | x + return res +``` + +```java +public class Solution { + public long minEnd(int n, int x) { + long res = x; + for (int i = 0; i < n - 1; i++) { + res = (res + 1) | x; + } + return res; + } +} +``` + +```cpp +class Solution { +public: + long long minEnd(int n, int x) { + long long res = x; + for (int i = 0; i < n - 1; i++) { + res = (res + 1) | x; + } + return res; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number} n + * @param {number} x + * @return {number} + */ + minEnd(n, x) { + let res = BigInt(x); + for (let i = 0; i < n - 1; i++) { + res = (res + BigInt(1)) | BigInt(x); + } + return Number(res); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n)$ +* Space complexity: $O(1)$ + +--- + +## 2. Binary Representation And Bit Manipulation + +::tabs-start + +```python +class Solution: + def minEnd(self, n: int, x: int) -> int: + res = 0 + n -= 1 + + x_bin = [0] * 64 # Binary representation of x + n_bin = [0] * 64 # Binary representation of n-1 + + for i in range(32): + x_bin[i] = (x >> i) & 1 + n_bin[i] = (n >> i) & 1 + + i_x = 0 + i_n = 0 + while i_x < 63: + while i_x < 63 and x_bin[i_x] != 0: + i_x += 1 + x_bin[i_x] = n_bin[i_n] + i_x += 1 + i_n += 1 + + for i in range(64): + if x_bin[i] == 1: + res += (1 << i) + + return res +``` + +```java +public class Solution { + public long minEnd(int n, int x) { + long res = 0; + n -= 1; + + int[] x_bin = new int[64]; // Binary representation of x + int[] n_bin = new int[64]; // Binary representation of n-1 + + for (int i = 0; i < 32; i++) { + x_bin[i] = (x >> i) & 1; + n_bin[i] = (n >> i) & 1; + } + + int i_x = 0; + int i_n = 0; + while (i_x < 63) { + while (i_x < 63 && x_bin[i_x] != 0) { + i_x++; + } + x_bin[i_x] = n_bin[i_n]; + i_x++; + i_n++; + } + + for (int i = 0; i < 64; i++) { + if (x_bin[i] == 1) { + res += (1L << i); + } + } + + return res; + } +} +``` + +```cpp +class Solution { +public: + long long minEnd(int n, int x) { + long long res = 0; + n -= 1; + + vector x_bin(64, 0); // Binary representation of x + vector n_bin(64, 0); // Binary representation of n-1 + + for (int i = 0; i < 32; i++) { + x_bin[i] = (x >> i) & 1; + n_bin[i] = (n >> i) & 1; + } + + int i_x = 0; + int i_n = 0; + while (i_x < 63) { + while (i_x < 63 && x_bin[i_x] != 0) { + i_x++; + } + x_bin[i_x] = n_bin[i_n]; + i_x++; + i_n++; + } + + for (int i = 0; i < 64; i++) { + if (x_bin[i] == 1) { + res += (1LL << i); + } + } + + return res; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number} n + * @param {number} x + * @return {number} + */ + minEnd(n, x) { + let res = 0n; + n -= 1; + + const x_bin = new Array(64).fill(0); // Binary representation of x + const n_bin = new Array(64).fill(0); // Binary representation of n-1 + + for (let i = 0; i < 32; i++) { + x_bin[i] = (x >> i) & 1; + n_bin[i] = (n >> i) & 1; + } + + let i_x = 0; + let i_n = 0; + while (i_x < 63) { + while (i_x < 63 && x_bin[i_x] !== 0) { + i_x++; + } + x_bin[i_x] = n_bin[i_n]; + i_x++; + i_n++; + } + + for (let i = 0; i < 64; i++) { + if (x_bin[i] === 1) { + res += BigInt(1) << BigInt(i); + } + } + + return Number(res); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(\log n)$ +* Space complexity: $O(\log n)$ + +--- + +## 3. Bit Manipulation + +::tabs-start + +```python +class Solution: + def minEnd(self, n: int, x: int) -> int: + res = x + i_x = 1 + i_n = 1 # for n-1 + + while i_n <= n - 1: + if i_x & x == 0: + if i_n & (n - 1): + res = res | i_x + i_n = i_n << 1 + i_x = i_x << 1 + + return res +``` + +```java +public class Solution { + public long minEnd(int n, int x) { + long res = x; + long i_x = 1; + long i_n = 1; // for n - 1 + + while (i_n <= n - 1) { + if ((i_x & x) == 0) { + if ((i_n & (n - 1)) != 0) { + res = res | i_x; + } + i_n = i_n << 1; + } + i_x = i_x << 1; + } + + return res; + } +} +``` + +```cpp +class Solution { +public: + long long minEnd(int n, int x) { + long long res = x; + long long i_x = 1; + long long i_n = 1; // for n - 1 + + while (i_n <= n - 1) { + if ((i_x & x) == 0) { + if (i_n & (n - 1)) { + res = res | i_x; + } + i_n = i_n << 1; + } + i_x = i_x << 1; + } + + return res; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number} n + * @param {number} x + * @return {number} + */ + minEnd(n, x) { + let res = BigInt(x); + let i_x = 1n; + let i_n = 1n; + n = BigInt(n - 1); + + while (i_n <= n) { + if ((i_x & res) === 0n) { + if ((i_n & n) !== 0n) { + res = res | i_x; + } + i_n = i_n << 1n; + } + i_x = i_x << 1n; + } + + return Number(res); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(\log n)$ +* Space complexity: $O(1)$ \ No newline at end of file diff --git a/articles/minimum-height-trees.md b/articles/minimum-height-trees.md new file mode 100644 index 000000000..19f63684e --- /dev/null +++ b/articles/minimum-height-trees.md @@ -0,0 +1,847 @@ +## 1. Brute Force (DFS) + +::tabs-start + +```python +class Solution: + def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]: + adj = [[] for _ in range(n)] + for u, v in edges: + adj[u].append(v) + adj[v].append(u) + + def dfs(node, parent): + hgt = 0 + for nei in adj[node]: + if nei == parent: + continue + hgt = max(hgt, 1 + dfs(nei, node)) + return hgt + + minHgt = n + res = [] + for i in range(n): + curHgt = dfs(i, -1) + if curHgt == minHgt: + res.append(i) + elif curHgt < minHgt: + res = [i] + minHgt = curHgt + + return res +``` + +```java +public class Solution { + private List> adj; + + public List findMinHeightTrees(int n, int[][] edges) { + adj = new ArrayList<>(); + for (int i = 0; i < n; i++) { + adj.add(new ArrayList<>()); + } + for (int[] edge : edges) { + adj.get(edge[0]).add(edge[1]); + adj.get(edge[1]).add(edge[0]); + } + + int minHgt = n; + List result = new ArrayList<>(); + for (int i = 0; i < n; i++) { + int curHgt = dfs(i, -1); + if (curHgt == minHgt) { + result.add(i); + } else if (curHgt < minHgt) { + result = new ArrayList<>(); + result.add(i); + minHgt = curHgt; + } + } + return result; + } + + private int dfs(int node, int parent) { + int hgt = 0; + for (int nei : adj.get(node)) { + if (nei == parent) { + continue; + } + hgt = Math.max(hgt, 1 + dfs(nei, node)); + } + return hgt; + } +} +``` + +```cpp +class Solution { +private: + vector> adj; + + int dfs(int node, int parent) { + int hgt = 0; + for (int nei : adj[node]) { + if (nei == parent) + continue; + hgt = max(hgt, 1 + dfs(nei, node)); + } + return hgt; + } + +public: + vector findMinHeightTrees(int n, vector>& edges) { + adj.resize(n); + for (const auto& edge : edges) { + adj[edge[0]].push_back(edge[1]); + adj[edge[1]].push_back(edge[0]); + } + + int minHgt = n; + vector result; + for (int i = 0; i < n; i++) { + int curHgt = dfs(i, -1); + if (curHgt == minHgt) { + result.push_back(i); + } else if (curHgt < minHgt) { + result = {i}; + minHgt = curHgt; + } + } + return result; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number} n + * @param {number[][]} edges + * @return {number[]} + */ + findMinHeightTrees(n, edges) { + const adj = Array.from({ length: n }, () => []); + for (const [u, v] of edges) { + adj[u].push(v); + adj[v].push(u); + } + + const dfs = (node, parent) => { + let hgt = 0; + for (const nei of adj[node]) { + if (nei === parent) continue; + hgt = Math.max(hgt, 1 + dfs(nei, node)); + } + return hgt; + }; + + let minHgt = n; + const result = []; + for (let i = 0; i < n; i++) { + const curHgt = dfs(i, -1); + if (curHgt === minHgt) { + result.push(i); + } else if (curHgt < minHgt) { + result.length = 0; + result.push(i); + minHgt = curHgt; + } + } + return result; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(V * (V + E))$ +* Space complexity: $O(V)$ + +> Where $V$ is the number of vertices and $E$ is the number of edges. + +--- + +## 2. Dynamic Programming On Trees (Rerooting) + +::tabs-start + +```python +class Solution: + def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]: + adj = [[] for _ in range(n)] + for u, v in edges: + adj[u].append(v) + adj[v].append(u) + + dp = [[0] * 2 for _ in range(n)] # top two heights for each node + + def dfs(node, parent): + for nei in adj[node]: + if nei == parent: + continue + dfs(nei, node) + curHgt = 1 + dp[nei][0] + if curHgt > dp[node][0]: + dp[node][1] = dp[node][0] + dp[node][0] = curHgt + elif curHgt > dp[node][1]: + dp[node][1] = curHgt + + def dfs1(node, parent, topHgt): + if topHgt > dp[node][0]: + dp[node][1] = dp[node][0] + dp[node][0] = topHgt + elif topHgt > dp[node][1]: + dp[node][1] = topHgt + + for nei in adj[node]: + if nei == parent: + continue + toChild = 1 + (dp[node][1] if dp[node][0] == 1 + dp[nei][0] else dp[node][0]) + dfs1(nei, node, toChild) + + dfs(0, -1) + dfs1(0, -1, 0) + + minHgt, res = n, [] + for i in range(n): + minHgt = min(minHgt, dp[i][0]) + for i in range(n): + if minHgt == dp[i][0]: + res.append(i) + return res +``` + +```java +class Solution { + private List> adj; + private int[][] dp; + + public List findMinHeightTrees(int n, int[][] edges) { + adj = new ArrayList<>(); + for (int i = 0; i < n; i++) { + adj.add(new ArrayList<>()); + } + for (int[] edge : edges) { + adj.get(edge[0]).add(edge[1]); + adj.get(edge[1]).add(edge[0]); + } + + dp = new int[n][2]; // top two heights for each node + dfs(0, -1); + dfs1(0, -1, 0); + + int minHgt = n; + List res = new ArrayList<>(); + for (int i = 0; i < n; i++) { + minHgt = Math.min(minHgt, dp[i][0]); + } + for (int i = 0; i < n; i++) { + if (minHgt == dp[i][0]) { + res.add(i); + } + } + return res; + } + + private void dfs(int node, int parent) { + for (int nei : adj.get(node)) { + if (nei == parent) continue; + dfs(nei, node); + int curHgt = 1 + dp[nei][0]; + if (curHgt > dp[node][0]) { + dp[node][1] = dp[node][0]; + dp[node][0] = curHgt; + } else if (curHgt > dp[node][1]) { + dp[node][1] = curHgt; + } + } + } + + private void dfs1(int node, int parent, int topHgt) { + if (topHgt > dp[node][0]) { + dp[node][1] = dp[node][0]; + dp[node][0] = topHgt; + } else if (topHgt > dp[node][1]) { + dp[node][1] = topHgt; + } + + for (int nei : adj.get(node)) { + if (nei == parent) continue; + int toChild = 1 + ((dp[node][0] == 1 + dp[nei][0]) ? dp[node][1] : dp[node][0]); + dfs1(nei, node, toChild); + } + } +} +``` + +```cpp +class Solution { +private: + vector> adj; + vector> dp; + + void dfs(int node, int parent) { + for (int nei : adj[node]) { + if (nei == parent) continue; + dfs(nei, node); + int curHgt = 1 + dp[nei][0]; + if (curHgt > dp[node][0]) { + dp[node][1] = dp[node][0]; + dp[node][0] = curHgt; + } else if (curHgt > dp[node][1]) { + dp[node][1] = curHgt; + } + } + } + + void dfs1(int node, int parent, int topHgt) { + if (topHgt > dp[node][0]) { + dp[node][1] = dp[node][0]; + dp[node][0] = topHgt; + } else if (topHgt > dp[node][1]) { + dp[node][1] = topHgt; + } + + for (int nei : adj[node]) { + if (nei == parent) continue; + int toChild = 1 + ((dp[node][0] == 1 + dp[nei][0]) ? dp[node][1] : dp[node][0]); + dfs1(nei, node, toChild); + } + } + +public: + vector findMinHeightTrees(int n, vector>& edges) { + adj.resize(n); + dp.assign(n, vector(2, 0)); // top two heights for each node + for (const auto& edge : edges) { + adj[edge[0]].push_back(edge[1]); + adj[edge[1]].push_back(edge[0]); + } + + dfs(0, -1); + dfs1(0, -1, 0); + + int minHgt = n; + vector res; + for (int i = 0; i < n; i++) { + minHgt = min(minHgt, dp[i][0]); + } + for (int i = 0; i < n; i++) { + if (minHgt == dp[i][0]) { + res.push_back(i); + } + } + return res; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number} n + * @param {number[][]} edges + * @return {number[]} + */ + findMinHeightTrees(n, edges) { + const adj = Array.from({ length: n }, () => []); + for (const [u, v] of edges) { + adj[u].push(v); + adj[v].push(u); + } + + // top two heights for each node + const dp = Array.from({ length: n }, () => [0, 0]); + + const dfs = (node, parent) => { + for (const nei of adj[node]) { + if (nei === parent) continue; + dfs(nei, node); + const curHgt = 1 + dp[nei][0]; + if (curHgt > dp[node][0]) { + dp[node][1] = dp[node][0]; + dp[node][0] = curHgt; + } else if (curHgt > dp[node][1]) { + dp[node][1] = curHgt; + } + } + }; + + const dfs1 = (node, parent, topHgt) => { + if (topHgt > dp[node][0]) { + dp[node][1] = dp[node][0]; + dp[node][0] = topHgt; + } else if (topHgt > dp[node][1]) { + dp[node][1] = topHgt; + } + + for (const nei of adj[node]) { + if (nei === parent) continue; + const toChild = 1 + ((dp[node][0] === 1 + dp[nei][0]) ? dp[node][1] : dp[node][0]); + dfs1(nei, node, toChild); + } + }; + + dfs(0, -1); + dfs1(0, -1, 0); + + let minHgt = n; + const res = []; + for (let i = 0; i < n; i++) { + minHgt = Math.min(minHgt, dp[i][0]); + } + for (let i = 0; i < n; i++) { + if (minHgt === dp[i][0]) { + res.push(i); + } + } + return res; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(V + E)$ +* Space complexity: $O(V)$ + +> Where $V$ is the number of vertices and $E$ is the number of edges. + +--- + +## 3. Find Centroids of the Tree (DFS) + +::tabs-start + +```python +class Solution: + def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]: + if n == 1: + return [0] + + adj = [[] for _ in range(n)] + for u, v in edges: + adj[u].append(v) + adj[v].append(u) + + def dfs(node, parent): + farthest_node = node + max_distance = 0 + for nei in adj[node]: + if nei != parent: + nei_node, nei_distance = dfs(nei, node) + if nei_distance + 1 > max_distance: + max_distance = nei_distance + 1 + farthest_node = nei_node + return farthest_node, max_distance + + node_a, _ = dfs(0, -1) + node_b, diameter = dfs(node_a, -1) + + centroids = [] + + def find_centroids(node, parent): + if node == node_b: + centroids.append(node) + return True + for nei in adj[node]: + if nei != parent: + if find_centroids(nei, node): + centroids.append(node) + return True + return False + + find_centroids(node_a, -1) + L = len(centroids) + if diameter % 2 == 0: + return [centroids[L // 2]] + else: + return [centroids[L // 2 - 1], centroids[L // 2]] +``` + +```java +public class Solution { + private List[] adj; + private List centroids; + private int nodeB; + + public List findMinHeightTrees(int n, int[][] edges) { + if (n == 1) + return Collections.singletonList(0); + + adj = new ArrayList[n]; + for (int i = 0; i < n; ++i) + adj[i] = new ArrayList<>(); + + for (int[] edge : edges) { + adj[edge[0]].add(edge[1]); + adj[edge[1]].add(edge[0]); + } + + int nodeA = dfs(0, -1)[0]; + nodeB = dfs(nodeA, -1)[0]; + centroids = new ArrayList<>(); + findCentroids(nodeA, -1); + + int L = centroids.size(); + if (dfs(nodeA, -1)[1] % 2 == 0) { + return Collections.singletonList(centroids.get(L / 2)); + } else { + return Arrays.asList(centroids.get(L / 2 - 1), centroids.get(L / 2)); + } + } + + private int[] dfs(int node, int parent) { + int farthestNode = node, maxDistance = 0; + for (int neighbor : adj[node]) { + if (neighbor != parent) { + int[] res = dfs(neighbor, node); + if (res[1] + 1 > maxDistance) { + maxDistance = res[1] + 1; + farthestNode = res[0]; + } + } + } + return new int[] { farthestNode, maxDistance }; + } + + private boolean findCentroids(int node, int parent) { + if (node == nodeB) { + centroids.add(node); + return true; + } + for (int neighbor : adj[node]) { + if (neighbor != parent) { + if (findCentroids(neighbor, node)) { + centroids.add(node); + return true; + } + } + } + return false; + } +} +``` + +```cpp +class Solution { +public: + vector> adj; + vector centroids; + int nodeB; + + vector findMinHeightTrees(int n, vector>& edges) { + if (n == 1) + return {0}; + + adj.resize(n); + for (const auto& edge : edges) { + adj[edge[0]].push_back(edge[1]); + adj[edge[1]].push_back(edge[0]); + } + + int nodeA = dfs(0, -1).first; + nodeB = dfs(nodeA, -1).first; + findCentroids(nodeA, -1); + + int L = centroids.size(); + if (dfs(nodeA, -1).second % 2 == 0) + return {centroids[L / 2]}; + else + return {centroids[L / 2 - 1], centroids[L / 2]}; + } + +private: + pair dfs(int node, int parent) { + int farthestNode = node, maxDistance = 0; + for (int neighbor : adj[node]) { + if (neighbor != parent) { + auto res = dfs(neighbor, node); + if (res.second + 1 > maxDistance) { + maxDistance = res.second + 1; + farthestNode = res.first; + } + } + } + return {farthestNode, maxDistance}; + } + + bool findCentroids(int node, int parent) { + if (node == nodeB) { + centroids.push_back(node); + return true; + } + for (int neighbor : adj[node]) { + if (neighbor != parent) { + if (findCentroids(neighbor, node)) { + centroids.push_back(node); + return true; + } + } + } + return false; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number} n + * @param {number[][]} edges + * @return {number[]} + */ + findMinHeightTrees(n, edges) { + if (n === 1) return [0]; + + const adj = Array.from({ length: n }, () => []); + + for (const [u, v] of edges) { + adj[u].push(v); + adj[v].push(u); + } + + const dfs = (node, parent) => { + let farthestNode = node; + let maxDistance = 0; + + for (const neighbor of adj[node]) { + if (neighbor !== parent) { + const [farthest, dist] = dfs(neighbor, node); + if (dist + 1 > maxDistance) { + maxDistance = dist + 1; + farthestNode = farthest; + } + } + } + return [farthestNode, maxDistance]; + }; + + const findCentroids = (node, parent, centroids) => { + if (node === nodeB) { + centroids.push(node); + return true; + } + + for (const neighbor of adj[node]) { + if (neighbor !== parent) { + if (findCentroids(neighbor, node, centroids)) { + centroids.push(node); + return true; + } + } + } + return false; + }; + + const [nodeA] = dfs(0, -1); + const [nodeB, diameter] = dfs(nodeA, -1); + this.nodeB = nodeB; + + const centroids = []; + findCentroids(nodeA, -1, centroids); + + const L = centroids.length; + return diameter % 2 === 0 + ? [centroids[Math.floor(L / 2)]] + : [centroids[Math.floor(L / 2) - 1], centroids[Math.floor(L / 2)]]; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(V + E)$ +* Space complexity: $O(V)$ + +> Where $V$ is the number of vertices and $E$ is the number of edges. + +--- + +## 4. Topological Sorting (BFS) + +::tabs-start + +```python +class Solution: + def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]: + if n == 1: + return [0] + + adj = defaultdict(list) + for n1, n2 in edges: + adj[n1].append(n2) + adj[n2].append(n1) + + edge_cnt = {} + leaves = deque() + + for src, neighbors in adj.items(): + edge_cnt[src] = len(neighbors) + if len(neighbors) == 1: + leaves.append(src) + + while leaves: + if n <= 2: + return list(leaves) + for _ in range(len(leaves)): + node = leaves.popleft() + n -= 1 + for nei in adj[node]: + edge_cnt[nei] -= 1 + if edge_cnt[nei] == 1: + leaves.append(nei) +``` + +```java +public class Solution { + public List findMinHeightTrees(int n, int[][] edges) { + if (n == 1) return Collections.singletonList(0); + + List[] adj = new ArrayList[n]; + for (int i = 0; i < n; ++i) + adj[i] = new ArrayList<>(); + + for (int[] edge : edges) { + adj[edge[0]].add(edge[1]); + adj[edge[1]].add(edge[0]); + } + + int[] edge_cnt = new int[n]; + Queue leaves = new LinkedList<>(); + + for (int i = 0; i < n; i++) { + edge_cnt[i] = adj[i].size(); + if (adj[i].size() == 1) + leaves.offer(i); + } + + while (!leaves.isEmpty()) { + if (n <= 2) return new ArrayList<>(leaves); + int size = leaves.size(); + for (int i = 0; i < size; ++i) { + int node = leaves.poll(); + n--; + for (int nei : adj[node]) { + edge_cnt[nei]--; + if (edge_cnt[nei] == 1) + leaves.offer(nei); + } + } + } + + return new ArrayList<>(); + } +} +``` + +```cpp +class Solution { +public: + vector findMinHeightTrees(int n, vector>& edges) { + if (n == 1) return {0}; + + vector> adj(n); + for (auto& edge : edges) { + adj[edge[0]].push_back(edge[1]); + adj[edge[1]].push_back(edge[0]); + } + + vector edge_cnt(n); + queue leaves; + + for (int i = 0; i < n; ++i) { + edge_cnt[i] = adj[i].size(); + if (adj[i].size() == 1) + leaves.push(i); + } + + while (!leaves.empty()) { + if (n <= 2) { + vector result; + while (!leaves.empty()) { + result.push_back(leaves.front()); + leaves.pop(); + } + return result; + } + int size = leaves.size(); + for (int i = 0; i < size; ++i) { + int node = leaves.front(); + leaves.pop(); + --n; + for (int& nei : adj[node]) { + --edge_cnt[nei]; + if (edge_cnt[nei] == 1) + leaves.push(nei); + } + } + } + + return {}; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number} n + * @param {number[][]} edges + * @return {number[]} + */ + findMinHeightTrees(n, edges) { + if (n === 1) return [0]; + + const adj = Array.from({ length: n }, () => []); + + for (const [n1, n2] of edges) { + adj[n1].push(n2); + adj[n2].push(n1); + } + + const edgeCnt = Array(n).fill(0); + const leaves = new Queue(); + + for (let i = 0; i < n; i++) { + edgeCnt[i] = adj[i].length; + if (adj[i].length === 1) { + leaves.push(i); + } + } + + while (!leaves.isEmpty()) { + if (n <= 2) return leaves.toArray(); + const size = leaves.size(); + for (let i = 0; i < size; i++) { + const node = leaves.pop(); + n--; + for (const nei of adj[node]) { + edgeCnt[nei]--; + if (edgeCnt[nei] === 1) { + leaves.push(nei); + } + } + } + } + + return []; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(V + E)$ +* Space complexity: $O(V)$ + +> Where $V$ is the number of vertices and $E$ is the number of edges. \ No newline at end of file diff --git a/articles/path-with-minimum-effort.md b/articles/path-with-minimum-effort.md new file mode 100644 index 000000000..bccc0e7b9 --- /dev/null +++ b/articles/path-with-minimum-effort.md @@ -0,0 +1,851 @@ +## 1. Dijkstra's Algorithm + +::tabs-start + +```python +class Solution: + def minimumEffortPath(self, heights: List[List[int]]) -> int: + ROWS, COLS = len(heights), len(heights[0]) + minHeap = [[0, 0, 0]] # [diff, row, col] + visit = set() + directions = [[0, 1], [0, -1], [1, 0], [-1, 0]] + + while minHeap: + diff, r, c = heapq.heappop(minHeap) + + if (r, c) in visit: + continue + + visit.add((r, c)) + + if (r, c) == (ROWS - 1, COLS - 1): + return diff + + for dr, dc in directions: + newR, newC = r + dr, c + dc + if ( + newR < 0 or newC < 0 or + newR >= ROWS or newC >= COLS or + (newR, newC) in visit + ): + continue + + newDiff = max(diff, abs(heights[r][c] - heights[newR][newC])) + heapq.heappush(minHeap, [newDiff, newR, newC]) + + return 0 +``` + +```java +public class Solution { + public int minimumEffortPath(int[][] heights) { + int rows = heights.length; + int cols = heights[0].length; + int[][] dist = new int[rows][cols]; + for (int[] row : dist) { + Arrays.fill(row, Integer.MAX_VALUE); + } + dist[0][0] = 0; + + PriorityQueue minHeap = new PriorityQueue<>((a, b) -> a[0] - b[0]); + minHeap.offer(new int[]{0, 0, 0}); // {diff, row, col} + + int[][] directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; + + while (!minHeap.isEmpty()) { + int[] curr = minHeap.poll(); + int diff = curr[0], r = curr[1], c = curr[2]; + + if (r == rows - 1 && c == cols - 1) return diff; + if (dist[r][c] < diff) continue; + + for (int[] dir : directions) { + int newR = r + dir[0], newC = c + dir[1]; + if (newR < 0 || newC < 0 || newR >= rows || newC >= cols) { + continue; + } + + int newDiff = Math.max(diff, Math.abs(heights[r][c] - heights[newR][newC])); + if (newDiff < dist[newR][newC]) { + dist[newR][newC] = newDiff; + minHeap.offer(new int[]{newDiff, newR, newC}); + } + } + } + + return 0; + } +} +``` + +```cpp +class Solution { +public: + int minimumEffortPath(vector>& heights) { + int rows = heights.size(), cols = heights[0].size(); + vector> dist(rows, vector(cols, INT_MAX)); + dist[0][0] = 0; + + priority_queue, vector>, greater<>> minHeap; + minHeap.push({0, 0, 0}); // {diff, row, col} + + vector> directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; + + while (!minHeap.empty()) { + auto curr = minHeap.top(); + minHeap.pop(); + int diff = curr[0], r = curr[1], c = curr[2]; + + if (r == rows - 1 && c == cols - 1) return diff; + if (dist[r][c] < diff) continue; + + for (auto& dir : directions) { + int newR = r + dir[0], newC = c + dir[1]; + if (newR < 0 || newC < 0 || newR >= rows || newC >= cols) { + continue; + } + + int newDiff = max(diff, abs(heights[r][c] - heights[newR][newC])); + if (newDiff < dist[newR][newC]) { + dist[newR][newC] = newDiff; + minHeap.push({newDiff, newR, newC}); + } + } + } + + return 0; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number[][]} heights + * @return {number} + */ + minimumEffortPath(heights) { + const rows = heights.length; + const cols = heights[0].length; + const dist = Array.from({ length: rows }, () => + Array(cols).fill(Infinity) + ); + dist[0][0] = 0; + + const minHeap = new MinPriorityQueue({ priority: (a) => a[0] }); + minHeap.enqueue([0, 0, 0]); // [diff, row, col] + + const directions = [ + [0, 1], [0, -1], + [1, 0], [-1, 0], + ]; + + while (!minHeap.isEmpty()) { + const [diff, r, c] = minHeap.dequeue().element; + + if (r === rows - 1 && c === cols - 1) return diff; + if (dist[r][c] < diff) continue; + + for (const [dr, dc] of directions) { + const newR = r + dr; + const newC = c + dc; + if (newR < 0 || newC < 0 || newR >= rows || newC >= cols) { + continue; + } + + const newDiff = Math.max( + diff, + Math.abs(heights[r][c] - heights[newR][newC]) + ); + if (newDiff < dist[newR][newC]) { + dist[newR][newC] = newDiff; + minHeap.enqueue([newDiff, newR, newC]); + } + } + } + + return 0; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(m * n * \log (m * n))$ +* Space complexity: $O(m * n)$ + +> Where $m$ is the number of rows and $n$ is the number of columns in the given matrix. + +--- + +## 2. Binary Search + DFS + +::tabs-start + +```python +class Solution: + def minimumEffortPath(self, heights: List[List[int]]) -> int: + ROWS, COLS = len(heights), len(heights[0]) + directions = [[0, 1], [0, -1], [1, 0], [-1, 0]] + + def dfs(r, c, limit, visited): + if r == ROWS - 1 and c == COLS - 1: + return True + + visited.add((r, c)) + for dr, dc in directions: + newR, newC = r + dr, c + dc + if (newR < 0 or newC < 0 or + newR >= ROWS or newC >= COLS or + (newR, newC) in visited or + abs(heights[newR][newC] - heights[r][c]) > limit): + continue + + if dfs(newR, newC, limit, visited): + return True + return False + + l, r = 0, 1000000 + res = r + + while l <= r: + mid = (l + r) // 2 + if dfs(0, 0, mid, set()): + res = mid + r = mid - 1 + else: + l = mid + 1 + + return res +``` + +```java +public class Solution { + private int ROWS, COLS; + private int[][] heights; + private int[][] directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; + private boolean[][] visited; + + public int minimumEffortPath(int[][] heights) { + this.heights = heights; + this.ROWS = heights.length; + this.COLS = heights[0].length; + this.visited = new boolean[ROWS][COLS]; + + int l = 0, r = 1_000_000, res = r; + + while (l <= r) { + int mid = (l + r) / 2; + for (boolean[] row : visited) Arrays.fill(row, false); + if (dfs(0, 0, mid)) { + res = mid; + r = mid - 1; + } else { + l = mid + 1; + } + } + + return res; + } + + private boolean dfs(int r, int c, int limit) { + if (r == ROWS - 1 && c == COLS - 1) { + return true; + } + + visited[r][c] = true; + for (int[] dir : directions) { + int newR = r + dir[0]; + int newC = c + dir[1]; + if (newR < 0 || newC < 0 || newR >= ROWS || newC >= COLS || visited[newR][newC]) { + continue; + } + if (Math.abs(heights[newR][newC] - heights[r][c]) > limit) { + continue; + } + if (dfs(newR, newC, limit)) { + return true; + } + } + return false; + } +} +``` + +```cpp +class Solution { +private: + int ROWS, COLS; + vector> heights; + vector> visited; + vector> directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; + +public: + int minimumEffortPath(vector>& heights) { + this->heights = heights; + this->ROWS = heights.size(); + this->COLS = heights[0].size(); + this->visited = vector>(ROWS, vector(COLS, false)); + + int l = 0, r = 1'000'000, res = r; + while (l <= r) { + int mid = (l + r) / 2; + for (auto& row : visited) { + fill(row.begin(), row.end(), false); + } + if (dfs(0, 0, mid)) { + res = mid; + r = mid - 1; + } else { + l = mid + 1; + } + } + return res; + } + +private: + bool dfs(int r, int c, int limit) { + if (r == ROWS - 1 && c == COLS - 1) { + return true; + } + + visited[r][c] = true; + for (const auto& dir : directions) { + int newR = r + dir[0]; + int newC = c + dir[1]; + if (newR < 0 || newC < 0 || newR >= ROWS || newC >= COLS || visited[newR][newC]) { + continue; + } + if (abs(heights[newR][newC] - heights[r][c]) > limit) { + continue; + } + if (dfs(newR, newC, limit)) { + return true; + } + } + return false; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number[][]} heights + * @return {number} + */ + minimumEffortPath(heights) { + const ROWS = heights.length; + const COLS = heights[0].length; + const directions = [ + [0, 1], [0, -1], + [1, 0], [-1, 0] + ]; + let visited = Array.from({ length: ROWS }, () => + Array(COLS).fill(false) + ); + + const dfs = (r, c, limit) => { + if (r === ROWS - 1 && c === COLS - 1) { + return true; + } + + visited[r][c] = true; + for (const [dr, dc] of directions) { + const newR = r + dr; + const newC = c + dc; + + if (newR < 0 || newC < 0 || + newR >= ROWS || newC >= COLS || + visited[newR][newC]) { + continue; + } + if (Math.abs(heights[newR][newC] - heights[r][c]) > limit) { + continue; + } + if (dfs(newR, newC, limit)) { + return true; + } + } + return false; + }; + + let l = 0, r = 1_000_000, res = r; + while (l <= r) { + const mid = Math.floor((l + r) / 2); + for (const row of visited) { + row.fill(false); + } + if (dfs(0, 0, mid)) { + res = mid; + r = mid - 1; + } else { + l = mid + 1; + } + } + return res; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(m * n * \log (m * n))$ +* Space complexity: $O(m * n)$ + +> Where $m$ is the number of rows and $n$ is the number of columns in the given matrix. + +--- + +## 3. Kruskal's Algorithm + +::tabs-start + +```python +class DSU: + def __init__(self, n): + self.Parent = list(range(n + 1)) + self.Size = [1] * (n + 1) + + def find(self, node): + if self.Parent[node] != node: + self.Parent[node] = self.find(self.Parent[node]) + return self.Parent[node] + + def union(self, u, v): + pu = self.find(u) + pv = self.find(v) + if pu == pv: + return False + if self.Size[pu] < self.Size[pv]: + pu, pv = pv, pu + self.Size[pu] += self.Size[pv] + self.Parent[pv] = pu + return True + + +class Solution: + def minimumEffortPath(self, heights: List[List[int]]) -> int: + ROWS, COLS = len(heights), len(heights[0]) + edges = [] + for r in range(ROWS): + for c in range(COLS): + if r + 1 < ROWS: + edges.append((abs(heights[r][c] - heights[r + 1][c]), r * COLS + c, (r + 1) * COLS + c)) + if c + 1 < COLS: + edges.append((abs(heights[r][c] - heights[r][c + 1]), r * COLS + c, r * COLS + c + 1)) + + edges.sort() + dsu = DSU(ROWS * COLS) + for weight, u, v in edges: + dsu.union(u, v) + if dsu.find(0) == dsu.find(ROWS * COLS - 1): + return weight + return 0 +``` + +```java +class DSU { + private int[] Parent; + private int[] Size; + + public DSU(int n) { + Parent = new int[n + 1]; + Size = new int[n + 1]; + for (int i = 0; i <= n; i++) { + Parent[i] = i; + Size[i] = 1; + } + } + + public int find(int node) { + if (Parent[node] != node) { + Parent[node] = find(Parent[node]); + } + return Parent[node]; + } + + public boolean union(int u, int v) { + int pu = find(u); + int pv = find(v); + if (pu == pv) { + return false; + } + if (Size[pu] < Size[pv]) { + int temp = pu; + pu = pv; + pv = temp; + } + Size[pu] += Size[pv]; + Parent[pv] = pu; + return true; + } +} + +public class Solution { + public int minimumEffortPath(int[][] heights) { + int ROWS = heights.length; + int COLS = heights[0].length; + List edges = new ArrayList<>(); + for (int r = 0; r < ROWS; r++) { + for (int c = 0; c < COLS; c++) { + if (r + 1 < ROWS) { + edges.add(new int[]{Math.abs(heights[r][c] - heights[r + 1][c]), r * COLS + c, (r + 1) * COLS + c}); + } + if (c + 1 < COLS) { + edges.add(new int[]{Math.abs(heights[r][c] - heights[r][c + 1]), r * COLS + c, r * COLS + c + 1}); + } + } + } + + edges.sort(Comparator.comparingInt(a -> a[0])); + DSU dsu = new DSU(ROWS * COLS); + for (int[] edge : edges) { + int weight = edge[0], u = edge[1], v = edge[2]; + dsu.union(u, v); + if (dsu.find(0) == dsu.find(ROWS * COLS - 1)) { + return weight; + } + } + return 0; + } +} +``` + +```cpp +class DSU { +private: + vector Parent, Size; + +public: + DSU(int n) { + Parent.resize(n + 1); + Size.resize(n + 1, 1); + for (int i = 0; i <= n; i++) { + Parent[i] = i; + } + } + + int find(int node) { + if (Parent[node] != node) { + Parent[node] = find(Parent[node]); + } + return Parent[node]; + } + + bool unionSets(int u, int v) { + int pu = find(u); + int pv = find(v); + if (pu == pv) { + return false; + } + if (Size[pu] < Size[pv]) { + swap(pu, pv); + } + Size[pu] += Size[pv]; + Parent[pv] = pu; + return true; + } +}; + +class Solution { +public: + int minimumEffortPath(vector>& heights) { + int ROWS = heights.size(); + int COLS = heights[0].size(); + vector> edges; + for (int r = 0; r < ROWS; r++) { + for (int c = 0; c < COLS; c++) { + if (r + 1 < ROWS) { + edges.push_back({abs(heights[r][c] - heights[r + 1][c]), r * COLS + c, (r + 1) * COLS + c}); + } + if (c + 1 < COLS) { + edges.push_back({abs(heights[r][c] - heights[r][c + 1]), r * COLS + c, r * COLS + c + 1}); + } + } + } + + sort(edges.begin(), edges.end()); + DSU dsu(ROWS * COLS); + for (auto& edge : edges) { + int weight, u, v; + tie(weight, u, v) = edge; + dsu.unionSets(u, v); + if (dsu.find(0) == dsu.find(ROWS * COLS - 1)) { + return weight; + } + } + return 0; + } +}; +``` + +```javascript +class DSU { + /** + * @constructor + * @param {number} n + */ + constructor(n) { + this.Parent = Array.from({ length: n + 1 }, (_, i) => i); + this.Size = Array(n + 1).fill(1); + } + + /** + * @param {number} node + * @return {number} + */ + find(node) { + if (this.Parent[node] !== node) { + this.Parent[node] = this.find(this.Parent[node]); + } + return this.Parent[node]; + } + + /** + * @param {number} u + * @param {number} v + * @return {boolean} + */ + union(u, v) { + let pu = this.find(u), pv = this.find(v); + if (pu === pv) return false; + if (this.Size[pu] < this.Size[pv]) [pu, pv] = [pv, pu]; + this.Size[pu] += this.Size[pv]; + this.Parent[pv] = pu; + return true; + } +} + +class Solution { + /** + * @param {number[][]} heights + * @return {number} + */ + minimumEffortPath(heights) { + const ROWS = heights.length; + const COLS = heights[0].length; + const edges = []; + for (let r = 0; r < ROWS; r++) { + for (let c = 0; c < COLS; c++) { + if (r + 1 < ROWS) { + edges.push([Math.abs(heights[r][c] - heights[r + 1][c]), r * COLS + c, (r + 1) * COLS + c]); + } + if (c + 1 < COLS) { + edges.push([Math.abs(heights[r][c] - heights[r][c + 1]), r * COLS + c, r * COLS + c + 1]); + } + } + } + + edges.sort((a, b) => a[0] - b[0]); + const dsu = new DSU(ROWS * COLS); + for (const [weight, u, v] of edges) { + dsu.union(u, v); + if (dsu.find(0) === dsu.find(ROWS * COLS - 1)) { + return weight; + } + } + return 0; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(m * n * \log (m * n))$ +* Space complexity: $O(m * n)$ + +> Where $m$ is the number of rows and $n$ is the number of columns in the given matrix. + +--- + +## 4. Shortest Path Faster Algorithm + +::tabs-start + +```python +class Solution: + def minimumEffortPath(self, heights: List[List[int]]) -> int: + ROWS, COLS = len(heights), len(heights[0]) + dist = [float('inf')] * (ROWS * COLS) + dist[0] = 0 + in_queue = [False] * (ROWS * COLS) + + def index(r, c): + return r * COLS + c + + queue = deque([0]) + in_queue[0] = True + directions = [(0, 1), (0, -1), (1, 0), (-1, 0)] + + while queue: + u = queue.popleft() + in_queue[u] = False + r, c = divmod(u, COLS) + + for dr, dc in directions: + newR, newC = r + dr, c + dc + if 0 <= newR < ROWS and 0 <= newC < COLS: + v = index(newR, newC) + weight = abs(heights[r][c] - heights[newR][newC]) + new_dist = max(dist[u], weight) + if new_dist < dist[v]: + dist[v] = new_dist + if not in_queue[v]: + queue.append(v) + in_queue[v] = True + + return dist[ROWS * COLS - 1] +``` + +```java +public class Solution { + public int minimumEffortPath(int[][] heights) { + int ROWS = heights.length, COLS = heights[0].length; + int[] dist = new int[ROWS * COLS]; + Arrays.fill(dist, Integer.MAX_VALUE); + dist[0] = 0; + + boolean[] inQueue = new boolean[ROWS * COLS]; + Queue queue = new LinkedList<>(); + queue.offer(0); + inQueue[0] = true; + + int[][] directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; + + while (!queue.isEmpty()) { + int u = queue.poll(); + inQueue[u] = false; + + int r = u / COLS, c = u % COLS; + + for (int[] dir : directions) { + int newR = r + dir[0], newC = c + dir[1]; + if (newR >= 0 && newC >= 0 && newR < ROWS && newC < COLS) { + int v = newR * COLS + newC; + int weight = Math.abs(heights[r][c] - heights[newR][newC]); + int newDist = Math.max(dist[u], weight); + if (newDist < dist[v]) { + dist[v] = newDist; + if (!inQueue[v]) { + queue.offer(v); + inQueue[v] = true; + } + } + } + } + } + + return dist[ROWS * COLS - 1]; + } +} +``` + +```cpp +class Solution { +public: + int minimumEffortPath(vector>& heights) { + int ROWS = heights.size(), COLS = heights[0].size(); + vector dist(ROWS * COLS, INT_MAX); + dist[0] = 0; + + vector inQueue(ROWS * COLS, false); + queue q; + q.push(0); + inQueue[0] = true; + + vector> directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; + + while (!q.empty()) { + int u = q.front(); + q.pop(); + inQueue[u] = false; + + int r = u / COLS, c = u % COLS; + + for (const auto& dir : directions) { + int newR = r + dir[0], newC = c + dir[1]; + if (newR >= 0 && newC >= 0 && newR < ROWS && newC < COLS) { + int v = newR * COLS + newC; + int weight = abs(heights[r][c] - heights[newR][newC]); + int newDist = max(dist[u], weight); + if (newDist < dist[v]) { + dist[v] = newDist; + if (!inQueue[v]) { + q.push(v); + inQueue[v] = true; + } + } + } + } + } + + return dist[ROWS * COLS - 1]; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number[][]} heights + * @return {number} + */ + minimumEffortPath(heights) { + const ROWS = heights.length, COLS = heights[0].length; + const dist = Array(ROWS * COLS).fill(Infinity); + dist[0] = 0; + + const inQueue = Array(ROWS * COLS).fill(false); + const queue = new Queue(); + queue.push(0); + inQueue[0] = true; + + const directions = [ + [0, 1], [0, -1], + [1, 0], [-1, 0] + ]; + + while (!queue.isEmpty()) { + const u = queue.pop(); + inQueue[u] = false; + + const r = Math.floor(u / COLS), c = u % COLS; + for (const [dr, dc] of directions) { + const newR = r + dr, newC = c + dc; + if (newR >= 0 && newC >= 0 && newR < ROWS && newC < COLS) { + const v = newR * COLS + newC; + const weight = Math.abs(heights[r][c] - heights[newR][newC]); + const newDist = Math.max(dist[u], weight); + if (newDist < dist[v]) { + dist[v] = newDist; + if (!inQueue[v]) { + queue.push(v); + inQueue[v] = true; + } + } + } + } + } + + return dist[ROWS * COLS - 1]; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: + * $O(m * n)$ time in average case. + * $O(m ^ 2 * n ^ 2)$ time in worst case. +* Space complexity: $O(m * n)$ + +> Where $m$ is the number of rows and $n$ is the number of columns in the given matrix. \ No newline at end of file diff --git a/articles/reorganize-string.md b/articles/reorganize-string.md new file mode 100644 index 000000000..57d672327 --- /dev/null +++ b/articles/reorganize-string.md @@ -0,0 +1,546 @@ +## 1. Frequency Count + +::tabs-start + +```python +class Solution: + def reorganizeString(self, s: str) -> str: + freq = [0] * 26 + for char in s: + freq[ord(char) - ord('a')] += 1 + + max_freq = max(freq) + if max_freq > (len(s) + 1) // 2: + return "" + + res = [] + while len(res) < len(s): + maxIdx = freq.index(max(freq)) + char = chr(maxIdx + ord('a')) + res.append(char) + freq[maxIdx] -= 1 + if freq[maxIdx] == 0: + continue + + tmp = freq[maxIdx] + freq[maxIdx] = float("-inf") + nextMaxIdx = freq.index(max(freq)) + char = chr(nextMaxIdx + ord('a')) + res.append(char) + freq[maxIdx] = tmp + freq[nextMaxIdx] -= 1 + + return ''.join(res) +``` + +```java +public class Solution { + public String reorganizeString(String s) { + int[] freq = new int[26]; + for (char c : s.toCharArray()) { + freq[c - 'a']++; + } + + int maxFreq = Arrays.stream(freq).max().getAsInt(); + if (maxFreq > (s.length() + 1) / 2) { + return ""; + } + + StringBuilder res = new StringBuilder(); + while (res.length() < s.length()) { + int maxIdx = findMaxIndex(freq); + char maxChar = (char) (maxIdx + 'a'); + res.append(maxChar); + freq[maxIdx]--; + + if (freq[maxIdx] == 0) { + continue; + } + + int tmp = freq[maxIdx]; + freq[maxIdx] = Integer.MIN_VALUE; + int nextMaxIdx = findMaxIndex(freq); + char nextMaxChar = (char) (nextMaxIdx + 'a'); + res.append(nextMaxChar); + freq[maxIdx] = tmp; + freq[nextMaxIdx]--; + } + + return res.toString(); + } + + private int findMaxIndex(int[] freq) { + int maxIdx = 0; + for (int i = 1; i < freq.length; i++) { + if (freq[i] > freq[maxIdx]) { + maxIdx = i; + } + } + return maxIdx; + } +} +``` + +```cpp +class Solution { +public: + string reorganizeString(string s) { + vector freq(26, 0); + for (char c : s) { + freq[c - 'a']++; + } + + int maxFreq = *max_element(freq.begin(), freq.end()); + if (maxFreq > (s.size() + 1) / 2) { + return ""; + } + + string res; + while (res.size() < s.size()) { + int maxIdx = findMaxIndex(freq); + char maxChar = 'a' + maxIdx; + res += maxChar; + freq[maxIdx]--; + + if (freq[maxIdx] == 0) { + continue; + } + + int tmp = freq[maxIdx]; + freq[maxIdx] = INT_MIN; + int nextMaxIdx = findMaxIndex(freq); + char nextMaxChar = 'a' + nextMaxIdx; + res += nextMaxChar; + freq[maxIdx] = tmp; + freq[nextMaxIdx]--; + } + + return res; + } + +private: + int findMaxIndex(const vector& freq) { + int maxIdx = 0; + for (int i = 1; i < freq.size(); i++) { + if (freq[i] > freq[maxIdx]) { + maxIdx = i; + } + } + return maxIdx; + } +}; +``` + +```javascript +class Solution { + /** + * @param {string} s + * @return {string} + */ + reorganizeString(s) { + const freq = new Array(26).fill(0); + for (const char of s) { + freq[char.charCodeAt(0) - 'a'.charCodeAt(0)]++; + } + + const maxFreq = Math.max(...freq); + if (maxFreq > Math.floor((s.length + 1) / 2)) { + return ""; + } + + const findMaxIndex = () => { + let maxIdx = 0; + for (let i = 1; i < freq.length; i++) { + if (freq[i] > freq[maxIdx]) { + maxIdx = i; + } + } + return maxIdx; + }; + + const res = []; + while (res.length < s.length) { + const maxIdx = findMaxIndex(); + const maxChar = String.fromCharCode(maxIdx + 'a'.charCodeAt(0)); + res.push(maxChar); + freq[maxIdx]--; + + if (freq[maxIdx] === 0) { + continue; + } + + const tmp = freq[maxIdx]; + freq[maxIdx] = -Infinity; + const nextMaxIdx = findMaxIndex(); + const nextMaxChar = String.fromCharCode(nextMaxIdx + 'a'.charCodeAt(0)); + res.push(nextMaxChar); + freq[maxIdx] = tmp; + freq[nextMaxIdx]--; + } + + return res.join(''); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n)$ +* Space complexity: + * $O(1)$ extra space, since we have at most $26$ different characters. + * $O(n)$ space for the output string. + +--- + +## 2. Frequency Count (Max-Heap) + +::tabs-start + +```python +class Solution: + def reorganizeString(self, s: str) -> str: + count = Counter(s) + maxHeap = [[-cnt, char] for char, cnt in count.items()] + heapq.heapify(maxHeap) + + prev = None + res = "" + while maxHeap or prev: + if prev and not maxHeap: + return "" + + cnt, char = heapq.heappop(maxHeap) + res += char + cnt += 1 + + if prev: + heapq.heappush(maxHeap, prev) + prev = None + + if cnt != 0: + prev = [cnt, char] + + return res +``` + +```java +public class Solution { + public String reorganizeString(String s) { + int[] freq = new int[26]; + for (char c : s.toCharArray()) { + freq[c - 'a']++; + } + + PriorityQueue maxHeap = new PriorityQueue<>((a, b) -> b[0] - a[0]); + for (int i = 0; i < 26; i++) { + if (freq[i] > 0) { + maxHeap.offer(new int[]{freq[i], i}); + } + } + + StringBuilder res = new StringBuilder(); + int[] prev = null; + while (!maxHeap.isEmpty() || prev != null) { + if (prev != null && maxHeap.isEmpty()) { + return ""; + } + + int[] curr = maxHeap.poll(); + res.append((char) (curr[1] + 'a')); + curr[0]--; + + if (prev != null) { + maxHeap.offer(prev); + prev = null; + } + + if (curr[0] > 0) { + prev = curr; + } + } + + return res.toString(); + } +} +``` + +```cpp +class Solution { +public: + string reorganizeString(string s) { + vector freq(26, 0); + for (char& c : s) { + freq[c - 'a']++; + } + + priority_queue> maxHeap; + for (int i = 0; i < 26; i++) { + if (freq[i] > 0) { + maxHeap.push({freq[i], 'a' + i}); + } + } + + string res = ""; + pair prev = {0, ' '}; + while (!maxHeap.empty() || prev.first > 0) { + if (prev.first > 0 && maxHeap.empty()) { + return ""; + } + + auto [count, char_] = maxHeap.top(); + maxHeap.pop(); + res += char_; + count--; + + if (prev.first > 0) { + maxHeap.push(prev); + } + + prev = {count, char_}; + } + + return res; + } +}; +``` + +```javascript +class Solution { + /** + * @param {string} s + * @return {string} + */ + reorganizeString(s) { + const freq = new Array(26).fill(0); + for (const char of s) { + freq[char.charCodeAt(0) - 'a'.charCodeAt(0)]++; + } + + const maxHeap = new MaxPriorityQueue({ + priority: ([count]) => count, + }); + for (let i = 0; i < 26; i++) { + if (freq[i] > 0) { + maxHeap.enqueue([freq[i], String.fromCharCode(i + 'a'.charCodeAt(0))]); + } + } + + let res = ''; + let prev = null; + + while (!maxHeap.isEmpty() || prev) { + if (prev && maxHeap.isEmpty()) { + return ''; + } + + const [count, char] = maxHeap.dequeue().element; + res += char; + + if (prev) { + maxHeap.enqueue(prev); + prev = null; + } + if (count > 1) { + prev = [count - 1, char]; + } + } + + return res; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n)$ +* Space complexity: + * $O(1)$ extra space, since we have at most $26$ different characters. + * $O(n)$ space for the output string. + +--- + +## 3. Frequency Count (Greedy) + +::tabs-start + +```python +class Solution: + def reorganizeString(self, s: str) -> str: + freq = [0] * 26 + for char in s: + freq[ord(char) - ord('a')] += 1 + + max_idx = freq.index(max(freq)) + max_freq = freq[max_idx] + if max_freq > (len(s) + 1) // 2: + return "" + + res = [''] * len(s) + idx = 0 + max_char = chr(max_idx + ord('a')) + + while freq[max_idx] > 0: + res[idx] = max_char + idx += 2 + freq[max_idx] -= 1 + + for i in range(26): + while freq[i] > 0: + if idx >= len(s): + idx = 1 + res[idx] = chr(i + ord('a')) + idx += 2 + freq[i] -= 1 + + return ''.join(res) +``` + +```java +public class Solution { + public String reorganizeString(String s) { + int[] freq = new int[26]; + for (char c : s.toCharArray()) { + freq[c - 'a']++; + } + + int maxIdx = 0; + for (int i = 1; i < 26; i++) { + if (freq[i] > freq[maxIdx]) { + maxIdx = i; + } + } + + int maxFreq = freq[maxIdx]; + if (maxFreq > (s.length() + 1) / 2) { + return ""; + } + + char[] res = new char[s.length()]; + int idx = 0; + char maxChar = (char) (maxIdx + 'a'); + + while (freq[maxIdx] > 0) { + res[idx] = maxChar; + idx += 2; + freq[maxIdx]--; + } + + for (int i = 0; i < 26; i++) { + while (freq[i] > 0) { + if (idx >= s.length()) { + idx = 1; + } + res[idx] = (char) (i + 'a'); + idx += 2; + freq[i]--; + } + } + + return new String(res); + } +} +``` + +```cpp +class Solution { +public: + string reorganizeString(string s) { + vector freq(26, 0); + for (char& c : s) { + freq[c - 'a']++; + } + + int maxIdx = max_element(freq.begin(), freq.end()) - freq.begin(); + int maxFreq = freq[maxIdx]; + if (maxFreq > (s.size() + 1) / 2) { + return ""; + } + + string res(s.size(), ' '); + int idx = 0; + char maxChar = 'a' + maxIdx; + + while (freq[maxIdx] > 0) { + res[idx] = maxChar; + idx += 2; + freq[maxIdx]--; + } + + for (int i = 0; i < 26; i++) { + while (freq[i] > 0) { + if (idx >= s.size()) { + idx = 1; + } + res[idx] = 'a' + i; + idx += 2; + freq[i]--; + } + } + + return res; + } +}; +``` + +```javascript +class Solution { + /** + * @param {string} s + * @return {string} + */ + reorganizeString(s) { + const freq = new Array(26).fill(0); + for (const char of s) { + freq[char.charCodeAt(0) - 'a'.charCodeAt(0)]++; + } + + let maxIdx = 0; + for (let i = 1; i < 26; i++) { + if (freq[i] > freq[maxIdx]) { + maxIdx = i; + } + } + + const maxFreq = freq[maxIdx]; + if (maxFreq > Math.floor((s.length + 1) / 2)) { + return ''; + } + + const res = new Array(s.length).fill(''); + let idx = 0; + const maxChar = String.fromCharCode(maxIdx + 'a'.charCodeAt(0)); + + while (freq[maxIdx] > 0) { + res[idx] = maxChar; + idx += 2; + freq[maxIdx]--; + } + + for (let i = 0; i < 26; i++) { + while (freq[i] > 0) { + if (idx >= s.length) { + idx = 1; + } + res[idx] = String.fromCharCode(i + 'a'.charCodeAt(0)); + idx += 2; + freq[i]--; + } + } + + return res.join(''); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n)$ +* Space complexity: + * $O(1)$ extra space, since we have at most $26$ different characters. + * $O(n)$ space for the output string. \ No newline at end of file diff --git a/articles/roman-to-integer.md b/articles/roman-to-integer.md new file mode 100644 index 000000000..b21b20e18 --- /dev/null +++ b/articles/roman-to-integer.md @@ -0,0 +1,95 @@ +## 1. Hash Map + +::tabs-start + +```python +class Solution: + def romanToInt(self, s: str) -> int: + roman = { + "I": 1, "V": 5, "X": 10, + "L": 50, "C": 100, "D": 500, "M": 1000 + } + res = 0 + for i in range(len(s)): + if i + 1 < len(s) and roman[s[i]] < roman[s[i + 1]]: + res -= roman[s[i]] + else: + res += roman[s[i]] + return res +``` + +```java +public class Solution { + public int romanToInt(String s) { + Map roman = new HashMap<>(); + roman.put('I', 1); roman.put('V', 5); + roman.put('X', 10); roman.put('L', 50); + roman.put('C', 100); roman.put('D', 500); + roman.put('M', 1000); + + int res = 0; + for (int i = 0; i < s.length(); i++) { + if (i + 1 < s.length() && roman.get(s.charAt(i)) < roman.get(s.charAt(i + 1))) { + res -= roman.get(s.charAt(i)); + } else { + res += roman.get(s.charAt(i)); + } + } + return res; + } +} +``` + +```cpp +class Solution { +public: + int romanToInt(string s) { + unordered_map roman = { + {'I', 1}, {'V', 5}, {'X', 10}, + {'L', 50}, {'C', 100}, {'D', 500}, {'M', 1000} + }; + + int res = 0; + for (int i = 0; i < s.size(); i++) { + if (i + 1 < s.size() && roman[s[i]] < roman[s[i + 1]]) { + res -= roman[s[i]]; + } else { + res += roman[s[i]]; + } + } + return res; + } +}; +``` + +```javascript +class Solution { + /** + * @param {string} s + * @return {number} + */ + romanToInt(s) { + const roman = { + "I": 1, "V": 5, "X": 10, + "L": 50, "C": 100, "D": 500, "M": 1000 + }; + + let res = 0; + for (let i = 0; i < s.length; i++) { + if (i + 1 < s.length && roman[s[i]] < roman[s[i + 1]]) { + res -= roman[s[i]]; + } else { + res += roman[s[i]]; + } + } + return res; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n)$ +* Space complexity: $O(1)$ since we have $7$ characters in the hash map. \ No newline at end of file diff --git a/articles/stone-game-iii.md b/articles/stone-game-iii.md new file mode 100644 index 000000000..e95a8e508 --- /dev/null +++ b/articles/stone-game-iii.md @@ -0,0 +1,491 @@ +## 1. Dynamic Programming (Top-Down) - I + +::tabs-start + +```python +class Solution: + def stoneGameIII(self, stoneValue: List[int]) -> str: + n = len(stoneValue) + dp = [[None] * 2 for _ in range(n)] + + def dfs(i, alice): + if i >= n: + return 0 + if dp[i][alice] is not None: + return dp[i][alice] + + res = float("-inf") if alice == 1 else float("inf") + score = 0 + for j in range(i, min(i + 3, n)): + if alice == 1: + score += stoneValue[j] + res = max(res, score + dfs(j + 1, 0)) + else: + score -= stoneValue[j] + res = min(res, score + dfs(j + 1, 1)) + + dp[i][alice] = res + return res + + result = dfs(0, 1) + if result == 0: + return "Tie" + return "Alice" if result > 0 else "Bob" +``` + +```java +public class Solution { + private Integer[][] dp; + private int n; + + public String stoneGameIII(int[] stoneValue) { + n = stoneValue.length; + dp = new Integer[n][2]; + + int result = dfs(0, 1, stoneValue); + if (result == 0) return "Tie"; + return result > 0 ? "Alice" : "Bob"; + } + + private int dfs(int i, int alice, int[] stoneValue) { + if (i >= n) return 0; + if (dp[i][alice] != null) return dp[i][alice]; + + int res = alice == 1 ? Integer.MIN_VALUE : Integer.MAX_VALUE; + int score = 0; + for (int j = i; j < Math.min(i + 3, n); j++) { + if (alice == 1) { + score += stoneValue[j]; + res = Math.max(res, score + dfs(j + 1, 0, stoneValue)); + } else { + score -= stoneValue[j]; + res = Math.min(res, score + dfs(j + 1, 1, stoneValue)); + } + } + + dp[i][alice] = res; + return res; + } +} +``` + +```cpp +class Solution { + vector> dp; + int n; + +public: + string stoneGameIII(vector& stoneValue) { + n = stoneValue.size(); + dp.assign(n, vector(2, INT_MIN)); + + int result = dfs(0, 1, stoneValue); + if (result == 0) return "Tie"; + return result > 0 ? "Alice" : "Bob"; + } + +private: + int dfs(int i, int alice, vector& stoneValue) { + if (i >= n) return 0; + if (dp[i][alice] != INT_MIN) return dp[i][alice]; + + int res = alice == 1 ? INT_MIN : INT_MAX; + int score = 0; + for (int j = i; j < min(i + 3, n); j++) { + if (alice == 1) { + score += stoneValue[j]; + res = max(res, score + dfs(j + 1, 0, stoneValue)); + } else { + score -= stoneValue[j]; + res = min(res, score + dfs(j + 1, 1, stoneValue)); + } + } + + dp[i][alice] = res; + return res; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number[]} stoneValue + * @return {string} + */ + stoneGameIII(stoneValue) { + const n = stoneValue.length; + const dp = Array.from({ length: n }, () => [null, null]); + + const dfs = (i, alice) => { + if (i >= n) return 0; + if (dp[i][alice] !== null) return dp[i][alice]; + + let res = alice === 1 ? -Infinity : Infinity; + let score = 0; + for (let j = i; j < Math.min(i + 3, n); j++) { + if (alice === 1) { + score += stoneValue[j]; + res = Math.max(res, score + dfs(j + 1, 0)); + } else { + score -= stoneValue[j]; + res = Math.min(res, score + dfs(j + 1, 1)); + } + } + + dp[i][alice] = res; + return res; + }; + + const result = dfs(0, 1); + if (result === 0) return "Tie"; + return result > 0 ? "Alice" : "Bob"; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n)$ +* Space complexity: $O(n)$ + +--- + +## 2. Dynamic Programming (Top-Down) - II + +::tabs-start + +```python +class Solution: + def stoneGameIII(self, stoneValue: List[int]) -> str: + n = len(stoneValue) + dp = {} + + def dfs(i): + if i >= n: + return 0 + if i in dp: + return dp[i] + + res, total = float("-inf"), 0 + for j in range(i, min(i + 3, n)): + total += stoneValue[j] + res = max(res, total - dfs(j + 1)) + + dp[i] = res + return res + + result = dfs(0) + if result == 0: + return "Tie" + return "Alice" if result > 0 else "Bob" +``` + +```java +public class Solution { + private int[] dp; + private int n; + + public String stoneGameIII(int[] stoneValue) { + this.n = stoneValue.length; + this.dp = new int[n]; + Arrays.fill(dp, Integer.MIN_VALUE); + + int result = dfs(0, stoneValue); + if (result == 0) return "Tie"; + return result > 0 ? "Alice" : "Bob"; + } + + private int dfs(int i, int[] stoneValue) { + if (i >= n) return 0; + if (dp[i] != Integer.MIN_VALUE) return dp[i]; + + int res = Integer.MIN_VALUE, total = 0; + for (int j = i; j < Math.min(i + 3, n); j++) { + total += stoneValue[j]; + res = Math.max(res, total - dfs(j + 1, stoneValue)); + } + + dp[i] = res; + return res; + } +} +``` + +```cpp +class Solution { +public: + string stoneGameIII(vector& stoneValue) { + n = stoneValue.size(); + dp.assign(n, INT_MIN); + + int result = dfs(0, stoneValue); + if (result == 0) return "Tie"; + return result > 0 ? "Alice" : "Bob"; + } + +private: + vector dp; + int n; + + int dfs(int i, vector& stoneValue) { + if (i >= n) return 0; + if (dp[i] != INT_MIN) return dp[i]; + + int res = INT_MIN, total = 0; + for (int j = i; j < min(i + 3, n); j++) { + total += stoneValue[j]; + res = max(res, total - dfs(j + 1, stoneValue)); + } + + dp[i] = res; + return res; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number[]} stoneValue + * @return {string} + */ + stoneGameIII(stoneValue) { + const n = stoneValue.length; + const dp = new Array(n); + + const dfs = (i) => { + if (i >= n) return 0; + if (dp[i] !== undefined) return dp[i]; + + let res = -Infinity, total = 0; + for (let j = i; j < Math.min(i + 3, n); j++) { + total += stoneValue[j]; + res = Math.max(res, total - dfs(j + 1)); + } + + dp[i] = res; + return res; + }; + + const result = dfs(0); + if (result === 0) return "Tie"; + return result > 0 ? "Alice" : "Bob"; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n)$ +* Space complexity: $O(n)$ + +--- + +## 3. Dynamic Programming (Bottom-Up) + +::tabs-start + +```python +class Solution: + def stoneGameIII(self, stoneValue: List[int]) -> str: + n = len(stoneValue) + dp = [float("-inf")] * (n + 1) + dp[n] = 0 + + for i in range(n - 1, -1, -1): + total = 0 + for j in range(i, min(i + 3, n)): + total += stoneValue[j] + dp[i] = max(dp[i], total - dp[j + 1]) + + result = dp[0] + if result == 0: + return "Tie" + return "Alice" if result > 0 else "Bob" +``` + +```java +public class Solution { + public String stoneGameIII(int[] stoneValue) { + int n = stoneValue.length; + int[] dp = new int[n + 1]; + Arrays.fill(dp, Integer.MIN_VALUE); + dp[n] = 0; + + for (int i = n - 1; i >= 0; i--) { + int total = 0; + dp[i] = Integer.MIN_VALUE; + for (int j = i; j < Math.min(i + 3, n); j++) { + total += stoneValue[j]; + dp[i] = Math.max(dp[i], total - dp[j + 1]); + } + } + + int result = dp[0]; + if (result == 0) return "Tie"; + return result > 0 ? "Alice" : "Bob"; + } +} +``` + +```cpp +class Solution { +public: + string stoneGameIII(vector& stoneValue) { + int n = stoneValue.size(); + vector dp(n + 1, INT_MIN); + dp[n] = 0; + + for (int i = n - 1; i >= 0; i--) { + int total = 0; + dp[i] = INT_MIN; + for (int j = i; j < min(i + 3, n); j++) { + total += stoneValue[j]; + dp[i] = max(dp[i], total - dp[j + 1]); + } + } + + int result = dp[0]; + if (result == 0) return "Tie"; + return result > 0 ? "Alice" : "Bob"; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number[]} stoneValue + * @return {string} + */ + stoneGameIII(stoneValue) { + const n = stoneValue.length; + const dp = new Array(n + 1).fill(-Infinity); + dp[n] = 0; + + for (let i = n - 1; i >= 0; i--) { + let total = 0; + dp[i] = -Infinity; + for (let j = i; j < Math.min(i + 3, n); j++) { + total += stoneValue[j]; + dp[i] = Math.max(dp[i], total - dp[j + 1]); + } + } + + const result = dp[0]; + if (result === 0) return "Tie"; + return result > 0 ? "Alice" : "Bob"; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n)$ +* Space complexity: $O(n)$ + +--- + +## 4. Dynamic Programming (Space Optimized) + +::tabs-start + +```python +class Solution: + def stoneGameIII(self, stoneValue: List[int]) -> str: + n = len(stoneValue) + dp = [0] * 4 + + for i in range(n - 1, -1, -1): + total = 0 + dp[i % 4] = float("-inf") + for j in range(i, min(i + 3, n)): + total += stoneValue[j] + dp[i % 4] = max(dp[i % 4], total - dp[(j + 1) % 4]) + + if dp[0] == 0: + return "Tie" + return "Alice" if dp[0] > 0 else "Bob" +``` + +```java +public class Solution { + public String stoneGameIII(int[] stoneValue) { + int n = stoneValue.length; + int[] dp = new int[4]; + + for (int i = n - 1; i >= 0; i--) { + int total = 0; + dp[i % 4] = Integer.MIN_VALUE; + for (int j = i; j < Math.min(i + 3, n); j++) { + total += stoneValue[j]; + dp[i % 4] = Math.max(dp[i % 4], total - dp[(j + 1) % 4]); + } + } + + if (dp[0] == 0) return "Tie"; + return dp[0] > 0 ? "Alice" : "Bob"; + } +} +``` + +```cpp +class Solution { +public: + string stoneGameIII(vector& stoneValue) { + int n = stoneValue.size(); + vector dp(4, 0); + + for (int i = n - 1; i >= 0; --i) { + int total = 0; + dp[i % 4] = INT_MIN; + for (int j = i; j < min(i + 3, n); ++j) { + total += stoneValue[j]; + dp[i % 4] = max(dp[i % 4], total - dp[(j + 1) % 4]); + } + } + + if (dp[0] == 0) return "Tie"; + return dp[0] > 0 ? "Alice" : "Bob"; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number[]} stoneValue + * @return {string} + */ + stoneGameIII(stoneValue) { + const n = stoneValue.length; + const dp = new Array(4).fill(0); + + for (let i = n - 1; i >= 0; i--) { + let total = 0; + dp[i % 4] = -Infinity; + for (let j = i; j < Math.min(i + 3, n); j++) { + total += stoneValue[j]; + dp[i % 4] = Math.max(dp[i % 4], total - dp[(j + 1) % 4]); + } + } + + if (dp[0] === 0) return "Tie"; + return dp[0] > 0 ? "Alice" : "Bob"; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n)$ +* Space complexity: $O(1)$ extra space. \ No newline at end of file diff --git a/hints/cheapest-flight-path.md b/hints/cheapest-flight-path.md index 85fcd7ec4..b42275442 100644 --- a/hints/cheapest-flight-path.md +++ b/hints/cheapest-flight-path.md @@ -18,7 +18,7 @@
Hint 2

- We can use the Bellman-Ford algorithm. Initialize a prices array of size n with Infinity, setting prices[source] = 0. THese values describe the cost to reach a city from the source city. Iterate (k + 1) times (stops are 0-indexed), updating the cost to each city by extending paths from cities with valid costs. We only update the cost for a city if it is less than the previous cost. How would you implement this? + We can use the Bellman-Ford algorithm. Initialize a prices array of size n with Infinity, setting prices[source] = 0. These values describe the cost to reach a city from the source city. Iterate (k + 1) times (stops are 0-indexed), updating the cost to each city by extending paths from cities with valid costs. We only update the cost for a city if it is less than the previous cost. How would you implement this?