November 2023 – Recap

10th day vs 25th day
  • 57 problems solved
  • Studied: Arrays, Strings, Binary Search, Hash Tables

Hello. I’m writing this at 5:00 AM. This month was somewhat busy, but since November 11, when I decided to commit at least 1 hour to PS every day, I have been keeping it. No matter if I have a midterm tomorrow, an internship application due, or if I’ve slept only 3 hours and my brain is not working, I just sit down for an hour. It’s inefficient at times, but it’s symbolic of my commitment to not losing track. I hope I maintain this pace next month too.

I often get scared doing PS/CP since this is definitely a field where effort can’t win talent and I doubt if I have talent. Oftentimes I think “what if I just spend all this time on learning full-stack dev or machine learning”? If there is someone who have done CP and decided to go to data science or quant field, I want to ask “How do you overcome the fear stemming from spending time on uncertain things?” But anyways there is no going back.

Well. This was reflection for this month. Thank you for reading whoever reading this. I don’t usually share this blog to anyone I know because I want to show them when I have a solid result and not the process.

1. Two Sum – Solution

Difficulty: Easy
Topics: Array, Hash Table

Question

Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.

You may assume that each input would have exactly one solution, and you may not use the same element twice.

You can return the answer in any order.

Example 1:Input: nums = [2,7,11,15], target = 9 Output: [0,1] Explanation: Because nums[0] + nums[1] == 9, we return [0, 1].

Example 2:Input: nums = [3,2,4], target = 6 Output: [1,2]

Example 3:Input: nums = [3,3], target = 6 Output: [0,1]

Solution

While storing the numbers in hashmap, check if the complementary (target – number) is in the hashmap.

Note that hashmap[nums[i]] = i; should not be before the part where we search for comp. This is because search for comp should be done in values previously put in, not including the current value.

vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> hashmap;
        vector<int> res;
        for (int i = 0; i < nums.size(); i++){
            int comp = target - nums[i];
            if (hashmap.count(comp) > 0){
                res = {hashmap[comp], i};
                return res;
            }
            hashmap[nums[i]] = i;
        }
        return res;
    }

Hashmap in C++

Basic Usages

unordered_map<int, int> hashmap;
// 2. insert a new (key, value) pair
hashmap.insert(make_pair(2, 3));
// 3. insert a new (key, value) pair or update the value of existed key
hashmap[1] = 1;
hashmap[1] = 2;
// 4. get the value of a specific key
hashmap[1]
// 5. delete a key
hashmap.erase(2);
// 6. check if a key is in the hash map
if (hashmap.count(2) <= 0);
// 7. get the size of the hash map
hashmap.size()
// 8. iterate the hash map
for (auto it = hashmap.begin(); it != hashmap.end(); ++it) {
cout << it->first << "," << it->second << endl;
}
// 9. clear the hash map
hashmap.clear();
// 10. check if the hash map is empty
if (hashmap.empty())
Iterating over list of keys:
for (Type key : keys) {
        if (hashmap.count(key) > 0) {
            if (hashmap[key] satisfies the requirement) {
                return needed_information;
            }
        }
        // Value can be any information you needed (e.g. index)
        hashmap[key] = value;
    }

(to be continued…)

Iterating over map
for (auto& it : myMap) {
vec.push_back(it);
}
Sorting map based on value
#include <iostream>
#include <map>
#include <vector>
#include <algorithm>

// Comparator function to sort pairs by second value
bool sortByVal(const std::pair<int, int>& a, const std::pair<int, int>& b) {
return (a.second < b.second);
}

int main() {
// Define a map
std::map<int, int> myMap = {{1, 40}, {2, 30}, {3, 60}, {4, 20}};

// Copy elements to a vector of pairs
std::vector<std::pair<int, int>> vec;
for (auto& it : myMap) {
vec.push_back(it);
}

// Sort the vector by value
std::sort(vec.begin(), vec.end(), sortByVal);

// Print the sorted vector
for (auto& it : vec) {
std::cout << it.first << ": " << it.second << std::endl;
}

return 0;
}

Tips

  • When counting frequency, don’t need to check if the hashmap[x] exists.
// don't do this:
if (hashmap.count(x) > 0) hashmap[x]++;
else hashmap[x] = 1;
// do this:
hashmap[x]++;

// same for adding up the frequency:
for (...){
sum += hashmap[x];
}

