Showing posts with label Bit Mask. Show all posts
Showing posts with label Bit Mask. Show all posts

Tuesday, September 8, 2020

Leetcode 1542: Find Longest Awesome Substring

 https://leetcode.com/problems/find-longest-awesome-substring/description/



Notes:

This question is similar to another question, Leetcode 1371

The basic idea is the same: use a mask to memorize the counting of each letters. In this question, the letters are numbers from 0 to 9. 

Then we need to know whether the counting of each letter is even or odd. So we can use a bit to indicate this. For each bit, 0 means even count; 1 means odd.

If the same mask shows up again, then the substring between these two masks should have even numbers of every letter; and this is what we need to construct a palindrome.

Another possible situation for a palindrome is to have one letter with odd counting. This can also be checked by add one more letter (for each kind) to the mask.

See the code below:

class Solution {
public:
    int longestAwesome(string s) {
        int n = s.size(), res = 0, mask = 0;
        vector<int> dp(1<<10, -2);
        dp[0] = -1;
        for(int i=0; i<n; ++i) {
            mask ^= 1<<(s[i] - '0');
            if(dp[mask] != -2) res = max(res, i - dp[mask]);
            else dp[mask] = i;// memorize the first position
            for(int j=0; j<10; ++j) {// check one letter with odd count
                int t = mask ^ (1<<j);
                if(dp[t] != -2) res = max(res, i - dp[t]);
            }
        }
        return res;
    }
};

Sunday, September 6, 2020

Leetcode 1371: Find the Longest Substring Containing Vowels in Even Counts

 https://leetcode.com/problems/find-the-longest-substring-containing-vowels-in-even-counts/description/


Notes:

This question can be solved by a method similar to "how many sub-array sum equals to a target value". If you have question about this, we can go step by step.

The naive method is to scan all the sub-strings, and then check the counts in each sub-string, and find the longest one. This needs O(n^2) time, so this is the upper-bound for this question.

Actually the largest case is n = 5 x 10^5, so we need a better method.

Let think about one letter only: what is the longest sub-string containing even count of one specific letter?

There are many ways to do this, but in order to achieve O(n) time complexity, we can use a bit to represents the count of the letter: 0 for even, 1 for odd. Then as we scan through the string, at each position, we know the count is even or odd. 

If even: the current longest sub-string is from the first even to the current position;
if odd:  the current longest sub-string is from the first odd to the current position.

Now, we need to monitor five letters, but the idea is the same. We just use a bit mask to replace one bit.

If we use a bit mask to record the counts of each letter; 0 for even, 1 for odd, then when the mask appear again, the sub-string between these two mask should contain even number of each letter.

One hash-map can be used to record the position (or index) of each mask when it appears for the first time, since we need this information to calculate the length of the current longest valid sub-string.

See the code below:

class Solution {
public:
    int findTheLongestSubstring(string s) {
        int res = 0, n = s.size(), mask = 0;
        vector<int> dp(1<<5, s.size());// only 5 letters, so 5 bits
        dp[0] = -1;// special case for boundary condition
        for(int i=0; i<n; ++i) {
            char a = s[i];
            if(a=='a') mask ^= 1<<0;
            else if(a=='e') mask ^= 1<<1;
            else if(a=='i') mask ^= 1<<2;
            else if(a=='o') mask ^= 1<<3;
            else if(a=='u') mask ^= 1<<4;
            res = max(res, i - dp[mask]);
            dp[mask] = min(i, dp[mask]);// record the first position
        }
        return res;
    }
};

Friday, September 20, 2019

Leetcode 1066: Campus Bikes II

On a campus represented as a 2D grid, there are N workers and M bikes, with N <= M. Each worker and bike is a 2D coordinate on this grid.
We assign one unique bike to each worker so that the sum of the Manhattan distances between each worker and their assigned bike is minimized.
The Manhattan distance between two points p1 and p2 is Manhattan(p1, p2) = |p1.x - p2.x| + |p1.y - p2.y|.
Return the minimum possible sum of Manhattan distances between each worker and their assigned bike.

Example 1:
Input: workers = [[0,0],[2,1]], bikes = [[1,2],[3,3]]
Output: 6
Explanation: 
We assign bike 0 to worker 0, bike 1 to worker 1. The Manhattan distance of both assignments is 3, so the output is 6.
Example 2:
Input: workers = [[0,0],[1,1],[2,0]], bikes = [[1,0],[2,2],[2,1]]
Output: 4
Explanation: 
We first assign bike 0 to worker 0, then assign bike 1 to worker 1 or worker 2, bike 2 to worker 2 or worker 1. Both assignments lead to sum of the Manhattan distances as 4.

Note:
  1. 0 <= workers[i][0], workers[i][1], bikes[i][0], bikes[i][1] < 1000
  2. All worker and bike locations are distinct.
  3. 1 <= workers.length <= bikes.length <= 10


Notes:

It is said that this question is from one of the onsite interviews of Google company.

The naive method is that we try all the possible combinations using backtracking, but apparently we re-calculate a lot of sub-states.

