Showing posts with label Backtracking. Show all posts
Showing posts with label Backtracking. Show all posts

Sunday, November 10, 2019

Leetcode 1255. Maximum Score Words Formed by Letters

https://leetcode.com/contest/weekly-contest-162/problems/maximum-score-words-formed-by-letters/


Notes:

The question has a small size of input, so it can be solved by brute force backtracking directly. (If size is larger, memorization may be applied, since it is an optimal combination problem in nature.)

The basic idea is that:

1): we need to count the letter numbers that can be used in total;

2): then we can go back to the strings. If any string has one or more kinds of letter with more than the previous counting, we simply cannot use it. Otherwise, we can record the counting of letter of the string, which can be used later;

3): after the first and the second steps which work like a pre-processing, we just do a backtracking to find the optimal solution.


See the code below:

class Solution {
public:
    int maxScoreWords(vector<string>& words, vector<char>& letters, vector<int>& score) {
        vector<int> ct(26, 0);
        for(auto &a : letters) ++ct[a-'a'];
        vector<vector<int>> cts;
        for(int i=0; i<words.size(); ++i) {
            vector<int> ct1(26, 0);
            bool valid = true;
            for(auto &a : words[i]) {
                int id = a - 'a';
                ++ct1[id];
                if(ct1[id] > ct[id]) {
                    valid = false;
                    break;
                }
            }
            if(valid) cts.push_back(ct1);
            else {words.erase(words.begin() + i); --i;}
        }
        vector<int> cur(26, 0);
        return dfs(words, 0, cts, cur, ct, score);
    }

private:
    int dfs(vector<string>& ws, int id, vector<vector<int>>& cts, vector<int> cur, vector<int>& ct, vector<int>& ss) {
        if(id == ws.size()) return 0;
        int res = 0;
        for(int i=id; i<ws.size(); ++i) {
            vector<int> t(26, 0);
            bool valid = true;
            for(int j=0; j<26; ++j) {
                t[j] = cur[j] + cts[i][j];
                if(t[j] > ct[j]) {valid = false; break;}
            }
            if(valid) {
                int one = 0;//one possible solution
                for(auto &a : ws[i]) one += ss[a-'a'];
                one += dfs(ws, i+1, cts, t, ct, ss);
                res = max(res, one);
            }
        }
        return res;
    }
};

Friday, November 1, 2019

Leetcode 638: Shopping Offers

https://leetcode.com/problems/shopping-offers/description/