658. Find K Closest Elements – Solution

Question

Difficulty: medium
Topics: Array, Two Pointers, Binary Search, Sliding Window

Given a sorted integer array arr, two integers k and x, return the k closest integers to x in the array. The result should also be sorted in ascending order.

An integer a is closer to x than an integer b if:

  • |a - x| < |b - x|, or
  • |a - x| == |b - x| and a < b

Example 1:Input: arr = [1,2,3,4,5], k = 4, x = 3 Output: [1,2,3,4]

Example 2:Input: arr = [1,2,3,4,5], k = 4, x = -1 Output: [1,2,3,4]

Solution

This was a question that I struggled a lot. It was harder than usual binary search problems. There are many ways to think for this problem.

Solution 1: O(N⋅log(N)+k⋅log(k))

compute the distances between the elements and x, sort it and display the first k elements. When displaying it, sort again.

Solution 2: O(log(N)+k) – Bin. Search + 2 Pointers

Search the index of element closest to x. Then, start l and r pointers from there and expand them based on the smallest distance.

Solution 3: O(log(Nk)) – Find the left bound.

Search the left bound with binary search. Let l = 0 and r = N-k which is the maximum index that a left bound can be.

compute the mid point. Now, assuming the mid is the left bound, mid+k is the right bound. Then,

If value at mid is closer to x:

  • mid and further cannot be the left bound because then the right bound would be more distant from x than mid.

But, since there can be an even better left bound in the left side, we continue the bin search with the left side.

so we do r = mid;

If value at mid+k is closer to x:

  • index at mid or lower can’t be left bound. The left bound is somewhere between mid+1 ~ mid+k

so we do l = mid+1;

class Solution {
public:
vector<int> findClosestElements(vector<int>& arr, int k, int x) {
int l = 0, r = arr.size() - k;
while (l < r){
int mid = (l+r)/2;
if (x - arr[mid] > arr[mid+k] - x){
l = mid + 1;
}
else{
r = mid;
}
}
return vector<int>(arr.begin() + l, arr.begin() + l + k);
}
};

There is another way of seeing this: (from lee215’s solution post)

We compare the distance between x - A[mid] and A[mid + k] - x
If we assume A[mid] ~ A[mid + k] is sliding window

case 1: x – A[mid] < A[mid + k] – x, need to move window go left
——-x—-A[mid]—————–A[mid + k]———-

case 2: x – A[mid] < A[mid + k] – x, need to move window go left again
——-A[mid]—-x—————–A[mid + k]———-

case 3: x – A[mid] > A[mid + k] – x, need to move window go right
——-A[mid]——————x—A[mid + k]———-

case 4: x – A[mid] > A[mid + k] – x, need to move window go right
——-A[mid]———————A[mid + k]—-x——

Reflection

I think whenever using binary search, I should think about based on what criteria I will choose the left side or the right side given a mid point. This was particularly harder because I didn’t think of finding the left bound. Let’s remember that when finding a range, I can try to find only left or right bound.

50. Pow(x,n) – Solution

Difficulty: medium
Topics: math, recursion

Implement pow(x, n), which calculates x raised to the power n

Example 1:
Input: x = 2.00000, n = 10
Output: 1024.00000

Example 2:
Input: x = 2.10000, n = 3
Output: 9.26100

Example 3:
Input: x = 2.00000, n = -2
Output: 0.25000

Solution

O(n) approach would be just multiplying x n times:

Pow(x,n)=x*x*x\dotsm \Large

O(logn) approach is using an algorithm called “binary exponentiation“. This requires understanding that x^n can be decomposed in 2 ways:

if n is even:

x^{n} = {x}^{\frac{n}{2}}*{x}^{\frac{n}{2}} \Large

if n is odd:

x^{n} = x * x^{n-1} = x * {x}^{\frac{n-1}{2}} * {x}^{\frac{n-1}{2}} \Large

This can be handled recursively. x^n is decomposed into expressions with smaller ‘n’s until n becomes 0. When n becomes 0, we return 1.

class Solution {
public:
    double binaryExp(double x, long long n) {
        if (n == 0) return 1;
       
        // Handle case where, n < 0.
        if (n < 0) return 1.0 / binaryExp(x, -1 * n);
       
        // Perform Binary Exponentiation.
        double rec = binaryExp(x, n / 2);
        if (n % 2 == 1) return x * rec * rec;
        else return rec * rec;
    }

