diff --git a/articles/combination-target-sum-ii.md b/articles/combination-target-sum-ii.md new file mode 100644 index 000000000..3994f5081 --- /dev/null +++ b/articles/combination-target-sum-ii.md @@ -0,0 +1,752 @@ +## 1. Brute Force + +::tabs-start + +```python +class Solution: + def combinationSum2(self, candidates, target): + res = set() + candidates.sort() + + def generate_subsets(i, cur, total): + if total == target: + res.add(tuple(cur)) + return + if total > target or i == len(candidates): + return + + cur.append(candidates[i]) + generate_subsets(i + 1, cur, total + candidates[i]) + cur.pop() + + generate_subsets(i + 1, cur, total) + + generate_subsets(0, [], 0) + return [list(combination) for combination in res] +``` + +```java +public class Solution { + private Set> res; + + public List> combinationSum2(int[] candidates, int target) { + res = new HashSet<>(); + Arrays.sort(candidates); + generateSubsets(candidates, target, 0, new ArrayList<>(), 0); + return new ArrayList<>(res); + } + + private void generateSubsets(int[] candidates, int target, int i, List cur, int total) { + if (total == target) { + res.add(new ArrayList<>(cur)); + return; + } + if (total > target || i == candidates.length) { + return; + } + + cur.add(candidates[i]); + generateSubsets(candidates, target, i + 1, cur, total + candidates[i]); + cur.remove(cur.size() - 1); + + generateSubsets(candidates, target, i + 1, cur, total); + } +} +``` + +```cpp +class Solution { +public: + set> res; + + vector> combinationSum2(vector& candidates, int target) { + res.clear(); + sort(candidates.begin(), candidates.end()); + vector cur; + generateSubsets(candidates, target, 0, cur, 0); + return vector>(res.begin(), res.end()); + } + +private: + void generateSubsets(vector& candidates, int target, int i, vector& cur, int total) { + if (total == target) { + res.insert(cur); + return; + } + if (total > target || i == candidates.size()) { + return; + } + + cur.push_back(candidates[i]); + generateSubsets(candidates, target, i + 1, cur, total + candidates[i]); + cur.pop_back(); + + generateSubsets(candidates, target, i + 1, cur, total); + } +}; +``` + +```javascript +class Solution { + constructor() { + this.res = new Set(); + } + + /** + * @param {number[]} candidates + * @param {number} target + * @return {number[][]} + */ + combinationSum2(candidates, target) { + this.res.clear(); + candidates.sort((a, b) => a - b); + this.generateSubsets(candidates, target, 0, [], 0); + return Array.from(this.res, subset => JSON.parse(subset)); + } + + /** + * @param {number[]} candidates + * @param {number} target + * @param {number} i + * @param {number[]} cur + * @param {number} total + * @return {void} + */ + generateSubsets(candidates, target, i, cur, total) { + if (total === target) { + this.res.add(JSON.stringify([...cur])); + return; + } + if (total > target || i === candidates.length) { + return; + } + + cur.push(candidates[i]); + this.generateSubsets(candidates, target, i + 1, cur, total + candidates[i]); + cur.pop(); + + this.generateSubsets(candidates, target, i + 1, cur, total); + } +} +``` + +```csharp +public class Solution { + private HashSet res; + + public List> CombinationSum2(int[] candidates, int target) { + res = new HashSet(); + Array.Sort(candidates); + GenerateSubsets(candidates, target, 0, new List(), 0); + return res.Select(s => s.Split(',').Select(int.Parse).ToList()).ToList(); + } + + private void GenerateSubsets(int[] candidates, int target, int i, List cur, int total) { + if (total == target) { + res.Add(string.Join(",", cur)); + return; + } + if (total > target || i == candidates.Length) { + return; + } + + cur.Add(candidates[i]); + GenerateSubsets(candidates, target, i + 1, cur, total + candidates[i]); + cur.RemoveAt(cur.Count - 1); + + GenerateSubsets(candidates, target, i + 1, cur, total); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n * 2 ^ n)$ +* Space complexity: $O(n * 2 ^ n)$ + +--- + +## 2. Backtracking + +::tabs-start + +```python +class Solution: + def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]: + res = [] + candidates.sort() + + def dfs(i, cur, total): + if total == target: + res.append(cur.copy()) + return + if total > target or i == len(candidates): + return + + cur.append(candidates[i]) + dfs(i + 1, cur, total + candidates[i]) + cur.pop() + + + while i + 1 < len(candidates) and candidates[i] == candidates[i+1]: + i += 1 + dfs(i + 1, cur, total) + + dfs(0, [], 0) + return res +``` + +```java +public class Solution { + private List> res; + + public List> combinationSum2(int[] candidates, int target) { + res = new ArrayList<>(); + Arrays.sort(candidates); + dfs(candidates, target, 0, new ArrayList<>(), 0); + return res; + } + + private void dfs(int[] candidates, int target, int i, List cur, int total) { + if (total == target) { + res.add(new ArrayList<>(cur)); + return; + } + if (total > target || i == candidates.length) { + return; + } + + cur.add(candidates[i]); + dfs(candidates, target, i + 1, cur, total + candidates[i]); + cur.remove(cur.size() - 1); + + while (i + 1 < candidates.length && candidates[i] == candidates[i + 1]) { + i++; + } + dfs(candidates, target, i + 1, cur, total); + } +} +``` + +```cpp +class Solution { +public: + vector> res; + vector> combinationSum2(vector& candidates, int target) { + res.clear(); + sort(candidates.begin(), candidates.end()); + vector cur; + dfs(candidates, target, 0, cur, 0); + return res; + } + +private: + void dfs(vector& candidates, int target, int i, vector& cur, int total) { + if (total == target) { + res.push_back(cur); + return; + } + if (total > target || i == candidates.size()) { + return; + } + + cur.push_back(candidates[i]); + dfs(candidates, target, i + 1, cur, total + candidates[i]); + cur.pop_back(); + + while (i + 1 < candidates.size() && candidates[i] == candidates[i + 1]) { + i++; + } + dfs(candidates, target, i + 1, cur, total); + } +}; +``` + +```javascript +class Solution { + constructor() { + this.res = []; + } + + /** + * @param {number[]} candidates + * @param {number} target + * @return {number[][]} + */ + combinationSum2(candidates, target) { + this.res = []; + candidates.sort((a, b) => a - b); + this.dfs(candidates, target, 0, [], 0); + return this.res; + } + + /** + * @param {number[]} candidates + * @param {number} target + * @param {number} i + * @param {number[]} cur + * @param {number} total + * @return {void} + */ + dfs(candidates, target, i, cur, total) { + if (total === target) { + this.res.push([...cur]); + return; + } + if (total > target || i === candidates.length) { + return; + } + + cur.push(candidates[i]); + this.dfs(candidates, target, i + 1, cur, total + candidates[i]); + cur.pop(); + + while (i + 1 < candidates.length && candidates[i] === candidates[i + 1]) { + i++; + } + this.dfs(candidates, target, i + 1, cur, total); + } +} +``` + +```csharp +public class Solution { + private List> res; + + public List> CombinationSum2(int[] candidates, int target) { + res = new List>(); + Array.Sort(candidates); + Dfs(candidates, target, 0, new List(), 0); + return res; + } + + private void Dfs(int[] candidates, int target, int i, List cur, int total) { + if (total == target) { + res.Add(new List(cur)); + return; + } + if (total > target || i == candidates.Length) { + return; + } + + cur.Add(candidates[i]); + Dfs(candidates, target, i + 1, cur, total + candidates[i]); + cur.RemoveAt(cur.Count - 1); + + while (i + 1 < candidates.Length && candidates[i] == candidates[i + 1]) { + i++; + } + Dfs(candidates, target, i + 1, cur, total); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n * 2 ^n)$ +* Space complexity: $O(n)$ + +--- + +## 3. Backtracking (Hash Map) + +::tabs-start + +```python +class Solution: + def combinationSum2(self, nums, target): + self.res = [] + self.count = defaultdict(int) + cur = [] + A = [] + + for num in nums: + if self.count[num] == 0: + A.append(num) + self.count[num] += 1 + self.backtrack(A, target, cur, 0) + return self.res + + def backtrack(self, nums, target, cur, i): + if target == 0: + self.res.append(cur.copy()) + return + if target < 0 or i >= len(nums): + return + + if self.count[nums[i]] > 0: + cur.append(nums[i]) + self.count[nums[i]] -= 1 + self.backtrack(nums, target - nums[i], cur, i) + self.count[nums[i]] += 1 + cur.pop() + + self.backtrack(nums, target, cur, i + 1) +``` + +```java +public class Solution { + List> res = new ArrayList<>(); + Map count = new HashMap<>(); + + public List> combinationSum2(int[] nums, int target) { + List cur = new ArrayList<>(); + List A = new ArrayList<>(); + + for (int num : nums) { + if (!count.containsKey(num)) { + A.add(num); + } + count.put(num, count.getOrDefault(num, 0) + 1); + } + backtrack(A, target, cur, 0); + return res; + } + + private void backtrack(List nums, int target, List cur, int i) { + if (target == 0) { + res.add(new ArrayList<>(cur)); + return; + } + if (target < 0 || i >= nums.size()) { + return; + } + + if (count.get(nums.get(i)) > 0) { + cur.add(nums.get(i)); + count.put(nums.get(i), count.get(nums.get(i)) - 1); + backtrack(nums, target - nums.get(i), cur, i); + count.put(nums.get(i), count.get(nums.get(i)) + 1); + cur.remove(cur.size() - 1); + } + + backtrack(nums, target, cur, i + 1); + } +} +``` + +```cpp +class Solution { +public: + vector> res; + unordered_map count; + vector> combinationSum2(vector& nums, int target) { + vector cur; + vector A; + for (int num : nums) { + if (!count[num]) { + A.push_back(num); + } + count[num]++; + } + backtrack(A, target, cur, 0); + return res; + } + + void backtrack(vector& nums, int target, vector& cur, int i) { + if (target == 0) { + res.push_back(cur); + return; + } + if (target < 0 || i >= nums.size()) { + return; + } + + if (count[nums[i]]) { + cur.push_back(nums[i]); + count[nums[i]]--; + backtrack(nums, target - nums[i], cur, i); + count[nums[i]]++; + cur.pop_back(); + } + + backtrack(nums, target, cur, i + 1); + } +}; +``` + +```javascript +class Solution { + constructor() { + this.res = []; + this.count = new Map(); + } + + /** + * @param {number[]} candidates + * @param {number} target + * @return {number[][]} + */ + combinationSum2(nums, target) { + const cur = []; + const A = []; + + for (const num of nums) { + if (!this.count.has(num)) { + A.push(num); + } + this.count.set(num, (this.count.get(num) || 0) + 1); + } + this.backtrack(A, target, cur, 0); + return this.res; + } + + /** + * @param {number[]} nums + * @param {number} target + * @param {number[]} cur + * @param {number} i + * @return {void} + */ + backtrack(nums, target, cur, i) { + if (target === 0) { + this.res.push([...cur]); + return; + } + if (target < 0 || i >= nums.length) { + return; + } + + if (this.count.get(nums[i]) > 0) { + cur.push(nums[i]); + this.count.set(nums[i], this.count.get(nums[i]) - 1); + this.backtrack(nums, target - nums[i], cur, i); + this.count.set(nums[i], this.count.get(nums[i]) + 1); + cur.pop(); + } + + this.backtrack(nums, target, cur, i + 1); + } +} +``` + +```csharp +public class Solution { + public List> res = new List>(); + public Dictionary count = new Dictionary(); + + public List> CombinationSum2(int[] nums, int target) { + List cur = new List(); + List A = new List(); + + foreach (int num in nums) { + if (!count.ContainsKey(num)) { + A.Add(num); + } + if (count.ContainsKey(num)) { + count[num]++; + } else { + count[num] = 1; + } + } + Backtrack(A, target, cur, 0); + return res; + } + + private void Backtrack(List nums, int target, List cur, int i) { + if (target == 0) { + res.Add(new List(cur)); + return; + } + if (target < 0 || i >= nums.Count) { + return; + } + + if (count[nums[i]] > 0) { + cur.Add(nums[i]); + count[nums[i]]--; + Backtrack(nums, target - nums[i], cur, i); + count[nums[i]]++; + cur.RemoveAt(cur.Count - 1); + } + + Backtrack(nums, target, cur, i + 1); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n * 2 ^ n)$ +* Space complexity: $O(n)$ + +--- + +## 4. Backtracking (Optimal) + +::tabs-start + +```python +class Solution: + def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]: + res = [] + candidates.sort() + + def dfs(idx, path, cur): + if cur == target: + res.append(path.copy()) + return + for i in range(idx, len(candidates)): + if i > idx and candidates[i] == candidates[i - 1]: + continue + if cur + candidates[i] > target: + break + + path.append(candidates[i]) + dfs(i + 1, path, cur + candidates[i]) + path.pop() + + dfs(0, [], 0) + return res +``` + +```java +public class Solution { + private static List> res = new ArrayList<>(); + + public List> combinationSum2(int[] candidates, int target) { + res.clear(); + Arrays.sort(candidates); + dfs(0, new ArrayList<>(), 0, candidates, target); + return res; + } + + private static void dfs(int idx, List path, int cur, int[] candidates, int target) { + if (cur == target) { + res.add(new ArrayList<>(path)); + return; + } + for (int i = idx; i < candidates.length; i++) { + if (i > idx && candidates[i] == candidates[i - 1]) { + continue; + } + if (cur + candidates[i] > target) { + break; + } + + path.add(candidates[i]); + dfs(i + 1, path, cur + candidates[i], candidates, target); + path.remove(path.size() - 1); + } + } +} +``` + +```cpp +class Solution { +public: + vector> res; + vector> combinationSum2(vector& candidates, int target) { + res.clear(); + sort(candidates.begin(), candidates.end()); + dfs(0, {}, 0, candidates, target); + return res; + } + +private: + void dfs(int idx, vector path, int cur, vector& candidates, int target) { + if (cur == target) { + res.push_back(path); + return; + } + for (int i = idx; i < candidates.size(); i++) { + if (i > idx && candidates[i] == candidates[i - 1]) { + continue; + } + if (cur + candidates[i] > target) { + break; + } + + path.push_back(candidates[i]); + dfs(i + 1, path, cur + candidates[i], candidates, target); + path.pop_back(); + } + } +}; +``` + +```javascript +class Solution { + constructor() { + this.res = []; + } + + /** + * @param {number[]} candidates + * @param {number} target + * @return {number[][]} + */ + combinationSum2(candidates, target) { + this.res = []; + candidates.sort((a, b) => a - b); + + const dfs = (idx, path, cur) => { + if (cur === target) { + this.res.push([...path]); + return; + } + for (let i = idx; i < candidates.length; i++) { + if (i > idx && candidates[i] === candidates[i - 1]) { + continue; + } + if (cur + candidates[i] > target) { + break; + } + + path.push(candidates[i]); + dfs(i + 1, path, cur + candidates[i]); + path.pop(); + } + }; + + dfs(0, [], 0); + return this.res; + } +} +``` + +```csharp +public class Solution { + private static List> res = new List>(); + + public List> CombinationSum2(int[] candidates, int target) { + res.Clear(); + Array.Sort(candidates); + + dfs(0, new List(), 0, candidates, target); + return res; + } + + private void dfs(int idx, List path, int cur, int[] candidates, int target) { + if (cur == target) { + res.Add(new List(path)); + return; + } + for (int i = idx; i < candidates.Length; i++) { + if (i > idx && candidates[i] == candidates[i - 1]) { + continue; + } + if (cur + candidates[i] > target) { + break; + } + + path.Add(candidates[i]); + dfs(i + 1, path, cur + candidates[i], candidates, target); + path.RemoveAt(path.Count - 1); + } + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n * 2 ^ n)$ +* Space complexity: $O(n)$ \ No newline at end of file diff --git a/articles/design-twitter-feed.md b/articles/design-twitter-feed.md new file mode 100644 index 000000000..8b057df6e --- /dev/null +++ b/articles/design-twitter-feed.md @@ -0,0 +1,546 @@ +## 1. Sorting + +::tabs-start + +```python +class Twitter: + + def __init__(self): + self.time = 0 + self.followMap = defaultdict(set) + self.tweetMap = defaultdict(list) + + def postTweet(self, userId: int, tweetId: int) -> None: + self.tweetMap[userId].append((self.time, tweetId)) + self.time += 1 + + def getNewsFeed(self, userId: int) -> List[int]: + feed = self.tweetMap[userId][:] + for followeeId in self.followMap[userId]: + feed.extend(self.tweetMap[followeeId]) + + feed.sort(key=lambda x: -x[0]) + return [tweetId for _, tweetId in feed[:10]] + + def follow(self, followerId: int, followeeId: int) -> None: + if followerId != followeeId: + self.followMap[followerId].add(followeeId) + + def unfollow(self, followerId: int, followeeId: int) -> None: + self.followMap[followerId].discard(followeeId) +``` + +```java +public class Twitter { + private int time; + private Map> followMap; + private Map> tweetMap; + + public Twitter() { + time = 0; + followMap = new HashMap<>(); + tweetMap = new HashMap<>(); + } + + public void postTweet(int userId, int tweetId) { + tweetMap.putIfAbsent(userId, new ArrayList<>()); + tweetMap.get(userId).add(new int[]{time++, tweetId}); + } + + public List getNewsFeed(int userId) { + List feed = new ArrayList<>(tweetMap.getOrDefault(userId, new ArrayList<>())); + for (int followeeId : followMap.getOrDefault(userId, new HashSet<>())) { + feed.addAll(tweetMap.getOrDefault(followeeId, new ArrayList<>())); + } + feed.sort((a, b) -> b[0] - a[0]); + List res = new ArrayList<>(); + for (int i = 0; i < Math.min(10, feed.size()); i++) { + res.add(feed.get(i)[1]); + } + return res; + } + + public void follow(int followerId, int followeeId) { + if (followerId != followeeId) { + followMap.putIfAbsent(followerId, new HashSet<>()); + followMap.get(followerId).add(followeeId); + } + } + + public void unfollow(int followerId, int followeeId) { + followMap.getOrDefault(followerId, new HashSet<>()).remove(followeeId); + } +} +``` + +```cpp +class Twitter { + int time; + unordered_map> followMap; + unordered_map>> tweetMap; +public: + Twitter() : time(0) {} + + void postTweet(int userId, int tweetId) { + tweetMap[userId].push_back({time++, tweetId}); + } + + vector getNewsFeed(int userId) { + vector> feed = tweetMap[userId]; + for (int followeeId : followMap[userId]) { + feed.insert(feed.end(), tweetMap[followeeId].begin(), + tweetMap[followeeId].end()); + } + sort(feed.begin(), feed.end(), [](auto &a, auto &b) { + return a.first > b.first; + }); + vector res; + for (int i = 0; i < min(10, (int)feed.size()); ++i) { + res.push_back(feed[i].second); + } + return res; + } + + void follow(int followerId, int followeeId) { + if (followerId != followeeId) { + followMap[followerId].insert(followeeId); + } + } + + void unfollow(int followerId, int followeeId) { + followMap[followerId].erase(followeeId); + } +}; +``` + +```javascript +class Twitter { + constructor() { + this.time = 0; + this.followMap = new Map(); + this.tweetMap = new Map(); + } + + /** + * @param {number} userId + * @param {number} tweetId + * @return {void} + */ + postTweet(userId, tweetId) { + if (!this.tweetMap.has(userId)) this.tweetMap.set(userId, []); + this.tweetMap.get(userId).push([this.time++, tweetId]); + } + + /** + * @param {number} userId + * @return {number[]} + */ + getNewsFeed(userId) { + let feed = [...(this.tweetMap.get(userId) || [])]; + (this.followMap.get(userId) || new Set()).forEach(followeeId => { + feed.push(...(this.tweetMap.get(followeeId) || [])); + }); + feed.sort((a, b) => b[0] - a[0]); + return feed.slice(0, 10).map(x => x[1]); + } + + /** + * @param {number} followerId + * @param {number} followeeId + * @return {void} + */ + follow(followerId, followeeId) { + if (followerId !== followeeId) { + if (!this.followMap.has(followerId)) this.followMap.set(followerId, new Set()); + this.followMap.get(followerId).add(followeeId); + } + } + + /** + * @param {number} followerId + * @param {number} followeeId + * @return {void} + */ + unfollow(followerId, followeeId) { + if (this.followMap.has(followerId)) { + this.followMap.get(followerId).delete(followeeId); + } + } +} +``` + +```csharp +class Twitter { + private int time; + private Dictionary> followMap; + private Dictionary> tweetMap; + + public Twitter() { + time = 0; + followMap = new Dictionary>(); + tweetMap = new Dictionary>(); + } + + public void PostTweet(int userId, int tweetId) { + if (!tweetMap.ContainsKey(userId)) { + tweetMap[userId] = new List<(int, int)>(); + } + tweetMap[userId].Add((time++, tweetId)); + } + + public List GetNewsFeed(int userId) { + var feed = new List<(int, int)>(tweetMap.GetValueOrDefault(userId, new List<(int, int)>())); + foreach (var followeeId in followMap.GetValueOrDefault(userId, new HashSet())) { + feed.AddRange(tweetMap.GetValueOrDefault(followeeId, new List<(int, int)>())); + } + feed.Sort((a, b) => b.Item1 - a.Item1); + var res = new List(); + for (int i = 0; i < Math.Min(10, feed.Count); i++) { + res.Add(feed[i].Item2); + } + return res; + } + + public void Follow(int followerId, int followeeId) { + if (followerId != followeeId) { + if (!followMap.ContainsKey(followerId)) { + followMap[followerId] = new HashSet(); + } + followMap[followerId].Add(followeeId); + } + } + + public void Unfollow(int followerId, int followeeId) { + if (followMap.ContainsKey(followerId)) { + followMap[followerId].Remove(followeeId); + } + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n \log n)$ for $getNewsFeed()$ and $O(1)$ for remaining methods. +* Space complexity: $O(1)$ + +> Where $n$ is the number of tweets associated with the $useId$ and its $followeeIds$. + +--- + +## 2. Heap + +::tabs-start + +```python +class Twitter: + def __init__(self): + self.count = 0 + self.tweetMap = defaultdict(list) # userId -> list of [count, tweetIds] + self.followMap = defaultdict(set) # userId -> set of followeeId + + def postTweet(self, userId: int, tweetId: int) -> None: + self.tweetMap[userId].append([self.count, tweetId]) + self.count -= 1 + + def getNewsFeed(self, userId: int) -> List[int]: + res = [] + minHeap = [] + + self.followMap[userId].add(userId) + for followeeId in self.followMap[userId]: + if followeeId in self.tweetMap: + index = len(self.tweetMap[followeeId]) - 1 + count, tweetId = self.tweetMap[followeeId][index] + heapq.heappush(minHeap, [count, tweetId, followeeId, index - 1]) + + while minHeap and len(res) < 10: + count, tweetId, followeeId, index = heapq.heappop(minHeap) + res.append(tweetId) + if index >= 0: + count, tweetId = self.tweetMap[followeeId][index] + heapq.heappush(minHeap, [count, tweetId, followeeId, index - 1]) + return res + + def follow(self, followerId: int, followeeId: int) -> None: + self.followMap[followerId].add(followeeId) + + def unfollow(self, followerId: int, followeeId: int) -> None: + if followeeId in self.followMap[followerId]: + self.followMap[followerId].remove(followeeId) +``` + +```java +public class Twitter { + + private int count; + private Map> tweetMap; + private Map> followMap; + + public Twitter() { + count = 0; + tweetMap = new HashMap<>(); + followMap = new HashMap<>(); + } + + public void postTweet(int userId, int tweetId) { + tweetMap.computeIfAbsent(userId, k -> new ArrayList<>()).add(new int[]{count--, tweetId}); + } + + public List getNewsFeed(int userId) { + List res = new ArrayList<>(); + PriorityQueue minHeap = new PriorityQueue<>(Comparator.comparingInt(a -> a[0])); + + followMap.computeIfAbsent(userId, k -> new HashSet<>()).add(userId); + for (int followeeId : followMap.get(userId)) { + if (tweetMap.containsKey(followeeId)) { + List tweets = tweetMap.get(followeeId); + int index = tweets.size() - 1; + int[] tweet = tweets.get(index); + minHeap.offer(new int[]{tweet[0], tweet[1], followeeId, index}); + } + } + + while (!minHeap.isEmpty() && res.size() < 10) { + int[] curr = minHeap.poll(); + res.add(curr[1]); + int index = curr[3]; + if (index > 0) { + int[] tweet = tweetMap.get(curr[2]).get(index - 1); + minHeap.offer(new int[]{tweet[0], tweet[1], curr[2], index - 1}); + } + } + return res; + } + + public void follow(int followerId, int followeeId) { + followMap.computeIfAbsent(followerId, k -> new HashSet<>()).add(followeeId); + } + + public void unfollow(int followerId, int followeeId) { + followMap.computeIfPresent(followerId, (k, v) -> { + v.remove(followeeId); + return v; + }); + } +} +``` + +```cpp +class Twitter { + int count; + unordered_map>> tweetMap; + unordered_map> followMap; + +public: + Twitter() { + count = 0; + } + + void postTweet(int userId, int tweetId) { + tweetMap[userId].push_back({count++, tweetId}); + } + + vector getNewsFeed(int userId) { + vector res; + auto compare = [](const vector& a, const vector& b) { + return a[0] < b[0]; + }; + priority_queue, vector>, decltype(compare)> minHeap(compare); + + followMap[userId].insert(userId); + for (int followeeId : followMap[userId]) { + if (tweetMap.count(followeeId)) { + const vector>& tweets = tweetMap[followeeId]; + int index = tweets.size() - 1; + minHeap.push({tweets[index][0], tweets[index][1], followeeId, index}); + } + } + + while (!minHeap.empty() && res.size() < 10) { + vector curr = minHeap.top(); + minHeap.pop(); + res.push_back(curr[1]); + int index = curr[3]; + if (index > 0) { + const vector& tweet = tweetMap[curr[2]][index - 1]; + minHeap.push({tweet[0], tweet[1], curr[2], index - 1}); + } + } + return res; + } + + void follow(int followerId, int followeeId) { + followMap[followerId].insert(followeeId); + } + + void unfollow(int followerId, int followeeId) { + followMap[followerId].erase(followeeId); + } +}; +``` + +```javascript +/** + * const { MaxPriorityQueue } = require('@datastructures-js/priority-queue'); + */ + +class Twitter { + constructor() { + this.users = new Map(); + this.timestamp = 0; + } + + /** + * @param {number} userId + * @param {number} tweetId + * @return {void} + */ + postTweet(userId, tweetId) { + if (!this.users.has(userId)) { + this.users.set(userId, { tweets: [], following: new Set() }); + } + this.users + .get(userId) + .tweets.push({ timestamp: this.timestamp, tweetId }); + this.timestamp += 1; + } + + /** + * @param {number} userId + * @return {number[]} + */ + getNewsFeed(userId) { + if (!this.users.has(userId)) { + return []; + } + + const maxPQ = new MaxPriorityQueue(tweet => tweet.timestamp); + const seenTweets = new Set(); + + const user = this.users.get(userId); + user.tweets.forEach(tweet => { + if (!seenTweets.has(tweet.tweetId)) { + maxPQ.enqueue(tweet); + seenTweets.add(tweet.tweetId); + } + }); + + user.following.forEach(followeeId => { + if (this.users.has(followeeId)) { + this.users.get(followeeId).tweets.forEach(tweet => { + if (!seenTweets.has(tweet.tweetId)) { + maxPQ.enqueue(tweet); + seenTweets.add(tweet.tweetId); + } + }); + } + }); + + const newsFeed = []; + for (let i = 0; i < 10 && !maxPQ.isEmpty(); i++) { + newsFeed.push(maxPQ.dequeue().tweetId); + } + + return newsFeed; + } + + /** + * @param {number} followerId + * @param {number} followeeId + * @return {void} + */ + follow(followerId, followeeId) { + if (!this.users.has(followerId)) { + this.users.set(followerId, { tweets: [], following: new Set() }); + } + this.users.get(followerId).following.add(followeeId); + } + + /** + * @param {number} followerId + * @param {number} followeeId + * @return {void} + */ + unfollow(followerId, followeeId) { + if (this.users.has(followerId)) { + this.users.get(followerId).following.delete(followeeId); + } + } +} +``` + +```csharp +class Twitter { + private int count; + private Dictionary> tweetMap; + private Dictionary> followMap; + + public Twitter() { + count = 0; + tweetMap = new Dictionary>(); + followMap = new Dictionary>(); + } + + public void PostTweet(int userId, int tweetId) { + if (!tweetMap.ContainsKey(userId)) { + tweetMap[userId] = new List(); + } + tweetMap[userId].Add(new int[] { count++, tweetId }); + } + + public List GetNewsFeed(int userId) { + List res = new List(); + PriorityQueue minHeap = new PriorityQueue(); + + if (!followMap.ContainsKey(userId)) { + followMap[userId] = new HashSet(); + } + followMap[userId].Add(userId); + + foreach (int followeeId in followMap[userId]) { + if (tweetMap.ContainsKey(followeeId) && tweetMap[followeeId].Count > 0) { + List tweets = tweetMap[followeeId]; + int index = tweets.Count - 1; + int[] latestTweet = tweets[index]; + minHeap.Enqueue(new int[] { latestTweet[0], latestTweet[1], followeeId, index }, -latestTweet[0]); + } + } + + while (minHeap.Count > 0 && res.Count < 10) { + int[] curr = minHeap.Dequeue(); + res.Add(curr[1]); + int index = curr[3]; + if (index > 0) { + int[] tweet = tweetMap[curr[2]][index - 1]; + minHeap.Enqueue(new int[] { tweet[0], tweet[1], curr[2], index - 1 }, -tweet[0]); + } + } + + return res; + } + + public void Follow(int followerId, int followeeId) { + if (!followMap.ContainsKey(followerId)) { + followMap[followerId] = new HashSet(); + } + followMap[followerId].Add(followeeId); + } + + public void Unfollow(int followerId, int followeeId) { + if (followMap.ContainsKey(followerId)) { + followMap[followerId].Remove(followeeId); + } + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n+\log n)$ for $getNewsFeed()$ and $O(1)$ for remaining methods. +* Space complexity: $O(1)$ + +> Where $n$ is the number of tweets associated with the $useId$ and its $followeeIds$. \ No newline at end of file diff --git a/articles/find-median-in-a-data-stream.md b/articles/find-median-in-a-data-stream.md new file mode 100644 index 000000000..6e5ec7e0e --- /dev/null +++ b/articles/find-median-in-a-data-stream.md @@ -0,0 +1,328 @@ +## 1. Sorting + +::tabs-start + +```python +class MedianFinder: + + def __init__(self): + self.data = [] + + def addNum(self, num: int) -> None: + self.data.append(num) + + def findMedian(self) -> float: + self.data.sort() + n = len(self.data) + return (self.data[n // 2] if (n & 1) else + (self.data[n // 2] + self.data[n // 2 - 1]) / 2) +``` + +```java +public class MedianFinder { + private ArrayList data; + + public MedianFinder() { + data = new ArrayList<>(); + } + + public void addNum(int num) { + data.add(num); + } + + public double findMedian() { + Collections.sort(data); + int n = data.size(); + if ((n & 1) == 1) { + return data.get(n / 2); + } else { + return (data.get(n / 2) + data.get(n / 2 - 1)) / 2.0; + } + } +} +``` + +```cpp +class MedianFinder { + vector data; + +public: + MedianFinder() {} + + void addNum(int num) { + data.push_back(num); + } + + double findMedian() { + sort(data.begin(), data.end()); + int n = data.size(); + if (n & 1) { + return data[n / 2]; + } else { + return (data[n / 2] + data[n / 2 - 1]) / 2.0; + } + } +}; +``` + +```javascript +class MedianFinder { + constructor() { + this.data = []; + } + + /** + * + * @param {number} num + * @return {void} + */ + addNum(num) { + this.data.push(num); + } + + /** + * @return {number} + */ + findMedian() { + this.data.sort((a, b) => a - b); + let n = this.data.length; + if (n & 1) { + return this.data[Math.floor(n / 2)]; + } else { + return (this.data[n / 2] + this.data[n / 2 - 1]) / 2; + } + } +} +``` + +```csharp +class MedianFinder { + private List data; + + public MedianFinder() { + data = new List(); + } + + public void AddNum(int num) { + data.Add(num); + } + + public double FindMedian() { + data.Sort(); + int n = data.Count; + if ((n & 1) == 1) { + return data[n / 2]; + } else { + return (data[n / 2] + data[n / 2 - 1]) / 2.0; + } + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(m)$ for $addNum()$, $O(m * n \log n)$ for $findMedian()$. +* Space complexity: $O(n)$ + +> Where $m$ is the number of function calls and $n$ is the length of the array. + +--- + +## 2. Heap + +::tabs-start + +```python +class MedianFinder: + def __init__(self): + # two heaps, large, small, minheap, maxheap + # heaps should be equal size + self.small, self.large = [], [] + + def addNum(self, num: int) -> None: + if self.large and num > self.large[0]: + heapq.heappush(self.large, num) + else: + heapq.heappush(self.small, -1 * num) + + if len(self.small) > len(self.large) + 1: + val = -1 * heapq.heappop(self.small) + heapq.heappush(self.large, val) + if len(self.large) > len(self.small) + 1: + val = heapq.heappop(self.large) + heapq.heappush(self.small, -1 * val) + + def findMedian(self) -> float: + if len(self.small) > len(self.large): + return -1 * self.small[0] + elif len(self.large) > len(self.small): + return self.large[0] + return (-1 * self.small[0] + self.large[0]) / 2.0 +``` + +```java +public class MedianFinder { + + private Queue smallHeap; //small elements - maxHeap + private Queue largeHeap; //large elements - minHeap + + public MedianFinder() { + smallHeap = new PriorityQueue<>((a, b) -> b - a); + largeHeap = new PriorityQueue<>((a, b) -> a - b); + } + + public void addNum(int num) { + smallHeap.add(num); + if ( + smallHeap.size() - largeHeap.size() > 1 || + !largeHeap.isEmpty() && + smallHeap.peek() > largeHeap.peek() + ) { + largeHeap.add(smallHeap.poll()); + } + if (largeHeap.size() - smallHeap.size() > 1) { + smallHeap.add(largeHeap.poll()); + } + } + + public double findMedian() { + if (smallHeap.size() == largeHeap.size()) { + return (double) (largeHeap.peek() + smallHeap.peek()) / 2; + } else if (smallHeap.size() > largeHeap.size()) { + return (double) smallHeap.peek(); + } else { + return (double) largeHeap.peek(); + } + } +} +``` + +```cpp +class MedianFinder { + priority_queue, less> smallHeap; + priority_queue, greater> largeHeap; + +public: + MedianFinder() {} + + void addNum(int num) { + smallHeap.push(num); + if (!largeHeap.empty() && smallHeap.top() > largeHeap.top()) { + largeHeap.push(smallHeap.top()); + smallHeap.pop(); + } + if (smallHeap.size() > largeHeap.size() + 1) { + largeHeap.push(smallHeap.top()); + smallHeap.pop(); + } + if (largeHeap.size() > smallHeap.size() + 1) { + smallHeap.push(largeHeap.top()); + largeHeap.pop(); + } + } + + double findMedian() { + if (smallHeap.size() == largeHeap.size()) { + return (largeHeap.top() + smallHeap.top()) / 2.0; + } else if (smallHeap.size() > largeHeap.size()) { + return smallHeap.top(); + } else { + return largeHeap.top(); + } + } +}; +``` + +```javascript +/** + * const { PriorityQueue, MaxPriorityQueue, MinPriorityQueue } = require('@datastructures-js/priority-queue'); + */ + +class MedianFinder { + constructor() { + this.small = new PriorityQueue((a, b) => b - a); // Max heap for smaller half + this.large = new PriorityQueue((a, b) => a - b); // Min heap for larger half + } + + /** + * @param {number} num + */ + addNum(num) { + if (this.large.isEmpty() || num > this.large.front()) { + this.large.enqueue(num); + } else { + this.small.enqueue(num); + } + + if (this.small.size() > this.large.size() + 1) { + this.large.enqueue(this.small.dequeue()); + } else if (this.large.size() > this.small.size() + 1) { + this.small.enqueue(this.large.dequeue()); + } + } + + /** + * @return {number} + */ + findMedian() { + if (this.small.size() > this.large.size()) { + return this.small.front(); + } else if (this.large.size() > this.small.size()) { + return this.large.front(); + } else { + return (this.small.front() + this.large.front()) / 2.0; + } + } +} +``` + +```csharp +public class MedianFinder { + + private PriorityQueue small; // Max heap for the smaller half + private PriorityQueue large; // Min heap for the larger half + + public MedianFinder() { + small = new PriorityQueue(Comparer.Create((a, b) => b.CompareTo(a))); + large = new PriorityQueue(); + } + + public void AddNum(int num) { + if (large.Count != 0 && num > large.Peek()) { + large.Enqueue(num, num); + } else { + small.Enqueue(num, num); + } + + if (small.Count > large.Count + 1) { + int val = small.Dequeue(); + large.Enqueue(val, val); + } else if (large.Count > small.Count + 1) { + int val = large.Dequeue(); + small.Enqueue(val, val); + } + } + + public double FindMedian() { + if (small.Count > large.Count) { + return small.Peek(); + } else if (large.Count > small.Count) { + return large.Peek(); + } + + int smallTop = small.Peek(); + return (smallTop + large.Peek()) / 2.0; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(m * \log n)$ for $addNum()$, $O(m)$ for $findMedian()$. +* Space complexity: $O(n)$ + +> Where $m$ is the number of function calls and $n$ is the length of the array. \ No newline at end of file diff --git a/articles/permutations.md b/articles/permutations.md new file mode 100644 index 000000000..c5e6c202b --- /dev/null +++ b/articles/permutations.md @@ -0,0 +1,667 @@ +## 1. Recursion + +::tabs-start + +```python +class Solution: + def permute(self, nums: List[int]) -> List[List[int]]: + if len(nums) == 0: + return [[]] + + perms = self.permute(nums[1:]) + res = [] + for p in perms: + for i in range(len(p) + 1): + p_copy = p.copy() + p_copy.insert(i, nums[0]) + res.append(p_copy) + return res +``` + +```java +public class Solution { + public List> permute(int[] nums) { + if (nums.length == 0) { + return Arrays.asList(new ArrayList<>()); + } + + List> perms = permute(Arrays.copyOfRange(nums, 1, nums.length)); + List> res = new ArrayList<>(); + for (List p : perms) { + for (int i = 0; i <= p.size(); i++) { + List p_copy = new ArrayList<>(p); + p_copy.add(i, nums[0]); + res.add(p_copy); + } + } + return res; + } +} +``` + +```cpp +class Solution { +public: + vector> permute(vector& nums) { + if (nums.empty()) { + return {{}}; + } + + vector tmp = vector(nums.begin() + 1, nums.end()); + vector> perms = permute(tmp); + vector> res; + for (const auto& p : perms) { + for (int i = 0; i <= p.size(); i++) { + vector p_copy = p; + p_copy.insert(p_copy.begin() + i, nums[0]); + res.push_back(p_copy); + } + } + return res; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number[]} nums + * @return {number[][]} + */ + permute(nums) { + if (nums.length === 0) { + return [[]]; + } + + let perms = this.permute(nums.slice(1)); + let res = []; + for (let p of perms) { + for (let i = 0; i <= p.length; i++) { + let p_copy = p.slice(); + p_copy.splice(i, 0, nums[0]); + res.push(p_copy); + } + } + return res; + } +} +``` + +```csharp +public class Solution { + public List> Permute(int[] nums) { + if (nums.Length == 0) { + return new List> { new List() }; + } + + var perms = Permute(nums[1..]); + var res = new List>(); + foreach (var p in perms) { + for (int i = 0; i <= p.Count; i++) { + var p_copy = new List(p); + p_copy.Insert(i, nums[0]); + res.Add(p_copy); + } + } + return res; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n! * n ^ 2)$ +* Space complexity: $O(n! * n)$ + +--- + +## 2. Iteration + +::tabs-start + +```python +class Solution: + def permute(self, nums: List[int]) -> List[List[int]]: + perms = [[]] + for num in nums: + new_perms = [] + for p in perms: + for i in range(len(p) + 1): + p_copy = p.copy() + p_copy.insert(i, num) + new_perms.append(p_copy) + perms = new_perms + return perms +``` + +```java +public class Solution { + public List> permute(int[] nums) { + List> perms = new ArrayList<>(); + perms.add(new ArrayList<>()); + + for (int num : nums) { + List> new_perms = new ArrayList<>(); + for (List p : perms) { + for (int i = 0; i <= p.size(); i++) { + List p_copy = new ArrayList<>(p); + p_copy.add(i, num); + new_perms.add(p_copy); + } + } + perms = new_perms; + } + return perms; + } +} +``` + +```cpp +class Solution { +public: + vector> permute(vector& nums) { + vector> perms = {{}}; + for (int num : nums) { + vector> new_perms; + for (const auto& p : perms) { + for (int i = 0; i <= p.size(); i++) { + vector p_copy = p; + p_copy.insert(p_copy.begin() + i, num); + new_perms.push_back(p_copy); + } + } + perms = new_perms; + } + return perms; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number[]} nums + * @return {number[][]} + */ + permute(nums) { + let perms = [[]]; + for (let num of nums) { + let new_perms = []; + for (let p of perms) { + for (let i = 0; i <= p.length; i++) { + let p_copy = p.slice(); + p_copy.splice(i, 0, num); + new_perms.push(p_copy); + } + } + perms = new_perms; + } + return perms; + } +} +``` + +```csharp +public class Solution { + public List> Permute(int[] nums) { + var perms = new List>() { new List() }; + foreach (int num in nums) { + var new_perms = new List>(); + foreach (var p in perms) { + for (int i = 0; i <= p.Count; i++) { + var p_copy = new List(p); + p_copy.Insert(i, num); + new_perms.Add(p_copy); + } + } + perms = new_perms; + } + return perms; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n! * n ^ 2)$ +* Space complexity: $O(n! * n)$ + +--- + +## 3. Backtracking + +::tabs-start + +```python +class Solution: + def permute(self, nums: List[int]) -> List[List[int]]: + self.res = [] + self.backtrack([], nums, [False] * len(nums)) + return self.res + + def backtrack(self, perm: List[int], nums: List[int], pick: List[bool]): + if len(perm) == len(nums): + self.res.append(perm[:]) + return + for i in range(len(nums)): + if not pick[i]: + perm.append(nums[i]) + pick[i] = True + self.backtrack(perm, nums, pick) + perm.pop() + pick[i] = False +``` + +```java +public class Solution { + List> res; + public List> permute(int[] nums) { + res = new ArrayList<>(); + backtrack(new ArrayList<>(), nums, new boolean[nums.length]); + return res; + } + + public void backtrack(List perm, int[] nums, boolean[] pick) { + if (perm.size() == nums.length) { + res.add(new ArrayList<>(perm)); + return; + } + for (int i = 0; i < nums.length; i++) { + if (!pick[i]) { + perm.add(nums[i]); + pick[i] = true; + backtrack(perm, nums, pick); + perm.remove(perm.size() - 1); + pick[i] = false; + } + } + } +} +``` + +```cpp +class Solution { + vector> res; +public: + vector> permute(vector& nums) { + vector pick(nums.size(), false); + vector perm; + backtrack(perm, nums, pick); + return res; + } + + void backtrack(vector& perm, vector& nums, vector& pick) { + if (perm.size() == nums.size()) { + res.push_back(perm); + return; + } + for (int i = 0; i < nums.size(); i++) { + if (!pick[i]) { + perm.push_back(nums[i]); + pick[i] = true; + backtrack(perm, nums, pick); + perm.pop_back(); + pick[i] = false; + } + } + } +}; +``` + +```javascript +class Solution { + /** + * @param {number[]} nums + * @return {number[][]} + */ + permute(nums) { + let res = []; + backtrack([], nums, new Array(nums.length).fill(false)); + return res; + + function backtrack(perm, nums, pick) { + if (perm.length === nums.length) { + res.push([...perm]); + return; + } + for (let i = 0; i < nums.length; i++) { + if (!pick[i]) { + perm.push(nums[i]); + pick[i] = true; + backtrack(perm, nums, pick); + perm.pop(); + pick[i] = false; + } + } + } + } +} +``` + +```csharp +public class Solution { + List> res; + public List> Permute(int[] nums) { + res = new List>(); + Backtrack(new List(), nums, new bool[nums.Length]); + return res; + } + + private void Backtrack(List perm, int[] nums, bool[] pick) { + if (perm.Count == nums.Length) { + res.Add(new List(perm)); + return; + } + for (int i = 0; i < nums.Length; i++) { + if (!pick[i]) { + perm.Add(nums[i]); + pick[i] = true; + Backtrack(perm, nums, pick); + perm.RemoveAt(perm.Count - 1); + pick[i] = false; + } + } + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n! * n)$ +* Space complexity: $O(n)$ + +--- + +## 4. Backtracking (Bit Mask) + +::tabs-start + +```python +class Solution: + def permute(self, nums: List[int]) -> List[List[int]]: + self.res = [] + self.backtrack([], nums, 0) + return self.res + + def backtrack(self, perm: List[int], nums: List[int], mask: int): + if len(perm) == len(nums): + self.res.append(perm[:]) + return + for i in range(len(nums)): + if not (mask & (1 << i)): + perm.append(nums[i]) + self.backtrack(perm, nums, mask | (1 << i)) + perm.pop() +``` + +```java +public class Solution { + List> res = new ArrayList<>(); + + public List> permute(int[] nums) { + backtrack(new ArrayList<>(), nums, 0); + return res; + } + + private void backtrack(List perm, int[] nums, int mask) { + if (perm.size() == nums.length) { + res.add(new ArrayList<>(perm)); + return; + } + for (int i = 0; i < nums.length; i++) { + if ((mask & (1 << i)) == 0) { + perm.add(nums[i]); + backtrack(perm, nums, mask | (1 << i)); + perm.remove(perm.size() - 1); + } + } + } +} +``` + +```cpp +class Solution { +public: + vector> res; + + vector> permute(vector& nums) { + backtrack({}, nums, 0); + return res; + } + + void backtrack(vector perm, vector& nums, int mask) { + if (perm.size() == nums.size()) { + res.push_back(perm); + return; + } + for (int i = 0; i < nums.size(); i++) { + if (!(mask & (1 << i))) { + perm.push_back(nums[i]); + backtrack(perm, nums, mask | (1 << i)); + perm.pop_back(); + } + } + } +}; +``` + +```javascript +class Solution { + /** + * @param {number[]} nums + * @return {number[][]} + */ + permute(nums) { + let res = []; + this.backtrack([], nums, 0, res); + return res; + } + + /** + * @param {number[]} perm + * @param {number[]} nums + * @param {number} mask + * @param {number[][]} res + * @return {void} + */ + backtrack(perm, nums, mask, res) { + if (perm.length === nums.length) { + res.push([...perm]); + return; + } + for (let i = 0; i < nums.length; i++) { + if (!(mask & (1 << i))) { + perm.push(nums[i]); + this.backtrack(perm, nums, mask | (1 << i), res); + perm.pop(); + } + } + } +} +``` + +```csharp +public class Solution { + List> res = new List>(); + + public List> Permute(int[] nums) { + Backtrack(new List(), nums, 0); + return res; + } + + private void Backtrack(List perm, int[] nums, int mask) { + if (perm.Count == nums.Length) { + res.Add(new List(perm)); + return; + } + for (int i = 0; i < nums.Length; i++) { + if ((mask & (1 << i)) == 0) { + perm.Add(nums[i]); + Backtrack(perm, nums, mask | (1 << i)); + perm.RemoveAt(perm.Count - 1); + } + } + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n! * n)$ +* Space complexity: $O(n)$ + +--- + +## 5. Backtracking (Optimal) + +::tabs-start + +```python +class Solution: + def permute(self, nums: List[int]) -> List[List[int]]: + self.res = [] + self.backtrack(nums, 0) + return self.res + + def backtrack(self, nums: List[int], idx: int): + if idx == len(nums): + self.res.append(nums[:]) + return + for i in range(idx, len(nums)): + nums[idx], nums[i] = nums[i], nums[idx] + self.backtrack(nums, idx + 1) + nums[idx], nums[i] = nums[i], nums[idx] +``` + +```java +public class Solution { + List> res; + public List> permute(int[] nums) { + res = new ArrayList<>(); + backtrack(nums, 0); + return res; + } + + public void backtrack(int[] nums, int idx) { + if (idx == nums.length) { + List perm = new ArrayList<>(); + for (int num : nums) perm.add(num); + res.add(perm); + return; + } + for (int i = idx; i < nums.length; i++) { + swap(nums, idx, i); + backtrack(nums, idx + 1); + swap(nums, idx, i); + } + } + + private void swap(int[] nums, int i, int j) { + int temp = nums[i]; + nums[i] = nums[j]; + nums[j] = temp; + } +} +``` + +```cpp +class Solution { + vector> res; +public: + vector> permute(vector& nums) { + backtrack(nums, 0); + return res; + } + + void backtrack(vector& nums, int idx) { + if (idx == nums.size()) { + res.push_back(nums); + return; + } + for (int i = idx; i < nums.size(); i++) { + swap(nums[idx], nums[i]); + backtrack(nums, idx + 1); + swap(nums[idx], nums[i]); + } + } +}; +``` + +```javascript +class Solution { + constructor() { + this.res = []; + } + + /** + * @param {number[]} nums + * @return {number[][]} + */ + permute(nums) { + this.backtrack(nums, 0); + return this.res; + } + + /** + * @param {number[]} nums + * @param {number} idx + * @return {void} + */ + backtrack(nums, idx) { + if (idx === nums.length) { + this.res.push([...nums]); + return; + } + for (let i = idx; i < nums.length; i++) { + [nums[idx], nums[i]] = [nums[i], nums[idx]]; + this.backtrack(nums, idx + 1); + [nums[idx], nums[i]] = [nums[i], nums[idx]]; + } + } +} +``` + +```csharp +public class Solution { + private List> res; + + public List> Permute(int[] nums) { + res = new List>(); + Backtrack(nums, 0); + return res; + } + + private void Backtrack(int[] nums, int idx) { + if (idx == nums.Length) { + res.Add(new List(nums)); + return; + } + for (int i = idx; i < nums.Length; i++) { + Swap(nums, idx, i); + Backtrack(nums, idx + 1); + Swap(nums, idx, i); + } + } + + private void Swap(int[] nums, int i, int j) { + int temp = nums[i]; + nums[i] = nums[j]; + nums[j] = temp; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n! * n)$ +* Space complexity: $O(n)$ \ No newline at end of file diff --git a/articles/subsets-ii.md b/articles/subsets-ii.md new file mode 100644 index 000000000..3db9cce85 --- /dev/null +++ b/articles/subsets-ii.md @@ -0,0 +1,562 @@ +## 1. Brute Force + +::tabs-start + +```python +class Solution: + def subsetsWithDup(self, nums: List[int]) -> List[List[int]]: + res = set() + + def backtrack(i, subset): + if i == len(nums): + res.add(tuple(subset)) + return + + subset.append(nums[i]) + backtrack(i + 1, subset) + subset.pop() + backtrack(i + 1, subset) + + nums.sort() + backtrack(0, []) + return [list(s) for s in res] +``` + +```java +public class Solution { + Set> res = new HashSet<>(); + + public List> subsetsWithDup(int[] nums) { + Arrays.sort(nums); + backtrack(nums, 0, new ArrayList<>()); + return new ArrayList<>(res); + } + + private void backtrack(int[] nums, int i, List subset) { + if (i == nums.length) { + res.add(new ArrayList<>(subset)); + return; + } + + subset.add(nums[i]); + backtrack(nums, i + 1, subset); + subset.remove(subset.size() - 1); + backtrack(nums, i + 1, subset); + } +} +``` + +```cpp +class Solution { + set> res; +public: + vector> subsetsWithDup(vector& nums) { + sort(nums.begin(), nums.end()); + backtrack(nums, 0, {}); + return vector>(res.begin(), res.end()); + } + + void backtrack(vector& nums, int i, vector subset) { + if (i == nums.size()) { + res.insert(subset); + return; + } + + subset.push_back(nums[i]); + backtrack(nums, i + 1, subset); + subset.pop_back(); + backtrack(nums, i + 1, subset); + } +}; +``` + +```javascript +class Solution { + constructor() { + this.res = new Set(); + } + + /** + * @param {number[]} nums + * @return {number[][]} + */ + subsetsWithDup(nums) { + nums.sort((a, b) => a - b); + this.backtrack(nums, 0, []); + return Array.from(this.res).map(subset => JSON.parse(subset)); + } + + /** + * @param {number[]} nums + * @param {number[]} subset + * @return {void} + */ + backtrack(nums, i, subset) { + if (i === nums.length) { + this.res.add(JSON.stringify(subset)); + return; + } + + subset.push(nums[i]); + this.backtrack(nums, i + 1, subset); + subset.pop(); + this.backtrack(nums, i + 1, subset); + } +} +``` + +```csharp +public class Solution { + HashSet res = new HashSet(); + + public List> SubsetsWithDup(int[] nums) { + Array.Sort(nums); + Backtrack(nums, 0, new List()); + List> result = new List>(); + result.Add(new List()); + res.Remove(""); + foreach (string str in res) { + List subset = new List(); + string[] arr = str.Split(','); + foreach (string num in arr) { + subset.Add(int.Parse(num)); + } + result.Add(subset); + } + return result; + } + + private void Backtrack(int[] nums, int i, List subset) { + if (i == nums.Length) { + res.Add(string.Join(",", subset)); + return; + } + + subset.Add(nums[i]); + Backtrack(nums, i + 1, subset); + subset.RemoveAt(subset.Count - 1); + Backtrack(nums, i + 1, subset); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n * 2 ^n)$ +* Space complexity: $O(2 ^ n)$ + +--- + +## 2. Backtracking (Pick / Not Pick) + +::tabs-start + +```python +class Solution: + def subsetsWithDup(self, nums: List[int]) -> List[List[int]]: + res = [] + nums.sort() + + def backtrack(i, subset): + if i == len(nums): + res.append(subset[::]) + return + + subset.append(nums[i]) + backtrack(i + 1, subset) + subset.pop() + + while i + 1 < len(nums) and nums[i] == nums[i + 1]: + i += 1 + backtrack(i + 1, subset) + + backtrack(0, []) + return res +``` + +```java +public class Solution { + List> res = new ArrayList<>(); + + public List> subsetsWithDup(int[] nums) { + Arrays.sort(nums); + backtrack(0, new ArrayList<>(), nums); + return res; + } + + private void backtrack(int i, List subset, int[] nums) { + if (i == nums.length) { + res.add(new ArrayList<>(subset)); + return; + } + + subset.add(nums[i]); + backtrack(i + 1, subset, nums); + subset.remove(subset.size() - 1); + + while (i + 1 < nums.length && nums[i] == nums[i + 1]) { + i++; + } + backtrack(i + 1, subset, nums); + } +} +``` + +```cpp +class Solution { + vector> res; +public: + vector> subsetsWithDup(vector& nums) { + sort(nums.begin(), nums.end()); + backtrack(0, {}, nums); + return res; + } + + void backtrack(int i, vector subset, vector& nums) { + if (i == nums.size()) { + res.push_back(subset); + return; + } + + subset.push_back(nums[i]); + backtrack(i + 1, subset, nums); + subset.pop_back(); + + while (i + 1 < nums.size() && nums[i] == nums[i + 1]) { + i++; + } + backtrack(i + 1, subset, nums); + } +}; +``` + +```javascript +class Solution { + /** + * @param {number[]} nums + * @return {number[][]} + */ + subsetsWithDup(nums) { + const res = []; + nums.sort((a, b) => a - b); + this.backtrack(0, [], nums, res); + return res; + } + + /** + * @param {number} start + * @param {number[]} subset + * @param {number[]} nums + * @param {number[][]} res + * @return {void} + */ + backtrack(start, subset, nums, res) { + res.push([...subset]); + for (let i = start; i < nums.length; i++) { + if (i > start && nums[i] === nums[i - 1]) { + continue; + } + subset.push(nums[i]); + this.backtrack(i + 1, subset, nums, res); + subset.pop(); + } + } +} +``` + +```csharp +public class Solution { + List> res = new List>(); + + public List> SubsetsWithDup(int[] nums) { + Array.Sort(nums); + Backtrack(0, new List(), nums); + return res; + } + + private void Backtrack(int i, List subset, int[] nums) { + if (i == nums.Length) { + res.Add(new List(subset)); + return; + } + + subset.Add(nums[i]); + Backtrack(i + 1, subset, nums); + subset.RemoveAt(subset.Count - 1); + + while (i + 1 < nums.Length && nums[i] == nums[i + 1]) { + i++; + } + Backtrack(i + 1, subset, nums); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n * 2 ^ n)$ +* Space complexity: $O(n)$ + +--- + +## 3. Backtracking + +::tabs-start + +```python +class Solution: + def subsetsWithDup(self, nums: List[int]) -> List[List[int]]: + nums.sort() + res = [] + def backtrack(i, subset): + res.append(subset[::]) + + for j in range(i, len(nums)): + if j > i and nums[j] == nums[j - 1]: + continue + subset.append(nums[j]) + backtrack(j + 1, subset) + subset.pop() + + backtrack(0, []) + return res +``` + +```java +public class Solution { + List> res = new ArrayList<>(); + + public List> subsetsWithDup(int[] nums) { + Arrays.sort(nums); + backtrack(0, new ArrayList<>(), nums); + return res; + } + + private void backtrack(int i, List subset, int[] nums) { + res.add(new ArrayList<>(subset)); + for (int j = i; j < nums.length; j++) { + if (j > i && nums[j] == nums[j - 1]) { + continue; + } + subset.add(nums[j]); + backtrack(j + 1, subset, nums); + subset.remove(subset.size() - 1); + } + } +} +``` + +```cpp +class Solution { +public: + vector> res; + + vector> subsetsWithDup(vector& nums) { + sort(nums.begin(), nums.end()); + backtrack(0, {}, nums); + return res; + } + + void backtrack(int i, vector subset, vector& nums) { + res.push_back(subset); + for (int j = i; j < nums.size(); j++) { + if (j > i && nums[j] == nums[j - 1]) { + continue; + } + subset.push_back(nums[j]); + backtrack(j + 1, subset, nums); + subset.pop_back(); + } + } +}; +``` + +```javascript +class Solution { + constructor() { + this.res = []; + } + + /** + * @param {number[]} nums + * @return {number[][]} + */ + subsetsWithDup(nums) { + nums.sort((a, b) => a - b); + this.backtrack(0, [], nums); + return this.res; + } + + /** + * @param {number} i + * @param {number[]} subset + * @param {number[]} nums + * @return {void} + */ + backtrack(i, subset, nums) { + this.res.push([...subset]); + for (let j = i; j < nums.length; j++) { + if (j > i && nums[j] === nums[j - 1]) { + continue; + } + subset.push(nums[j]); + this.backtrack(j + 1, subset, nums); + subset.pop(); + } + } +} +``` + +```csharp +public class Solution { + private List> res = new List>(); + + public List> SubsetsWithDup(int[] nums) { + Array.Sort(nums); + Backtrack(0, new List(), nums); + return res; + } + + private void Backtrack(int i, List subset, int[] nums) { + res.Add(new List(subset)); + for (int j = i; j < nums.Length; j++) { + if (j > i && nums[j] == nums[j - 1]) { + continue; + } + subset.Add(nums[j]); + Backtrack(j + 1, subset, nums); + subset.RemoveAt(subset.Count - 1); + } + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n * 2 ^ n)$ +* Space complexity: $O(n)$ + +--- + +## 4. Iteration + +::tabs-start + +```python +class Solution: + def subsetsWithDup(self, nums: List[int]) -> List[List[int]]: + nums.sort() + res = [[]] + prev_Idx = idx = 0 + for i in range(len(nums)): + idx = prev_idx if i >= 1 and nums[i] == nums[i - 1] else 0 + prev_idx = len(res) + for j in range(idx, prev_idx): + tmp = res[j].copy() + tmp.append(nums[i]) + res.append(tmp) + return res +``` + +```java +public class Solution { + public List> subsetsWithDup(int[] nums) { + Arrays.sort(nums); + List> res = new ArrayList<>(); + res.add(new ArrayList<>()); + int prevIdx = 0; + int idx = 0; + for (int i = 0; i < nums.length; i++) { + idx = (i >= 1 && nums[i] == nums[i - 1]) ? prevIdx : 0; + prevIdx = res.size(); + for (int j = idx; j < prevIdx; j++) { + List tmp = new ArrayList<>(res.get(j)); + tmp.add(nums[i]); + res.add(tmp); + } + } + return res; + } +} +``` + +```cpp +class Solution { +public: + vector> subsetsWithDup(vector& nums) { + sort(nums.begin(), nums.end()); + vector> res = {{}}; + int prevIdx = 0; + int idx = 0; + for (int i = 0; i < nums.size(); i++) { + idx = (i >= 1 && nums[i] == nums[i - 1]) ? prevIdx : 0; + prevIdx = res.size(); + for (int j = idx; j < prevIdx; j++) { + std::vector tmp = res[j]; + tmp.push_back(nums[i]); + res.push_back(tmp); + } + } + return res; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number[]} nums + * @return {number[][]} + */ + subsetsWithDup(nums) { + nums.sort((a, b) => a - b); + const res = [[]]; + let prevIdx = 0; + let idx = 0; + for (let i = 0; i < nums.length; i++) { + idx = (i >= 1 && nums[i] === nums[i - 1]) ? prevIdx : 0; + prevIdx = res.length; + for (let j = idx; j < prevIdx; j++) { + const tmp = [...res[j]]; + tmp.push(nums[i]); + res.push(tmp); + } + } + return res; + } +} +``` + +```csharp +public class Solution { + public List> SubsetsWithDup(int[] nums) { + Array.Sort(nums); + var res = new List> { new List() }; + int prevIdx = 0; + int idx = 0; + for (int i = 0; i < nums.Length; i++) { + idx = (i >= 1 && nums[i] == nums[i - 1]) ? prevIdx : 0; + prevIdx = res.Count; + for (int j = idx; j < prevIdx; j++) { + var tmp = new List(res[j]); + tmp.Add(nums[i]); + res.Add(tmp); + } + } + return res; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +* Time complexity: $O(n * 2 ^ n)$ +* Space complexity: $O(1)$ \ No newline at end of file