In LeetCode Store, there are some kinds of items to sell. Each item has a price.
However, there are some special offers, and a special offer consists of one or more different kinds of items with a sale price.
You are given the each item's price, a set of special offers, and the number we need to buy for each item. The job is to output the lowest price you have to pay for exactly certain items as given, where you could make optimal use of the special offers.
Each special offer is represented in the form of an array, the last number represents the price you need to pay for this special offer, other numbers represents how many specific items you could get if you buy this offer.
You could use any of special offers as many times as you want.
Example 1:
Input: [2,5], [[3,0,5],[1,2,10]], [3,2]
Output: 14
Explanation: 
There are two kinds of items, A and B. Their prices are $2 and $5 respectively. 
In special offer 1, you can pay $5 for 3A and 0B
In special offer 2, you can pay $10 for 1A and 2B. 
You need to buy 3A and 2B, so you may pay $10 for 1A and 2B (special offer #2), and $4 for 2A.
Example 2:
Input: [2,3,4], [[1,1,0,4],[2,2,1,9]], [1,2,1]
Output: 11
Explanation: 
The price of A is $2, and $3 for B, $4 for C. 
You may pay $4 for 1A and 1B, and $9 for 2A ,2B and 1C. 
You need to buy 1A ,2B and 1C, so you may pay $4 for 1A and 1B (special offer #1), and $3 for 1B, $4 for 1C. 
You cannot add more items, though only $9 for 2A ,2B and 1C.
Note:
  1. There are at most 6 kinds of items, 100 special offers.
  2. For each item, you need to buy at most 6 of them.
  3. You are not allowed to buy more items than you want, even if that would lower the overall price.

Notes:

This question can be solved as a knapsack problem: there are 6 bags... So the overall complexity is O(6 ^ 6 * 100).

This requires we need to define a 6 dimensional vector...

Let us try brute force backtracking first.

If we can find a usable deal, we can adjust the task after the deal, and continue (like a dfs) until reaching the end.

After calculating all the possible solutions, we choose the smallest one.

The time complexity is O(6! * 100).

And it turns out it can pass the online OJ. (since it is faster than DP!!!)

See the code below:

class Solution {
public:
    int shoppingOffers(vector<int>& price, vector<vector<int>>& special, vector<int>& needs) {
        int res = 0;
        for(int i=0; i<needs.size(); ++i) res += price[i]*needs[i];
        if(res == 0) return res;
        int t = -1;
        for(auto &a : special) {
            bool isV = true;
            for(int i=0; i<needs.size(); ++i) {
                if(a[i] > needs[i]) {
                    isV = false;
                    break;
                }
            }
            if(isV) {
                int one = a.back();
                for(int i=0; i<needs.size(); ++i) needs[i] -= a[i];
                one += shoppingOffers(price, special, needs);
                for(int i=0; i<needs.size(); ++i) needs[i] += a[i];
                if(t == -1) t = one;
                else t = min(t, one);
            }
        }
        return t > -1 ? min(res, t) : res;
    }
};

Monday, October 14, 2019

Leetcode 679: 24 Game

https://leetcode.com/problems/24-game/description/

You have 4 cards each containing a number from 1 to 9. You need to judge whether they could operated through *, /, +, -, (, ) to get the value of 24.
Example 1:
Input: [4, 1, 8, 7]
Output: True
Explanation: (8-4) * (7-1) = 24
Example 2:
Input: [1, 2, 1, 2]
Output: False
Note:
  1. The division operator / represents real division, not integer division. For example, 4 / (1 - 2/3) = 12.
  2. Every operation done is between two numbers. In particular, we cannot use - as a unary operator. For example, with [1, 1, 1, 1] as input, the expression -1 - 1 - 1 - 1 is not allowed.
  3. You cannot concatenate numbers together. For example, if the input is [1, 2, 1, 2], we cannot write this as 12 + 12.

Notes:

This question can be solved by DFS (brutal force version). But here I want to share another solution using Divide and Conquer.

In some sense, due to the using of (), this question is similar to Leetcode 241: Different Ways to Add Parentheses.

First step, we can get all the permutations of the four numbers.

Then for each permutation, we can run divide and conquer to get all the possible results. The key is how to divide them first and then merge them back.

See the code below:

class Solution {
public:
    bool judgePoint24(vector<int>& nums) {
        double epslon = 0.00001;
        vector<vector<int>> allPermu = findAllPermu(nums);
        for(auto &a : allPermu) {
            vector<double> res = findAll(a, 0, (int)nums.size()-1);
            for(auto &a : res) {
                if(abs(a-24)<epslon) return true;
            }
        }
        return false;
    }

private:
    vector<vector<int>> findAllPermu(vector<int>& nums) {
        set<vector<int>> res;
        int len = nums.size();
        vector<bool> visit(len, false);
        vector<int> temp;
        dfs(nums, visit, temp, res);
        return vector<vector<int>> (res.begin(), res.end());
    }

    void dfs(vector<int>& nums, vector<bool>& visit, vector<int>& ts, set<vector<int>>& res) {
        if(ts.size() == nums.size()) {
            res.insert(ts);
            return;
        }
        for(int i=0; i<nums.size(); ++i) {
            if(visit[i]) continue;
            visit[i] = true;
            ts.push_back(nums[i]);
            dfs(nums, visit, ts, res);
            ts.pop_back();
            visit[i] = false;
        }
    }

    vector<double> findAll(vector<int>& nums, int left, int right) {
        if(left > right) return {};
        if(left == right) return {(double)nums[left]};
        set<double> res;
        for(int i=left; i<right; ++i) {
            auto lvs = findAll(nums, left, i);
            auto rvs = findAll(nums, i+1, right);
            for(auto &a: lvs) {
                for(auto &b: rvs) {
                    res.insert(a+b);
                    res.insert(a-b);
                    res.insert(a*b);
                    if(b != 0.0) res.insert(a/b);
                }
            }
        }
        return vector<double> (res.begin(), res.end());
    }
};

Wednesday, October 9, 2019

Leetcode 1088: Confusing Number II

https://leetcode.com › problems › confusing-number-ii

We can rotate digits by 180 degrees to form new digits. When 0, 1, 6, 8, 9 are rotated 180 degrees, they become 0, 1, 9, 8, 6 respectively. When 2, 3, 4, 5 and 7 are rotated 180 degrees, they become invalid.

A confusing number is a number that when rotated 180 degrees becomes a different number with each digit valid.(Note that the rotated number can be greater than the original number.)

Given a positive integer N, return the number of confusing numbers between 1 and N inclusive.



Example 1:

Input: 20
Output: 6
Explanation:
The confusing numbers are [6,9,10,16,18,19].
6 converts to 9.
9 converts to 6.
10 converts to 01 which is just 1.
16 converts to 91.
18 converts to 81.
19 converts to 61.
Example 2:

Input: 100
Output: 19
Explanation:
The confusing numbers are [6,9,10,16,18,19,60,61,66,68,80,81,86,89,90,91,98,99,100].


Note:

1 <= N <= 10^9


Notes:

The N to this question can be very large, so checking one by one is not a good choice.

As indicated by the question itself, only 0, 1, 6, 8, and 9 are valid numbers. So we just need to check the numbers composed by them.

Since we will compare digits at different positions, so string can be a good choice for easier coding than integer.

Now the question becomes a typical backtracking one: we will try all the possible combinations, then check the qualification. If valid, count +1.

See the code below:

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

using namespace std;

int findAllConfNums(int n);

void dfs(string t, string &s, string &nums, int &res);

int main() {
    int n = 100;
    int num = findAllConfNums(n);
    cout<<num;
    return 0;
}

int findAllConfNums(int n) {
    int res = 0;
    string s = to_string(n);
    string nums = "01689";
    dfs("", s, nums, res);
    return res;
}

void dfs(string t, string &s, string &nums, int &res) {
    if(t.size() > s.size() || t.size() == s.size() && t.compare(s) > 0) return;
    if(t.size() && t[0] == '0') return;
    string temp = t;
    reverse(temp.begin(), temp.end());
    for(int i=0; i<temp.size(); ++i) {
        if(temp[i] == '6') temp[i] = '9';
        else if(temp[i] == '9') temp[i] = '6';
    }
    if(t != temp) ++res;
    for(int i=0; i<nums.size(); ++i) {
        dfs(t + nums.substr(i, 1), s, nums, res);
    }
}

Thursday, September 26, 2019

Lintcode 829: Word Pattern II

https://www.lintcode.com/problem/word-pattern-ii/description


Notes:

This question is a very good back-tracking problem.

The key for back-tracking is how to recover (or go back) the previous condition.

In this question, we need to correlate the letter in pattern with the substring in the string. So we need two hash maps to record this bijection relationship.

When we plan to move from one step to the next step, we need to make judgement whether we should go or not.

(1) If we can make a decision right away without further exploring, we should stop immediately; and then move on to the next possible step;

(2) if we cannot tell right now, we have to continue the exploring to the next step (this is called dfs);

(3) before we move on to the next step, we should know exactly what kinds of changes have been made to the key parameters in order to move onto the next step (and this is the key to this question);

(4) if the result from the further exploring is not valid, we need to recover the changes made in step (3) (this is called back-tracking);

(5) if the result from the further exploring is valid, we find a valid solution and can return true;

(6) if we have tried all the possible paths but still cannot find a valid one, return false.


See the code below:

class Solution {
public:
    /**
     * @param pattern: a string,denote pattern string
     * @param str: a string, denote matching string
     * @return: a boolean
     */
    bool wordPatternMatch(string &pattern, string &str) {
        // write your code here
        unordered_map<char, string> m1;
        unordered_map<string, char> m2;
        return dfs(pattern, 0, str, 0, m1, m2);
    }

private:
    bool dfs(string &s1, int id1, string &s2, int id2, unordered_map<char, string> &m1, unordered_map<string, char> &m2) {
        if(id1 == s1.size()) return id2 == s2.size();
        if(id2 == s2.size()) return id1 == s1.size();
        if((int)s1.size() - id1 > (int)s2.size() - id2) return false;
        char c = s1[id1];
        for(int i=id2; i<s2.size(); ++i) {
            string s = s2.substr(id2, i-id2+1);
            if(m1.count(c) && m1[c] != s) continue;
            if(m2.count(s) && m2[s] != c) continue;
            bool f1 = false, f2 = false;
            if(!m1.count(c)) {//f1 used to label the real change in m1;
                m1[c] = s;
                f1 = true;
            }
            if(!m2.count(s)) {//f2 used to label the real change in m2;
                m2[s] = c;
                f2 = true;
            }
            if(dfs(s1, id1+1, s2, i+1, m1, m2)) return true;
            if(f1) m1.erase(c);
            if(f2) m2.erase(s);
        }
        return false;
    }
}

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;
    }
};