    double myPow(double x, int n) {
        return binaryExp(x, (long long) n);
    }
};

34. Find First and Last Position of Element in Sorted Array – Solution

Difficulty: medium
Topics: array, binary search

Given an array of integers nums sorted in non-decreasing order, find the starting and ending position of a given target value.

If target is not found in the array, return [-1, -1].

You must write an algorithm with O(log n) runtime complexity.

Example 1:
Input: nums = [5,7,7,8,8,10], target = 8
Output: [3,4]

Example 2:
Input: nums = [5,7,7,8,8,10], target = 6
Output: [-1,-1]

Example 3:
Input: nums = [], target = 0
Output: [-1,-1]

Leetcode problem 34

Solution

The way to handle this is by finding start and end pos separately.

We can do this by implementing separate binary searches to find lower bound and higher bound of target.

OR just make one binary search to find the lower bound of target and find the end position by finding the lower bound of (target+1).

The first approach is shown below:

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        // if it's empty,
        if (nums.size() == 0){
            vector<int> res = {-1, -1};
            return res;
        }
        
        // search left bound
        int l = 0, r = nums.size()-1;
        int leftrange = 0;
        while (l < r){
            int mid = (l+r)/2;
            if (nums[mid] >= target) r = mid;
            else l = mid+1;
        }
        if (nums[l] != target) leftrange = -1; 
        else leftrange = l; 

        // search right bound
        l = 0, r = nums.size()-1;
        int rightrange = 0;
        while (l < r){
            int mid = (l+r+1)/2;
            if (nums[mid] <= target) l = mid;
            else r = mid-1;
        }
        if (nums[l] != target) rightrange = -1;
        else rightrange = l; 
        
        // print the result
        vector<int> res = {leftrange, rightrange};
        return res;
    }
};

By only implementing binSearch that finds lower bound:

class Solution {
private:
    int lower_bound(vector<int>& nums, int low, int high, int target){
        while(low <= high){
            int mid = (low + high) >> 1;
            if(nums[mid] < target){
                low = mid + 1;
            }
            else{
                high = mid - 1;
            }
        }
        return low;
    }

public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int low = 0, high = nums.size()-1;
        int startingPosition = lower_bound(nums, low, high, target);
        int endingPosition = lower_bound(nums, low, high, target + 1) - 1;
        if(startingPosition < nums.size() && nums[startingPosition] == target){
            return {startingPosition, endingPosition};
        }
        return {-1, -1};
    }
};

Since we already have a built in lower_bound library function, we can use that:

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int startingPosition = lower_bound(nums.begin(), nums.end(), target) - nums.begin();
        int endingPosition = lower_bound(nums.begin(), nums.end(), target+1) - nums.begin() - 1;
        if(startingPosition < nums.size() && nums[startingPosition] == target){
            return {startingPosition, endingPosition};
        }
        return {-1, -1};
    }
};

As I begin this…

There is a question that doesn’t seem to leave my mind these days.

Have I ever passionately devoted my time on something, losing the track of time and surroundings? Have I ever experienced a graceful failure, one that I feel no remorse or sadness as I know I couldn’t have done it better?

Seeing some people who possess the ‘grit’ to become the best in their field, I am always amazed and ashamed at the same time.

I have explored diverse areas like game development in Unity, app development with Android Studio, TensorFlow, Web Development, etc. But my greatest regret is not specializing in one area and failing to learn a unique skill that sets me apart.

These days, I think I have finally decided what job to pursue after months of contemplation and wavering. I am interested in quantitative development and finance, fields that require an extraordinary level of talent and effort. I am also interested in Data Science and will probably do that if not quant.

For a job in quant, mathematical and problem solving skills are really important. Therefore, as of November 2023, I am starting to study DS and Algorithms with C++. I have never seen my limits as I have never put deadly effort into something. Therefore, I can’t even conclude if I am suitable or not for this kind of math heavy career (which makes me mad). I am in my 1st year 1st semester of college as I write this, and now the time has come. It’s okay to fail. Let’s see how far I can go with this.

“My math genius friend is having difficulty getting internships. Do you think you are going to quant?”

“My sister’s friend who graduated from Caltech is working in quant. and my sister says she is so talented”

Remember. Grit and Patience. and Never give up.