Sunday, March 28, 2021

Representation and dot product of two vectors

 Question:

Given the following vectors (arbitrary numbers), design a more memory-efficient representation of these vectors.

A: [1, 1, 4, 4, 4, 4, 7, 7, 7, 7, 7, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
B: [2, 2, 5, 5, 5, 5, 5, 5, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]

Follow-up:
With the vectors represented as suggested, write a function to calculate the dot product of two vectors.

Example:

Input:
A: [1, 1, 4, 4, 4, 4, 7, 7, 7, 7, 7, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
B: [2, 2, 5, 5, 5, 5, 5, 5, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]

Output: 291

Explanation: 1 * 2 + 1 * 2 + 4 * 5 + ... + 2 * 3

Additional information:

  • The vectors are guaranteed to contain a large number of duplicate values, similar to the example given above.

======== https://leetcode.com/discuss/interview-question/286446/Representation-and-dot-product-of-two-vectors/271143
#include <bits/stdc++.h>
using namespace std;
typedef vector<int> vi;
typedef vector<vector<int>> vvi;

vector<vector<int>> rle(vi& A){
    int n = A.size();

    vvi ans;
    int cnt = 0;

    for(int i = 0; i < n;){        
        int curr = A[i];
        while(curr == A[i]){
            i++;
            cnt++;
        }
        ans.push_back({cnt, curr});
        cnt = 0;
    }

    return ans;
}

int dotP(vvi A, vvi B){

    int n = A.size(), m = B.size();
    int i = 0, j = 0;
    int sum = 0;
    while(i < n and j < m){
        vi& p = A[i];
        vi& q = B[j];

        int common = min(p[0], q[0]);// cnt - min(2, 5) = 2

        p[0] -= common;
        q[0] -= common;

        sum += p[1] * q[1] * common;

        if(p[0] == 0)
            i++;
        if(q[0] == 0)
            j++;
    }

    return sum;
}


Saturday, March 27, 2021

93. Restore IP Addresses

Given a string s containing only digits, return all possible valid IP addresses that can be obtained from s. You can return them in any order.

valid IP address consists of exactly four integers, each integer is between 0 and 255, separated by single dots and cannot have leading zeros. For example, "0.1.2.201" and "192.168.1.1" are valid IP addresses and "0.011.255.245", "192.168.1.312" and "192.168@1.1" are invalid IP addresses. 

 

Example 1:

Input: s = "25525511135"
Output: ["255.255.11.135","255.255.111.35"]

Example 2:

Input: s = "0000"
Output: ["0.0.0.0"]

Example 3:

Input: s = "1111"
Output: ["1.1.1.1"]

Example 4:

Input: s = "010010"
Output: ["0.10.0.10","0.100.1.0"]

Example 5:

Input: s = "101023"
Output: ["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]

 

Constraints:

  • 0 <= s.length <= 3000
  • s consists of digits only.
class Solution {
public:
    vector<string> restoreIpAddresses(string s) {
        vector<string> ans;
        if (s.size() > 12) {
            return ans;
        }
        
        string sol;
        helper(s, 0, sol, ans);
        return ans;
    }
    
    void helper(string s, int idx, string sol, vector<string>& ans) {
        if (idx == s.size()) {
            if (isValid(sol)) {
                ans.push_back(sol);
            }
            return;
        }
        
        for (int i = idx; i < s.size(); i++) {
            string str = s.substr(idx, i - idx + 1);
            if (digitCheck(str)) {
                string tmp = sol;
                if (sol.empty()) {
                    sol = str;
                } else {
                    sol += "." + str;
                }
                helper(s, i + 1, sol, ans);
                sol = tmp;
            }
        }
    }
    
    bool digitCheck(string str) {
        if (str.size() == 0 || str.size() > 3) {
            return false;
        }
        long digit = stol(str);
        if (str.size() == 1) {
            return (digit >= 0 && digit <= 9);
        } else if (str.size() == 2) {
            return (digit >= 10 && digit <= 99);
        } else {
            return (digit >= 100 && digit <= 255);
        }
    }
    
    bool isValid(string str) {
        if (str.size() == 0) {
            return false;
        }
        
        vector<string> strList = split(str, '.');
        if (strList.size() != 4) {
            return false;
        }
        return digitCheck(strList[0]) && digitCheck(strList[1]) &&
            digitCheck(strList[2]) && digitCheck(strList[3]);
    }
    
    vector<string> split(string str, char delim) {
        vector<string> ans;
        stringstream ss(str);
        string tmp;
        while (getline(ss, tmp, delim)) {
            ans.push_back(tmp);
        }
        return ans;
    }
};

========= 0 ms =========
class Solution { private: void helper(string s,vector<string>& temp,vector<string>& res,int length){ if(length==0 && temp.size() == 4){ string str = ""; str += (temp[0]+"."); str += (temp[1]+"."); str += (temp[2]+"."); str += (temp[3]); res.push_back(str); return; } else if(temp.size()==4 && length!=0) return; else if(temp.size()!=4 && length==0) return; for(int i=1;i<=3 && i<=length;i++){ string ts = s.substr(s.length()-length,i); int num = stoi(ts); if(num>=0 && num<=255 && (i==1 || ts[0]!='0')){ temp.push_back(ts); helper(s,temp,res,length-i); temp.pop_back(); } } } public: vector<string> restoreIpAddresses(string s) { vector<string> res; vector<string> temp; helper(s,temp,res,s.length()); return res; } };

 

Sunday, March 14, 2021

Target Sum

 You are given a list of non-negative integers, a1, a2, ..., an, and a target, S. Now you have 2 symbols + and -. For each integer, you should choose one from + and - as its new symbol.

Find out how many ways to assign symbols to make sum of integers equal to target S.

Example 1:

Input: nums is [1, 1, 1, 1, 1], S is 3. 
Output: 5
Explanation: 

-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3

There are 5 ways to assign symbols to make the sum of nums be target 3.

 

Constraints:

  • The length of the given array is positive and will not exceed 20.
  • The sum of elements in the given array will not exceed 1000.
  • Your output answer is guaranteed to be fitted in a 32-bit integer.

==== Recursive =====

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int S) {
        int count = 0;
        int sum = 0;
        helper(nums, 0, S, sum, count);
        return count;
    }
    
    void helper(vector<int>& nums, int idx, int S, int sum, int& count) {
        if (sum == S && idx == nums.size()) {
            count++;
            return;
        }
        if (idx >= nums.size()) {
            return;
        }        

        helper(nums, idx + 1, S, sum + nums[idx], count);
        helper(nums, idx + 1, S, sum - nums[idx], count);
    }
};


int findTargetSumWays(vector<int>& nums, int S) {
        int count = 0;
        int sum = 0;
        // Recursive.
        // helper(nums, 0, S, sum, count);
        // return count;
        
        // Dynamic
        map<string, int> mp;
        return dp_helper(nums, 0, S, sum, mp);
    }
    
    int dp_helper(vector<int>& nums, int idx, int S, int sum,
                   map<string, int>& mp) {
        
        string key = to_string(idx) + ":" + to_string(sum);
        if (mp.count(key) != 0) {
            return mp[key];
        }
        if (sum == S && idx == nums.size()) {
            mp[key] = 1;
            return 1;
        }
        if (idx >= nums.size()) {
            mp[key] = 0;
            return 0;
        }
        
        int option1 = dp_helper(nums, idx + 1, S, sum + nums[idx], mp);
        int option2 = dp_helper(nums, idx + 1, S, sum - nums[idx], mp);
        
        mp[key] = option1 + option2;
        return mp[key];
        
    }

Solution


Approach 1: Brute Force

Algorithm

The brute force approach is based on recursion. We need to try to put both the + and -symbols at every location in the given nums array and find out the assignments which lead to the required result S.

For this, we make use of a recursive function calculate(nums, i, sum, S), which returns the assignments leading to the sum S, starting from the i^{th} index onwards, provided the sum of elements upto the i^{th} element is sum. This function appends a + sign and a - sign both to the element at the current index and calls itself with the updated sum as sum + nums[i] and sum - nums[i] repectively along with the updated current index as i+1. Whenver, we reach the end of the array, we compare the sum obtained with S. If they are equal, we increment the count value to be returned.

Thus, the function call calculate(nums, 0, 0, S) retuns the required no. of assignments.

Complexity Analysis

  • Time complexity : O(2^n). Size of recursion tree will be 2^nn refers to the size of nums array.

  • Space complexity : O(n). The depth of the recursion tree can go upto n


Approach 2: Recursion with Memoization

Algorithm

It can be easily observed that in the last approach, a lot of redundant function calls could be made with the same value of i as the current index and the same value of sum as the current sum, since the same values could be obtained through multiple paths in the recursion tree. In order to remove this redundancy, we make use of memoization as well to store the results which have been calculated earlier.

Thus, for every call to calculate(nums, i, sum, S), we store the result obtained in memo[i][sum + 1000]. The factor of 1000 has been added as an offset to the sum value to map all the sums possible to positive integer range. By making use of memoization, we can prune the search space to a good extent.

Complexity Analysis

  • Time complexity: \mathcal{O}(l \cdot n). The memo array of size l*n has been filled just once. Here, l refers to the range of sum and n refers to the size of nums array.

  • Space complexity: \mathcal{O}(l \cdot n). The depth of recursion tree can go upto n. The memoarray contains l \cdot n elements. 


Approach 3: 2D Dynamic Programming

Algorithm

The idea behind this approach is as follows. Suppose we can find out the number of times a particular sum, say sum_i is possible upto a particular index, say i, in the given nums array, which is given by say count_i. Now, we can find out the number of times the sum sum_i + nums[i] can occur easily as count_i. Similarly, the number of times the sum sum_i - nums[i] occurs is also given by count_i.

Thus, if we know all the sums sum_j's which are possible upto the j^{th} index by using various assignments, along with the corresponding count of assignments, count_j, leading to the same sum, we can determine all the sums possible upto the (j+1)^{th} index along with the corresponding count of assignments leading to the new sums.

Based on this idea, we make use of a dp to determine the number of assignments which can lead to the given sum. dp[i][j] refers to the number of assignments which can lead to a sum of j upto the i^{th} index. To determine the number of assignments which can lead to a sum of sum + nums[i] upto the (i+1)^{th} index, we can use dp[i][sum + nums[i]] = dp[i][sum + nums[i]] + dp[i-1][sum]. Similarly, dp[i][sum - nums[i]] = dp[i][sum + nums[i]] + dp[i-1][sum]. We iterate over the dp array in a rowwise fashion i.e. Firstly we obtain all the sums which are possible upto a particular index along with the corresponding count of assignments and then proceed for the next element(index) in the nums array.

But, since the sum can range from -1000 to +1000, we need to add an offset of 1000 to the sum indices (column number) to map all the sums obtained to positive range only.

At the end, the value of dp[n-1][S+1000] gives us the required number of assignments. Here, n refers to the number of elements in the nums array.

The animation below shows the way various sums are generated along with the corresponding indices. The example assumes sum values to lie in the range of -6 to +6 just for the purpose of illustration. This animation is inspired by @Chidong

Current
7 / 7

Complexity Analysis

  • Time complexity : O(l*n). The entire nums array is travesed 2001(constant no.: l) times. n refers to the size of nums array. l refers to the range of sum possible.

  • Space complexity : O(l*n)dp array of size l*n is used. 


Approach 4: 1D Dynamic Programming

Algorithm

If we look closely at the last solution, we can observe that for the evaluation of the current row of dp, only the values of the last row of dp are needed. Thus, we can save some space by using a 1D DP array instead of a 2-D DP array. The only difference that needs to be made is that now the same dp array will be updated for every row traversed.

Below code is inspired by @Chidong

Complexity Analysis

  • Time complexity : O(l.n). The entire nums array is traversed l times. n refers to the size of nums array. l refers to the range of sum possible.

  • Space complexity : O(n)dp array of size n is used.