Monday, August 19, 2019

Leetcode 698: Partition to K Equal Sum Subsets

https://leetcode.com/problems/partition-to-k-equal-sum-subsets/description/


Notes:

This question can be viewed as a follow up of Leetcode 416.

However, we cannot apply knapsack method to this question when k > 2, since we need to remember which elements have been chosen, then continue the search until all the elements can be grouped with the sum required.

So it is a typical back-tracking problem.

See the code below:

class Solution {
public:
    bool canPartitionKSubsets(vector<int>& nums, int k) {
        if(nums.size() < k) return false;
        int sum = 0;
        for(auto &a : nums) sum += a;
        if(sum%k != 0) return false;
        sort(nums.begin(), nums.end());
        if(nums.back()>sum/k) return false;
        reverse(nums.begin(), nums.end());
        vector<bool> visit(nums.size(), false);
        return dsf(nums, visit, sum/k, k, 0);
    }
private:
    bool dsf(vector<int>& ns, vector<bool>& visit, int s, int k, int ac){
        if(k==0) return true;
        if(ac == s) return dsf(ns, visit, s, k-1, 0);
        for(int i=0; i<ns.size(); ++i){
            if(visit[i]) continue;
            if(ac + ns[i] > s) break;
            visit[i] = true;
            if(dsf(ns, visit, s, k, ac+ns[i])) return true;
            visit[i] = false;
        }
        return false;
    }
};