diff --git a/C++/0003-LongestSubstringWithoutRepeatingCharacters/Solution.cpp b/C++/0003-LongestSubstringWithoutRepeatingCharacters/Solution.cpp new file mode 100644 index 0000000..6aae155 --- /dev/null +++ b/C++/0003-LongestSubstringWithoutRepeatingCharacters/Solution.cpp @@ -0,0 +1,22 @@ +#include +#include + +using namespace std; +class Solution { +public: + int lengthOfLongestSubstring(string s) { + unordered_set unique; + int result = 0; + + int l = 0; + for (int r = 0; r < s.length(); ++r) { + while (unique.count(s[r])) { + unique.erase(s[l++]); + } + unique.insert(s[r]); + result = max(result, r - l + 1); + } + + return result; + } +}; diff --git a/C++/0005-LongestPalindromicSubstring/Solution.cpp b/C++/0005-LongestPalindromicSubstring/Solution.cpp new file mode 100644 index 0000000..864f408 --- /dev/null +++ b/C++/0005-LongestPalindromicSubstring/Solution.cpp @@ -0,0 +1,15 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +class Solution { +public: + string longestPalindrome(string s) {} +}; diff --git a/C++/0011-ContainerWithMostWater/Solution.cpp b/C++/0011-ContainerWithMostWater/Solution.cpp new file mode 100644 index 0000000..dd976a3 --- /dev/null +++ b/C++/0011-ContainerWithMostWater/Solution.cpp @@ -0,0 +1,23 @@ +#include + +using namespace std; +class Solution { +public: + int maxArea(vector &height) { + int left = 0; + int right = height.size() - 1; + + int result = 0; + while (left < right) { + int currentArea = min(height[left], height[right]) * (right - left); + result = max(result, currentArea); + + if (height[left] < height[right]) { + ++left; + } else { + --right; + } + } + return result; + } +}; diff --git a/C++/0014-LongestCommonPrefix/Solution.cpp b/C++/0014-LongestCommonPrefix/Solution.cpp new file mode 100644 index 0000000..9080f27 --- /dev/null +++ b/C++/0014-LongestCommonPrefix/Solution.cpp @@ -0,0 +1,28 @@ +#include +#include + +using namespace std; + +class Solution { +public: + string longestCommonPrefix(vector &strs) { + if (strs.empty()) { + return ""; + } + + string prefix = strs[0]; + for (int i = 1; i < strs.size(); ++i) { + int j = 0; + while (j < min(prefix.size(), strs[i].size()) && + prefix[j] == strs[i][j]) { + ++j; + } + prefix = prefix.substr(0, j); // Adjust the prefix + if (prefix.empty()) { + break; // Early exit if there's no common prefix + } + } + return prefix; + } +}; + diff --git a/C++/0015-ThreeSum/Solution.cpp b/C++/0015-ThreeSum/Solution.cpp new file mode 100644 index 0000000..a84bf7f --- /dev/null +++ b/C++/0015-ThreeSum/Solution.cpp @@ -0,0 +1,52 @@ +#include +#include + +using namespace std; +class Solution { +public: + vector> threeSum(vector &nums) { + sort(nums.begin(), nums.end()); + + vector> result; + for (int i = 0; i < nums.size() - 2; ++i) { + if (nums[i] > 0) { + // Considering the way we only look forward for matches, + // and the array is sorted, if the beginning number + // is positive, we can terminate the algorithm because + // there is no way to sum to zero. + break; + } + + if (i > 0 && nums[i] == nums[i - 1]) { + // Considering the array is sorted, if the value + // of the current is same as previous, then skip + // duplicates. + continue; + } + + // reduce to 2-sum + int target = -nums[i]; + + int j = i + 1; + int k = nums.size() - 1; + while (j < k) { + int sum = nums[j] + nums[k]; + if (sum < target) { + ++j; + } else if (sum > target) { + --k; + } else { + result.push_back({nums[i], nums[j++], nums[k--]}); + + while (j < k && nums[j] == nums[j - 1]) { + ++j; + } + while (j < k && nums[k] == nums[k + 1]) { + --k; + } + } + } + } + return result; + } +}; diff --git a/C++/0020-ValidParentheses/Solution.cpp b/C++/0020-ValidParentheses/Solution.cpp new file mode 100644 index 0000000..0b2a127 --- /dev/null +++ b/C++/0020-ValidParentheses/Solution.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +class Solution { +public: + bool isValid(string s) { + stack expected; + + for (char const &c : s) { + if (c == '(') { + expected.push(')'); + continue; + } + if (c == '{') { + expected.push('}'); + continue; + } + if (c == '[') { + expected.push(']'); + continue; + } + + if (expected.empty()) { + return false; + } + + char prev = expected.top(); + expected.pop(); + if (prev != c) { + return false; + } + } + + return expected.empty(); + } +}; diff --git a/C++/0036-ValidSudoku/Solution.cpp b/C++/0036-ValidSudoku/Solution.cpp new file mode 100644 index 0000000..f4107ba --- /dev/null +++ b/C++/0036-ValidSudoku/Solution.cpp @@ -0,0 +1,77 @@ +#include + +using namespace std; +class Solution { +private: + bool isRowValid(vector> const &board, int row) { + vector present = vector(9, false); + for (char num : board[row]) { + if (num == '.') { + continue; + } + if (present[num - '0']) { + return false; + } + present[num - '0'] = true; + } + return true; + } + + bool isColValid(vector> const &board, int col) { + vector present = vector(9, false); + for (auto row : board) { + if (row[col] == '.') { + continue; + } + if (present[row[col] - '0']) { + return false; + } + present[row[col] - '0'] = true; + } + return true; + } + + bool isBoxValid(vector> const &board, int row, int col) { + // no bounds check. assume inputs are correct. + vector present = vector(9, false); + for (int i = row; i < row + 3; ++i) { + for (int j = col; j < col + 3; ++j) { + if (board[i][j] == '.') { + continue; + } + if (present[board[i][j] - '0']) { + return false; + } + present[board[i][j] - '0'] = true; + } + } + return true; + } + +public: + bool isValidSudoku(vector> &board) { + for (int i = 0; i < 9; i += 3) { + for (int j = 0; j < 9; j += 3) { + bool boxResult = isBoxValid(board, i, j); + if (!boxResult) { + return false; + } + + for (int row = i; row < i + 3; ++row) { + bool rowResult = isRowValid(board, row); + if (!rowResult) { + return false; + } + } + + for (int col = j; col < j + 3; ++col) { + bool colResult = isColValid(board, col); + if (!colResult) { + return false; + } + } + } + } + return true; + } +}; diff --git a/C++/0042-TrappingRainWater/Solution.cpp b/C++/0042-TrappingRainWater/Solution.cpp new file mode 100644 index 0000000..cd0533b --- /dev/null +++ b/C++/0042-TrappingRainWater/Solution.cpp @@ -0,0 +1,81 @@ +#include +#include +#include + +using namespace std; +class Solution { +public: + // Water cannot be trapped at boundaries + int trap(vector const &height) { + if (height.size() < 3) { + return 0; + } + + int l = 0; + int r = height.size() - 1; + + int leftBoundary = height[l]; + int rightBoundary = height[r]; + int result = 0; + while (l < r) { + // if-checks guarantee somewhat of a monotonic property. + if (leftBoundary < rightBoundary) { + leftBoundary = max(leftBoundary, height[++l]); + result += leftBoundary - height[l]; + } else { + rightBoundary = max(rightBoundary, height[--r]); + result += rightBoundary - height[r]; + } + } + return result; + } + + int trapStack(vector &height) { + if (height.size() < 3) { + return 0; // Water cannot be trapped + } + + // Monotonically non-increasing stack (same or decreasing) + stack indices; + int result = 0; + for (int i = 0; i < height.size(); ++i) { + if (indices.empty() || height[i] <= height[indices.top()]) { + indices.push(i); + continue; + } + + while (!indices.empty() && height[i] > height[indices.top()]) { + int popIndex = indices.top(); + indices.pop(); + + // Handle boundaries (literally edge cases) and adjacent bars. + if (indices.empty()) { + break; + } + + int leftBoundaryIndex = indices.top(); + // Take the min of the leftBoundary and rightBoundary (height[i]), + // minus the depth of the poppedIndex, then multiply by the distance + // between the left and right bounds. Visualize it as adding horizontal + // layers of water instead of vertical layers. + result += + (min(height[leftBoundaryIndex], height[i]) - height[popIndex]) * + (i - leftBoundaryIndex - 1); + } + + indices.push(i); + } + + return result; + } +}; + +int main(int argc, char *argv[]) { + vector heights = {0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1}; + cout << "want 6, got " << Solution().trap(heights) << endl; + + heights = {4, 2, 0, 3, 2, 5}; + cout << "want 9, got " << Solution().trap(heights) << endl; + + return 0; +} diff --git a/C++/0046-Permutations/Solution.cpp b/C++/0046-Permutations/Solution.cpp new file mode 100644 index 0000000..d1334a8 --- /dev/null +++ b/C++/0046-Permutations/Solution.cpp @@ -0,0 +1,28 @@ +#include +#include + +using namespace std; +class Solution { +private: + void generatePermutations(vector &nums, + vector> &permutations, int index) { + if (index >= nums.size()) { + permutations.push_back(nums); + return; + } + + for (int i = index; i < nums.size(); ++i) { + swap(nums[index], nums[i]); + generatePermutations(nums, permutations, index + 1); + swap(nums[index], nums[i]); + } + return; + } + +public: + vector> permute(vector &nums) { + vector> permutations; + generatePermutations(nums, permutations, 0); + return permutations; + } +}; diff --git a/C++/0049-GroupAnagrams/Solution.cpp b/C++/0049-GroupAnagrams/Solution.cpp new file mode 100644 index 0000000..fd1714a --- /dev/null +++ b/C++/0049-GroupAnagrams/Solution.cpp @@ -0,0 +1,25 @@ +#include +#include +#include + +using namespace std; +class Solution { +public: + vector> groupAnagrams(vector &strs) { + unordered_map> groups; + + for (string s : strs) { + string sorted = s; + sort(sorted.begin(), sorted.end()); + + groups[sorted].push_back(s); + } + + vector> result; + for (auto const &[key, group] : groups) { + result.push_back(group); + } + + return result; + } +}; diff --git a/C++/0075-SortColors/Solution.cpp b/C++/0075-SortColors/Solution.cpp new file mode 100644 index 0000000..32330ee --- /dev/null +++ b/C++/0075-SortColors/Solution.cpp @@ -0,0 +1,23 @@ +#include +#include + +using namespace std; +class Solution { +public: + // Essentially Three-way QuickSort. Pick '1' as pivot. + void sortColors(vector &nums) { + int left = 0; // the next index to place a '0' at. + int right = nums.size() - 1; + + int mid = 0; + while (mid <= right) { + if (nums[mid] == 0) { + swap(nums[left++], nums[mid++]); + } else if (nums[mid] == 1) { + mid++; + } else { + swap(nums[mid], nums[right--]); + } + } + } +}; diff --git a/C++/0076-MinimumWindowSubstring/Solution.cpp b/C++/0076-MinimumWindowSubstring/Solution.cpp new file mode 100644 index 0000000..15f6186 --- /dev/null +++ b/C++/0076-MinimumWindowSubstring/Solution.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +class Solution { +public: + string minWindow(string s, string t) { + if (s.length() < t.length()) { + return ""; + } + + int counter = 0; + unordered_map frequencies; + for (char c : t) { + ++frequencies[c]; + ++counter; + } + + int minLeft = 0; + int minLength = INT_MAX; + + int l = 0; + for (int r = 0; r < s.length(); ++r) { + if (!frequencies.count(s[r])) { + continue; + } + + if (frequencies[s[r]] > 0) { + --counter; + } + --frequencies[s[r]]; + + while (counter == 0) { + if (r - l + 1 < minLength) { + minLeft = l; + minLength = r - l + 1; + } + if (frequencies.count(s[l])) { + if (++frequencies[s[l]] > 0) { + ++counter; + } + } + ++l; + } + } + + if (minLength == INT_MAX) { + return ""; + } + + return s.substr(minLeft, minLength); + } +}; diff --git a/C++/0078-Subsets/Solution.cpp b/C++/0078-Subsets/Solution.cpp new file mode 100644 index 0000000..c7e9490 --- /dev/null +++ b/C++/0078-Subsets/Solution.cpp @@ -0,0 +1,26 @@ +#include + +using namespace std; +class Solution { +public: + vector> subsets(vector &nums) { + vector> subsets; + vector currSet; + generateSubsets(nums, subsets, currSet, 0); + return subsets; + } + +private: + void generateSubsets(vector const &nums, vector> &subsets, + vector &currSet, int idx) { + if (idx >= nums.size()) { + subsets.push_back(currSet); + return; + } + generateSubsets(nums, subsets, currSet, idx + 1); + currSet.push_back(nums[idx]); + generateSubsets(nums, subsets, currSet, idx + 1); + currSet.pop_back(); + return; + } +}; diff --git a/C++/0121-BestTimeToBuyAndSellStock/Solution.cpp b/C++/0121-BestTimeToBuyAndSellStock/Solution.cpp new file mode 100644 index 0000000..edd2ce4 --- /dev/null +++ b/C++/0121-BestTimeToBuyAndSellStock/Solution.cpp @@ -0,0 +1,26 @@ +#include + +using namespace std; +class Solution { +public: + int maxProfit(vector &prices) { + // Two Choices: Buy or Sell (if already bought a stock) + // For this variant, only one single stock can be bought and sold. + // Sliding window / monotonically non-decreasing stack. + if (prices.size() < 2) { + return 0; + } + + int profit = 0; + int l = 0; + for (int r = 1; r < prices.size(); ++r) { + if (prices[r] < prices[l]) { + l = r; // try buying at r + continue; + } + profit = max(profit, prices[r] - prices[l]); + } + + return profit; + } +}; diff --git a/C++/0125-ValidPalindrome/Solution.cpp b/C++/0125-ValidPalindrome/Solution.cpp new file mode 100644 index 0000000..71e4c7e --- /dev/null +++ b/C++/0125-ValidPalindrome/Solution.cpp @@ -0,0 +1,35 @@ +#include +#include + +using namespace std; + +bool isalphanumeric(char c) { return isalpha(c) || isdigit(c); } + +class Solution { +public: + bool isPalindrome(string s) { + int i = 0; + int j = s.size() - 1; + + while (i < j) { + while (!isalphanumeric(s[i]) && i < j) { + ++i; + } + + while (!isalphanumeric(s[j]) && i < j) { + --j; + } + + if (i >= j) { + break; + } + + char left = tolower(s[i++]); + char right = tolower(s[j--]); + if (left != right) { + return false; + } + } + return true; + } +}; diff --git a/C++/0128-LongestConsecutiveSequence/Solution.cpp b/C++/0128-LongestConsecutiveSequence/Solution.cpp new file mode 100644 index 0000000..5e262d0 --- /dev/null +++ b/C++/0128-LongestConsecutiveSequence/Solution.cpp @@ -0,0 +1,35 @@ +#include +#include + +using namespace std; +class Solution { +public: + int longestConsecutive(vector &nums) { + unordered_set present; + for (int num : nums) { + present.insert(num); + } + + int result = 0; + for (int num : nums) { + if (!present.count(num)) { + continue; + } + + int currentCount = 0; + // find smallest number + while (present.count(num)) { + --num; + } + + ++num; // compensate for extra minus + while (present.count(num)) { + present.erase(num); + ++num; + ++currentCount; + } + result = max(result, currentCount); + } + return result; + } +}; diff --git a/C++/0131-PalindromePartitioning/Solution.cpp b/C++/0131-PalindromePartitioning/Solution.cpp new file mode 100644 index 0000000..2acf692 --- /dev/null +++ b/C++/0131-PalindromePartitioning/Solution.cpp @@ -0,0 +1,57 @@ +#include +#include + +using namespace std; + +class Solution { +public: + vector> partition(string s) { + vector> partitions = getPalindromicPartitions(s); + return partitions; + } + +private: + vector> getPalindromicPartitions(string s) { + vector> partitions; + vector currPartition; + + getPartitions(s, partitions, currPartition, 0); + return partitions; + } + + void getPartitions(string s, vector> &partitions, + vector &curr, int start) { + if (start >= s.length()) { + partitions.push_back(curr); + return; + } + for (int end = start; end < s.size(); ++end) { + if (isPalindrome(s, start, end)) { + curr.push_back(s.substr(start, end - start + 1)); + getPartitions(s, partitions, curr, end + 1); + curr.pop_back(); + } + } + } + + bool isPalindrome(string const &s, int left, int right) { + while (left < right) { + if (s[left] != s[right]) { + return false; + } + ++left; + --right; + } + return true; + } +}; + +int main() { + auto partitions = Solution().partition("aab"); + for (auto part : partitions) { + for (auto c : part) { + cout << c << ", "; + } + cout << endl; + } +} diff --git a/C++/0140-WordBreakTwo/Solution.cpp b/C++/0140-WordBreakTwo/Solution.cpp new file mode 100644 index 0000000..f735275 --- /dev/null +++ b/C++/0140-WordBreakTwo/Solution.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +using namespace std; +class Solution { +public: + vector wordBreak(string s, vector &wordDict) { + unordered_set wordSet = + unordered_set(wordDict.begin(), wordDict.end()); + + unordered_map> memo; + return dfs(s, wordSet, memo); + } + + vector dfs(string s, unordered_set const &wordSet, + unordered_map> &memo) { + if (memo.count(s) > 0) { + return memo[s]; + } + if (s.empty()) { + return {""}; + } + vector result; + for (int end = 1; end <= s.size(); ++end) { + string word = s.substr(0, end); + if (wordSet.count(word) > 0) { + vector sublist = dfs(s.substr(end), wordSet, memo); + for (string st : sublist) { + if (st.empty()) { + result.push_back(word); + } else { + result.push_back(word + " " + st); + } + } + } + } + memo[s] = result; + return result; + } +}; + +int main() { + string s = "pineapplepenapple"; + vector wordDict = {"apple", "pen", "applepen", "pine", "pineapple"}; + + vector result = Solution().wordBreak(s, wordDict); + for (auto s : result) { + cout << s << " "; + } + cout << endl; +} diff --git a/C++/0167-TwoSumTwo/Solution.cpp b/C++/0167-TwoSumTwo/Solution.cpp new file mode 100644 index 0000000..98ff278 --- /dev/null +++ b/C++/0167-TwoSumTwo/Solution.cpp @@ -0,0 +1,24 @@ +#include + +using namespace std; +class Solution { +public: + vector twoSum(vector &nums, int target) { + int i = 0; + int j = nums.size() - 1; + + while (i < j) { + int sum = nums[i] + nums[j]; + if (sum == target) { + break; + } + + if (sum > target) { + --j; + continue; + } + ++i; + } + return {i + 1, j + 1}; + } +}; diff --git a/C++/0198-HouseRobber/Solution.cpp b/C++/0198-HouseRobber/Solution.cpp new file mode 100644 index 0000000..f6f1d79 --- /dev/null +++ b/C++/0198-HouseRobber/Solution.cpp @@ -0,0 +1,18 @@ +#include + +using namespace std; +class Solution { +public: + int rob(vector &nums) { + int currMax = 0; + int prevMax = 0; + + for (int num : nums) { + int newMax = max(currMax, prevMax + num); + + prevMax = currMax; + currMax = newMax; + } + return currMax; + } +}; diff --git a/C++/0213-HouseRobberTwo/Solution.cpp b/C++/0213-HouseRobberTwo/Solution.cpp new file mode 100644 index 0000000..3700047 --- /dev/null +++ b/C++/0213-HouseRobberTwo/Solution.cpp @@ -0,0 +1,32 @@ +#include + +using namespace std; +class Solution { +private: + // adapted from House Robber One + int robRow(vector &nums, int begin, int end) { + int currMax = 0; + int prevMax = 0; + + for (int i = begin; i < end; ++i) { + int newMax = max(currMax, prevMax + nums[i]); + + prevMax = currMax; + currMax = newMax; + } + return currMax; + } + +public: + // Difference from House Robber One is that the houses are arranged in a + // circle. That means that the first and last house are adjacent + int rob(vector &nums) { + if (nums.size() == 1) { + return nums[0]; + } + // Since the first and the last house cannot be robbed at the same time, + // take the max of robRow(nums[0:n-1]) and robRow(nums[1:n]) + int n = nums.size(); + return max(robRow(nums, 0, n - 1), robRow(nums, 1, n)); + } +}; diff --git a/C++/0236-LowestCommonAncestorOfABinaryTree/Solution.cpp b/C++/0236-LowestCommonAncestorOfABinaryTree/Solution.cpp new file mode 100644 index 0000000..6227c2a --- /dev/null +++ b/C++/0236-LowestCommonAncestorOfABinaryTree/Solution.cpp @@ -0,0 +1,33 @@ +struct TreeNode { + int val; + TreeNode *left; + TreeNode *right; + + TreeNode() : val(0), left(nullptr), right(nullptr) {} + TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + TreeNode(int x, TreeNode *left, TreeNode *right) + : val(x), left(left), right(right) {} +}; + +class Solution { +public: + TreeNode *lowestCommonAncestor(TreeNode *root, TreeNode *p, TreeNode *q) { + if (root == nullptr || root == p || root == q) { + return root; + } + + TreeNode *left = lowestCommonAncestor(root->left, p, q); + TreeNode *right = lowestCommonAncestor(root->right, p, q); + + if (left && right) { + return root; + } + + // Return the non-null left or right + if (left) { + return left; + } + + return right; + } +}; diff --git a/C++/0238-ProductOfArrayExceptSelf/Solution.cpp b/C++/0238-ProductOfArrayExceptSelf/Solution.cpp new file mode 100644 index 0000000..0e5affe --- /dev/null +++ b/C++/0238-ProductOfArrayExceptSelf/Solution.cpp @@ -0,0 +1,23 @@ +#include + +using namespace std; +class Solution { +public: + vector productExceptSelf(vector &nums) { + int n = nums.size(); + vector postfixProduct = vector(n, 1); + + for (int i = n - 2; i >= 0; --i) { + postfixProduct[i] = postfixProduct[i + 1] * nums[i + 1]; + } + + int prefixProduct = nums[0]; + nums[0] = postfixProduct[0]; + for (int i = 1; i < n; ++i) { + int temp = nums[i]; + nums[i] = prefixProduct * postfixProduct[i]; + prefixProduct *= temp; + } + return nums; + } +}; diff --git a/C++/0239-SlidingWindowMaximum/Solution.cpp b/C++/0239-SlidingWindowMaximum/Solution.cpp new file mode 100644 index 0000000..10ab5fa --- /dev/null +++ b/C++/0239-SlidingWindowMaximum/Solution.cpp @@ -0,0 +1,61 @@ +#include +#include +#include + +using namespace std; +class Solution { +public: + vector maxSlidingWindow(vector &nums, int k) { + vector result; + result.reserve(nums.size() - k + 1); + + // the max element must be kept at the front for amortized constant time + // push/pop Since newer elements are added to the back of the deque, which + // the window will cover for k iterations, we can remove any smaller + // elements left in the deque, since they will never be considered the + // window's maximum anymore. Since every element is added, and removed from + // the deque exactly once, this solution runs in O(n) time. + deque dq; + + for (int i = 0; i < nums.size(); ++i) { + if (!dq.empty() && dq.front() == i - k) { + // remove index to the left of the window + dq.pop_front(); + } + + // Maintain invariant. + while (!dq.empty() && nums[i] >= nums[dq.back()]) { + dq.pop_back(); + } + dq.push_back(i); + + if (i >= k - 1) { + // if the window is valid + result.push_back(nums[dq.front()]); + } + } + + return result; + } + + vector maxSlidingWindowNLogN(vector &nums, int k) { + map pq; + for (int i = 0; i < k; ++i) { + ++pq[nums[i]]; + } + + vector maxSlidingWindow; + maxSlidingWindow.reserve(nums.size() - k + 1); + + maxSlidingWindow.push_back(pq.rbegin()->first); // largest element; + for (int i = k; i < nums.size(); ++i) { + if (!--pq[nums[i - k]]) { + pq.erase(nums[i - k]); + } + ++pq[nums[i]]; + maxSlidingWindow.push_back(pq.rbegin()->first); + } + + return maxSlidingWindow; + } +}; diff --git a/C++/0260-SingleNumberThree/Solution.cpp b/C++/0260-SingleNumberThree/Solution.cpp new file mode 100644 index 0000000..cdf5072 --- /dev/null +++ b/C++/0260-SingleNumberThree/Solution.cpp @@ -0,0 +1,46 @@ +#include + +using namespace std; +class Solution { +public: + // Find the two numbers that appear exactly once in the array. Would be + // simple, but the constraints are to use constant extra space. + // Using XOR properties where a XOR a = 0, and 0 XOR b = b, we can easily find + // the XOR of the two numbers that appear only once. + // + // a XOR a = 0, (a XOR a) XOR b = b, ((a XOR a) XOR b) XOR c = b XOR c + // + // The difficulty lies in extracting both b and c. If we know one, we can + // extract the other, since (b XOR c) XOR b = c + vector singleNumber(vector &nums) { + int totalXOR = 0; + for (int num : nums) { + totalXOR ^= num; + } + + // TODO internalize this... + // 1, 2, 1, 3, 2, 5 + // 001, 010, 001, 011, 010, 101 + // totalXOR = 6 = 110 + // Looking at the bits, starting from the LSB, + // the first "set" bit is the 2nd-last/middle bit 1 '1' 0, + // The unique property arising from knowing this is that we can + // partition the array into two distinct groups, numbers that + // have the corresponding bit as '0', and numbers that have it as '1'. + // Since the non-single numbers in either groups will cancel themselves + // out, we can extract the single numbers. + unsigned int leastSignificantSetBit = totalXOR & -(unsigned int)totalXOR; + + vector singleNumbers = vector(2, 0); + for (int num : nums) { + // find out which group the current number belong to + int group = num & leastSignificantSetBit; + if (group) { + singleNumbers[1] ^= num; + } else { + singleNumbers[0] ^= num; + } + } + return singleNumbers; + } +}; diff --git a/C++/0344-ReverseString/Solution.cpp b/C++/0344-ReverseString/Solution.cpp new file mode 100644 index 0000000..639da44 --- /dev/null +++ b/C++/0344-ReverseString/Solution.cpp @@ -0,0 +1,13 @@ +#include + +using namespace std; +class Solution { +public: + void reverseString(vector &s) { + int left = 0; + int right = s.size() - 1; + while (left < right) { + swap(s[left++], s[right--]); + } + } +}; diff --git a/C++/0347-TopKFrequentElements/Solution.cpp b/C++/0347-TopKFrequentElements/Solution.cpp new file mode 100644 index 0000000..4925920 --- /dev/null +++ b/C++/0347-TopKFrequentElements/Solution.cpp @@ -0,0 +1,33 @@ +#include +#include +#include + +using namespace std; +class Solution { +public: + vector topKFrequent(vector &nums, int k) { + unordered_map frequency; + + for (int num : nums) { + frequency[num]++; + } + + auto cmp = [&frequency](int a, int b) { + return frequency[a] < frequency[b]; + }; + + priority_queue, decltype(cmp)> pq(cmp); + + for (const auto &pair : frequency) { + pq.push(pair.first); + } + + vector result; + for (int i = 0; i < k && !pq.empty(); i++) { + result.push_back(pq.top()); + pq.pop(); + } + + return result; + } +}; diff --git a/C++/0350-IntersectionOfTwoArrays/Solution.cpp b/C++/0350-IntersectionOfTwoArrays/Solution.cpp new file mode 100644 index 0000000..0afc688 --- /dev/null +++ b/C++/0350-IntersectionOfTwoArrays/Solution.cpp @@ -0,0 +1,25 @@ +#include +#include + +using namespace std; +class Solution { +public: + vector intersect(vector &nums1, vector &nums2) { + if (nums2.size() < nums1.size()) { + return intersect(nums2, nums1); + } + + unordered_map frequencies; + for (int num : nums1) { + ++frequencies[num]; + } + + vector result; + for (int num : nums2) { + if (--frequencies[num] >= 0) { + result.push_back(num); + } + } + return result; + } +}; diff --git a/C++/0409-LongestPalindrome/Solution.cpp b/C++/0409-LongestPalindrome/Solution.cpp new file mode 100644 index 0000000..2ae3599 --- /dev/null +++ b/C++/0409-LongestPalindrome/Solution.cpp @@ -0,0 +1,27 @@ +#include +#include + +using namespace std; +class Solution { +public: + int longestPalindrome(string s) { + unordered_map freq; + for (char c : s) { + if (freq.count(c) == 0) { + freq[c] = 0; + } + ++freq[c]; + } + bool hasOdd = false; + int result = 0; + for (auto const &[_, val] : freq) { + if (val % 2 == 1) { + hasOdd = true; + result += val - 1; + continue; + } + result += val; + } + return result + hasOdd; + } +}; diff --git a/C++/0424-LongestRepeatingCharacterReplacement/Solution.cpp b/C++/0424-LongestRepeatingCharacterReplacement/Solution.cpp new file mode 100644 index 0000000..4842fc5 --- /dev/null +++ b/C++/0424-LongestRepeatingCharacterReplacement/Solution.cpp @@ -0,0 +1,34 @@ +#include +#include + +using namespace std; +class Solution { +public: + int characterReplacement(string s, int k) { + if (s.length() < 2) { + return s.length(); + } + + int result = 0; + vector count = vector(26, 0); + int maxCount = 0; + + int l = 0; + for (int r = 0; r < s.length(); ++r) { + maxCount = max(maxCount, ++count[s[r] - 'A']); + + // If all k replacements have been used up, adjust the window + // `r - l + 1 - maxCount` indicates the number of replacements used + // where `r - l + 1` indicate the length of the window. + while (r - l + 1 - maxCount > k) { + --count[s[l++] - 'A']; // some prefix/postfix addition fun here + // Not necessary to find search for the maximum count. + // maxCount = *max_element(count.begin(), count.end()); + } + + result = max(result, r - l + 1); + } + + return result; + } +}; diff --git a/C++/0523-ContinuousSubarraySum/Solution.cpp b/C++/0523-ContinuousSubarraySum/Solution.cpp new file mode 100644 index 0000000..8a85952 --- /dev/null +++ b/C++/0523-ContinuousSubarraySum/Solution.cpp @@ -0,0 +1,47 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +class Solution { +public: + bool checkSubarraySum(vector &nums, int k) { + if (nums.size() < 2) { + return false; + } + + int n = nums.size(); + + // Modulo property: + // 1. (a + b) % k = ((a % k) + (b % k)) % k + // 2. if a = b % k, then (a + c) % k = (b + c) % k for any c. + // + // sum(nums[i:j]) = prefixSum(nums[:j]) - prefixSum(nums[:i])) + // goal: sum(nums[i:j]) % k == 0 + // + // (prefixSum(nums[:j]) % k - prefixSum(nums[:i]) % k) % k == 0 + // prefixSum(nums[:j]) % k == prefixSum(nums[:i]) % k + int prefixSum = 0; + unordered_map prefixMods = {{0, -1}}; + + for (int i = 0; i < n; ++i) { + prefixSum += nums[i]; + int prefixMod = prefixSum % k; + + if (prefixMods.count(prefixMod) && i - prefixMods[prefixMod] >= 2) { + return true; + } + + if (!prefixMods.count(prefixMod)) { + prefixMods[prefixMod] = i; + } + } + return false; + } +}; diff --git a/C++/0552-StudentAttendanceRecordTwo/Solution.cpp b/C++/0552-StudentAttendanceRecordTwo/Solution.cpp new file mode 100644 index 0000000..a8c0afe --- /dev/null +++ b/C++/0552-StudentAttendanceRecordTwo/Solution.cpp @@ -0,0 +1,61 @@ +#include +#include + +using namespace std; + +class Solution { +public: + int checkRecord(int n) { + const int MOD = 1000000007; + // Maintain number of Absents, and number of consecutive Lates as state. + // 0, or 1 Absent. 2 => no award. + // 3 consecutive lates => no award + // dp[numberOfDays][numberOfAbsents][numberOfConsecutiveLates] + vector>> dp = vector>>( + n + 1, vector>(2, vector(3, 0))); + + // Base case when days = 0 => number of possible ways = 1 + dp[0][0][0] = 1; + + // Number of absent days cannot decrement! + for (int day = 1; day < n + 1; ++day) { + for (int totalAbsents = 0; totalAbsents < 2; ++totalAbsents) { + for (int consecutiveLates = 0; consecutiveLates < 3; + ++consecutiveLates) { + // Choose "P". Reset consecutiveLates + dp[day][totalAbsents][0] += + dp[day - 1][totalAbsents][consecutiveLates]; + dp[day][totalAbsents][0] %= MOD; + + // Choose "A" if totalAbsents + 1 < 2. Reset consecutiveLates + if (totalAbsents + 1 < 2) { + dp[day][totalAbsents + 1][0] += + dp[day - 1][totalAbsents][consecutiveLates]; + dp[day][totalAbsents + 1][0] %= MOD; + } + + // Choose "L" if consecutiveLates + 1 < 3. + if (consecutiveLates + 1 < 3) { + dp[day][totalAbsents][consecutiveLates + 1] += + dp[day - 1][totalAbsents][consecutiveLates]; + dp[day][totalAbsents][consecutiveLates + 1] %= MOD; + } + } + } + } + + int result = 0; + for (int i = 0; i < 2; ++i) { + for (int j = 0; j < 3; ++j) { + result += dp[n][i][j]; + result %= MOD; + } + } + return result; + } +}; + +int main(int argc, char *arv[]) { + cout << Solution().checkRecord(2) << endl; + return 0; +} diff --git a/C++/0567-PermutationInString/Solution.cpp b/C++/0567-PermutationInString/Solution.cpp new file mode 100644 index 0000000..975d725 --- /dev/null +++ b/C++/0567-PermutationInString/Solution.cpp @@ -0,0 +1,34 @@ +#include +#include + +using namespace std; +class Solution { +public: + bool checkInclusion(string s1, string s2) { + if (s1.length() > s2.length()) { + return false; + } + + vector s1Frequency(26, 0); + vector s2Frequency(26, 0); + for (int i = 0; i < s1.length(); ++i) { + ++s1Frequency[s1[i] - 'a']; + ++s2Frequency[s2[i] - 'a']; + } + + if (s1Frequency == s2Frequency) { + return true; + } + + for (int i = s1.length(); i < s2.length(); ++i) { + --s2Frequency[s2[i - s1.length()] - 'a']; + ++s2Frequency[s2[i] - 'a']; + + if (s1Frequency == s2Frequency) { + return true; + } + } + + return false; + } +}; diff --git a/C++/0648-ReplaceWords/Solution.cpp b/C++/0648-ReplaceWords/Solution.cpp new file mode 100644 index 0000000..e74ec2c --- /dev/null +++ b/C++/0648-ReplaceWords/Solution.cpp @@ -0,0 +1,73 @@ +#include +#include +#include + +using namespace std; + +class TrieNode { +public: + bool isEnd; + vector children; + + TrieNode() : children(26, nullptr) { isEnd = false; } +}; + +class Trie { +private: + TrieNode *root; + +public: + Trie() { root = new TrieNode(); } + + void insert(string word) { + TrieNode *current = root; + for (char c : word) { + if (current->children[c - 'a'] == nullptr) { + current->children[c - 'a'] = new TrieNode(); + } + current = current->children[c - 'a']; + } + current->isEnd = true; + } + + string findShortestRoot(string word) { + stringstream shortestRoot; + TrieNode *current = root; + for (char c : word) { + if (current->children[c - 'a'] == nullptr) { + return word; + } + + current = current->children[c - 'a']; + shortestRoot << c; + + if (current->isEnd) { + return shortestRoot.str(); + } + } + return word; + } +}; + +class Solution { +public: + string replaceWords(vector &dictionary, string sentence) { + // add each root in the dictionary into the Trie + Trie dictTrie; + for (string word : dictionary) { + dictTrie.insert(word); + } + + stringstream resultStream; + istringstream sentenceStream(sentence); + + string currWord; + while (sentenceStream >> currWord) { + string root = dictTrie.findShortestRoot(currWord); + resultStream << root << " "; + } + string result = resultStream.str(); + result.pop_back(); // remove extra space + return result; + } +}; diff --git a/C++/0726-NumberOfAtoms/Solution.cpp b/C++/0726-NumberOfAtoms/Solution.cpp new file mode 100644 index 0000000..6b90456 --- /dev/null +++ b/C++/0726-NumberOfAtoms/Solution.cpp @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include + +using namespace std; +class Solution { +public: + string countOfAtoms(string formula) { + // A( BC (DE)2 FG)3 + int n = formula.size(); + + map frequencies; + stack multiplicity; + int multiplier = 1; + + string currentElement; + string currentCount; + int i = n - 1; + while (i >= 0) { + if (isdigit(formula[i])) { + currentCount = formula[i] + currentCount; + } else if (islower(formula[i])) { + currentElement = formula[i] + currentElement; + } else if (formula[i] == ')') { + int count = 1; + if (!currentCount.empty()) { + count = stoi(currentCount); + currentCount.clear(); + } + multiplier *= count; + multiplicity.push(count); + } else if (formula[i] == '(') { + multiplier /= multiplicity.top(); + multiplicity.pop(); + } else if (isupper(formula[i])) { + int count = 1; + if (!currentCount.empty()) { + count = stoi(currentCount); + currentCount.clear(); + } + + currentElement = formula[i] + currentElement; + frequencies[currentElement] += count * multiplier; + currentElement.clear(); + } + --i; + } + + string result; + for (auto &[elem, freq] : frequencies) { + if (freq < 2) { + result += elem; + continue; + } + result += elem + to_string(freq); + } + + return result; + } +}; + +int main(int argc, char *argv[]) { + cout << Solution().countOfAtoms("H2O") << endl; + cout << Solution().countOfAtoms("Mg(OH)2") << endl; + cout << Solution().countOfAtoms("K4(ON(SO3)2)2") << endl; + return 0; +} diff --git a/C++/0846-HandOfStraights/Solution.cpp b/C++/0846-HandOfStraights/Solution.cpp new file mode 100644 index 0000000..8e90ce3 --- /dev/null +++ b/C++/0846-HandOfStraights/Solution.cpp @@ -0,0 +1,53 @@ +#include +#include +#include + +using namespace std; +class Solution { +public: + bool isNStraightHand(vector &hand, int groupSize) { + if (hand.size() % groupSize) { + return false; + } + + unordered_map freq; + for (int num : hand) { + ++freq[num]; + } + + int i = 0; + while (i < hand.size()) { + int start = hand[i]; + + // Decrement start till the minimum number possible + while (freq[start - 1]) { + --start; + } + + for (int next = start; next < start + groupSize; ++next) { + if (!freq[next]) { + return false; + } + --freq[next]; + } + + while (i < hand.size() && !freq[hand[i]]) { + ++i; + } + } + + return true; + } +}; + +int main() { + // 1 : 1 + // 2 : 2 + // 3 : 2 + // 4 : 1 + // 6 : 1 + // 7 : 1 + // 8 : 1 + vector tc = {1, 2, 3, 6, 2, 3, 4, 7, 8}; + cout << boolalpha << Solution().isNStraightHand(tc, 3) << endl; +} diff --git a/C++/0912-SortAnArray/Solution.cpp b/C++/0912-SortAnArray/Solution.cpp new file mode 100644 index 0000000..5740ac5 --- /dev/null +++ b/C++/0912-SortAnArray/Solution.cpp @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +class Solution { +private: + random_device seed; + mt19937 gen{seed()}; // seed the generator + int randomInRange(int left, int right) { + uniform_int_distribution<> dist{left, right}; + return dist(gen); + } + + void quickSort(vector &nums, int left, int right) { + if (left >= right) { + return; // sub-array is trivially sorted + } + + int pivotIndex = randomInRange(left, right); + + // Bring the pivot to the front of the sub-array + swap(nums[left], nums[pivotIndex]); + + auto [lt, gt] = partition(nums, left, right); + + quickSort(nums, left, lt - 1); + quickSort(nums, gt + 1, right); + } + + // Three Way Partition. Returns the index of the pivot after swapping all + // nums[i] < pivot to the left of the pivot, all nums[i] > pivot to the right + pair partition(vector &nums, int left, int right) { + int pivot = nums[left]; + + // lt represents the index to place a lower element + int lt = left; + + // gt represents the index to place a higher element + int gt = right; + + int i = left + 1; // scanning ptr + while (i <= gt) { + if (nums[i] < pivot) { + swap(nums[i++], nums[lt++]); + } else if (nums[i] == pivot) { + ++i; + } else { + swap(nums[i], nums[gt--]); + } + } + return {lt, gt}; + } + +public: + vector sortArray(vector &nums) { + quickSort(nums, 0, nums.size() - 1); + return nums; + } +}; diff --git a/C++/0945-MinimumIncrementToMakeArrayUnique/Solution.cpp b/C++/0945-MinimumIncrementToMakeArrayUnique/Solution.cpp new file mode 100644 index 0000000..38d4008 --- /dev/null +++ b/C++/0945-MinimumIncrementToMakeArrayUnique/Solution.cpp @@ -0,0 +1,34 @@ +#include +#include + +using namespace std; +class UnionFind { +private: + unordered_map root; + +public: + UnionFind() : root() {} + + int find(int x) { + if (!root.count(x)) { + root[x] = x; + return x; + } + + int nextAvailable = find(root[x] + 1); + root[x] = nextAvailable; // Path compression + return nextAvailable; + } +}; + +class Solution { +public: + int minIncrementForUnique(vector &nums) { + UnionFind root; + int moves = 0; + for (int num : nums) { + moves += root.find(num) - num; + } + return moves; + } +}; diff --git a/C++/0979-DistributeCoinsInBinaryTree/Solution.cpp b/C++/0979-DistributeCoinsInBinaryTree/Solution.cpp new file mode 100644 index 0000000..7f8d003 --- /dev/null +++ b/C++/0979-DistributeCoinsInBinaryTree/Solution.cpp @@ -0,0 +1,23 @@ +#include "../commons/TreeNode.h" +#include + +class Solution { +public: + int postOrderTraversal(TreeNode *root, int *moves) { + if (root == nullptr) { + return 0; + } + int leftExtra = postOrderTraversal(root->left, moves); + int rightExtra = postOrderTraversal(root->right, moves); + + *moves += std::abs(leftExtra) + std::abs(rightExtra); + + return root->val + leftExtra + rightExtra - 1; + } + + int distributeCoins(TreeNode *root) { + int moves = 0; + postOrderTraversal(root, &moves); + return moves; + } +}; diff --git a/C++/1002-FindCommonCharacters/Solution.cpp b/C++/1002-FindCommonCharacters/Solution.cpp new file mode 100644 index 0000000..11f1373 --- /dev/null +++ b/C++/1002-FindCommonCharacters/Solution.cpp @@ -0,0 +1,31 @@ +#include +#include +#include + +using namespace std; +class Solution { +public: + vector commonChars(vector &words) { + vector> present = + vector>(26, vector(words.size(), 0)); + for (int i = 0; i < words.size(); ++i) { + for (char c : words[i]) { + ++present[c - 'a'][i]; + } + } + + vector result; + for (int i = 0; i < present.size(); ++i) { + int occurences = INT_MAX; + for (int freq : present[i]) { + occurences = min(occurences, freq); + } + // Not really convention to use _ + for (int _ = 0; _ < occurences; ++_) { + // C++ char to string conversion is more cumbersome than expected... + result.push_back(string(1, i + 'a')); + } + } + return result; + } +}; diff --git a/C++/1051-HeightChecker/Solution.cpp b/C++/1051-HeightChecker/Solution.cpp new file mode 100644 index 0000000..4bd4b03 --- /dev/null +++ b/C++/1051-HeightChecker/Solution.cpp @@ -0,0 +1,19 @@ +#include +#include + +using namespace std; +class Solution { +public: + int heightChecker(vector &heights) { + vector sorted = heights; // copy + sort(sorted.begin(), sorted.end()); + + int count = 0; + for (int i = 0; i < heights.size(); ++i) { + if (heights[i] != sorted[i]) { + ++count; + } + } + return count; + } +}; diff --git a/C++/1105-FillingBookcaseShelves/Solution.cpp b/C++/1105-FillingBookcaseShelves/Solution.cpp new file mode 100644 index 0000000..4926b95 --- /dev/null +++ b/C++/1105-FillingBookcaseShelves/Solution.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +class Solution { +public: + int minHeightShelves(vector> &books, int shelfWidth) { + int n = books.size(); + + // dp[i][j] represents the minimum height of the bookshelf after placing the + // first i books + vector dp(n + 1, INT_MAX); + dp[0] = 0; + + int currentHeight = 0; + for (int i = 1; i < n + 1; ++i) { + // there are two choices for each book: place on the current shelf if the + // width allows, or place on the next shelf + // if placing on the current shelf, find the maximum height of the + // sequence of books that fit on the shelf + int width = 0; + int height = 0; + + // for each book starting from the current, try to add to the next shelf + for (int j = i - 1; j >= 0; --j) { + width += books[j][0]; + if (width > shelfWidth) { + break; + } + + height = max(height, books[j][1]); + dp[i] = min(dp[i], dp[j] + height); + } + } + return dp[n]; + } +}; diff --git a/C++/1110-DeleteNodesAndReturnForest/Solution.cpp b/C++/1110-DeleteNodesAndReturnForest/Solution.cpp new file mode 100644 index 0000000..b496b39 --- /dev/null +++ b/C++/1110-DeleteNodesAndReturnForest/Solution.cpp @@ -0,0 +1,61 @@ +#include +#include + +using namespace std; +struct TreeNode { + int val; + TreeNode *left; + TreeNode *right; + + TreeNode() : val(0), left(nullptr), right(nullptr) {} + TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + TreeNode(int x, TreeNode *left, TreeNode *right) + : val(x), left(left), right(right) {} +}; + +class Solution { +private: + TreeNode *deleteNodes(TreeNode *current, unordered_set const &toDelete, + vector &forest) { + if (current == nullptr) { + return nullptr; + } + + current->left = deleteNodes(current->left, toDelete, forest); + current->right = deleteNodes(current->right, toDelete, forest); + + if (toDelete.count(current->val)) { + // If a parent node is deleted, its children become disjointed + if (current->left) { + forest.push_back(current->left); + } + + if (current->right) { + forest.push_back(current->right); + } + + delete current; + return nullptr; + } + + return current; + } + +public: + vector delNodes(TreeNode *root, vector &to_delete) { + vector forest; + forest.reserve(1000); + + unordered_set toDelete = + unordered_set(to_delete.begin(), to_delete.end()); + + root = deleteNodes(root, toDelete, forest); + + // handle the case where the root is not deleted + if (root) { + forest.push_back(root); + } + + return forest; + } +}; diff --git a/C++/1122-RelativeSortArray/Solution.cpp b/C++/1122-RelativeSortArray/Solution.cpp new file mode 100644 index 0000000..f2f3188 --- /dev/null +++ b/C++/1122-RelativeSortArray/Solution.cpp @@ -0,0 +1,28 @@ +#include +#include + +using namespace std; +class Solution { +public: + vector relativeSortArray(vector &arr1, vector &arr2) { + unordered_map freq; + for (int num : arr2) { + ++freq[num]; + } + + int i = 0; + int j = arr2.size(); + vector result = vector(arr1.size()); + for (int num : arr1) { + if (freq[num]) { + result[i] = num; + ++i; + --freq[num]; + } else { + result[j] = num; + ++j; + } + } + return result; + } +}; diff --git a/C++/1190-ReverseSubstringsBetweenEachPairOfParentheses/Solution.go b/C++/1190-ReverseSubstringsBetweenEachPairOfParentheses/Solution.go new file mode 100644 index 0000000..a8a6d11 --- /dev/null +++ b/C++/1190-ReverseSubstringsBetweenEachPairOfParentheses/Solution.go @@ -0,0 +1,99 @@ +#include +#include +#include +#include +#include + +using namespace std; +class Solution { +private: + void reverse(string &s) { + int n = s.size(); + for (int i = 0; i < n / 2; ++i) { + swap(s[i], s[n - i - 1]); + } + } + +public: + string reverseParentheses(string s) { + int n = s.length(); + + stack openParenthesesIndices; + vector pairIndices(n); + for (int i = 0; i < n; ++i) { + if (s[i] == '(') { + openParenthesesIndices.push(i); + } else if (s[i] == ')') { + int j = openParenthesesIndices.top(); + openParenthesesIndices.pop(); + + pairIndices[i] = j; + pairIndices[j] = i; + } else { + continue; + } + } + + string result; + int direction = 1; + for (int curr = 0; curr < n; curr += direction) { + if (s[curr] == '(' || s[curr] == ')') { + direction = -direction; // change direction + curr = pairIndices[curr]; + } else { + result += s[curr]; + } + } + return result; + } + + string reverseParenthesesWrong(string s) { + vector strs = vector(s.length()); + for (string &str : strs) { + str.reserve(s.length()); + } + + // Group substrings together + int i = 0; + for (char const &c : s) { + if (c == '(') { + ++i; + } else if (c == ')') { + --i; + } else { + strs[i] += c; + } + } + + // reverse the strings + i = 0; + for (string &str : strs) { + if (i++ & 1) { + reverse(str); + } + } + + i = 0; + string result; + result.reserve(s.length()); + for (char const &c : s) { + if (c == '(') { + ++i; + } else if (c == ')') { + --i; + } else { + char toAdd = strs[i][0]; + strs[i] = strs[i].substr(1, strs[i].length() - 1); + result += toAdd; + } + } + return result; + } +}; + +int main() { + cout << Solution().reverseParentheses("(abcd)") << endl; + cout << Solution().reverseParentheses("(u(love)i)") << endl; + cout << Solution().reverseParentheses("(ed(et(oc))el)") << endl; + cout << Solution().reverseParentheses("a(bcdefghijkl(mno)p)q") << endl; +} diff --git a/C++/1208-GetEqualSubstringsWithinBudget/Solution.cpp b/C++/1208-GetEqualSubstringsWithinBudget/Solution.cpp new file mode 100644 index 0000000..ac6e4b6 --- /dev/null +++ b/C++/1208-GetEqualSubstringsWithinBudget/Solution.cpp @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include + +using namespace std; +class Solution { +public: + int equalSubstring(string s, string t, int maxCost) { + int n = s.length(); + vector costs = vector(n); + for (int i = 0; i < n; ++i) { + costs[i] = abs(s[i] - t[i]); + } + + int left = 0; + int maxWindowSize = INT_MIN; + int budgetUsed = 0; + for (int right = 0; right < n; ++right) { + budgetUsed += costs[right]; + while (budgetUsed > maxCost) { + budgetUsed -= costs[left]; + ++left; + } + maxWindowSize = max(maxWindowSize, right - left + 1); + } + if (maxWindowSize == INT_MIN) { + return 0; + } + return maxWindowSize; + } +}; + +int main() { + cout << Solution().equalSubstring("abcd", "bcdf", 3) << endl; + cout << Solution().equalSubstring("abcd", "cdef", 3) << endl; + cout << Solution().equalSubstring("abcd", "acde", 0) << endl; +} diff --git a/C++/1255-MaximumScoreWordsFormedByLetters/Solution.cpp b/C++/1255-MaximumScoreWordsFormedByLetters/Solution.cpp index e078360..5683d73 100644 --- a/C++/1255-MaximumScoreWordsFormedByLetters/Solution.cpp +++ b/C++/1255-MaximumScoreWordsFormedByLetters/Solution.cpp @@ -1,36 +1,47 @@ -#include #include using namespace std; + class Solution { public: int maxScoreWords(vector &words, vector &letters, vector &score) { - vector lettersCount = vector(score.size(), 0); + // pre-compute scores and character count + vector wordScore = vector(words.size(), 0); + for (int i = 0; i < words.size(); ++i) { + for (char c : words[i]) { + wordScore[i] += score[c - 'a']; + } + } + vector count = vector(26, 0); for (char c : letters) { - lettersCount[c - 'a']++; + ++count[c - 'a']; } + return knapsack(words, wordScore, count, 0); + } - vector wordScores = vector(words.size(), 0); - vector> wordLetterFreq = vector>(words.size()); - for (const string &word : words) { - int wordScore = 0; - vector lettersFreq = vector(score.size(), 0); - for (const char &c : word) { - wordScore += score[c - 'a']; - lettersFreq[c - 'a']++; - } - wordScores.push_back(wordScore); - wordLetterFreq.push_back(lettersFreq); +private: + int knapsack(vector const &words, vector const &scores, + vector &count, int idx) { + if (idx >= words.size()) { + return 0; } + int skip = knapsack(words, scores, count, idx + 1); - int maxScore = - getMaxScore(words, lettersCount, wordScores, wordLetterFreq, 0); - return maxScore; - } + int pickScore = scores[idx]; -private: - int getMaxScore(vector const &words, vector &count, - vector const &wordScores, - vector> const &wordLetterCount, int idx) {} + // update count + for (char c : words[idx]) { + --count[c - 'a']; + if (count[c - 'a'] < 0) { + pickScore = 0; // cannot take. + } + } + pickScore += knapsack(words, scores, count, idx + 1); + // backtrack count + for (char c : words[idx]) { + ++count[c - 'a']; + } + return max(skip, pickScore); + }; }; diff --git a/C++/1325-DeleteLeavesWithAGivenValue/Solution.cpp b/C++/1325-DeleteLeavesWithAGivenValue/Solution.cpp index 120fc3c..a147ce3 100644 --- a/C++/1325-DeleteLeavesWithAGivenValue/Solution.cpp +++ b/C++/1325-DeleteLeavesWithAGivenValue/Solution.cpp @@ -1,4 +1,13 @@ -#include "../commons/TreeNode.h" +struct TreeNode { + int val; + TreeNode *left; + TreeNode *right; + + TreeNode() : val(0), left(nullptr), right(nullptr) {} + TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + TreeNode(int x, TreeNode *left, TreeNode *right) + : val(x), left(left), right(right) {} +}; class Solution { public: diff --git a/C++/1334-FindTheCityWithTheSmallestNumberOfNeighboursAtAThresholdDistance/Solution.cpp b/C++/1334-FindTheCityWithTheSmallestNumberOfNeighboursAtAThresholdDistance/Solution.cpp new file mode 100644 index 0000000..01ce6cd --- /dev/null +++ b/C++/1334-FindTheCityWithTheSmallestNumberOfNeighboursAtAThresholdDistance/Solution.cpp @@ -0,0 +1,72 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +class Solution { +public: + // Floyd Warshall's All Pairs Shortest Paths + // Optimal Substructure: If a shortest path from i to j goes through an + // intermediate vertex k, then the path i to k and k to j must also be + // shortest paths. + // + // dist(i, j, k) represents the shortest path from vertex i to j using + // vertices {0, 1, ..., k} as intermediate vertices. + // + // Recursive Relation: + // dist(i, j, k) = min(dist(i, j, k-1), dist(i, k, k-1) + dist(k, j, k-1)) + // + // This means that the shortest path from i to j using vertices up to k is the + // minimum between the path that does not goes through k, dist(i, j, k - 1) + // and the path that goes through k, dist(i, k, k - 1) + dist(k, j, k - 1). + int findTheCity(int n, vector> &edges, int distanceThreshold) { + // dp[i][j] represents the shortest distance from i to j + vector> dp(n, vector(n, INT_MAX)); + + for (int i = 0; i < n; ++i) { + dp[i][i] = 0; // distance to self is zero + } + + for (const auto &edge : edges) { + int from = edge[0]; + int to = edge[1]; + int dist = edge[2]; + dp[from][to] = dist; + dp[to][from] = dist; + } + + for (int k = 0; k < n; ++k) { + for (int i = 0; i < n; ++i) { + for (int j = 0; j < n; ++j) { + if (dp[i][k] == INT_MAX || dp[k][j] == INT_MAX) { + continue; + } + dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]); + } + } + } + + int city = 0; + int minReachable = INT_MAX; + for (int i = 0; i < n; ++i) { + int currentCount = 0; + for (int j = 0; j < n; ++j) { + if (dp[i][j] <= distanceThreshold) { + ++currentCount; + } + } + if (currentCount <= minReachable) { + city = i; + minReachable = currentCount; + } + } + + return city; + } +}; diff --git a/C++/1380-LuckyNumbersInAMatrix/Solution.cpp b/C++/1380-LuckyNumbersInAMatrix/Solution.cpp new file mode 100644 index 0000000..927ae07 --- /dev/null +++ b/C++/1380-LuckyNumbersInAMatrix/Solution.cpp @@ -0,0 +1,36 @@ +#include +#include + +using namespace std; +class Solution { +public: + vector luckyNumbers(vector> const &matrix) { + int n = matrix.size(); + int m = matrix[0].size(); + + // the i-th index contains the column index of the min element in the i-th + // row + vector minOfRow(n, 0); + + // the i-th index contains the maximum element of the i-th column + vector maxOfCol(m, INT_MIN); + for (int row = 0; row < n; ++row) { + for (int col = 0; col < m; ++col) { + if (matrix[row][col] < matrix[row][minOfRow[row]]) { + minOfRow[row] = col; + } + + maxOfCol[col] = max(maxOfCol[col], matrix[row][col]); + } + } + + vector result; + result.reserve(n); + for (int i = 0; i < n; ++i) { + if (matrix[i][minOfRow[i]] == maxOfCol[minOfRow[i]]) { + result.push_back(matrix[i][minOfRow[i]]); + } + } + return result; + } +}; diff --git a/C++/1395-CountNumberOfTeams/Solution.cpp b/C++/1395-CountNumberOfTeams/Solution.cpp new file mode 100644 index 0000000..39abd63 --- /dev/null +++ b/C++/1395-CountNumberOfTeams/Solution.cpp @@ -0,0 +1,122 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +class Solution { +private: + void naive(const vector &rating, int &result, int i, int count, int prev, + bool isIncreasing) { + if (count == 3) { + ++result; + return; + } + + if (i >= rating.size()) { + return; + } + + // Skip this element + naive(rating, result, i + 1, count, prev, isIncreasing); + + // Use this element if it maintains the required order + if (count == 0 || (isIncreasing && rating[i] > prev) || + (!isIncreasing && rating[i] < prev)) { + naive(rating, result, i + 1, count + 1, rating[i], isIncreasing); + } + } + + int numTeamsNaive(vector &rating) { + int result = 0; + + // Count increasing sequences + naive(rating, result, 0, 0, INT_MAX, true); + + // Count decreasing sequences + naive(rating, result, 0, 0, INT_MIN, false); + + return result; + } + + int numTeamsNaiveDP(vector const &rating) { + int n = rating.size(); + + // dp[i][j] stores the number of increasing/decreasing sequences of length + // j + 1, ending at index i + vector> increasing(n, vector(3)); + vector> decreasing(n, vector(3)); + + // Initialize base case + for (int i = 0; i < n; ++i) { + increasing[i][0] = 1; + decreasing[i][0] = 1; + } + + // For each sequence length in {2, 3} + for (int len = 1; len < 3; ++len) { + // For each sequence ending with rating[i] + for (int i = 0; i < n; ++i) { + // For every subsequent soldier + for (int j = i + 1; j < n; ++j) { + // If monotonicity is maintained + if (rating[j] > rating[i]) { + increasing[j][len] += increasing[i][len - 1]; + } + if (rating[j] < rating[i]) { + decreasing[j][len] += decreasing[i][len - 1]; + } + } + } + } + + int result = 0; + for (int i = 0; i < n; ++i) { + result += increasing[i][2] + decreasing[i][2]; + } + return result; + } + +public: + // Consider the prefix and postfix elements that are larger/smaller than + // rating[i]. Multiply to get the result + int numTeams(vector const &rating) { + int n = rating.size(); + int result = 0; + + for (int mid = 0; mid < n; ++mid) { + // Consider increasing sequences + int leftSmaller = 0; + int rightLarger = 0; + + for (int left = mid - 1; left >= 0; --left) { + if (rating[left] < rating[mid]) { + ++leftSmaller; + } + } + + for (int right = mid + 1; right < n; ++right) { + if (rating[right] > rating[mid]) { + ++rightLarger; + } + } + + // Add the number of combinations of increasing sequences + result += leftSmaller * rightLarger; + + // Consider decreasing sequences + // since rating is unique, simply take the inverse of each side + int leftLarger = mid - leftSmaller; + int rightSmaller = (n - mid - 1) - rightLarger; + + // Add the number of combinations of increasing sequences + result += leftLarger * rightSmaller; + } + return result; + } +}; diff --git a/C++/1404-NumberOfStepsToReduceANumberInBinaryRepresentationToOne/Solution.cpp b/C++/1404-NumberOfStepsToReduceANumberInBinaryRepresentationToOne/Solution.cpp new file mode 100644 index 0000000..3392f2f --- /dev/null +++ b/C++/1404-NumberOfStepsToReduceANumberInBinaryRepresentationToOne/Solution.cpp @@ -0,0 +1,35 @@ +#include +#include + +using namespace std; +class Solution { +public: + // even => divide by 2 / right shift + // odd => add 1, next step will right shift + // 110, 11, 100, 10, 1 + // 1001, 1010, 101, 110, 11, 100, 10, 1 + // the goal is to have all 1s remaining in the string => every operation will + // be a right shift until the leading 1 is reached. + // The key insight is that the 'carry' will always be present after a '1' is + // encountered. + int numSteps(string s) { + int count = 0; + int carry = 0; + for (int i = s.length() - 1; i > 0; --i) { + int current = s[i] - '0'; + if ((current + carry) % 2 == 0) { + ++count; + } else { + count += 2; + carry = 1; + } + } + return count + carry; + } +}; + +int main() { + cout << Solution().numSteps("1101") << endl; + cout << Solution().numSteps("10") << endl; + cout << Solution().numSteps("1") << endl; +} diff --git a/C++/1442-CountTripletsThatCanFormTwoArraysOfEqualXOR/Solution.cpp b/C++/1442-CountTripletsThatCanFormTwoArraysOfEqualXOR/Solution.cpp new file mode 100644 index 0000000..fe88655 --- /dev/null +++ b/C++/1442-CountTripletsThatCanFormTwoArraysOfEqualXOR/Solution.cpp @@ -0,0 +1,21 @@ +#include + +using namespace std; +class Solution { +public: + int countTriplets(vector &arr) { + // xor(arr[i..k]) == 0 + int count = 0; + for (int i = 0; i < arr.size(); ++i) { + int prefixXOR = 0; + for (int k = i; k < arr.size(); ++k) { + prefixXOR ^= arr[k]; + if (prefixXOR == 0) { + // Problem constraint allows j to be same as k + count += k - i; // k-i possible combinations of j + } + } + } + return count; + } +}; diff --git a/C++/1509-MinimumDifferenceBetweenLargestAndSmallestValueInThreeMoves/Solution.cpp b/C++/1509-MinimumDifferenceBetweenLargestAndSmallestValueInThreeMoves/Solution.cpp new file mode 100644 index 0000000..26fe1ee --- /dev/null +++ b/C++/1509-MinimumDifferenceBetweenLargestAndSmallestValueInThreeMoves/Solution.cpp @@ -0,0 +1,39 @@ +#include +#include +#include +#include + +using namespace std; +class Solution { +public: + int minDifference(vector &nums) { + if (nums.size() <= 4) { + return 0; + } + sort(nums.begin(), nums.end()); + + // Three moves => 4 combinations + // Increase first three numbers + // Increase first two, decrease last + // Increase first, decrease last 2 + // Decrease last three numbers + int left = 0; + int right = nums.size() - 4; + + int minDiff = INT_MAX; + // four scenarios, i.e. left moves up to 4th element + while (left < 4) { + minDiff = min(minDiff, nums[right++] - nums[left++]); + } + return minDiff; + } +}; + +int main() { + + // 9, 31, 48, 48, 81, 92 + // 61, 72 + // 44, + vector nums = {9, 48, 92, 48, 81, 31}; + cout << Solution().minDifference(nums); +} diff --git a/C++/1530-NumberOfGoodLeafNodesPairs/Solution.cpp b/C++/1530-NumberOfGoodLeafNodesPairs/Solution.cpp new file mode 100644 index 0000000..cada9ef --- /dev/null +++ b/C++/1530-NumberOfGoodLeafNodesPairs/Solution.cpp @@ -0,0 +1,65 @@ +#include + +using namespace std; +struct TreeNode { + int val; + TreeNode *left; + TreeNode *right; + + TreeNode() : val(0), left(nullptr), right(nullptr) {} + TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + TreeNode(int x, TreeNode *left, TreeNode *right) + : val(x), left(left), right(right) {} +}; + +class Solution { +private: + const int MAX_DISTANCE = 10; + +public: + // Returns the number of leaf nodes d distance away + // 1 <= distance <= 10 + vector dfs(TreeNode *node, int distance, int &result) { + if (node == nullptr) { + return vector(MAX_DISTANCE + 1, 0); + } + + if (node->left == nullptr && node->right == nullptr) { + vector nodesAtDistance(MAX_DISTANCE + 1, 0); + nodesAtDistance[0] = 1; + return nodesAtDistance; + } + + vector leftNodesAtDistance = dfs(node->left, distance, result); + vector rightNodesAtDistance = dfs(node->right, distance, result); + + // Iterate over all pairs of distances to find good pairs + for (int i = 0; i <= distance; ++i) { + for (int j = 0; j <= distance; ++j) { + if ((i + 1) + (j + 1) <= distance) { + result += leftNodesAtDistance[i] * rightNodesAtDistance[j]; + } + } + } + + // Update nodesAtDistance to be returned up the call stack + vector nodesAtDistance(MAX_DISTANCE + 1, 0); + for (int i = 0; i < MAX_DISTANCE; ++i) { + // Increment the distance of each node in the left and right subtree, + // combine them into the current nodesAtDistance vector. + // Similar to "right-shifting" every element + nodesAtDistance[i + 1] = leftNodesAtDistance[i] + rightNodesAtDistance[i]; + } + + return nodesAtDistance; // intermediate nodes return 0 + } + + int countPairs(TreeNode *root, int distance) { + // The shortest path between two different nodes in a Tree is simply + // the shortest path from their Lowest Common Ancestor + // DFS to any node in the tree will traverse the shortest path + int result = 0; + dfs(root, distance, result); + return result; + } +}; diff --git a/C++/1550-ThreeConsecutiveOdds/Solution.cpp b/C++/1550-ThreeConsecutiveOdds/Solution.cpp new file mode 100644 index 0000000..1627c99 --- /dev/null +++ b/C++/1550-ThreeConsecutiveOdds/Solution.cpp @@ -0,0 +1,19 @@ +#include + +using namespace std; +class Solution { +public: + bool threeConsecutiveOdds(vector &arr) { + int consecutiveOdds = 0; + for (int num : arr) { + if (num & 1) { + if (++consecutiveOdds >= 3) { + return true; + } + } else { + consecutiveOdds = 0; + } + } + return false; + } +}; diff --git a/C++/1598-CrawlerLogFolder/Solution.cpp b/C++/1598-CrawlerLogFolder/Solution.cpp new file mode 100644 index 0000000..0b8c1c0 --- /dev/null +++ b/C++/1598-CrawlerLogFolder/Solution.cpp @@ -0,0 +1,22 @@ +#include + +using namespace std; +class Solution { +public: + int minOperations(vector &logs) { + int operations = 0; + for (string log : logs) { + if (log.substr(0, 3) == "../") { + operations = max(0, operations - 1); + continue; + } + if (log.substr(0, 2) == "./") { + continue; + } + + operations += 1; + } + + return operations; + } +}; diff --git a/C++/1605-FindValidMatrixGivenRowAndColumnSums/Solution.cpp b/C++/1605-FindValidMatrixGivenRowAndColumnSums/Solution.cpp new file mode 100644 index 0000000..6b3909e --- /dev/null +++ b/C++/1605-FindValidMatrixGivenRowAndColumnSums/Solution.cpp @@ -0,0 +1,26 @@ +#include + +using namespace std; +class Solution { +public: + vector> restoreMatrix(vector &rowSum, vector &colSum) { + int n = rowSum.size(); + int m = colSum.size(); + + vector> matrix(n, vector(m, 0)); + for (int i = 0; i < n; ++i) { + for (int j = 0; j < m; ++j) { + // Greedily take the most + int current = min(rowSum[i], colSum[j]); + + // update the remaining sum + rowSum[i] -= current; + colSum[j] -= current; + + matrix[i][j] = current; + } + } + + return matrix; + } +}; diff --git a/C++/1608-SpecialArrayWithXElementsGreaterThanOrEqualX/Solution.cpp b/C++/1608-SpecialArrayWithXElementsGreaterThanOrEqualX/Solution.cpp new file mode 100644 index 0000000..ba03c90 --- /dev/null +++ b/C++/1608-SpecialArrayWithXElementsGreaterThanOrEqualX/Solution.cpp @@ -0,0 +1,69 @@ +#include +#include + +using namespace std; +class Solution { +public: + // nums is special iff there exists a number x such that there are exactly x + // numbers in nums that are GTE to x. If nums is special, the value for x is + // unique. x does not have to be an element in nums. + int specialArray(vector &nums) { + // binary search? x is upper bounded by nums.length + // 1 <= nums.length <= 100, 0 <= nums[i] <= 1000 + // monotonic property here is the number of elements GTE to y decreases as y + // increases. + vector freq = vector(1001, 0); + for (int i = 0; i < nums.size(); ++i) { + ++freq[nums[i]]; + } + + int left = 0; + int right = 101; + while (left < right) { + int x = left + (right - left) / 2; + cout << x << "\n"; + + // find the number of elements GTE to x + int count = 0; + for (int i = x; i < freq.size(); ++i) { + count += freq[i]; + } + + if (count == x) { + return x; + } + + // update bounds + if (count < x) { + right = x; + } else { + left = x + 1; + } + } + return -1; + } +}; + +int main(int argc, char *argv[]) { + vector nums = {3, 5}; + cout << Solution().specialArray(nums) << endl; // expect 2 + + nums = {0, 0}; + cout << Solution().specialArray(nums) << endl; // expect -1 + + nums = {0, 4, 3, 0, 4}; + cout << Solution().specialArray(nums) << endl; // expect 3 + + nums = {3, 6, 7, 7, 0}; + cout << Solution().specialArray(nums) << endl; + + nums = {100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, + 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, + 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, + 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, + 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, + 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, + 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, + 100, 100, 100, 100, 100, 100, 100, 100, 100}; + cout << Solution().specialArray(nums) << endl; // expect 100 +} diff --git a/C++/1636-SortArrayByIncreasingFrequency/Solution.cpp b/C++/1636-SortArrayByIncreasingFrequency/Solution.cpp new file mode 100644 index 0000000..1024759 --- /dev/null +++ b/C++/1636-SortArrayByIncreasingFrequency/Solution.cpp @@ -0,0 +1,27 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +class Solution { +public: + vector frequencySort(vector &nums) { + vector frequencies(201, 0); + for (int num : nums) { + ++frequencies[num + 100]; + } + sort(nums.rbegin(), nums.rend()); + + stable_sort(nums.begin(), nums.end(), [&frequencies](int num1, int num2) { + return frequencies[num1 + 100] < frequencies[num2 + 100]; + }); + + return nums; + } +}; diff --git a/C++/1653-MinimumDeletionsToMakeStringsBalanced/Solution.cpp b/C++/1653-MinimumDeletionsToMakeStringsBalanced/Solution.cpp new file mode 100644 index 0000000..96020c7 --- /dev/null +++ b/C++/1653-MinimumDeletionsToMakeStringsBalanced/Solution.cpp @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +class Solution { +public: + int minimumDeletions(string s) { + int n = s.length(); + + // dp[i] represents the number of deletions required to balance + // the substring s[0:i + 1] + vector dp(n + 1, 0); + dp[0] = 0; + + // At each element, there are 2 choices, without loss of generality: + // keep 'a' => that means to delete 'b', since there are more 'a's. + // delete 'a' => keep 'b', since there are more 'b's + int bees = 0; + for (int i = 1; i < n + 1; ++i) { + if (s[i - 1] == 'b') { + dp[i] = dp[i - 1]; + ++bees; + continue; + } + + dp[i] = min(dp[i - 1] + 1, bees); + } + return dp[n]; + } +}; diff --git a/C++/1701-AverageWaitingTime/Solution.cpp b/C++/1701-AverageWaitingTime/Solution.cpp new file mode 100644 index 0000000..4c3912a --- /dev/null +++ b/C++/1701-AverageWaitingTime/Solution.cpp @@ -0,0 +1,24 @@ +#include + +using namespace std; +class Solution { +public: + double averageWaitingTime(vector> &customers) { + if (customers.size() < 1) { + return 0; + } + double totalWaitingTime = 0; + + // indicates the time when the customer's order is finished + int nextIdleTime = 0; + + for (int i = 0; i < customers.size(); ++i) { + int arrival = customers[i][0]; + int waitingTime = customers[i][1]; + nextIdleTime = max(arrival, nextIdleTime) + waitingTime; + + totalWaitingTime += nextIdleTime - arrival; + } + return totalWaitingTime / customers.size(); + } +}; diff --git a/C++/1717-MaximumScoreFromRemovingSubstrings/Solution.cpp b/C++/1717-MaximumScoreFromRemovingSubstrings/Solution.cpp new file mode 100644 index 0000000..28584e8 --- /dev/null +++ b/C++/1717-MaximumScoreFromRemovingSubstrings/Solution.cpp @@ -0,0 +1,54 @@ +#include + +using namespace std; +class Solution { +public: + int maximumGain(string s, int x, int y) { + char currentTarget = 'b'; + char topTarget = 'a'; + int points = x; + + // Look for "ba" first, since y is higher + if (y > x) { + currentTarget = 'a'; + topTarget = 'b'; + points = y; + } + + int result = 0; + string remaining; + for (char c : s) { + if (c == currentTarget && !remaining.empty() && + remaining.back() == topTarget) { + remaining.pop_back(); + result += points; + continue; + } + remaining += c; + } + + s = remaining; + remaining.clear(); + + char temp = currentTarget; + currentTarget = topTarget; + topTarget = temp; + if (points == x) { + points = y; + } else { + points = x; + } + + for (char c : s) { + if (c == currentTarget && !remaining.empty() && + remaining.back() == topTarget) { + remaining.pop_back(); + result += points; + continue; + } + remaining += c; + } + + return result; + } +}; diff --git a/C++/1823-FindTheWinnerOfTheCircularGame/Solution.cpp b/C++/1823-FindTheWinnerOfTheCircularGame/Solution.cpp new file mode 100644 index 0000000..781864a --- /dev/null +++ b/C++/1823-FindTheWinnerOfTheCircularGame/Solution.cpp @@ -0,0 +1,13 @@ +class Solution { +private: +private: + int solve(int n, int k) { + if (n == 1) { + return 0; + } + return (solve(n - 1, k) + k) % n; + } + +public: + int findTheWinner(int n, int k) { return solve(n, k) + 1; } +}; diff --git a/C++/2037-MinimumNumberOfMovesToSeatEveryone/Solution.cpp b/C++/2037-MinimumNumberOfMovesToSeatEveryone/Solution.cpp new file mode 100644 index 0000000..b8b761c --- /dev/null +++ b/C++/2037-MinimumNumberOfMovesToSeatEveryone/Solution.cpp @@ -0,0 +1,16 @@ +#include +#include + +using namespace std; +class Solution { +public: + int minMovesToSeat(vector &seats, vector &students) { + sort(seats.begin(), seats.end()); + sort(students.begin(), students.end()); + int moves = 0; + for (int i = 0; i < seats.size(); ++i) { + moves += abs(seats[i] - students[i]); + } + return moves; + } +}; diff --git a/C++/2045-SecondMinimumTimeToReachDestination/Solution.cpp b/C++/2045-SecondMinimumTimeToReachDestination/Solution.cpp new file mode 100644 index 0000000..37b74dd --- /dev/null +++ b/C++/2045-SecondMinimumTimeToReachDestination/Solution.cpp @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +class Solution { +public: + // BFS for minimum path, remove all edges in the minimum path, then + // BFS again (until the new value is strictly larger) + int secondMinimum(int n, vector> &edges, int time, int change) { + vector> adj(n); + for (auto const &edge : edges) { + int from = edge[0]; + int to = edge[1]; + + // convert to 0-index + adj[from - 1].push_back(to - 1); + adj[to - 1].push_back(from - 1); // bidirectional + } + + // Store shortest distance/time to the node + vector dist1(n, INT_MAX); + + // Store second-shortest distance/ time to the node + vector dist2(n, INT_MAX); + + // {node, numberOfTimesSeen} + queue> frontier; + frontier.push({0, 1}); + dist1[0] = 0; + while (!frontier.empty()) { + auto [node, freq] = frontier.front(); + frontier.pop(); + + // If the node is seen for the first time, then the + // timeTaken is the shortest time to the node. + int timeTaken = freq == 1 ? dist1[node] : dist2[node]; + + // traffic signal is red at every odd number of `change` minutes + // 2i * change + change + if ((timeTaken / change) & 1) { + // If red, then wait till the next time where it is green + // can be difficult to understand, but its calculating the + // next interval/time when the signal is green. + timeTaken = ((timeTaken / change) + 1) * change; + } + // Add the time taken to traverse the edge + timeTaken += time; + + for (int const &neighbour : adj[node]) { + if (dist1[neighbour] == INT_MAX) { + // First time seeing node, update shortest time + dist1[neighbour] = timeTaken; + frontier.push({neighbour, 1}); + continue; + } + + // check that the current path is not traversed before, by comparing + // dist1[neighbour] and timeTaken. + if (dist2[neighbour] == INT_MAX && dist1[neighbour] != timeTaken) { + // Check for early return + if (neighbour + 1 == n) { + return timeTaken; + } + + dist2[neighbour] = timeTaken; + frontier.push({neighbour, 2}); + continue; + } + // effectively ignores nodes that have been seen twice + } + } + + return 0; // Only one node. 0 time taken + } +}; diff --git a/C++/2058-FindTheMinimumAndMaximumNumberOfNodesBetweenCriticalPoints/Solution.cpp b/C++/2058-FindTheMinimumAndMaximumNumberOfNodesBetweenCriticalPoints/Solution.cpp new file mode 100644 index 0000000..99a72b2 --- /dev/null +++ b/C++/2058-FindTheMinimumAndMaximumNumberOfNodesBetweenCriticalPoints/Solution.cpp @@ -0,0 +1,59 @@ +#include +#include + +using namespace std; + +struct ListNode { + int val; + ListNode *next; + + ListNode() : val(0), next(nullptr) {} + ListNode(int x) : val(x), next(nullptr) {} + ListNode(int x, ListNode *next) : val(x), next(next) {} +}; + +class Solution { +public: + vector nodesBetweenCriticalPoints(ListNode *head) { + // minDistance, maxDistance + // minDistance will be the minimum of the distances between adjacent + // critical points + // maxDistance will be the distance between the left-most and the right-most + // critical points + vector result = {INT_MAX, -1}; + + int firstCriticalIndex = -1; + int prevCriticalIndex = -1; + + ListNode *prev = head; + ListNode *iter = head->next; + int i = 1; + while (iter != nullptr && iter->next != nullptr) { + // Check if its a critical node + if (prev->val < iter->val && iter->val > iter->next->val || + prev->val > iter->val && iter->val < iter->next->val) { + // check if there was a first/prevCriticalIndex + if (firstCriticalIndex == -1) { + firstCriticalIndex = i; + } + + // if there was a prevCriticalNode, then check minDistance + if (prevCriticalIndex != -1) { + result[0] = min(result[0], i - prevCriticalIndex); + } + prevCriticalIndex = i; + } + + ++i; + prev = iter; + iter = iter->next; + } + + if (result[0] == INT_MAX) { + return {-1, -1}; + } + + result[1] = prevCriticalIndex - firstCriticalIndex; + return result; + } +}; diff --git a/C++/2096-StepByStepDirectionsFromABinaryTreeNodeToAnother/Solution.cpp b/C++/2096-StepByStepDirectionsFromABinaryTreeNodeToAnother/Solution.cpp new file mode 100644 index 0000000..67fbc0c --- /dev/null +++ b/C++/2096-StepByStepDirectionsFromABinaryTreeNodeToAnother/Solution.cpp @@ -0,0 +1,65 @@ +#include + +using namespace std; +struct TreeNode { + int val; + TreeNode *left; + TreeNode *right; + + TreeNode() : val(0), left(nullptr), right(nullptr) {} + TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + TreeNode(int x, TreeNode *left, TreeNode *right) + : val(x), left(left), right(right) {} +}; + +class Solution { +private: + bool dfs(TreeNode *current, int target, string &directions) { + if (current == nullptr) { + return false; + } + if (current->val == target) { + return true; + } + + directions += "L"; + bool isFound = dfs(current->left, target, directions); + if (isFound) { + return true; + } + directions.pop_back(); + + directions += "R"; + isFound = dfs(current->right, target, directions); + if (isFound) { + return true; + } + directions.pop_back(); + return false; + } + +public: + // From their Lowest Common Ancestor: + // shortest path from start to dest = shortest path to start + shortest path + // to dest. + // + // Not a Binary Search Tree => DFS/BFS to find both. + string getDirections(TreeNode *root, int startValue, int destValue) { + string directionToStart; + dfs(root, startValue, directionToStart); + + string directionToDest; + dfs(root, destValue, directionToDest); + + // Find LowestCommonAncestor by matching prefix. + int lca = 0; + while (directionToStart[lca] == directionToDest[lca]) { + ++lca; + } + + string directions = + string(directionToStart.length() - lca, 'U') + + directionToDest.substr(lca, directionToDest.length() - lca); + return directions; + } +}; diff --git a/C++/2181-MergeNodesInBetweenZeros/Solution.cpp b/C++/2181-MergeNodesInBetweenZeros/Solution.cpp new file mode 100644 index 0000000..7b357c0 --- /dev/null +++ b/C++/2181-MergeNodesInBetweenZeros/Solution.cpp @@ -0,0 +1,31 @@ +struct ListNode { + int val; + ListNode *next; + ListNode() : val(0), next(nullptr) {} + ListNode(int x) : val(x), next(nullptr) {} + ListNode(int x, ListNode *next) : val(x), next(next) {} +}; + +class Solution { +public: + ListNode *mergeSequence(ListNode *accumulator, ListNode *current) { + if (current == nullptr || current->next == nullptr) { + return nullptr; + } + if (current->val == 0) { + return current; + } + accumulator->val += current->val; + return mergeSequence(accumulator, current->next); + } + + ListNode *mergeNodes(ListNode *head) { + // The beginning and the end are guaranteed to have Node->val = 0 + ListNode *iter = head; + while (iter != nullptr) { + iter->next = mergeSequence(iter, iter->next); + iter = iter->next; + } + return head; + } +}; diff --git a/C++/2191-SortTheJumbledNumbers/Solution.cpp b/C++/2191-SortTheJumbledNumbers/Solution.cpp new file mode 100644 index 0000000..89473fe --- /dev/null +++ b/C++/2191-SortTheJumbledNumbers/Solution.cpp @@ -0,0 +1,40 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +class Solution { +public: + vector sortJumbled(vector &mapping, vector &nums) { + unordered_map mappedNumbers; + for (int i = 0; i < nums.size(); ++i) { + int mapped = 0; + int exp = 0; + + int num = nums[i]; + if (num == 0) { + // Handle edge case + mappedNumbers[0] = mapping[0]; + continue; + } + while (num > 0) { + int digit = num % 10; + mapped += mapping[digit] * pow(10, exp++); + + num /= 10; + } + mappedNumbers[nums[i]] = mapped; + } + + stable_sort(nums.begin(), nums.end(), [&mappedNumbers](int num1, int num2) { + return mappedNumbers[num1] < mappedNumbers[num2]; + }); + return nums; + } +}; diff --git a/C++/2196-CreateBinaryTreeFromDescriptions/Solution.cpp b/C++/2196-CreateBinaryTreeFromDescriptions/Solution.cpp new file mode 100644 index 0000000..27da98a --- /dev/null +++ b/C++/2196-CreateBinaryTreeFromDescriptions/Solution.cpp @@ -0,0 +1,56 @@ +#include +#include +#include + +using namespace std; + +struct TreeNode { + int val; + TreeNode *left; + TreeNode *right; + + TreeNode() : val(0), left(nullptr), right(nullptr) {} + TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + TreeNode(int x, TreeNode *left, TreeNode *right) + : val(x), left(left), right(right) {} +}; + +class Solution { +public: + // Creating the Binary Tree is simple. Finding the root is not as simple. + // A root will have no parent + TreeNode *createBinaryTree(vector> &descriptions) { + unordered_map nodes; + unordered_set childNodes; + for (auto description : descriptions) { + int parent = description[0]; + int child = description[1]; + bool isLeft = description[2]; + + if (!nodes.count(parent)) { + nodes[parent] = new TreeNode(parent); + } + if (!nodes.count(child)) { + nodes[child] = new TreeNode(child); + } + childNodes.insert(child); + + // Construct the Binary Tree + if (isLeft) { + nodes[parent]->left = nodes[child]; + } else { + nodes[parent]->right = nodes[child]; + } + } + + TreeNode *root; + for (auto const &[key, node] : nodes) { + if (!childNodes.count(key)) { + root = node; + break; + } + } + + return root; + } +}; diff --git a/C++/2285-MaximumTotalImportanceOfRoads/Solution.cpp b/C++/2285-MaximumTotalImportanceOfRoads/Solution.cpp new file mode 100644 index 0000000..5d18f4c --- /dev/null +++ b/C++/2285-MaximumTotalImportanceOfRoads/Solution.cpp @@ -0,0 +1,23 @@ +#include + +using namespace std; + +class Solution { +private: +public: + long long maximumImportance(int n, vector> &roads) { + vector frequencies = vector(n, 0); + for (auto const &edge : roads) { + ++frequencies[edge[0]]; + ++frequencies[edge[1]]; + } + + sort(frequencies.begin(), frequencies.end()); + + long long maximumScore = 0; + for (int i = 0; i < n; ++i) { + maximumScore += (long long)frequencies[i] * (long long)(i + 1); + } + return maximumScore; + } +}; diff --git a/C++/2486-AppendCharactersToMakeSubsequence/Solution.cpp b/C++/2486-AppendCharactersToMakeSubsequence/Solution.cpp new file mode 100644 index 0000000..fff465f --- /dev/null +++ b/C++/2486-AppendCharactersToMakeSubsequence/Solution.cpp @@ -0,0 +1,23 @@ +#include + +using namespace std; +class Solution { +public: + // Returns the minimum number of characters that need to be appended to the + // end of s so that t becomes a subsequence of s. + // First intuition was that it was similar to Longest Common Subseqeuence. + // I.e. if same character, then length + 1, different character, recurse on + // two choices (two strings). + // The difference here is that we only need to consider t as a subseqeuence of + // s. + int appendCharacters(string s, string t) { + int ptrT = 0; + for (int i = 0; i < s.size(); ++i) { + if (s[i] == t[ptrT]) { + ++ptrT; + } + } + + return t.size() - ptrT; + } +}; diff --git a/C++/2976-MinimumCostToConvertStringOne/Solution.cpp b/C++/2976-MinimumCostToConvertStringOne/Solution.cpp new file mode 100644 index 0000000..5911b08 --- /dev/null +++ b/C++/2976-MinimumCostToConvertStringOne/Solution.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +class Solution { +public: + long long minimumCost(string source, string target, vector &original, + vector &changed, vector &cost) { + vector> dist(26, vector(26, LONG_LONG_MAX)); + for (int i = 0; i < 26; ++i) { + dist[i][i] = 0; + } + + for (int i = 0; i < original.size(); ++i) { + int from = original[i] - 'a'; + int to = changed[i] - 'a'; + long long weight = cost[i]; + + dist[from][to] = min(dist[from][to], weight); + } + + for (int k = 0; k < 26; ++k) { + for (int i = 0; i < 26; ++i) { + for (int j = 0; j < 26; ++j) { + if (dist[i][k] == LONG_LONG_MAX || dist[k][j] == LONG_LONG_MAX) { + continue; + } + dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]); + } + } + } + + long long result = 0; + int n = source.length(); + for (int i = 0; i < n; ++i) { + long long shortestDistance = dist[source[i] - 'a'][target[i] - 'a']; + if (shortestDistance == LONG_LONG_MAX) { + return -1; + } + result += shortestDistance; + } + return result; + } +}; diff --git a/C++/3110-ScoreOfAString/Solution.cpp b/C++/3110-ScoreOfAString/Solution.cpp new file mode 100644 index 0000000..bf38ff4 --- /dev/null +++ b/C++/3110-ScoreOfAString/Solution.cpp @@ -0,0 +1,19 @@ +#include + +using namespace std; +class Solution { +public: + // score of a string is defined as the SUM of the absolute difference between + // the ASCII values of adjacent characters. + int scoreOfString(string s) { + if (s.length() < 2) { + return 0; + } + + int score = 0; + for (int i = 1; i < s.length(); ++i) { + score += abs(s[i] - s[i - 1]); + } + return score; + } +}; diff --git a/C++/MinimumStack.cpp b/C++/MinimumStack.cpp new file mode 100644 index 0000000..d0d806f --- /dev/null +++ b/C++/MinimumStack.cpp @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +class MinStack { +private: + stack stk; + stack minStack; + +public: + MinStack() {} + + void push(int val) { + stk.push(val); + if (!minStack.empty()) { + minStack.push(min(val, minStack.top())); + } else { + minStack.push(val); + } + } + + void pop() { + stk.pop(); + minStack.pop(); + } + + int top() { return stk.top(); } + + int getMin() { return minStack.top(); } +}; diff --git a/C++/StringEncodeAndDecode.cpp b/C++/StringEncodeAndDecode.cpp new file mode 100644 index 0000000..6496ed1 --- /dev/null +++ b/C++/StringEncodeAndDecode.cpp @@ -0,0 +1,35 @@ +#include +#include +#include + +using namespace std; +class Solution { +private: + char DELIMITER = '#'; + +public: + string encode(vector &strs) { + string result; + for (string str : strs) { + result += to_string(str.size()) + DELIMITER + str; + } + return result; + } + + vector decode(string s) { + vector result; + int i = 0; + while (i < s.size()) { + int j = i; + while (s[j] != DELIMITER) { + ++j; + } + + int numTokens = stoi(s.substr(i, j - i)); + string str = s.substr(j + 1, numTokens); + result.push_back(str); + i = j + 1 + numTokens; + } + return result; + } +}; diff --git a/C++/TreeNode.cpp b/C++/TreeNode.cpp new file mode 100644 index 0000000..6ec0206 --- /dev/null +++ b/C++/TreeNode.cpp @@ -0,0 +1,10 @@ +struct TreeNode { + int val; + TreeNode *left; + TreeNode *right; + + TreeNode() : val(0), left(nullptr), right(nullptr) {} + TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + TreeNode(int x, TreeNode *left, TreeNode *right) + : val(x), left(left), right(right) {} +}; diff --git a/Go/0022-GenerateParentheses/Solution.go b/Go/0022-GenerateParentheses/Solution.go new file mode 100644 index 0000000..7e89168 --- /dev/null +++ b/Go/0022-GenerateParentheses/Solution.go @@ -0,0 +1,29 @@ +package lc22 + +// generateParenthesis returns the set of well-formed Parentheses that can be made by n pairs +// +// This follows the sequence of Catalan Numbers, and has the following recursive formulation: +// "" in C (i.e. empty string is in the set) +// if a and b in C, then (a)b is in C. +// +// Therefore, C_n = {(C_0)C_n-1, (C_1)C_n-2, ..., (C_n-1)C_0} +// |C_n| = (1/n+1)*(2nCn) (1 over n+1 times 2n Choose n) +// => number of ways to choose n pairs among 2n pairs, normalized by 1/(n+1). +func generateParenthesis(n int) []string { + dp := make([][]string, n+1) // Include n=0 base case. + + // Base case: empty string + dp[0] = []string{""} + + for i := 1; i < n+1; i++ { + for j := 0; j < i; j++ { + // Iterate and form pairs over C_j and C_i-j-1 + for _, alpha := range dp[j] { + for _, beta := range dp[i-j-1] { + dp[i] = append(dp[i], "("+alpha+")"+beta) + } + } + } + } + return dp[n] +} diff --git a/Go/0022-GenerateParentheses/Solution_test.go b/Go/0022-GenerateParentheses/Solution_test.go new file mode 100644 index 0000000..6efb253 --- /dev/null +++ b/Go/0022-GenerateParentheses/Solution_test.go @@ -0,0 +1,39 @@ +package lc22 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGenerateParenthesis(t *testing.T) { + testCases := map[string]struct { + pairs int + expected []string + }{ + "Three pairs of parentheses": { + pairs: 3, + expected: []string{"((()))", "(()())", "(())()", "()(())", "()()()"}, + }, + "Two pairs of parentheses": { + pairs: 2, + expected: []string{"(())", "()()"}, + }, + "One pair of parentheses": { + pairs: 1, + expected: []string{"()"}, + }, + "Zero pairs - Base case": { + pairs: 0, + expected: []string{""}, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := generateParenthesis(tc.pairs) + assert.ElementsMatch(t, tc.expected, actual) + }) + } +} diff --git a/Go/0042-TrappingRainWater/Solution.go b/Go/0042-TrappingRainWater/Solution.go new file mode 100644 index 0000000..ae16d82 --- /dev/null +++ b/Go/0042-TrappingRainWater/Solution.go @@ -0,0 +1,84 @@ +package lc42 + +type IntStack []int + +// The calculation of the exact unit amount of water trapped can be +// visualized as a histogram, where due to the monotonically decreasing +// property of our stack, we can inductively add to the resultant amount of +// water trapped. To elaborate, suppose we have some arbitrary indices +// {0, 1, 2} representing elevations {3, 2, 1} in the Stack, and we +// encounter an elevation of 4 at index 3. +// +// We first pop the most minimum value in the stack, with (index, +// elevation) corresponding to (2, 1). Then, looking to its left, we have +// (1, 2). The amount of water trapped by this set of elevations is simply +// the difference of min(leftBound, rightBound) = min(2, 4) = 2 and the +// popped value 1. Thus we get 1 unit of water trapped. +// +// The next iteration, we pop (1, 2) and look at (0, 3). The height of +// water bounded will be +// `min(leftBound, rightBound) - popped = min(3, 4) = 3 - 2 = 1 unit` +// As such, we add 1 unit of water to the result. However, this does not +// account for the previously popped (2, 1), which has a lower elevation +// than the current popped (1, 2), and will therefore have the same height +// of trapped water. Thus we find the horizontal distance between the +// leftBound and rightBound, denoted by `i - len(indices) - 1`, and +// multiply it by the height of water added in the current iteration, +// giving us the correct result of 3 units of water trapped for the current +// iteration. +func trap(height []int) int { + if len(height) < 3 { + // Water cannot be trapped + return 0 + } + + // Monotonically decreasing stack. + // Look for an elevation that is equal to or higher than the top of the stack. + indices := make(IntStack, 0, len(height)) + + result := 0 + for i, currentElevation := range height { + for !indices.isEmpty() && currentElevation > height[indices.peek()] { + // Pop top-most of the stack + topMostElevation := height[indices.pop()] + + // No left boundary i.e. initial size of stack < 3 + if indices.isEmpty() { + break + } + + // Check left boundary of popped left elevation + leftElevation := height[indices.peek()] + + // Calculate the height of the water trapped **between** the currentElevation and the leftElevation + boundedHeight := min(leftElevation, currentElevation) - topMostElevation + + // Trickiest part. + result += boundedHeight * (i - indices.peek() - 1) + } + + indices.push(i) + } + + return result +} + +func (s *IntStack) push(val int) { + *s = append(*s, val) +} + +// Assumes an isEmpty check is done already. +func (s *IntStack) pop() int { + popped := (*s)[len(*s)-1] + *s = (*s)[:len(*s)-1] + return popped +} + +func (s *IntStack) peek() int { + top := (*s)[len(*s)-1] + return top +} + +func (s *IntStack) isEmpty() bool { + return len(*s) == 0 +} diff --git a/Go/0042-TrappingRainWater/Solution_test.go b/Go/0042-TrappingRainWater/Solution_test.go new file mode 100644 index 0000000..0e2dbbc --- /dev/null +++ b/Go/0042-TrappingRainWater/Solution_test.go @@ -0,0 +1,31 @@ +package lc42 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTrap(t *testing.T) { + testCases := map[string]struct { + height []int + expected int + }{ + "TODO 1": { + height: []int{0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1}, + expected: 6, + }, + "TODO 2": { + height: []int{4, 2, 0, 3, 2, 5}, + expected: 9, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + // t.Parallel() + actual := trap(tc.height) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0045-JumpGame/Solution.go b/Go/0045-JumpGame/Solution.go new file mode 100644 index 0000000..5be016b --- /dev/null +++ b/Go/0045-JumpGame/Solution.go @@ -0,0 +1,48 @@ +package lc45 + +// Greedy solution. O(n) +// Greedy Choice property: +// Let nums[i]* be the jump that reaches the furthest index, such that +// i + nums[i]* = max(j + nums[j]) for all 0<=j<=len(nums) +// Then, there exists an optimal solution which includes nums[i]*. +func Jump(nums []int) int { + jumps := 1 + furthest := nums[0] // represents the current furthest index reachable with the number of jumps + + for i := 1; i < len(nums); i++ { + if i > furthest { + // Cannot reach the end + return 0 + } + if i+nums[i] > furthest { + jumps++ + furthest = i + nums[i] + } + if furthest >= len(nums)-1 { + // Can already reach last index + break + } + } + + return jumps +} + +// Naive DP solution. O(n^2) +func jumpNaive(nums []int) int { + dp := make([]int, len(nums)) + // dp[i] represents the minimum number of jumps required to reach the i-th position. + for i := 0; i < len(nums); i++ { + dp[i] = 10000 // 0 <= nums[i] <= 1000 + } + dp[0] = 0 + + for i, jumpLength := range nums { + for j := 1; j <= jumpLength; j++ { + if i+j >= len(nums) { + break + } + dp[i+j] = min(1+dp[i], dp[i+j]) + } + } + return dp[len(nums)-1] +} diff --git a/Go/0045-JumpGame/Solution_test.go b/Go/0045-JumpGame/Solution_test.go new file mode 100644 index 0000000..f4cb2f2 --- /dev/null +++ b/Go/0045-JumpGame/Solution_test.go @@ -0,0 +1,43 @@ +package lc45 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestJump(t *testing.T) { + testCases := map[string]struct { + nums []int + expected int + }{ + "Jump to nums[1] instead of nums[2], then to nums[4] i.e. last index": { + nums: []int{2, 3, 1, 1, 4}, + expected: 2, + }, + "Impossible to reach last index": { + nums: []int{3, 2, 1, 0, 1, 4}, + expected: 0, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := Jump(tc.nums) + assert.Equal(t, tc.expected, actual) + }) + } +} + +func BenchmarkGreedyJump(b *testing.B) { + for i := 0; i < b.N; i++ { + Jump([]int{2, 3, 1, 1, 4, 5, 2, 1, 3, 2, 4}) + } +} + +func BenchmarkNaiveJump(b *testing.B) { + for i := 0; i < b.N; i++ { + jumpNaive([]int{2, 3, 1, 1, 4, 5, 2, 1, 3, 2, 4}) + } +} diff --git a/Go/0055-JumpGame/Solution.go b/Go/0055-JumpGame/Solution.go new file mode 100644 index 0000000..04205d0 --- /dev/null +++ b/Go/0055-JumpGame/Solution.go @@ -0,0 +1,10 @@ +package lc55 + +import lc45 "gitlab.com/euchangxian/leetcode/Go/0045-JumpGame" + +// lc45.Jump returns the minimum number of jumps required to reach the last index. 0 represents impossible to reach the last index +// As such, we can reduce the optimization problem: find the minimum number of jumps to reach the last index, to a +// decision problem: is it possible to reach the last index in k jumps. +func canJump(nums []int) bool { + return lc45.Jump(nums) > 0 +} diff --git a/Go/0055-JumpGame/Solution_test.go b/Go/0055-JumpGame/Solution_test.go new file mode 100644 index 0000000..345fcf4 --- /dev/null +++ b/Go/0055-JumpGame/Solution_test.go @@ -0,0 +1,31 @@ +package lc55 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCanJump(t *testing.T) { + testCases := map[string]struct { + nums []int + expected bool + }{ + "All positive jump lengths": { + nums: []int{2, 3, 1, 1, 4}, + expected: true, + }, + "All prefix jumps arrive at 0": { + nums: []int{3, 2, 1, 0, 4}, + expected: false, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := canJump(tc.nums) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0062-UniquePaths/Solution.go b/Go/0062-UniquePaths/Solution.go new file mode 100644 index 0000000..69a6b69 --- /dev/null +++ b/Go/0062-UniquePaths/Solution.go @@ -0,0 +1,17 @@ +package lc62 + +func uniquePaths(m int, n int) int { + // After the i-th iteration, dp[j] represents the number of ways to reach cell (i, j) + dp := make([]int, n) + // Base case. Only 1 way to reach anything on the first row. + for j := range dp { + dp[j] = 1 + } + + for i := 1; i < m; i++ { + for j := 1; j < n; j++ { + dp[j] += dp[j-1] + } + } + return dp[n-1] +} diff --git a/Go/0062-UniquePaths/Solution_test.go b/Go/0062-UniquePaths/Solution_test.go new file mode 100644 index 0000000..c84cc99 --- /dev/null +++ b/Go/0062-UniquePaths/Solution_test.go @@ -0,0 +1,34 @@ +package lc62 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUniquePaths(t *testing.T) { + testCases := map[string]struct { + m int + n int + expected int + }{ + "3x2 grid ": { + m: 3, + n: 2, + expected: 3, + }, + "3x7 grid": { + m: 3, + n: 7, + expected: 28, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := uniquePaths(tc.m, tc.n) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0063-UniquePathsII/Solution.go b/Go/0063-UniquePathsII/Solution.go new file mode 100644 index 0000000..96630cb --- /dev/null +++ b/Go/0063-UniquePathsII/Solution.go @@ -0,0 +1,32 @@ +package lc63 + +func uniquePathsWithObstacles(obstacleGrid [][]int) int { + m := len(obstacleGrid) + n := len(obstacleGrid[0]) + // After the i-th iteration, dp[j] represents the number of ways to reach cell (i, j) + dp := make([]int, n) + + // Base case. Only 1 way to reach anything on the first row. + for j := range dp { + if obstacleGrid[0][j] == 1 { + break + } + dp[j] = 1 + } + + for i := 1; i < m; i++ { + for j := 0; j < n; j++ { + if obstacleGrid[i][j] == 1 { + dp[j] = 0 + continue + } + + // Handle first column + if j <= 0 { + continue + } + dp[j] += dp[j-1] + } + } + return dp[n-1] +} diff --git a/Go/0063-UniquePathsII/Solution_test.go b/Go/0063-UniquePathsII/Solution_test.go new file mode 100644 index 0000000..6390d27 --- /dev/null +++ b/Go/0063-UniquePathsII/Solution_test.go @@ -0,0 +1,51 @@ +package lc63 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUniquePathsWithObstacles(t *testing.T) { + testCases := map[string]struct { + obstacleGrid [][]int + expected int + }{ + "3x3 grid, obstacle in middle cell": { + obstacleGrid: [][]int{ + {0, 0, 0}, + {0, 1, 0}, + {0, 0, 0}, + }, + expected: 2, + }, + "2x2 grid, obstacle on top-right cell": { + obstacleGrid: [][]int{ + {0, 1}, + {0, 0}, + }, + expected: 1, + }, + "1x2 grid, obstacle on goal cell": { + obstacleGrid: [][]int{ + {0, 1}, + }, + expected: 0, + }, + "2x1 grid, obstacle on goal cell": { + obstacleGrid: [][]int{ + {0}, + {1}, + }, + expected: 0, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := uniquePathsWithObstacles(tc.obstacleGrid) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0064-MinimumPathSum/Solution.go b/Go/0064-MinimumPathSum/Solution.go new file mode 100644 index 0000000..543c7e5 --- /dev/null +++ b/Go/0064-MinimumPathSum/Solution.go @@ -0,0 +1,25 @@ +package lc64 + +func minPathSum(grid [][]int) int { + n, m := len(grid), len(grid[0]) + + // dp[j] represents the minimum path sum obtainable after the i-th iteration + dp := make([]int, m) + + // Set up base case for first row + dp[0] = grid[0][0] + for i := 1; i < m; i++ { + dp[i] = grid[0][i] + dp[i-1] + } + + for i := 1; i < n; i++ { + temp := make([]int, m) // avoid using results from current iteration + temp[0] = grid[i][0] + dp[0] + for j := 1; j < m; j++ { + temp[j] = grid[i][j] + min(dp[j], temp[j-1]) + } + dp = temp + } + + return dp[m-1] +} diff --git a/Go/0064-MinimumPathSum/Solution_test.go b/Go/0064-MinimumPathSum/Solution_test.go new file mode 100644 index 0000000..bd47fe5 --- /dev/null +++ b/Go/0064-MinimumPathSum/Solution_test.go @@ -0,0 +1,38 @@ +package lc64 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMinPathSum(t *testing.T) { + testCases := map[string]struct { + grid [][]int + expected int + }{ + "3x3 grid": { + grid: [][]int{ + {1, 3, 1}, + {1, 5, 1}, + {4, 2, 1}, + }, + expected: 7, + }, + "2x3 grid": { + grid: [][]int{ + {1, 2, 3}, + {4, 5, 6}, + }, + expected: 12, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := minPathSum(tc.grid) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0067-AddBinary/Solution.go b/Go/0067-AddBinary/Solution.go new file mode 100644 index 0000000..b135430 --- /dev/null +++ b/Go/0067-AddBinary/Solution.go @@ -0,0 +1,38 @@ +package lc67 + +import ( + "strconv" + "strings" +) + +func addBinary(a string, b string) string { + if len(a) < len(b) { + a, b = b, a + } + carry := 0 + result := make([]string, 0, len(a)) + i, j := len(a)-1, len(b)-1 + for i >= 0 || j >= 0 || carry > 0 { + sum := carry + + if i >= 0 { + sum += int(a[i] - '0') + i-- + } + + if j >= 0 { + sum += int(b[j] - '0') + j-- + } + + // sum is at most 3 here. + result = append(result, strconv.Itoa(sum%2)) + carry = sum / 2 + } + + var sb strings.Builder + for k := len(result) - 1; k >= 0; k-- { + sb.WriteString(result[k]) + } + return sb.String() +} diff --git a/Go/0067-AddBinary/Solution_test.go b/Go/0067-AddBinary/Solution_test.go new file mode 100644 index 0000000..a518e90 --- /dev/null +++ b/Go/0067-AddBinary/Solution_test.go @@ -0,0 +1,39 @@ +package lc67 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAddBinary(t *testing.T) { + testCases := map[string]struct { + a string + b string + expected string + }{ + "Add 3 and 1 to make 4": { + a: "11", + b: "1", + expected: "100", + }, + "Add 10 and 11 to make 21": { + a: "1010", + b: "1011", + expected: "10101", + }, + "Add 4 and 50 to make 54": { + a: "100", + b: "110010", + expected: "110110", + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + // t.Parallel() + actual := addBinary(tc.a, tc.b) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0070-ClimbingStairs/Solution.go b/Go/0070-ClimbingStairs/Solution.go new file mode 100644 index 0000000..63e88ee --- /dev/null +++ b/Go/0070-ClimbingStairs/Solution.go @@ -0,0 +1,21 @@ +package lc70 + +func climbStairs(n int) int { + if n < 2 { + return 1 + } + // Array of size n + dp := make([]int, n) + + // Base case n == 1 => only 1 way + dp[0] = 1 + + // Add another base case for simplification of loop + dp[1] = 2 + + for i := 2; i < n; i++ { + dp[i] = dp[i-1] + dp[i-2] + } + + return dp[n-1] +} diff --git a/Go/0070-ClimbingStairs/Solution_test.go b/Go/0070-ClimbingStairs/Solution_test.go new file mode 100644 index 0000000..6ca34eb --- /dev/null +++ b/Go/0070-ClimbingStairs/Solution_test.go @@ -0,0 +1,35 @@ +package lc70 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test(t *testing.T) { + testCases := map[string]struct { + input int + expected int + }{ + "Two steps - two ways": { + input: 2, + expected: 2, + }, + "Three steps - three ways": { + input: 3, + expected: 3, + }, + "Four steps - five ways": { + input: 4, + expected: 5, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := climbStairs(tc.input) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0094-BinaryTreeInorderTraversal/Solution.go b/Go/0094-BinaryTreeInorderTraversal/Solution.go new file mode 100644 index 0000000..b58126c --- /dev/null +++ b/Go/0094-BinaryTreeInorderTraversal/Solution.go @@ -0,0 +1,23 @@ +package lc94 + +import ( + "gitlab.com/euchangxian/leetcode/Go/internal/bst" +) + +type Result []int + +func InorderTraversal(root *bst.TreeNode) []int { + var result Result + result.depthFirstSearch(root) + return result +} + +func (result *Result) depthFirstSearch(node *bst.TreeNode) { + if node == nil { + return + } + + result.depthFirstSearch(node.Left) + *result = append(*result, node.Val) + result.depthFirstSearch(node.Right) +} diff --git a/Go/0094-BinaryTreeInorderTraversal/Solution_test.go b/Go/0094-BinaryTreeInorderTraversal/Solution_test.go new file mode 100644 index 0000000..570ae35 --- /dev/null +++ b/Go/0094-BinaryTreeInorderTraversal/Solution_test.go @@ -0,0 +1,56 @@ +package lc94 + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "gitlab.com/euchangxian/leetcode/Go/internal/bst" +) + +func TestInorderTraversal(t *testing.T) { + multipleNodes := &bst.TreeNode{ + Right: &bst.TreeNode{ + Left: &bst.TreeNode{ + Val: 3, + }, + Val: 2, + }, + Val: 1, + } + + var emptyTree *bst.TreeNode + + singleNode := &bst.TreeNode{ + Val: 1, + } + + testCases := []struct { + name string + input *bst.TreeNode + expected []int + }{ + { + name: "Multiple Nodes", + input: multipleNodes, + expected: []int{1, 3, 2}, + }, + { + name: "No Nodes", + input: emptyTree, + expected: nil, + }, + { + name: "Single Node", + input: singleNode, + expected: []int{1}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + actual := InorderTraversal(tc.input) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0098-ValidateBinarySearchTree/Solution.go b/Go/0098-ValidateBinarySearchTree/Solution.go new file mode 100644 index 0000000..0803b7d --- /dev/null +++ b/Go/0098-ValidateBinarySearchTree/Solution.go @@ -0,0 +1,30 @@ +package lc98 + +import ( + "math" + + "gitlab.com/euchangxian/leetcode/Go/internal/bst" +) + +func IsValidBST(root *bst.TreeNode) bool { + if root == nil { + return true + } + + return isValidChild(root.Left, math.MinInt64, root.Val) && + isValidChild(root.Right, root.Val, math.MaxInt64) +} + +func isValidChild(node *bst.TreeNode, low int, high int) bool { + if node == nil { + return true + } + return isInRange(node.Val, low, high) && + isValidChild(node.Left, low, node.Val) && + isValidChild(node.Right, node.Val, high) +} + +// isInRange evaluates whether a given value is in the range (low, high), exclusive. +func isInRange(val int, low int, high int) bool { + return val > low && val < high +} diff --git a/Go/0098-ValidateBinarySearchTree/Solution_test.go b/Go/0098-ValidateBinarySearchTree/Solution_test.go new file mode 100644 index 0000000..15348fc --- /dev/null +++ b/Go/0098-ValidateBinarySearchTree/Solution_test.go @@ -0,0 +1,101 @@ +package lc98 + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "gitlab.com/euchangxian/leetcode/Go/internal/bst" +) + +// --2 +// 1---3 +var validBST = &bst.TreeNode{ + Val: 2, + Left: &bst.TreeNode{ + Val: 1, + }, + Right: &bst.TreeNode{ + Val: 3, + }, +} + +var validBSTMinMaxInt = &bst.TreeNode{ + Val: -2147483648, + Right: &bst.TreeNode{ + Val: 2147483647, + }, +} + +// --5 +// 1---4 +// ---3---6 +var invalidBSTRightChild = &bst.TreeNode{ + Val: 5, + Left: &bst.TreeNode{ + Val: 1, + }, + Right: &bst.TreeNode{ + Val: 4, + Left: &bst.TreeNode{ + Val: 3, + }, + Right: &bst.TreeNode{ + Val: 6, + }, + }, +} + +// ----- 5 +// --- 4 -- 6 +// ------- 3 7 +var invalidBSTRightLeaf = &bst.TreeNode{ + Val: 5, + Left: &bst.TreeNode{ + Val: 4, + }, + Right: &bst.TreeNode{ + Val: 6, + Left: &bst.TreeNode{ + Val: 3, + }, + Right: &bst.TreeNode{ + Val: 7, + }, + }, +} + +func TestIsValidBST(t *testing.T) { + testCases := map[string]struct { + input *bst.TreeNode + expected bool + }{ + "Valid BST - Empty Tree": { + input: nil, + expected: true, + }, + "Valid BST - Trivial Case": { + input: validBST, + expected: true, + }, + "Valid BST - Min / Max integer value nodes": { + input: validBSTMinMaxInt, + expected: true, + }, + "Invalid BST - Right child is less than root": { + input: invalidBSTRightChild, + expected: false, + }, + "Invalid BST - Right leaf is less than root": { + input: invalidBSTRightLeaf, + expected: false, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := IsValidBST(tc.input) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0118-PascalsTriangle/Solution.go b/Go/0118-PascalsTriangle/Solution.go new file mode 100644 index 0000000..c917b16 --- /dev/null +++ b/Go/0118-PascalsTriangle/Solution.go @@ -0,0 +1,24 @@ +package lc118 + +func generate(numRows int) [][]int { + dp := make([][]int, numRows) + + // Base case + dp[0] = []int{1} + + for i := 1; i < numRows; i++ { + row := make([]int, i+1) // i + 1 elements in the ith row, i is 0-indexed + + // Start and End of the row is 1 + row[0] = 1 + row[i] = 1 + + // Calculate middle elements + for j := 1; j < i; j++ { + row[j] = dp[i-1][j-1] + dp[i-1][j] + } + + dp[i] = row + } + return dp +} diff --git a/Go/0118-PascalsTriangle/Solution_test.go b/Go/0118-PascalsTriangle/Solution_test.go new file mode 100644 index 0000000..f6b7d5a --- /dev/null +++ b/Go/0118-PascalsTriangle/Solution_test.go @@ -0,0 +1,39 @@ +package lc118 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGenerate(t *testing.T) { + testCases := map[string]struct { + numRows int + expected [][]int + }{ + "Base case": { + numRows: 1, + expected: [][]int{ + {1}, + }, + }, + "Five rows": { + numRows: 5, + expected: [][]int{ + {1}, + {1, 1}, + {1, 2, 1}, + {1, 3, 3, 1}, + {1, 4, 6, 4, 1}, + }, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := generate(tc.numRows) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0120-Triangle/Solution.go b/Go/0120-Triangle/Solution.go new file mode 100644 index 0000000..e5166f9 --- /dev/null +++ b/Go/0120-Triangle/Solution.go @@ -0,0 +1,25 @@ +package lc120 + +import "slices" + +func minimumTotal(triangle [][]int) int { + // Initialise to size of last row. Formulaically, every row is +1 the length of the + // previous row, so it can simply be len(triangle). + n := len(triangle[len(triangle)-1]) + // After the i-th iteration, dp[j] represents the minimum path sum to reach the + // j-th number of the i-th row. + dp := make([]int, n) + dp[0] = triangle[0][0] + + for i := 1; i < len(triangle); i++ { + temp := make([]int, n) // temporary dp array to store results of current iteration + temp[0] = dp[0] + triangle[i][0] + for j := 1; j < len(triangle[i]); j++ { + // min(j, len(triangle[i-1]) - 1) handles the case when the current element does not have + // an element directly above it i.e. a corner element. + temp[j] = min(dp[j-1], dp[min(j, len(triangle[i-1])-1)]) + triangle[i][j] + } + dp = temp + } + return slices.Min(dp) +} diff --git a/Go/0120-Triangle/Solution_test.go b/Go/0120-Triangle/Solution_test.go new file mode 100644 index 0000000..d6064b6 --- /dev/null +++ b/Go/0120-Triangle/Solution_test.go @@ -0,0 +1,38 @@ +package lc120 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMinimumTotal(t *testing.T) { + testCases := map[string]struct { + triangle [][]int + expected int + }{ + "Test Case 1": { + triangle: [][]int{ + {2}, + {3, 4}, + {6, 5, 7}, + {4, 1, 8, 3}, + }, + expected: 11, + }, + "Test Case 2": { + triangle: [][]int{ + {-10}, + }, + expected: -10, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := minimumTotal(tc.triangle) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0121-BestTimeToBuyAndSellStock/Solution.go b/Go/0121-BestTimeToBuyAndSellStock/Solution.go new file mode 100644 index 0000000..07c2c04 --- /dev/null +++ b/Go/0121-BestTimeToBuyAndSellStock/Solution.go @@ -0,0 +1,19 @@ +package lc121 + +func maxProfit(prices []int) int { + if len(prices) <= 1 { + return 0 + } + + currMax := 0 // 0 <= prices[i] <= 10^4 + + i := 0 // left pointer + for j := 1; j < len(prices); j++ { + if prices[j] < prices[i] { + i = j + } + currMax = max(currMax, prices[j]-prices[i]) + } + + return currMax +} diff --git a/Go/0121-BestTimeToBuyAndSellStock/Solution_test.go b/Go/0121-BestTimeToBuyAndSellStock/Solution_test.go new file mode 100644 index 0000000..ecdc01c --- /dev/null +++ b/Go/0121-BestTimeToBuyAndSellStock/Solution_test.go @@ -0,0 +1,31 @@ +package lc121 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMaxProfit(t *testing.T) { + testCases := map[string]struct { + prices []int + expected int + }{ + "Buy on day 2, sell on day 5": { + prices: []int{7, 1, 5, 3, 6, 4}, + expected: 5, + }, + "No transaction - non-increasing": { + prices: []int{7, 5, 4, 3, 1}, + expected: 0, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := maxProfit(tc.prices) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0122-BestTimeToBuyAndSellStockTwo/Solution.go b/Go/0122-BestTimeToBuyAndSellStockTwo/Solution.go new file mode 100644 index 0000000..b1d4fd6 --- /dev/null +++ b/Go/0122-BestTimeToBuyAndSellStockTwo/Solution.go @@ -0,0 +1,20 @@ +package lc122 + +// Differs from 121-BestTimeToBuyAndSellStock. 122 allows purchasing and selling of the +// stock multiple times on same/different days +func maxProfit(prices []int) int { + if len(prices) <= 1 { + return 0 + } + + maxProfit := 0 + // Simply loop through, "buying" a stock on the (i-1)-th day, and "selling" it on the i-th day + // if price[i] > price[i - 1]. Visualize this with the positive slope of a graph. + for i := 1; i < len(prices); i++ { + if prices[i] > prices[i-1] { + maxProfit += prices[i] - prices[i-1] + } + } + + return maxProfit +} diff --git a/Go/0122-BestTimeToBuyAndSellStockTwo/Solution_test.go b/Go/0122-BestTimeToBuyAndSellStockTwo/Solution_test.go new file mode 100644 index 0000000..1327b52 --- /dev/null +++ b/Go/0122-BestTimeToBuyAndSellStockTwo/Solution_test.go @@ -0,0 +1,35 @@ +package lc122 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMaxProfit(t *testing.T) { + testCases := map[string]struct { + prices []int + expected int + }{ + "Buy and sell twice - day 2, day 3 and day 4, day 5": { + prices: []int{7, 1, 5, 3, 6, 4}, + expected: 7, + }, + "Non-decreasing prices - buy once on day 1, sell on day 5": { + prices: []int{1, 2, 3, 4, 5}, + expected: 4, + }, + "Non-increasing prices - never buy": { + prices: []int{7, 6, 4, 3, 1}, + expected: 0, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := maxProfit(tc.prices) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0136-SingleNumber/Solution.go b/Go/0136-SingleNumber/Solution.go new file mode 100644 index 0000000..d4fd578 --- /dev/null +++ b/Go/0136-SingleNumber/Solution.go @@ -0,0 +1,12 @@ +package lc136 + +// Find the number that occurs only once in the nums array, where every other number occurs twice. +// Uses XOR, where a XOR a = 0, and 0 XOR a = a. +func singleNumber(nums []int) int { + single := 0 + + for _, num := range nums { + single ^= num + } + return single +} diff --git a/Go/0136-SingleNumber/Solution_test.go b/Go/0136-SingleNumber/Solution_test.go new file mode 100644 index 0000000..7b5a5ad --- /dev/null +++ b/Go/0136-SingleNumber/Solution_test.go @@ -0,0 +1,31 @@ +package lc136 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSingleNumber(t *testing.T) { + testCases := map[string]struct { + nums []int + expected int + }{ + "Trivial case": { + nums: []int{1}, + expected: 1, + }, + "Multiple unique elements": { + nums: []int{4, 1, 2, 1, 2}, + expected: 4, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := singleNumber(tc.nums) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0144-BinaryTreePreorderTraversal/Solution.go b/Go/0144-BinaryTreePreorderTraversal/Solution.go new file mode 100644 index 0000000..0c78e97 --- /dev/null +++ b/Go/0144-BinaryTreePreorderTraversal/Solution.go @@ -0,0 +1,18 @@ +package lc144 + +import "gitlab.com/euchangxian/leetcode/Go/internal/bst" + +func PreorderTraversal(root *bst.TreeNode) []int { + var result []int + search(&result, root) + return result +} + +func search(result *[]int, node *bst.TreeNode) { + if node == nil { + return + } + *result = append(*result, node.Val) + search(result, node.Left) + search(result, node.Right) +} diff --git a/Go/0144-BinaryTreePreorderTraversal/Solution_test.go b/Go/0144-BinaryTreePreorderTraversal/Solution_test.go new file mode 100644 index 0000000..5d9c9b2 --- /dev/null +++ b/Go/0144-BinaryTreePreorderTraversal/Solution_test.go @@ -0,0 +1,56 @@ +package lc144 + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "gitlab.com/euchangxian/leetcode/Go/internal/bst" +) + +func TestPreorderTraversal(t *testing.T) { + multipleNodes := &bst.TreeNode{ + Right: &bst.TreeNode{ + Left: &bst.TreeNode{ + Val: 3, + }, + Val: 2, + }, + Val: 1, + } + + var emptyTree *bst.TreeNode + + singleNode := &bst.TreeNode{ + Val: 1, + } + + testCases := []struct { + name string + input *bst.TreeNode + expected []int + }{ + { + name: "Multiple Nodes", + input: multipleNodes, + expected: []int{1, 2, 3}, + }, + { + name: "No Nodes", + input: emptyTree, + expected: nil, + }, + { + name: "Single Node", + input: singleNode, + expected: []int{1}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + actual := PreorderTraversal(tc.input) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0145-BinaryTreePostorderTraversal/Solution.go b/Go/0145-BinaryTreePostorderTraversal/Solution.go new file mode 100644 index 0000000..85fb834 --- /dev/null +++ b/Go/0145-BinaryTreePostorderTraversal/Solution.go @@ -0,0 +1,21 @@ +package lc145 + +import ( + "gitlab.com/euchangxian/leetcode/Go/internal/bst" +) + +func PostorderTraversal(root *bst.TreeNode) []int { + var result []int + search(&result, root) + return result +} + +func search(result *[]int, node *bst.TreeNode) { + if node == nil { + return + } + + search(result, node.Left) + search(result, node.Right) + *result = append(*result, node.Val) +} diff --git a/Go/0145-BinaryTreePostorderTraversal/Solution_test.go b/Go/0145-BinaryTreePostorderTraversal/Solution_test.go new file mode 100644 index 0000000..277fb7c --- /dev/null +++ b/Go/0145-BinaryTreePostorderTraversal/Solution_test.go @@ -0,0 +1,56 @@ +package lc145 + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "gitlab.com/euchangxian/leetcode/Go/internal/bst" +) + +func TestPostorderTraversal(t *testing.T) { + multipleNodes := &bst.TreeNode{ + Right: &bst.TreeNode{ + Left: &bst.TreeNode{ + Val: 3, + }, + Val: 2, + }, + Val: 1, + } + + var emptyTree *bst.TreeNode + + singleNode := &bst.TreeNode{ + Val: 1, + } + + testCases := []struct { + name string + input *bst.TreeNode + expected []int + }{ + { + name: "Multiple Nodes", + input: multipleNodes, + expected: []int{3, 2, 1}, + }, + { + name: "No Nodes", + input: emptyTree, + expected: nil, + }, + { + name: "Single Node", + input: singleNode, + expected: []int{1}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + actual := PostorderTraversal(tc.input) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0165-CompareVersionNumbers/Solution.go b/Go/0165-CompareVersionNumbers/Solution.go new file mode 100644 index 0000000..3318f15 --- /dev/null +++ b/Go/0165-CompareVersionNumbers/Solution.go @@ -0,0 +1,47 @@ +package lc165 + +import ( + "strconv" + "strings" +) + +func compareVersion(version1 string, version2 string) int { + revisions1 := strings.Split(version1, ".") + revisions2 := strings.Split(version2, ".") + + // Compare common revisions first i.e up till min(len(revisions1), len(revisions2)). If equal till then, + // then check version2 if it has revisions > 0 + for i := 0; i < min(len(revisions1), len(revisions2)); i++ { + // Errors ignored since there will only be integer strings. + ithRevision1, _ := strconv.ParseUint(revisions1[i], 10, 32) + ithRevision2, _ := strconv.ParseUint(revisions2[i], 10, 32) + + if ithRevision1 == ithRevision2 { + continue + } + if ithRevision1 < ithRevision2 { + return -1 + } + if ithRevision1 > ithRevision2 { + return 1 + } + } + + if len(revisions1) > len(revisions2) { + for i := len(revisions2); i < len(revisions1); i++ { + ithRevision1, _ := strconv.ParseUint(revisions1[i], 10, 32) + if ithRevision1 > 0 { + return 1 + } + } + } + if len(revisions2) > len(revisions1) { + for i := len(revisions1); i < len(revisions2); i++ { + ithRevision2, _ := strconv.ParseUint(revisions2[i], 10, 32) + if ithRevision2 > 0 { + return -1 + } + } + } + return 0 +} diff --git a/Go/0165-CompareVersionNumbers/Solution_test.go b/Go/0165-CompareVersionNumbers/Solution_test.go new file mode 100644 index 0000000..cd6e592 --- /dev/null +++ b/Go/0165-CompareVersionNumbers/Solution_test.go @@ -0,0 +1,39 @@ +package lc165 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCompareVersion(t *testing.T) { + testCases := map[string]struct { + version1 string + version2 string + expected int + }{ + "Leading zeroes should be ignored. Equal versions": { + version1: "1.01", + version2: "1.001", + expected: 0, + }, + "Unspecified revisions should be ignored. Equal versions": { + version1: "1.0", + version2: "1.0.0", + expected: 0, + }, + "Revisions should be compared in left-to-right order": { + version1: "0.1", + version2: "1.0", + expected: -1, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := compareVersion(tc.version1, tc.version2) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0199-BinaryTreeRightSideView/Solution.go b/Go/0199-BinaryTreeRightSideView/Solution.go new file mode 100644 index 0000000..62906cd --- /dev/null +++ b/Go/0199-BinaryTreeRightSideView/Solution.go @@ -0,0 +1,62 @@ +package lc199 + +import ( + "gitlab.com/euchangxian/leetcode/Go/internal/bst" +) + +// RightSideView returns the values of the nodes as seen from the right side of the tree, ordered +// from top to bottom. +// Approach: Level by level, pick the right-most node => LevelOrderSearch. +func RightSideView(root *bst.TreeNode) []int { + var result []int + levelOrderSearch(&result, root) + return result +} + +func levelOrderSearch(result *[]int, root *bst.TreeNode) { + if root == nil { + return + } + + var frontier Queue // Slice-backed Queue + + frontier.Enqueue(root) // Initialize the Queue; Add the root node + for !frontier.IsEmpty() { + var nextFrontier Queue + hasRightMost := false // Flag to add only the right-most node of each frontier to the results + + for !frontier.IsEmpty() { + currentNode := frontier.Dequeue() + if !hasRightMost { + *result = append(*result, currentNode.Val) + hasRightMost = true + } + + // Adds bst.TreeNode.Right, then bst.TreeNode.Left. In this way, the first node popped from + // each frontier will always be the right-most node, and will be in the result. *result = append(*result, currentNode.Val) + if currentNode.Right != nil { + nextFrontier = append(nextFrontier, currentNode.Right) + } + if currentNode.Left != nil { + nextFrontier = append(nextFrontier, currentNode.Left) + } + } + frontier = nextFrontier + } +} + +type Queue []*bst.TreeNode + +func (q *Queue) IsEmpty() bool { + return len(*q) == 0 // Assumes len() will never return negative. +} + +func (q *Queue) Enqueue(node *bst.TreeNode) { + *q = append(*q, node) +} + +func (q *Queue) Dequeue() *bst.TreeNode { + popped := (*q)[0] // Retrieves the first element in the Queue + *q = (*q)[1:] // Slices the Queue, removing the first element + return popped +} diff --git a/Go/0199-BinaryTreeRightSideView/Solution_test.go b/Go/0199-BinaryTreeRightSideView/Solution_test.go new file mode 100644 index 0000000..760f9f7 --- /dev/null +++ b/Go/0199-BinaryTreeRightSideView/Solution_test.go @@ -0,0 +1,71 @@ +package lc199 + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "gitlab.com/euchangxian/leetcode/Go/internal/bst" +) + +// 1 +// / \ +// +// 2 3 +// +// \ \ +// 5 4 +var multipleLevels = &bst.TreeNode{ + Val: 1, + Left: &bst.TreeNode{ + Val: 2, + Right: &bst.TreeNode{ + Val: 5, + }, + }, + Right: &bst.TreeNode{ + Val: 3, + Right: &bst.TreeNode{ + Val: 4, + }, + }, +} + +var emptyTree *bst.TreeNode + +// 1 +// +// 3 +var oneLevel = &bst.TreeNode{ + Val: 1, + Right: &bst.TreeNode{ + Val: 3, + }, +} + +func TestRightSideView(t *testing.T) { + testCases := map[string]struct { + input *bst.TreeNode + expected []int + }{ + "Multiple levels": { + input: multipleLevels, + expected: []int{1, 3, 4}, + }, + "Empty tree": { + input: emptyTree, + expected: nil, + }, + "One level": { + input: oneLevel, + expected: []int{1, 3}, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := RightSideView(tc.input) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0237-DeleteNodeInALinkedList/Solution.go b/Go/0237-DeleteNodeInALinkedList/Solution.go new file mode 100644 index 0000000..1fd3260 --- /dev/null +++ b/Go/0237-DeleteNodeInALinkedList/Solution.go @@ -0,0 +1,21 @@ +package lc237 + +import "gitlab.com/euchangxian/leetcode/Go/internal/linkedlist" + +func wrappedDeleteNode(head *linkedlist.ListNode, node *linkedlist.ListNode) *linkedlist.ListNode { + deleteNode(node) + return head +} + +func deleteNode(node *linkedlist.ListNode) { + delete(node) +} + +func delete(node *linkedlist.ListNode) *linkedlist.ListNode { + if node == nil || node.Next == nil { + return nil + } + node.Val = node.Next.Val + node.Next = delete(node.Next) + return node +} diff --git a/Go/0237-DeleteNodeInALinkedList/Solution_test.go b/Go/0237-DeleteNodeInALinkedList/Solution_test.go new file mode 100644 index 0000000..b4b4f4e --- /dev/null +++ b/Go/0237-DeleteNodeInALinkedList/Solution_test.go @@ -0,0 +1,57 @@ +package lc237 + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "gitlab.com/euchangxian/leetcode/Go/internal/linkedlist" +) + +var head = &linkedlist.ListNode{ + Val: 4, + Next: &linkedlist.ListNode{ + Val: 5, + Next: &linkedlist.ListNode{ + Val: 1, + Next: &linkedlist.ListNode{ + Val: 9, + Next: nil, + }, + }, + }, +} + +func TestDeleteNode(t *testing.T) { + testCases := map[string]struct { + head *linkedlist.ListNode + node *linkedlist.ListNode + expected *linkedlist.ListNode + }{ + "Delete second node from head": { + head: head, + node: head.Next, + expected: &linkedlist.ListNode{ + Val: 4, + Next: &linkedlist.ListNode{ + Val: 1, + Next: &linkedlist.ListNode{ + Val: 9, + Next: nil, + }, + }, + }, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := wrappedDeleteNode(tc.head, tc.node) + for tc.expected != nil { + assert.Equal(t, tc.expected.Val, actual.Val) + tc.expected = tc.expected.Next + actual = actual.Next + } + }) + } +} diff --git a/Go/0402-RemoveKDigits/Solution.go b/Go/0402-RemoveKDigits/Solution.go new file mode 100644 index 0000000..c272009 --- /dev/null +++ b/Go/0402-RemoveKDigits/Solution.go @@ -0,0 +1,60 @@ +package lc402 + +import ( + "strconv" + "strings" +) + +func removeKDigits(num string, k int) string { + if len(num) <= k { + return "0" + } + + // Monotonically non-decreasing stack. Represents the final number after removing k digits. + nums := make([]int, 0, len(num)) + + for _, r := range num { + // Unicode: Characters '0', '1', ..., '9' are encoded consecutively in the Unicode + // table. That is, '0': 48, '1': 49, ..., '9': 57. + // Suppose the rune r represents a digit. Then, taking the unicode difference + // between r and '0' returns the actual integer representation of the digit. + digit := int(r - '0') + + // Since the digits are iterated in decreasing significance, + // while there are still digits to be removed (k > 0) + for k > 0 && len(nums) > 0 { + previousDigit := nums[len(nums)-1] + + if previousDigit <= digit { + break // simply add digit to the stack + } + + // Pop previousDigit and decrement k to maintain monotonicity of stack. + nums = nums[:len(nums)-1] + k-- + } + + // Numbers do not start with a 0, i.e. discard leading zeros + if len(nums) == 0 && digit == 0 { + continue + } + nums = append(nums, digit) + } + + // Case where digits in num are non-decreasing, resulting in no digits removed earlier + if k > 0 { + nums = nums[:len(nums)-min(len(nums), k)] // remove top k digits + } + + // No remaining digits that are non-zero + if len(nums) == 0 { + return "0" + } + + var result strings.Builder + for _, digit := range nums { + result.WriteString(strconv.Itoa(digit)) + } + + return result.String() +} diff --git a/Go/0402-RemoveKDigits/Solution_test.go b/Go/0402-RemoveKDigits/Solution_test.go new file mode 100644 index 0000000..8881653 --- /dev/null +++ b/Go/0402-RemoveKDigits/Solution_test.go @@ -0,0 +1,54 @@ +package lc402 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRemoveKDigits(t *testing.T) { + testCases := map[string]struct { + num string + k int + expected string + }{ + "No zeroes in num": { + num: "1432219", + k: 3, + expected: "1219", + }, + "Some zeroes in num": { + num: "10200", + k: 1, + expected: "200", + }, + "k is equal to digits in num": { + num: "10", + k: 2, + expected: "0", + }, + "Removing 1 digit results in 0": { + num: "100000", + k: 1, + expected: "0", + }, + "num is monotonically non-decreasing": { + num: "123456789", + k: 3, + expected: "123456", + }, + "More zeroes than non-zeros": { + num: "100001", + k: 4, + expected: "0", + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := removeKDigits(tc.num, tc.k) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0404-SumOfLeftLeaves/Solution.go b/Go/0404-SumOfLeftLeaves/Solution.go new file mode 100644 index 0000000..5986acc --- /dev/null +++ b/Go/0404-SumOfLeftLeaves/Solution.go @@ -0,0 +1,18 @@ +package lc404 + +import "gitlab.com/euchangxian/leetcode/Go/internal/bst" + +func sumOfLeftLeaves(root *bst.TreeNode) int { + return helper(root, false) +} + +func helper(node *bst.TreeNode, isLeft bool) int { + if node == nil { + return 0 + } + toAdd := 0 + if isLeft && (node.Left == nil && node.Right == nil) { + toAdd = node.Val + } + return toAdd + helper(node.Left, true) + helper(node.Right, false) +} diff --git a/Go/0404-SumOfLeftLeaves/Solution_test.go b/Go/0404-SumOfLeftLeaves/Solution_test.go new file mode 100644 index 0000000..81e9902 --- /dev/null +++ b/Go/0404-SumOfLeftLeaves/Solution_test.go @@ -0,0 +1,58 @@ +package lc404 + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "gitlab.com/euchangxian/leetcode/Go/internal/bst" +) + +func TestSumOfLeftLeaves(t *testing.T) { + multipleNodes := &bst.TreeNode{ + Val: 3, + Left: &bst.TreeNode{ + Val: 9, + }, + Right: &bst.TreeNode{ + Val: 20, + Left: &bst.TreeNode{ + Val: 15, + }, + Right: &bst.TreeNode{ + Val: 7, + }, + }, + } + + var emptyTree *bst.TreeNode + + singleNode := &bst.TreeNode{ + Val: 1, + } + + testCases := map[string]struct { + input *bst.TreeNode + expected int + }{ + "Two left leaves: 9 and 12": { + input: multipleNodes, + expected: 24, + }, + "Trivial case": { + input: emptyTree, + expected: 0, + }, + "A single node can not be a left child": { + input: singleNode, + expected: 0, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := sumOfLeftLeaves(tc.input) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0452-MinimumNumberOfArrowsToBurstBalloons/Solution.go b/Go/0452-MinimumNumberOfArrowsToBurstBalloons/Solution.go new file mode 100644 index 0000000..0ab4c3c --- /dev/null +++ b/Go/0452-MinimumNumberOfArrowsToBurstBalloons/Solution.go @@ -0,0 +1,28 @@ +package lc452 + +import "slices" + +func findMinArrowShots(points [][]int) int { + // Sort the points array by the right element (ending x-coordinates) of each pair in ascending order. + slices.SortFunc(points, func(a []int, b []int) int { + return a[1] - b[1] + }) + + shots := 1 + currentEnd := points[0][1] + // Optimal Substructure: if interval [start, end] is picked in the optimal solution, + // we can recurse on the subproblem where the interval, and any other overlapping intervals + // are removed. + // Greedy Choice: There is always an optimal solution where the balloon with the earliest ending + // x-coordinates is picked (the arrow is shot in that interval) + for i := 1; i < len(points); i++ { + currentPair := points[i] + + if currentPair[0] > currentEnd { + // No overlap in interval. + shots++ + currentEnd = currentPair[1] + } + } + return shots +} diff --git a/Go/0452-MinimumNumberOfArrowsToBurstBalloons/Solution_test.go b/Go/0452-MinimumNumberOfArrowsToBurstBalloons/Solution_test.go new file mode 100644 index 0000000..b8c02d3 --- /dev/null +++ b/Go/0452-MinimumNumberOfArrowsToBurstBalloons/Solution_test.go @@ -0,0 +1,70 @@ +package lc452 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFindMinArrowShots(t *testing.T) { + testCases := map[string]struct { + points [][]int + expected int + }{ + "Unsorted, some overlapping balloons": { + points: [][]int{ + {10, 16}, + {2, 8}, + {1, 6}, + {7, 12}, + }, + expected: 2, + }, + "Non-overlapping balloons": { + points: [][]int{ + {1, 2}, + {3, 4}, + {5, 6}, + {7, 8}, + }, + expected: 4, + }, + "Sorted, some overlapping balloons": { + points: [][]int{ + {1, 2}, + {2, 3}, + {3, 4}, + {4, 5}, + }, + expected: 2, + }, + "Unsorted, no overlap at centre, requiring two arrows": { + points: [][]int{ + {1, 2}, + {4, 5}, + {1, 5}, + }, + expected: 2, + }, + "Unsorted, first balloon has large interval": { + points: [][]int{ + {9, 12}, + {1, 10}, + {4, 11}, + {8, 12}, + {3, 9}, + {6, 9}, + {6, 7}, + }, + expected: 2, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := findMinArrowShots(tc.points) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0506-RelativeRanks/Solution.go b/Go/0506-RelativeRanks/Solution.go new file mode 100644 index 0000000..f60cf15 --- /dev/null +++ b/Go/0506-RelativeRanks/Solution.go @@ -0,0 +1,69 @@ +package lc506 + +import ( + "container/heap" + "strconv" +) + +func findRelativeRanks(score []int) []string { + comparator := func(i int, j int) bool { + return score[i] > score[j] + } + indices := make([]int, len(score)) + for i := range score { + indices[i] = i + } + + pq := &IntPriorityQueue{ + values: indices, + comp: comparator, + } + heap.Init(pq) + + ranks := make([]string, len(score)) + + for i := range len(score) { + idx := heap.Pop(pq).(int) + + switch i { + case 0: + ranks[idx] = "Gold Medal" + case 1: + ranks[idx] = "Silver Medal" + case 2: + ranks[idx] = "Bronze Medal" + default: + ranks[idx] = strconv.Itoa(i + 1) + } + } + return ranks +} + +type IntPriorityQueue struct { + values []int + comp func(i int, j int) bool +} + +func (pq IntPriorityQueue) Len() int { + return len(pq.values) +} + +func (pq IntPriorityQueue) Less(i int, j int) bool { + return pq.comp(pq.values[i], pq.values[j]) +} + +func (pq IntPriorityQueue) Swap(i int, j int) { + pq.values[i], pq.values[j] = pq.values[j], pq.values[i] +} + +func (pq *IntPriorityQueue) Push(x any) { + pq.values = append(pq.values, x.(int)) +} + +func (pq *IntPriorityQueue) Pop() any { + old := pq.values + n := len(old) + item := old[n-1] + pq.values = old[0 : n-1] + return item +} diff --git a/Go/0506-RelativeRanks/Solution_test.go b/Go/0506-RelativeRanks/Solution_test.go new file mode 100644 index 0000000..0df0cc8 --- /dev/null +++ b/Go/0506-RelativeRanks/Solution_test.go @@ -0,0 +1,31 @@ +package lc506 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFindRelativeRanks(t *testing.T) { + testCases := map[string]struct { + score []int + expected []string + }{ + "Input array sorted in decreasing order": { + score: []int{5, 4, 3, 2, 1}, + expected: []string{"Gold Medal", "Silver Medal", "Bronze Medal", "4", "5"}, + }, + "Unsorted array": { + score: []int{10, 3, 8, 9, 4}, + expected: []string{"Gold Medal", "5", "Bronze Medal", "Silver Medal", "4"}, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := findRelativeRanks(tc.score) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0605-CanPlaceFlowers/Solution.go b/Go/0605-CanPlaceFlowers/Solution.go new file mode 100644 index 0000000..0077664 --- /dev/null +++ b/Go/0605-CanPlaceFlowers/Solution.go @@ -0,0 +1,26 @@ +package lc605 + +func CanPlaceFlowers(flowerbed []int, n int) bool { + isAdjacent := false + for _, pot := range flowerbed { + if hasFlower(pot) { + if isAdjacent { + n++ // Previous flower was invalid + } + isAdjacent = true + continue + } + if isAdjacent { + isAdjacent = false + continue + } + isAdjacent = true + n-- + } + + return n <= 0 +} + +func hasFlower(pot int) bool { + return pot == 1 +} diff --git a/Go/0605-CanPlaceFlowers/Solution_test.go b/Go/0605-CanPlaceFlowers/Solution_test.go new file mode 100644 index 0000000..54d31be --- /dev/null +++ b/Go/0605-CanPlaceFlowers/Solution_test.go @@ -0,0 +1,39 @@ +package lc605 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCanPlaceFlowers(t *testing.T) { + testCases := map[string]struct { + flowerbed []int + newFlowers int + expected bool + }{ + "Can plant 1 flower": { + flowerbed: []int{1, 0, 0, 0, 1}, + newFlowers: 1, + expected: true, + }, + "Cannot plant 2 flowers": { + flowerbed: []int{1, 0, 0, 0, 1}, + newFlowers: 2, + expected: false, + }, + "Cannot plant 2 flowers, need to check next flower": { + flowerbed: []int{1, 0, 0, 0, 0, 1}, + newFlowers: 2, + expected: false, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := CanPlaceFlowers(tc.flowerbed, tc.newFlowers) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0739-DailyTemperatures/Solution.go b/Go/0739-DailyTemperatures/Solution.go new file mode 100644 index 0000000..4c366c8 --- /dev/null +++ b/Go/0739-DailyTemperatures/Solution.go @@ -0,0 +1,47 @@ +package lc739 + +func dailyTemperatures(temperatures []int) []int { + result := make([]int, len(temperatures)) + + // Stack stores indices i such that for all i, j in {0, ..., n-1}, i != j, i < j, + // temperatures[i] >= temperatures[j] + // i.e. temperatures that are monotonically non-increasing. + // Initialized with a capacity the size of temperatures array to avoid unnecessary resizing. + indices := make([]int, 0, len(temperatures)) + + // Iterate through the daily temperatures + for i, dailyTemp := range temperatures { + // If stack is non-empty, let the temperature represented by the index on the top of the stack be x. + // Then, there are two possible cases: + // Case 1: Current dailyTemp is less than or equal to x. + // Then, simply add dailyTemp to the stack and continue + // Case 2: Current dailyTemp is greater than (warmer!) than x. + // Then, pop x and update the resultant array, since we found a warmer day. + // Repeat until either the stack is empty, or dailyTemp is less than or equal to x. + // Add dailyTemp to the stack. + for len(indices) != 0 { + topIndex := indices[len(indices)-1] // peek at the top of the stack + topTemperature := temperatures[topIndex] + + // Case 1 + if dailyTemp <= topTemperature { + break // Move on to the next day + } + + // Case 2 + // pop topTemperature and update number of days till warmer day + indices = indices[:len(indices)-1] + result[topIndex] = i - topIndex + } + // For both cases, add the index of the current temperature to the stack. + indices = append(indices, i) + } + + // If the stack is still non-empty after iterating through all elements, then we know that + // the temperatures in the stack cannot have warmer days, and should be set to 0. (monotonically decreasing) + // Though, the result array is already intiialized to zero, thus this loop is not required. + // for _, index := range indices { + // result[index] = 0 + // } + return result +} diff --git a/Go/0739-DailyTemperatures/Solution_test.go b/Go/0739-DailyTemperatures/Solution_test.go new file mode 100644 index 0000000..503db1f --- /dev/null +++ b/Go/0739-DailyTemperatures/Solution_test.go @@ -0,0 +1,35 @@ +package lc739 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDailyTemperatures(t *testing.T) { + testCases := map[string]struct { + temperatures []int + expected []int + }{ + "Non-monotonic daily temperatures": { + temperatures: []int{73, 74, 75, 71, 69, 72, 76, 73}, + expected: []int{1, 1, 4, 2, 1, 1, 0, 0}, + }, + "Monotonically increasing daily temperatures": { + temperatures: []int{30, 40, 50, 60}, + expected: []int{1, 1, 1, 0}, + }, + "Small temperatures length": { + temperatures: []int{30, 60, 90}, + expected: []int{1, 1, 0}, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := dailyTemperatures(tc.temperatures) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Go/0881-BoatsToSavePeople/Solution.go b/Go/0881-BoatsToSavePeople/Solution.go new file mode 100644 index 0000000..9db0a4f --- /dev/null +++ b/Go/0881-BoatsToSavePeople/Solution.go @@ -0,0 +1,87 @@ +package lc881 + +import "slices" + +// Optimal Substructure: +// For any pair of persons that belong on a single boat in an optimal solution, +// $minBoats(people) = 1 + minBoats(people\{w_1, w_2})$ +// +// Greedy Choice Propery: +// If there exists a pair {w_1, w_2} that fit onto a single boat in an optimal +// solution, then there must be a pair that includes the smallest file in **some** +// optimal solution. +// +// O(n + k) where k is the limit. +// Benchmarks show that the Comparison-based implementation runs faster/has a lower time per operation +// than the counting sort-based implementation. This is due to the relatively small size of the array +// used in the test as compared to the range of the integer. +// +// Similar to CS3230 Greedy Algorithms Tutorial +func numRescueBoats(people []int, limit int) int { + countingSort(people, limit) + + numBoats := 0 + lightestIndex := 0 + heaviestIndex := len(people) - 1 + for lightestIndex <= heaviestIndex { + lighest := people[lightestIndex] + heaviest := people[heaviestIndex] + + if lighest+heaviest > limit { + heaviestIndex-- + } + if lighest+heaviest <= limit { + lightestIndex++ + heaviestIndex-- + } + numBoats += 1 + } + + return numBoats +} + +// Sorts the given array in-place using Counting Sort. +// Not Stable. +// O(k + n) where k is the maximum possible value of the integers in the array. +func countingSort(arr []int, k int) { + frequencies := make([]int, k+1) + for _, num := range arr { + frequencies[num]++ + } + + index := 0 + for num, freq := range frequencies { + if freq == 0 { + continue + } + for j := 0; j < freq; j++ { + arr[index] = num + index++ + } + } +} + +// Sorted using a Comparison-Based sorting algorithm. +// O(nlogn) +func numRescueBoatsComparisonBasedSorting(people []int, limit int) int { + slices.Sort(people) + + numBoats := 0 + lightestIndex := 0 + heaviestIndex := len(people) - 1 + for lightestIndex <= heaviestIndex { + lighest := people[lightestIndex] + heaviest := people[heaviestIndex] + + if lighest+heaviest > limit { + heaviestIndex-- + } + if lighest+heaviest <= limit { + lightestIndex++ + heaviestIndex-- + } + numBoats += 1 + } + + return numBoats +} diff --git a/Go/0881-BoatsToSavePeople/Solution_test.go b/Go/0881-BoatsToSavePeople/Solution_test.go new file mode 100644 index 0000000..aa76378 --- /dev/null +++ b/Go/0881-BoatsToSavePeople/Solution_test.go @@ -0,0 +1,59 @@ +package lc881 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNumRescueBoats(t *testing.T) { + testCases := map[string]struct { + people []int + limit int + expected int + }{ + "One boat is sufficient": { + people: []int{1, 2}, + limit: 3, + expected: 1, + }, + "Multiple boats needed. Not necessarily fully utilised": { + people: []int{1, 2, 2, 3}, + limit: 3, + expected: 3, + }, + "One boat for each person": { + people: []int{3, 5, 3, 4}, + limit: 5, + expected: 4, + }, + "Each boat carries at most 2 person": { + people: []int{1, 1, 1}, + limit: 3, + expected: 2, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := numRescueBoats(tc.people, tc.limit) + assert.Equal(t, tc.expected, actual) + + actual = numRescueBoatsComparisonBasedSorting(tc.people, tc.limit) + assert.Equal(t, tc.expected, actual) + }) + } +} + +func BenchmarkNumRescueBoats(b *testing.B) { + for i := 0; i < b.N; i++ { + numRescueBoats([]int{89, 59, 32, 74, 81, 12, 7, 49, 43, 51, 62, 91, 27}, 100) + } +} + +func BenchmarkNumRescueBoatsComparisonBasedSorting(b *testing.B) { + for i := 0; i < b.N; i++ { + numRescueBoatsComparisonBasedSorting([]int{89, 59, 32, 74, 81, 12, 7, 49, 43, 51, 62, 91, 27}, 100) + } +} diff --git a/Go/0938-RangeSumOfBST/Solution.go b/Go/0938-RangeSumOfBST/Solution.go new file mode 100644 index 0000000..3ffbb12 --- /dev/null +++ b/Go/0938-RangeSumOfBST/Solution.go @@ -0,0 +1,28 @@ +package lc938 + +import ( + "gitlab.com/euchangxian/leetcode/Go/internal/bst" +) + +func RangeSumBST(root *bst.TreeNode, low int, high int) int { + var sum int + search(&sum, root, low, high) + return sum +} + +func search(sum *int, node *bst.TreeNode, low int, high int) { + if node == nil { + return + } + + if inRange(node.Val, low, high) { + *sum += node.Val + } + search(sum, node.Left, low, high) + search(sum, node.Right, low, high) +} + +// inRange evaluates whether a given val is in the range [low, high], inclusive. +func inRange(val int, low int, high int) bool { + return val >= low && val <= high +} diff --git a/Go/0938-RangeSumOfBST/Solution_test.go b/Go/0938-RangeSumOfBST/Solution_test.go new file mode 100644 index 0000000..d15bca1 --- /dev/null +++ b/Go/0938-RangeSumOfBST/Solution_test.go @@ -0,0 +1,94 @@ +package lc938 + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "gitlab.com/euchangxian/leetcode/Go/internal/bst" +) + +// 10 +// 5 15 +// +// 3 7 18 +var input1 = &bst.TreeNode{ + Val: 10, + Left: &bst.TreeNode{ + Val: 5, + Left: &bst.TreeNode{ + Val: 3, + }, + Right: &bst.TreeNode{ + Val: 7, + }, + }, + Right: &bst.TreeNode{ + Val: 15, + Right: &bst.TreeNode{ + Val: 18, + }, + }, +} + +// 10 +// 5 15 +// 3 7 13 18 +// +// 1 6 +var input2 = &bst.TreeNode{ + Val: 10, + Left: &bst.TreeNode{ + Val: 5, + Left: &bst.TreeNode{ + Val: 3, + Left: &bst.TreeNode{ + Val: 1, + }, + }, + Right: &bst.TreeNode{ + Val: 7, + Left: &bst.TreeNode{ + Val: 6, + }, + }, + }, + Right: &bst.TreeNode{ + Val: 15, + Left: &bst.TreeNode{ + Val: 13, + }, + Right: &bst.TreeNode{ + Val: 18, + }, + }, +} + +func TestRangeSumBST(t *testing.T) { + testCases := map[string]struct { + inputTree *bst.TreeNode + low int + high int + expected int + }{ + "Small tree": { + inputTree: input1, + low: 7, + high: 15, + expected: 32, + }, + "Big tree": { + inputTree: input2, + low: 6, + high: 10, + expected: 23, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + actual := RangeSumBST(tc.inputTree, tc.low, tc.high) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/Rust/0002-AddTwoNumbers/Solution.rs b/Rust/0002-AddTwoNumbers/Solution.rs new file mode 100644 index 0000000..fe72e6d --- /dev/null +++ b/Rust/0002-AddTwoNumbers/Solution.rs @@ -0,0 +1,62 @@ +pub struct Solution {} + +use crate::commons::linked_list::ListNode; + +impl Solution { + pub fn add_two_numbers( + l1: Option>, + l2: Option>, + ) -> Option> { + match (l1, l2) { + (None, None) => None, // If both l1 and l2 are None (null) + (Some(n), None) | (None, Some(n)) => Some(n), // No need to add + (Some(n1), Some(n2)) => { + let sum = n1.val + n2.val; + if sum > 9 { + let carry = Some(Box::new(ListNode::new(1))); + return Some(Box::new(ListNode { + val: sum - 10, + next: Solution::add_two_numbers( + Solution::add_two_numbers(carry, n1.next), + n2.next, + ), + })); + } + Some(Box::new(ListNode { + val: sum, + next: Solution::add_two_numbers(n1.next, n2.next), + })) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_add_two_numbers() { + assert_eq!( + ListNode::from_vector(vec![7, 0, 8]), + Solution::add_two_numbers( + ListNode::from_vector(vec![2, 4, 3]), + ListNode::from_vector(vec![5, 6, 4]) + ) + ); + assert_eq!( + ListNode::from_vector(vec![0]), + Solution::add_two_numbers( + ListNode::from_vector(vec![0]), + ListNode::from_vector(vec![0]) + ) + ); + assert_eq!( + ListNode::from_vector(vec![8, 9, 9, 9, 0, 0, 0, 1]), + Solution::add_two_numbers( + ListNode::from_vector(vec![9, 9, 9, 9, 9, 9, 9]), + ListNode::from_vector(vec![9, 9, 9, 9]) + ) + ); + } +} diff --git a/Rust/0032-LongestValidParentheses/Solution.rs b/Rust/0032-LongestValidParentheses/Solution.rs new file mode 100644 index 0000000..aab8a9c --- /dev/null +++ b/Rust/0032-LongestValidParentheses/Solution.rs @@ -0,0 +1,59 @@ +pub struct Solution {} + +impl Solution { + pub fn longest_valid_parentheses(s: String) -> i32 { + if s.len() < 2 { + return 0; + } + + let s = s.as_bytes(); + + // dp[i] represents the length of the longest valid parentheses substrings ending at the + // i-th index + let mut dp = vec![0; s.len()]; + for i in 1..s.len() { + let c = s[i]; + if c == b'(' { + continue; // dp[i] = 0 + } + + // Check i - (dp[i-1] + 1) >= 0 + // Attempts to find the potential matching opening parentheses '(' index, + // since dp[i-1] represents the length of the longest valid parentheses at (i-1)-th index, + // subtract the length of the current longest valid parentheses, then subtract 1 to + // get the potential opening parentheses. E.g. for "()(())" at i = 5(last parentheses), + // dp[i-1] = 2. Thus, i - dp[i-1] = 3, which represents the index of the inner opening + // parentheses. Subtract 1 to get the index of the outer opening parentheses. + if let Some(idx) = i.checked_sub(dp[i - 1] + 1) { + if s[idx] == b'(' { + dp[i] = dp[i - 1] + 2; + + // Handle the case where theres a valid parentheses substring before the + // matching opening parentheses. + if idx > 0 { + dp[i] += dp[idx - 1]; + } + } + } + } + let max = dp.iter().max(); + match max { + Some(val) => *val as i32, + None => panic!("Vector of at least length 1 should have a max"), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_longest_valid_parentheses() { + assert_eq!(2, Solution::longest_valid_parentheses("(()".to_string())); + assert_eq!(4, Solution::longest_valid_parentheses(")()())".to_string())); + assert_eq!(0, Solution::longest_valid_parentheses("".to_string())); + assert_eq!(2, Solution::longest_valid_parentheses("()".to_string())); + assert_eq!(6, Solution::longest_valid_parentheses("()(())".to_string())); + } +} diff --git a/Rust/0058-LengthOfLastWord/Solution.rs b/Rust/0058-LengthOfLastWord/Solution.rs new file mode 100644 index 0000000..2cf7af3 --- /dev/null +++ b/Rust/0058-LengthOfLastWord/Solution.rs @@ -0,0 +1,30 @@ +pub struct Solution {} + +impl Solution { + pub fn length_of_last_word(s: String) -> i32 { + return s + .trim_end() + .chars() + .rev() + .take_while(|c| c.is_alphabetic()) + .count() as i32; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_length_of_last_word() { + assert_eq!(5, Solution::length_of_last_word("Hello World".to_string())); + assert_eq!( + 4, + Solution::length_of_last_word(" fly me to the moon ".to_string()) + ); + assert_eq!( + 6, + Solution::length_of_last_word("luffy is still joyboy".to_string()) + ); + } +} diff --git a/Rust/0072-EditDistance/Solution.rs b/Rust/0072-EditDistance/Solution.rs new file mode 100644 index 0000000..bd4ac91 --- /dev/null +++ b/Rust/0072-EditDistance/Solution.rs @@ -0,0 +1,96 @@ +pub struct Solution {} + +// Similar to Longest Common Subsequence at first glance. +impl Solution { + pub fn min_distance(word1: String, word2: String) -> i32 { + let m: usize = word1.len(); + let n: usize = word2.len(); + let word1 = word1.as_bytes(); + let word2 = word2.as_bytes(); + + // dp[i][j] represents the minimum number of operations required to convert word1[0..i] to + // word2[0..j]. + let mut dp: Vec> = Vec::with_capacity(m + 1); + for _ in 0..m + 1 { + dp.push(vec![0; n + 1]) + } + + // Init base cases: dp[i][0] = i, dp[0][j] = j + // i.e. edges of the 'matrix' + for (i, row) in dp.iter_mut().enumerate() { + // different loop style since Clippy raises a warning. + row[0] = i as i32; + } + for j in 0..n + 1 { + dp[0][j] = j as i32; + } + + for i in 1..m + 1 { + for j in 1..n + 1 { + // 1-indexed, minus 1 offset + if word1[i - 1] == word2[j - 1] { + // No operations needed + dp[i][j] = dp[i - 1][j - 1]; + continue; + } + // Compare insert (dp[i][j-1] + 1), replace (dp[i-1][j-1] + 1) + // and delete (dp[i-1][j] + 1) operations + dp[i][j] = + std::cmp::min(dp[i][j - 1], std::cmp::min(dp[i - 1][j - 1], dp[i - 1][j])) + 1; + } + } + + dp[m][n] + } + + pub fn min_distance_1d(word1: String, word2: String) -> i32 { + let m: usize = word1.len(); + let n: usize = word2.len(); + let word1 = word1.as_bytes(); + let word2 = word2.as_bytes(); + + // dp[j] represents the minimum number of operations required to convert word1[0..i] to + // word2[0..j] after the i-th iteration. + // Base case: 0-th iteration, dp[j] = j + let mut dp: Vec = (0..(n + 1) as i32).collect(); + + for i in 1..m + 1 { + let mut temp: Vec = dp.clone(); + temp[0] = i as i32; + for j in 1..n + 1 { + if word1[i - 1] == word2[j - 1] { + temp[j] = dp[j - 1]; + continue; + } + temp[j] = std::cmp::min(temp[j - 1], std::cmp::min(dp[j - 1], dp[j])) + 1; + } + dp = temp; + } + dp[n] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_min_distance() { + assert_eq!( + 3, + Solution::min_distance("horse".to_string(), "ros".to_string()) + ); + assert_eq!( + 5, + Solution::min_distance("intention".to_string(), "execution".to_string()) + ); + assert_eq!( + 3, + Solution::min_distance_1d("horse".to_string(), "ros".to_string()) + ); + assert_eq!( + 5, + Solution::min_distance_1d("intention".to_string(), "execution".to_string()) + ); + } +} diff --git a/Rust/0097-InterleavingString/Solution.rs b/Rust/0097-InterleavingString/Solution.rs new file mode 100644 index 0000000..bfcbbf5 --- /dev/null +++ b/Rust/0097-InterleavingString/Solution.rs @@ -0,0 +1,63 @@ +pub struct Solution {} + +impl Solution { + pub fn is_interleave(s1: String, s2: String, s3: String) -> bool { + let n: usize = s1.len(); + let m: usize = s2.len(); + + if n + m != s3.len() { + return false; + } + + let s1 = s1.as_bytes(); + let s2 = s2.as_bytes(); + let s3 = s3.as_bytes(); + + // After the i-th iteration, dp[j] represents whether s3[0..i+j] can be obtained + // from interleaving s1[0..i] and s2[0..j] + let mut dp: Vec = vec![false; m + 1]; + dp[0] = true; + for j in 1..m + 1 { + dp[j] = dp[j - 1] && s2[j - 1] == s3[j - 1]; + } + + for i in 1..n + 1 { + dp[0] = dp[0] && s3[i - 1] == s1[i - 1]; + for j in 1..m + 1 { + dp[j] = (dp[j] && s3[i + j - 1] == s1[i - 1]) + || (dp[j - 1] && s3[i + j - 1] == s2[j - 1]); + } + } + + dp[m] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_interleave() { + assert!(Solution::is_interleave( + "aabcc".to_string(), + "dbbca".to_string(), + "aadbbcbcac".to_string() + )); + assert!(!Solution::is_interleave( + "aabcc".to_string(), + "dbbca".to_string(), + "aadbbbaccc".to_string() + )); + assert!(Solution::is_interleave( + "".to_string(), + "".to_string(), + "".to_string() + )); + assert!(!Solution::is_interleave( + "".to_string(), + "".to_string(), + "a".to_string() + )); + } +} diff --git a/Rust/0786-KthSmallestPrimeFraction/Solution.rs b/Rust/0786-KthSmallestPrimeFraction/Solution.rs new file mode 100644 index 0000000..b216bba --- /dev/null +++ b/Rust/0786-KthSmallestPrimeFraction/Solution.rs @@ -0,0 +1,60 @@ +pub struct Solution {} + +// Given a strictly increasing array of 1 and prime numbers, it seems like a binary search +// problem intuitively. +// However, binary searching for the k-th smallest fraction given elements that could be +// numerators OR denominators is not straightforward. +// +// A naive approach would be to have two pointers, one at the left-most to take the +// smallest numerator, and one at the right-most to take the largest numerator, forming the +// smallest fraction. But this is wrong. +// E.g. arr = [1, 2, 5], k = 2. Naively taking 1/2 (0.5) as the second fraction will be +// wrong, since 2/5 (0.4) is a smaller fraction and is the second smallest fraction. +// +// Another naive (but correct) approach will simply be enumerating all possible fractions, +// then sorting and taking the k-th smallest fraction. This takes O(n^2) space-time. +impl Solution { + pub fn kth_smallest_prime_fraction(arr: Vec, k: i32) -> Vec { + let n: usize = arr.len(); + let mut fractions: Vec> = Vec::with_capacity(n * (n + 1) / 2); + + for i in 0..n { + for j in i + 1..n { + fractions.push(vec![arr[i], arr[j]]); + } + } + + fractions.sort_by(|frac1, frac2| { + let num1 = frac1[0] as f64; + let denom1 = frac1[1] as f64; + let num2 = frac2[0] as f64; + let denom2 = frac2[1] as f64; + + (num1 / denom1).total_cmp(&(num2 / denom2)) + }); + + let kth_fraction = &fractions[(k - 1) as usize]; // borrow occurs here... + vec![kth_fraction[0], kth_fraction[1]] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_kth_smallest_prime_fraction() { + assert_eq!( + vec![2, 5], + Solution::kth_smallest_prime_fraction(vec![1, 2, 5], 2) + ); + assert_eq!( + vec![2, 5], + Solution::kth_smallest_prime_fraction(vec![1, 2, 3, 5], 3) + ); + assert_eq!( + vec![1, 7], + Solution::kth_smallest_prime_fraction(vec![1, 7], 1) + ); + } +} diff --git a/Rust/0857-MinimumCostToHireKWorkers/Solution.rs b/Rust/0857-MinimumCostToHireKWorkers/Solution.rs new file mode 100644 index 0000000..0467d6a --- /dev/null +++ b/Rust/0857-MinimumCostToHireKWorkers/Solution.rs @@ -0,0 +1,69 @@ +pub struct Solution {} + +// Looks like a variant of 0/1 knapsack at first glance. +// Upon taking a closer look at the question requirements, it seems like the approach should be to +// take workers with a small difference between their wage/quality ratios. +impl Solution { + pub fn mincost_to_hire_workers(quality: Vec, wage: Vec, k: i32) -> f64 { + let k = k as usize; + let mut workers: Vec<(f64, i32)> = quality + .iter() + .zip(wage.iter()) + .map(|(&q, &w)| (w as f64 / q as f64, q)) + .collect(); + + // Sort workers by their wage-quality ratio + workers.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); + + let mut quality_heap = std::collections::BinaryHeap::new(); + let mut quality_sum = 0; + let mut min_cost = f64::MAX; + + for (ratio, quality) in workers.iter() { + quality_heap.push(quality); + quality_sum += quality; + + // If there are already k workers in the heap, remove the one with the highest quality + if quality_heap.len() > k { + if let Some(high_quality) = quality_heap.pop() { + quality_sum -= high_quality; + } + } + + // calculate the cost + if quality_heap.len() == k { + let cost = ratio * quality_sum as f64; + if cost < min_cost { + min_cost = cost; + } + } + } + + min_cost + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // Helper function to allow for tolerance when comparing floats. + fn are_close(a: f64, b: f64, tolerance: f64) -> bool { + (a - b).abs() < tolerance + } + const TOLERANCE: f64 = 1e-5; + + #[test] + fn test_mincost_to_hire_workers() { + assert!(are_close( + 105.00000, + Solution::mincost_to_hire_workers(vec![10, 20, 5], vec![70, 50, 30], 2), + TOLERANCE + )); + assert!(are_close( + 30.66667, + Solution::mincost_to_hire_workers(vec![3, 1, 10, 10, 1], vec![4, 8, 2, 2, 7], 3), + TOLERANCE + )); + } +} diff --git a/Rust/0861-ScoreAfterFlippingMatrix/Solution.rs b/Rust/0861-ScoreAfterFlippingMatrix/Solution.rs new file mode 100644 index 0000000..7867b0f --- /dev/null +++ b/Rust/0861-ScoreAfterFlippingMatrix/Solution.rs @@ -0,0 +1,60 @@ +pub struct Solution {} + +// For rows, greedily set the most-significant bits/left-most bits to 1. +// For columns, toggle such that there are more 1 bits than 0. +// A toggle can be intepreted as a bitwise XOR(^) operation. +// i32::from_str_radix(str: String, base: i32) +impl Solution { + pub fn matrix_score(grid: Vec>) -> i32 { + let mut grid: Vec> = grid; + let n: usize = grid[0].len(); + + // count the number of occurences of 1s in each column. + let mut frequencies: Vec = vec![0; n]; + + // Toggle rows if the MSB is 0. + for row in grid.iter_mut() { + let flip = row[0] == 0; + for (j, num) in row.iter_mut().enumerate() { + *num = if flip { 1 - *num } else { *num }; + frequencies[j] += *num; + } + } + // For each column, if the frequencies are LTE floor of m/2, then toggle it. + let m: usize = grid.len(); + for (j, freq) in frequencies.iter().enumerate() { + if *freq > (m as i32) / 2 { + continue; + } + for row in grid.iter_mut() { + row[j] = if row[j] == 1 { 0 } else { 1 }; + } + } + + let mut sum: i32 = 0; + + for row in grid { + let mut exp: u32 = n as u32; + for num in row { + sum += num * i32::pow(2, exp - 1); + exp -= 1; + } + } + + sum + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_matrix_score() { + assert_eq!( + 39, + Solution::matrix_score(vec![vec![0, 0, 1, 1], vec![1, 0, 1, 0], vec![1, 1, 0, 0]]) + ); + assert_eq!(1, Solution::matrix_score(vec![vec![0]])); + } +}