In order to better see this, let us consider two cases. One is that we let worker 1 choose bike 1, and worker 2 pick bike 2, then the rest workers choose the rest bikes (sub-state 1). The other case is that, worker 1 chooses bike 2, and worker 2 chooses bike 1, then the rest workers choose the rest bikes (sub-state 2). Apparently, sub-state 1 is the same as sub-state 2, so we just need to calculate it once, and then re-use the result directly if need it again.

To this question, bit mapping is a naturally choice to label the state. 0 represents un-used and 1 represents used. (The very small values of the number of both workers and bikes are good indicators to this.)

Since both the number of the workers and bikes are no larger than 10, an integer (32 bits) should be enough to record it. Thus, we can use two integers to memorize the combination conditions between workers and bikes. In the following code, integer state1 is for workers, and integer state2 for bikes.

Suppose there are N workers and M bikes, the total possible states is (2^N)*(2^M). So the space  complexity is O((2^N)*(2^M)). Since we only calculate once for each state, the time complexity is the same as the space complexity.

[update on 10/07/2019]: The time complexity is O(N*M*2^N*2^M), since for each substate calculation, or local minimum, we have run all the possible matches between workers and bikes.]

See the code below:

class Solution {
public:
    int minManHaDis(vector<vector<int>> &ws, vector<vector<int>> &bs) {
        int m = ws.size(), n = bs.size();//m <= n based on the question;
        vector<vector<int>> memo(1<<m, vector<int>(1<<n, -1));
        int state1 = 0, state2 = 0;
        int res = dfs(0, ws, state1, bs, state2, memo);
        //cout<<res<<endl;
        return res;
    }

private:
    int dfs(int i, vector<vector<int>> &ws, int st1, vector<vector<int>> &bs, int st2, vector<vector<int>> &memo) {
        if(i==ws.size()) return 0;//all the workers are assigned;
        if(memo[st1][st2] != -1) return memo[st1][st2];//calculated;
        int min_dis = INT_MAX;
        for(int j = 0; j < bs.size(); ++j) {//try all the bikes;
            if((st2>>j) & 1) continue;//if the bike is used;
            int one_dis = abs(ws[i][0] - bs[j][0]) + abs(ws[i][1] - bs[j][1]);
            one_dis += dfs(i+1, ws, st1 | (1<<i), bs, st2 | (1<<j), memo);//total dis in this case;
            min_dis = min(min_dis, one_dis);//min distance;
        }
        return memo[st1][st2] = min_dis;//memorize min distance;
    }
};

Leetcode 526: Beautiful Arrangement

https://leetcode.com/problems/beautiful-arrangement/description/



Notes:

This question needs backtracking. But one improvement can be added by memorization.

Let us see the code without memorization first:

class Solution {
public:
    int countArrangement(int N) {
        int res = 0, indx = 0;
        vector<int> visit(N, 0);
        dfs(indx, N, visit, res);
        return res;
    }

private:
    void dfs(int indx, int N, vector<int> &visit, int &res) {
        if(indx == N) {
            ++res;
            return;
        }
        for(int i=1; i<=N; ++i) {
            if(visit[i-1]) continue;
            if(!(i%(indx+1)) || !((indx+1)%i)) {
                visit[i-1] = 1;
                dfs(indx+1, N, visit, res);
                visit[i-1] = 0;
            }
        }
    }
};

Memorization can be applied to this question: let us think about two cases. One is that we put 1 at position 1, and 2 at position 2, and the rest are placed at positions from 3 to n. The other is that we put 2 at position 1, and 1 at position 2, and the res are placed at positions from 3 to n. So in this two cases, the state for the rest elements except 1 and 2 are the same. One of the fundamental is: for the same state, we just need to calculate once.

In this situation, we can use bit-mapping to represent the states.

See the code below:

class Solution {
public:
    int countArrangement(int N) {
        int id = 0, visit = 0;
        vector<int> memo(1<<N, -1);//for each bit, 0 means not selected, 1 selected;
        return dfs(id, N, visit, memo);
    }

private:
    int dfs(int id, int &N, int visit, vector<int> &memo) {
        if(id == N) return 1;
        if(memo[visit] != -1) return memo[visit];
        int t = 0;
        for(int i=1; i<=N; ++i) {
            if((visit>>(i-1)) & 1) continue;
            if(!(i%(id+1)) || !((id+1)%i)) {
                t += dfs(id+1, N, visit | (1<<(i-1)), memo);
            }
        }
        return memo[visit] = t;
    }
};

Thursday, September 19, 2019

Leetcode 421: Maximum XOR of Two Numbers in an Array

https://leetcode.com/problems/maximum-xor-of-two-numbers-in-an-array/description/



Notes:

This O(n) solution to this question is not that straightforward. There is one implicit condition: there are only 32 digits for each number in the binary form. So we can check them digit by digit. But how can we do this?

One solution is found here:



The basic idea is to scan all the number from most significant bit to the least one by using bit mask. For each step, we exam essentially 1 bit more, then gradually cover all of them.

See the code below:

class Solution {
public:
    int findMaximumXOR(vector<int>& nums) {
        int res = 0,mask = 0;
        for(int i=31; i>=0; --i){
            mask = mask | (1<<i);
            set<int> s;
            for(auto &n : nums) {
                s.insert(mask & n);
            }
            int nextTry = res | (1<<i);//exam whether the the ith bit is 1 or 0.
            for(auto &j : s){
                if(s.count(nextTry ^ j)){// is 1
                    res = nextTry;
                    break;
                }
            }
        }
        return res;
    }
};

There is another way implementing with a binary search tree (or trie). See the code below:

class Solution {
public:
    class TreeNode {
    public:
        TreeNode* next[2];
     //   TreeNode(): next[0](NULL), next[1](NULL) {}//constructor;
        TreeNode() {next[0] = NULL; next[1] = NULL;};//constructor;
        ~TreeNode() {//destructor;
            for(auto &a : next) delete a;
        }
    };

    int findMaximumXOR(vector<int>& nums) {
        int res = 0;
        TreeNode *root = buildTree(nums);
        for(auto &a : nums) {
            res = max(res, helper(root, a));
        }
        return res;
    }

    TreeNode* buildTree(vector<int> &vs) {//build a tree, return the root;
        TreeNode *root = new TreeNode(), *cur;
        for(auto &a : vs) {
            cur = root;
            for(int i=31; i>=0; --i) {
                int index = (a >> i) & 1;
                if(!cur->next[index]) cur->next[index] = new TreeNode();
                cur = cur->next[index];
            }
        }
        return root;
    }
   
    int helper(TreeNode* cur, int num) {
        int res = 0;
        for(int i=31; i>=0; --i) {
            int index = ((num >> i) & 1) ? 0 : 1;//XOR
            res <<= 1;
            if(cur->next[index]) {
                res |= 1;
                cur = cur->next[index];
            }
            else {
                res |= 0;
                cur = cur->next[index?0:1];
            }
        }
        return res;
    }
};

Saturday, August 31, 2019

Leetcode 1178. Number of Valid Words for Each Puzzle

https://leetcode.com/problems/number-of-valid-words-for-each-puzzle/description/

With respect to a given puzzle string, a word is valid if both the following conditions are satisfied:
  • word contains the first letter of puzzle.
  • For each letter in word, that letter is in puzzle.
    For example, if the puzzle is "abcdefg", then valid words are "faced", "cabbage", and "baggage"; while invalid words are "beefed" (doesn't include "a") and "based" (includes "s" which isn't in the puzzle).
Return an array answer, where answer[i] is the number of words in the given word list words that are valid with respect to the puzzle puzzles[i].

Example :
Input: 
words = ["aaaa","asas","able","ability","actt","actor","access"], 
puzzles = ["aboveyz","abrodyz","abslute","absoryz","actresz","gaswxyz"]
Output: [1,1,3,2,4,0]
Explanation:
1 valid word for "aboveyz" : "aaaa" 
1 valid word for "abrodyz" : "aaaa"
3 valid words for "abslute" : "aaaa", "asas", "able"
2 valid words for "absoryz" : "aaaa", "asas"
4 valid words for "actresz" : "aaaa", "asas", "actt", "access"
There're no valid words for "gaswxyz" cause none of the words in the list contains letter 'g'.

Constraints:
  • 1 <= words.length <= 10^5
  • 4 <= words[i].length <= 50
  • 1 <= puzzles.length <= 10^4
  • puzzles[i].length == 7
  • words[i][j], puzzles[i][j] are English lowercase letters.
  • Each puzzles[i] doesn't contain repeated characters.

Notes:

The key to this question is bit masking and recording. Since the words' length and puzzles' length are very long, a no-brainer method cannot pass the largest test case.

Some key information:
1): the size of each puzzles[i] is very short, and unique;
2): if a letter shows up in the words[i], then it is required to show up in puzzles[i] too; otherwise, it cannot be counted in;
3): from 2), the repeated letters in words[i] does not play a role. Thus, we can simply convert the string to a integer.

So we just need first to count the frequency of each words[i]. Then is the key: we will not go through the counting list for each puzzles[i] (which is too slow), instead, we will generate each sub-set (needs containing the first letter) of puzzles[i], then add the frequency recording previously together.

How to generate all the sub-set of a string or array, check this post.

See the code below:

class Solution {
public:
    vector<int> findNumOfValidWords(vector<string>& words, vector<string>& puzzles) {
        vector<int> res;
        unordered_map<int, int> mp; //count the frequency of the string in words
        for(auto &w : words) {
            int num = 0;
            for(auto &a : w) num |= 1<<(a-'a');
            ++mp[num];
        }
        for(auto &p : puzzles) {
            int ct = 0, len = p.size();
            for(int i=0; i<(1<<(len-1)); ++i) {//the first one is always in
                int mk = 1<<(p[0] - 'a');
                for(int j=1; j<len; ++j){
                    if(i & (1<<(j-1))) mk |= 1<<(p[j]-'a');
                }
                ct += mp[mk];
            }
            res.push_back(ct);
        }
        return res;
    }
};