Showing posts with label Strategy. Show all posts
Showing posts with label Strategy. Show all posts

LeetCode 1029 - Two City Scheduling


https://leetcode.com/problems/two-city-scheduling/
There are 2N people a company is planning to interview. The cost of flying the i-th person to city A is costs[i][0], and the cost of flying the i-th person to city B is costs[i][1].
Return the minimum cost to fly every person to a city such that exactly N people arrive in each city.

Example 1:
Input: [[10,20],[30,200],[400,50],[30,20]]
Output: 110
Explanation: 
The first person goes to city A for a cost of 10.
The second person goes to city A for a cost of 30.
The third person goes to city B for a cost of 50.
The fourth person goes to city B for a cost of 20.

The total minimum cost is 10 + 30 + 50 + 20 = 110 to have half the people interviewing in each city.

Note:
  1. 1 <= costs.length <= 100
  2. It is guaranteed that costs.length is even.
  3. 1 <= costs[i][0], costs[i][1] <= 1000

Greedy
Sort by cost_a – cost_b, Choose the first n/2 people for A, rest for B
https://leetcode.com/problems/two-city-scheduling/discuss/278716/C%2B%2B-O(n-log-n)-sort-by-savings
How much money can we save if we fly a person to A vs. B? To minimize the total cost, we should fly the person with the maximum saving to A, and with the minimum - to B.
Example: [30, 100], [40, 90], [50, 50], [70, 50].
Savings: 70, 50, 0, -20.
Obviously, first person should fly to A, and the last - to B.

Solution

We sort the array by the difference between costs for A and B. Then, we fly first N people to A, and the rest - to B.
int twoCitySchedCost(vector<vector<int>>& cs, int res = 0) {
  sort(begin(cs), end(cs), [](vector<int> &v1, vector<int> &v2) {
    return (v1[0] - v1[1] < v2[0] - v2[1]);
  });
  for (auto i = 0; i < cs.size() / 2; ++i) {
    res += cs[i][0] + cs[i + cs.size() / 2][1];
  }
  return res;
}

Optimized Solution

Actually, we do not need to perfectly sort all cost differences, we just need the biggest savings (to fly to A) to be in the first half of the array. So, we can use the quick select algorithm (nth_element in C++) and use the middle of the array as a pivot.
This brings the runtime down from 8 ms to 4 ms (thanks @popeye1 for the tip!)
int twoCitySchedCost(vector<vector<int>>& cs, int res = 0) {
  nth_element(begin(cs), begin(cs) + cs.size() / 2, end(cs), [](vector<int> &a, vector<int> &b) {
    return (a[0] - a[1] < b[0] - b[1]);
  });
  for (auto i = 0; i < cs.size() / 2; ++i) {
    res += cs[i][0] + cs[i + cs.size() / 2][1];
  }
  return res;
}

Complexity Analysis

Runtime: O(n log n). We sort the array then go through it once. The second solution has a better average case runtime.
Memory: O(1). We sort the array in-place

https://leetcode.com/problems/two-city-scheduling/discuss/278898/Java-2ms-sorting-solution-with-explanation
The cost of sending a flyer to city B is relative to the cost of sending them to city A. If we send them to city B, then we have to send some other flyer to city A because we need N people in both cities. We could generally find the highest costs to B and send those people to A, but if they also have a high cost to A, then we didn't save much. Similarly, if we find the lowest costs to B and send those people to B, then we might not have saved much money over sending them to A - and meanwhile if that action caused us to send someone to A who cost us a lot more, we've lost money overall.
Another way to look at it is that each person costs a certain amount to fly regardless of the city they go to, then they cost an additional premium to fly to one of the cities over the other. If their cost pair is [1000,1001] basically that person costs 1000 no matter what and we are only looking at saving or spending that extra dollar. We could reduce the solution by subtracting the minimum cost from both sides of each pair and then looking at optimizing the differential costs. That person's costs would then be fixed=1000, relative=[0,1]. It produces the same answer, but it seems simpler because now everyone has a 0 (relative) cost for one city and a non-zero cost to the other. The solution at this point would be fairly simple - send the people with the largest differential costs to the city of their 0 relative cost, and then when you get the people with large differences assigned you end up with a lot of people with small differences, you keep doing this, saving less and less each time until you might end up with a bunch of people who all cost extra money to send to one city, but you've already assigned everyone you need to the other city. For example, the cost pairs [10,5], [10,7], [10,8], [5,10] could be made differential and you would get the costs fixed = [5,7,8,5], differentials=[[5,0],[3,0],[2,0],[0,5]] You know it will cost you at least 5+7+8+5 == 25 to send everyone, but how can you save the remaining costs? You obviously send the first person to city B and the last to city A and now you have one more for each city and the remainig differential costs are [3,0],[2,0]. You can't send them both to city B since that would leave the cities improperly staffed, so you have to pick one to send to A and the other to B. Clearly you send the one that costs the least extra to send to A which is the latter of the two.
Subtracting out the fixed costs really makes it rather obvious who to send where, you just sort them in the order from most costly to send to A, to most costly to send to B and then send the first half to city B and the second half to city A. It seems odd to have a dual-sort where one value is decreasing while the other is increasing, but since all cost pairs have one zero and one non-zero it is pretty obvious that the sorted order starts with the pairs that have a zero differential cost for B and they are in order of decreasing relative cost to send to A, then that is followed by all of the people who have a zero differential cost to go to A in the order of increasing cost to send them to B. It would look something like this: [10,0],[5,0],[3,0],...[0,2],[0,5],[0,100] It's pretty clear that the first person goes to B and the last one goes to A and choosing the first half of the list to go to B would minimize the relative costs for city B and the latter half would minimize the relative costs to go to A.
But, you don't need to actually extract the fixed costs, you can do this with the original amounts just by looking at the difference between the numbers. Creating a bunch of relative costs just gives you a bunch of pairs of numbers were one of them is 0. If you subtract the A cost from the B cost, you get a single number that sorts the people by the relative cost to send them to B. You then send the ones with the highest relative B cost to A and vice versa.
Thus the technique here, sort the list first by the B-A cost, then the beginning of the list (smallest values of "relative cost to send to B") are the ones you would rather send to B and the ones at the end you'd rather send to A.
    public int twoCitySchedCost(int[][] costs) {
        Arrays.sort(costs, new Comparator<int[]>() {
            public int compare(int[] a, int[] b) {
                return (a[1] - a[0]) - (b[1] - b[0]);
            }
        });
        int cost = 0;
        for (int i = 0; i < costs.length / 2; i++) {
            cost += costs[i][1] + costs[costs.length-i-1][0];
        }
        return cost;
    }
https://leetcode.com/problems/two-city-scheduling/discuss/278816/Java-Sort-O(NlogN)
public int twoCitySchedCost(int[][] costs) {
        Arrays.sort(costs, new Comparator<int[]>() {
            @Override
            public int compare(int[] a, int[] b) {
                return a[0] - a[1] - (b[0] - b[1]);
            }
        });
        int res = 0;
        for (int i = 0; i < costs.length / 2; i++) {
            res += costs[i][0];
        }
        for (int i = costs.length / 2; i < costs.length;i++) {
            res += costs[i][1];
        }
        return res;
    }

X. DP
https://zxi.mytechroad.com/blog/tag/contest/
dp[i][j] := min cost to put j people into city A for the first i people
dp[0][0] = 0
dp[i][0] = dp[i -1][0] + cost_b
dp[i][j] = min(dp[i – 1][j] + cost_b, dp[i – 1][j – 1] + cost_a)
ans := dp[n][n/2]

Time complexity: O(n^2)
Space complexity: O(n^2)

https://leetcode.com/problems/two-city-scheduling/discuss/278731/Java-DP-Easy-to-Understand
dp[i][j] represents the cost when considering first (i + j) people in which i people assigned to city A and j people assigned to city B.
my explanation here, just in case other people need this:
for (i+j)th people, he/she can be assigned either to A city or B city,
the min cost if he is assigned to A city: dp[i-1][j]+costs[i+j-1][0]; //because it is to A, so we should use i-1
the min cost if he is assigned to B city: dp[i][j-1]+costs[i+j-1][1]; //because it is to B, so we should use j-1
so dp[i][j] = Math.min(dp[i-1][j]+costs[i+j-1][0] , dp[i][j-1]+costs[i+j-1][1]);
another way to represent the dp equation is: dp[totalPerson][personToA], toatalPerson is the number of people have been assigned, and personToA of them are assigned to city A, so the the equation:
dp[totalPerson][personToA]= Math.min(dp[totalPerson-1][personToA]+costs[totalPerson-1][1], //the last one to B
dp[totalPerson-1][personToA-1]+costs[totalPerson-1][0]);//the last one to A
    public int twoCitySchedCost(int[][] costs) {
        int N = costs.length / 2;
        int[][] dp = new int[N + 1][N + 1];
        for (int i = 1; i <= N; i++) {
            dp[i][0] = dp[i - 1][0] + costs[i - 1][0];
        }
        for (int j = 1; j <= N; j++) {
            dp[0][j] = dp[0][j - 1] + costs[j - 1][1];
        }
        for (int i = 1; i <= N; i++) {
            for (int j = 1; j <= N; j++) {
                dp[i][j] = Math.min(dp[i - 1][j] + costs[i + j - 1][0], dp[i][j - 1] + costs[i + j - 1][1]);
            }
        }
        return dp[N][N];
    }
X. DFS+Cache
https://leetcode.com/problems/two-city-scheduling/discuss/278799/Java-Easy-to-understand-DFS-with-cache-solution
    public int twoCitySchedCost(int[][] costs) {
        int len = costs.length / 2;
        int[][] memo = new int[len + 1][len + 1];
        dfs(memo, costs, len, len, 0);
        return memo[len][len];
    }
    
    private int dfs(int[][] memo, int[][] costs, int A, int B, int index) {
        if (index == costs.length) {
            return 0;
        }
        
        if (memo[A][B] != 0) {
            return memo[A][B];
        }
        
        int costA = costs[index][0];
        int costB = costs[index][1];
        
        if (A == 0 && B > 0) {
            memo[A][B] = costB + dfs(memo, costs, A, B - 1, index + 1);
        } else if (B == 0 && A > 0) {
            memo[A][B] = costA + dfs(memo, costs, A - 1, B, index + 1);
        } else {
            int sumA = costA + dfs(memo, costs, A - 1, B, index + 1);
            int sumB = costB + dfs(memo, costs, A, B - 1, index + 1);
            memo[A][B] = Math.min(sumA, sumB);
        }
        
        return memo[A][B];
    }

https://www.acwing.com/solution/LeetCode/content/1758/

公司计划面试 2N 人。第 i 人飞往 A 市的费用为 costs[i][0],飞往 B 市的费用为 costs[i][1]。
返回将每个人都飞到某座城市的最低费用,要求每个城市都有 N 人抵达。

样例

示例:

输入:[[10,20],[30,200],[400,50],[30,20]]
输出:110
解释:
第一个人去 A 市,费用为 10。
第二个人去 A 市,费用为 30。
第三个人去 B 市,费用为 50。
第四个人去 B 市,费用为 20。

最低总费用为 10 + 30 + 50 + 20 = 110,每个城市都有一半的人在面试。


提示:

1 <= costs.length <= 100
costs.length 为偶数
1 <= costs[i][0], costs[i][1] <= 1000
(排序) O(nlogn)
这道题先假设所有的人都去A市,然后我们只需要选取去B市的费用与去A市的费用的差最小的N个人改去B市即可。
时间复杂度分析:排序时间复杂度O(nlogn)


  1. 输入一个长度是2*N的二维数组, a[i][0]表示第i个人分配到A城市的价格, a[i][1]表示第i个人分配到B城市的价格
  2. 把这2*N个数组分成长度相等的两份, 其中一半选择A, 另外一半选择B, 问总共最小的总共价格是多少?
刷题感悟
  1. 这个题目暴力肯定不行, 直接简单的贪心选择最小的也不对, 卡壳了好一会儿。
    还好后来终于想出来了,感觉这样的题目能不能分析出来很没有把握。
解题思路分析
  1. 假设所有的人都选择城市A, 这时候sum=sum{a[i][0]},
    然后要选择一半的人改成B, 这个时候, 选择某一个人对sum的影响是d=a[i][1]-a[i][0],
    那么, 我们要让结果最小, 就需要让这个d最小, 那按照这个d对数组排序,然后选择最小的一半就好

public int twoCitySchedCost(int[][] costs) {
    Arrays.sort(costs, new Comparator<int[]>() {
        public int compare(int[] a, int[] b) {
            int v1 = a[1] - a[0];    
            int v2 = b[1] - b[0];
            return v1-v2;
        }
    });
    int sum = 0;
    for(int[] cost: costs) {
        sum += cost[0];
    }
    for(int i = 0; i < costs.length/2; ++i) {
        sum += costs[i][1] - costs[i][0];
    }
    return sum;
}
X. Video
花花酱 LeetCode Weekly Contest 133 (1029,1030,1031,1032)



Leetcode 1029 - Two City Scheduling | Greedy Algo | Company Interview Tags: Bloomberg

LeetCode 936 - Stamping The Sequence


https://leetcode.com/problems/stamping-the-sequence/
You want to form a target string of lowercase letters.
At the beginning, your sequence is target.length '?' marks.  You also have a stamp of lowercase letters.
On each turn, you may place the stamp over the sequence, and replace every letter in the sequence with the corresponding letter from the stamp.  You can make up to 10 * target.length turns.
For example, if the initial sequence is "?????", and your stamp is "abc",  then you may make "abc??", "?abc?", "??abc" in the first turn.  (Note that the stamp must be fully contained in the boundaries of the sequence in order to stamp.)
If the sequence is possible to stamp, then return an array of the index of the left-most letter being stamped at each turn.  If the sequence is not possible to stamp, return an empty array.
For example, if the sequence is "ababc", and the stamp is "abc", then we could return the answer [0, 2], corresponding to the moves "?????" -> "abc??" -> "ababc".
Also, if the sequence is possible to stamp, it is guaranteed it is possible to stamp within 10 * target.length moves.  Any answers specifying more than this number of moves will not be accepted.

Example 1:
Input: stamp = "abc", target = "ababc"
Output: [0,2]
([1,0,2] would also be accepted as an answer, as well as some other answers.)
Example 2:
Input: stamp = "abca", target = "aabcaca"
Output: [3,0,1]

Note:
  1. 1 <= stamp.length <= target.length <= 1000
  2. stamp and target only contain lowercase letters
X. Greedy
https://leetcode.com/problems/stamping-the-sequence/discuss/195324/50ms-JAVA-beat-100
https://leetcode.com/problems/stamping-the-sequence/discuss/201546/12ms-Java-Solution-Beats-100
The idea is the same as the most upvoted post. Think reversely!
Let's take an example to illustrate.
Stamp = "abc", Target = "ababcbc`
  1. Target = ab***bc
  2. Target = ab*****
  3. Target = *******
We will go through the whole Target string to find if there exists any substring equals to Stamp. And then replace the whole substring with *. You can see in the step 1, we replace substring abc with ***. Then we keep doing the same thing. In the step 2, you will find we replace substring *bc to **** can match to any character because *will be override by the next stamp. Finally we get the result and output the reversed result. When # of stars equals to target.length(), we will return the result. If during one scan, we are not able to replace even one substring with *, directly return empty array.
Comments for two helper functions:
canReplace(char[] T, int p, char[] S) is used to check if any substring from Target is existing to be replaced with *.
doReplace(char[] T, int p, int len, int count) is used to replace the substring with * and return the total number of * we have now.
class Solution {
    public int[] movesToStamp(String stamp, String target) {
        char[] S = stamp.toCharArray();
        char[] T = target.toCharArray();
        List<Integer> res = new ArrayList<>();
        boolean[] visited = new boolean[T.length];
        int stars = 0;
        
        while (stars < T.length) {
            boolean doneReplace = false;
            for (int i = 0; i <= T.length - S.length; i++) {
                if (!visited[i] && canReplace(T, i, S)) {
                    stars = doReplace(T, i, S.length, stars);
                    doneReplace = true;
                    visited[i] = true;
                    res.add(i);
                    if (stars == T.length) {
                        break;
                    }
                }
            }
            
            if (!doneReplace) {
                return new int[0];
            }
        }
        
        int[] resArray = new int[res.size()];
        for (int i = 0; i < res.size(); i++) {
            resArray[i] = res.get(res.size() - i - 1);
        }
        return resArray;
    }
    
    private boolean canReplace(char[] T, int p, char[] S) {
        for (int i = 0; i < S.length; i++) {
            if (T[i + p] != '*' && T[i + p] != S[i]) {
                return false;
            }
        }
        return true;
    }
    
    private int doReplace(char[] T, int p, int len, int count) {
        for (int i = 0; i < len; i++) {
            if (T[i + p] != '*') {
                T[i + p] = '*';
                count++;
            }
        }
        return count;
    } 
https://www.jianshu.com/p/cb4cbd11511b
https://www.acwing.com/solution/leetcode/content/552/
前面所贴的邮票对后面贴的邮票是造成不了影响的
所以反过来思考,将target -> ??????

stamp的长子串是优于stamp的短子串,因为长字串能匹配得到的,那么包含该长字串的子符串一定能匹配得到,而反过来不一定.
举例来说:stamp 为 abcae时, abc能在target匹配得到, 那么ab一定能匹配得到,反过来则不一定
所以枚举stamp的子串, 按照stamp的子串长度从大到小的顺序在target找寻相同的字符串.
因为是要整个stamp一起贴,那么将子串补充为stamp的长度
举例来说:stamp 为 abcae时, abc补充为abc??, ae补充为???ae

在枚举stamp的子串,有可能完整地枚举一次并不能完全的找到,需要多次循环
举例来说
stamp = “uskh”
target = “uskhkhhskh”
完整的枚举一次过程中,得到
????khhskh
??????hskh
???????skh
因为在枚举?skh时, 对应的是target是 ????khhskh,
而在枚举???h后,对应的target是???????skh.

https://github.com/cherryljr/LeetCode/blob/master/Stamping%20The%20Sequence.java

 * Approach: Reverse Simulation
 * 这道题目与 Strange Printer 有点类似,但是本题并不要求最优解。
 * 因此我们可以使用 贪心 的想法去做。
 * 但是这道题目如果按照题目要求的做法 正向(正序) 思考的话,并不能相处什么比较好的做法。
 * 因此我们可以考虑使用逆过来处理的方法来做这道题目。
 * 使用到类似处理方法的还有 Bricks Falling When Hit.
 * 做法为:
 *  因为最后一次的 stamp 必定是会将字符串中的一部分全部改成 stamp.
 *  也就是所谓的 full match.然后将替换的部分全部标记为 '*'.
 *  然后继续进行 match, 但是此时就是 partial match.
 *  并且 '*' 可以与任意字符进行匹配。因为这些字符之后必定会被 stamp 所重新覆写一遍,
 *  所以他们具体是什么并不会造成任何影响。
 *  最后我们只需要将每次得到的结果进行一次 reverse 即可。


    // 因为需要在 doStamp 中对 target 进行修改,所以需要一个成员变量以便访问(Java中字符串类型的限制)
    String myTarget = null;

    public int[] movesToStamp(String stamp, String target) {
        int M = stamp.length(), N = target.length();
        myTarget = target;
        int count = 0;  // 替换的字符个数
        boolean[] visited = new boolean[N];         // 用于记录哪些位置开始的字符被修改过
        List<Integer> index = new ArrayList<>();    // 用于记录每次修改的起始位置
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < M; i++) {
            sb.append('*');
        }
        // 事先构造出用于替换的字符串
        String replace = sb.toString();

        while (count < N) {
            boolean found = false;
            for (int i = 0; i <= N - M; i++) {
                if (visited[i]) {
                    continue;
                }
                int len = doStamp(stamp, i, replace);
                if (len == 0) {
                    continue;
                }
                visited[i] = true;
                count += len;
                index.add(i);
                found = true;
            }
            if (!found) {
                return new int[]{};
            }
        }

        int size = index.size();
        int[] rst = new int[size--];
        for (int i = 0; i <= size; i++) {
            rst[i] = index.get(size - i);
        }
        return rst;
    }

    // 对从 start 位置开始的字串进行stamp操作,返回替换的字符个数
    // 如果无法匹配的话返回0
    private int doStamp(String stamp, int start, String replace) {
        int changed = stamp.length();
        for (int i = 0; i < stamp.length(); i++) {
            if (myTarget.charAt(start + i) == '*') {
                changed--;
            } else if (myTarget.charAt(start + i) != stamp.charAt(i)) {
                return 0;
            }
        }

        if (changed != 0) {
            myTarget = myTarget.substring(0, start) + replace + myTarget.substring(start + stamp.length());
        }
        return changed;
    }

https://leetcode.com/problems/stamping-the-sequence/discuss/189576/C%2B%2B-simple-greedy
It's better explained through an example. Let's say the target is 'aabccbc', and stamp is 'abc'. We first try to find 'abc' and replace it with '***'. After there are no more replacements, we will try '*bc' and 'ab*', and so on. Now, turn by turn:
'aabccbc' ? 'abc' = [1]
'a***cbc' ? '*bc' = []
'a***cbc' ? 'ab*' = []
'a***cbc' ? 'a**' = [0]
'****cbc' ? '*b*' = []
'****cbc' ? '**c' = [2]
'*****bc' ? '*bc' = [4]
The result is: [4, 2, 0, 1]. It feels to me that this greedy solution produces the minumum possible number of stamps, though I cannot provide a strict proof 

X. https://leetcode.com/problems/stamping-the-sequence/discuss/189258/C%2B%2B-Reverse-Operation-30-ms-better-than-DFS
What I basiclly did here is trying to reverse the whole operations.
The operation token later will be more apperant than the operation token before. The letters which stamped later will cover the letters stamped before and we really don't care about the letters which are covered.
 *  *  *  *  *  *  *
 *  *  * |a  b  c  a|
|a  b  c  a| b  c  a
 a |a  b  c  a| c  a
We just try to match the stamp with the target. Since we do not care about the letters which are coverd by others, so we can apply a * match any letters. For example:
"aabcaca" -> "a****ca" -> "*****ca"->"*******"
It's just my contest code, needed to be revised.
    vector<int> movesToStamp(string stamp, string target) {
        vector<int> ans;
        vector<int> output;
        string str = target;
        string aim(target.length(),'*');
        while(str != aim){
            int tmp = remove(str,stamp);
            if(tmp == str.length()) return output;
            ans.push_back(tmp);
        }
        for(int iter=ans.size()-1;iter>=0;--iter) output.push_back(ans[iter]);
        return output;
    }
    int remove(string& str, string stamp){
        for(int iter=0;iter<str.length();++iter){
            int jter=0,tmp=iter;
            bool flag=false;
            while(jter<stamp.length() && tmp<str.length() && (str[tmp]=='*' || str[tmp]==stamp[jter])){
                if(str[tmp]==stamp[jter]) flag=true;
                tmp++;
                jter++;
            }
            if(jter==stamp.length() && flag){
                for(int kter=0;kter<stamp.length();++kter)
                    str[iter+kter]='*';
                return iter;
            }
        }
        return str.length();
    }
X. DFS
https://leetcode.com/problems/stamping-the-sequence/discuss/189254/Python-Greedy-and-DFS


Another idea is backtracking.
Try to find a path of target,
where path[i] equals to index of target[i] in stamp
Example 1:
Input: stamp = "abc", target = "ababc"
path = [0,1,0,1,2]
Example 2:
Input: stamp = "abca", target = "aabcaca"
path = [0,0,1,2,3,2,3]
The rule is that,
rule 0. path[i + 1] can equal to path[i] + 1
It means target[i] and target[i+1] are on the same stamp.
rule 1. path[i + 1] can equal to 0.
It means t[i + 1] is the start of another stamp
rule 2. if path[i] == stamp.size - 1, we reach the end of a stamp.
Under this stamp, it's another stamp, but not necessary the start.
path[i + 1] can equal to 0 ~ stamp.size - 1.
Step 2:
We need to change path to required moves.


    def movesToStamp(self, s, t):
        if s[0] != t[0] or s[-1] != t[-1]: return []
        n, m = len(s), len(t)
        path = [0] * m
        pos = collections.defaultdict(set)
        for i, c in enumerate(s): pos[c].add(i)

        def dfs(i, index):
            path[i] = index
            if i == m - 1: return index == n - 1
            nxt_index = set()
            if index == n - 1:  # rule 2
                nxt_index |= pos[t[i + 1]]
            elif s[index + 1] == t[i + 1]:  # rule 0
                nxt_index.add(index + 1)
            if s[0] == t[i + 1]:  # rule 1
                nxt_index.add(0)
            return any(dfs(i + 1, j) for j in nxt_index)

        def path2res(path):
            down, up = [], []
            for i in range(len(path)):
                if path[i] == 0:
                    up.append(i)
                elif i and path[i] - 1 != path[i - 1]:
                    down.append(i - path[i])
            return down[::-1] + up

        if not dfs(0, 0): return []
        return path2res(path)
https://leetcode.com/articles/stamping-the-sequence/
Approach 1: Work Backwards
Imagine we stamped the sequence with moves 
m_1, m_2, \cdots. Now, from the final position target, we will make those moves in reverse order.
Let's call the ith window, a subarray of target of length stamp.length that starts at i. Each move at position i is possible if the ith window matches the stamp. After, every character in the window becomes a wildcard that can match any character in the stamp.
For example, say we have stamp = "abca" and target = "aabcaca". Working backwards, we will reverse stamp at window 1 to get "a????ca", then reverse stamp at window 3 to get "a??????", and finally reverse stamp at position 0 to get "???????".

Let's keep track of every window. We want to know how many cells initially match the stamp (our "made" list), and which ones don't (our "todo" list). Any windows that are ready (ie. have no todo list), get enqueued.
Specifically, we enqueue the positions of each character. (To save time, we enqueue by character, not by window.) This represents that the character is ready to turn into a "?" in our working target string.
Now, how to process characters in our queue? For each character, let's look at all the windows that intersect it, and update their todo lists. If any todo lists become empty in this manner (window.todo is empty), then we enqueue the characters in window.made that we haven't processed yet.
  • Time Complexity: O(N(N-M)), where M, N are the lengths of stamptarget.
  • Space Complexity: O(N(N-M))
  public int[] movesToStamp(String stamp, String target) {
    int M = stamp.length(), N = target.length();
    Queue<Integer> queue = new ArrayDeque();
    boolean[] done = new boolean[N];
    Stack<Integer> ans = new Stack();
    List<Node> A = new ArrayList();

    for (int i = 0; i <= N - M; ++i) {
      // For each window [i, i+M), A[i] will contain
      // info on what needs to change before we can
      // reverse stamp at this window.

      Set<Integer> made = new HashSet();
      Set<Integer> todo = new HashSet();
      for (int j = 0; j < M; ++j) {
        if (target.charAt(i + j) == stamp.charAt(j))
          made.add(i + j);
        else
          todo.add(i + j);
      }

      A.add(new Node(made, todo));

      // If we can reverse stamp at i immediately,
      // enqueue letters from this window.
      if (todo.isEmpty()) {
        ans.push(i);
        for (int j = i; j < i + M; ++j)
          if (!done[j]) {
            queue.add(j);
            done[j] = true;
          }
      }
    }

    // For each enqueued letter (position),
    while (!queue.isEmpty()) {
      int i = queue.poll();

      // For each window that is potentially affected,
      // j: start of window
      for (int j = Math.max(0, i - M + 1); j <= Math.min(N - M, i); ++j) {
        if (A.get(j).todo.contains(i)) { // This window is affected
          A.get(j).todo.remove(i);
          if (A.get(j).todo.isEmpty()) {
            ans.push(j);
            for (int m : A.get(j).made)
              if (!done[m]) {
                queue.add(m);
                done[m] = true;
              }
          }
        }
      }
    }

    for (boolean b : done)
      if (!b)
        return new int[0];

    int[] ret = new int[ans.size()];
    int t = 0;
    while (!ans.isEmpty())
      ret[t++] = ans.pop();

    return ret;
  }
}

class Node {
  Set<Integer> made, todo;

  Node(Set<Integer> m, Set<Integer> t) {
    made = m;
    todo = t;
  }

}


http://www.noteanddata.com/leetcode-936-Stamping-The-Sequence-java-solution-note.html
  1. 首先,每次转换的时候,可以在任意位置上转换, 如果直接从初始字符串的每个位置全局暴力排列组合,那一定会超时
  2. 同时可以想到,如果存在这样一个转换,那么最多在target的每个位置上,都最多只需要stamp一次,
    因为如果在同一个位置上stamp两次的话,前一次操作就白做了,所以没有必要。
    并且如果后一次会在同一个位置上再次stamp的话,前一次操作不会对中间结果产生任何不同的影响
  3. 然后可以想到如果从前向后转换的话,会有很多种可能,然后会走到一些fail的case, 需要做backtrack,同时因为状态太多肯定也会超时
    但是如果从后往前转换的话,应该比较固定,
    a. 考虑最后一步转换,因为stamp会被完整的替换上去,所以最后一步的转换一定是在target上找一个子字符串完全和stamp相同来转换,
    否则就不会符合要求。 就想stamp=abc, target=ababc, 最后一步肯定是在尾部stamp一个
    b. 如果target有两个子字符串都是和stamp一样的话,最后一步可以选择两个里面的任何一个,对结果没有影响
    c. 如果最后这一步做了以后, 那么,再往前一步,可以选择其他位置的地方做stamp, 其中,可以覆盖已经stamp过的位置,
    也就是说,倒数第二步如果和最后一步有部分位置重叠的话,也没有关系, 因为倒数第二步的结果会被最后一步覆盖。
    d. 同样,可以依次从后往前不停的找位置做stamp,直到全部字符都被匹配过了,或者是尝试了所有的位置都不能得到可以正确解。
    e. 在从后向前遍历的过程中,为了做到后面的操作可以覆盖前面的操作,可以把每次stamp的字符串变成*,
    这样,前面的stamp操作就是匹配至少一个或多个字符,其他的可以是匹配*, 相当于先stamp一次以后, 后面的stamp可以再上面叠加
    但是每次都至少要匹配一个字符串,否则这个stamp就是一个重复的stamp,没有意义。
    同时,stamp的时候,如果不是*的字符,又必须完全匹配,否则stamp就和结果不一样
    f. 为了减少循环次数,可以利用一个数组保存已经stamp过的位置,也就是应用不能在一个位置上重复stamp的特点

Labels

LeetCode (1432) GeeksforGeeks (1122) LeetCode - Review (1067) Review (882) Algorithm (668) to-do (609) Classic Algorithm (270) Google Interview (237) Classic Interview (222) Dynamic Programming (220) DP (186) Bit Algorithms (145) POJ (141) Math (137) Tree (132) LeetCode - Phone (129) EPI (122) Cracking Coding Interview (119) DFS (115) Difficult Algorithm (115) Lintcode (115) Different Solutions (110) Smart Algorithm (104) Binary Search (96) BFS (91) HackerRank (90) Binary Tree (86) Hard (79) Two Pointers (78) Stack (76) Company-Facebook (75) BST (72) Graph Algorithm (72) Time Complexity (69) Greedy Algorithm (68) Interval (63) Company - Google (62) Geometry Algorithm (61) Interview Corner (61) LeetCode - Extended (61) Union-Find (60) Trie (58) Advanced Data Structure (56) List (56) Priority Queue (53) Codility (52) ComProGuide (50) LeetCode Hard (50) Matrix (50) Bisection (48) Segment Tree (48) Sliding Window (48) USACO (46) Space Optimization (45) Company-Airbnb (41) Greedy (41) Mathematical Algorithm (41) Tree - Post-Order (41) ACM-ICPC (40) Algorithm Interview (40) Data Structure Design (40) Graph (40) Backtracking (39) Data Structure (39) Jobdu (39) Random (39) Codeforces (38) Knapsack (38) LeetCode - DP (38) Recursive Algorithm (38) String Algorithm (38) TopCoder (38) Sort (37) Introduction to Algorithms (36) Pre-Sort (36) Beauty of Programming (35) Must Known (34) Binary Search Tree (33) Follow Up (33) prismoskills (33) Palindrome (32) Permutation (31) Array (30) Google Code Jam (30) HDU (30) Array O(N) (29) Logic Thinking (29) Monotonic Stack (29) Puzzles (29) Code - Detail (27) Company-Zenefits (27) Microsoft 100 - July (27) Queue (27) Binary Indexed Trees (26) TreeMap (26) to-do-must (26) 1point3acres (25) GeeksQuiz (25) Merge Sort (25) Reverse Thinking (25) hihocoder (25) Company - LinkedIn (24) Hash (24) High Frequency (24) Summary (24) Divide and Conquer (23) Proof (23) Game Theory (22) Topological Sort (22) Lintcode - Review (21) Tree - Modification (21) Algorithm Game (20) CareerCup (20) Company - Twitter (20) DFS + Review (20) DP - Relation (20) Brain Teaser (19) DP - Tree (19) Left and Right Array (19) O(N) (19) Sweep Line (19) UVA (19) DP - Bit Masking (18) LeetCode - Thinking (18) KMP (17) LeetCode - TODO (17) Probabilities (17) Simulation (17) String Search (17) Codercareer (16) Company-Uber (16) Iterator (16) Number (16) O(1) Space (16) Shortest Path (16) itint5 (16) DFS+Cache (15) Dijkstra (15) Euclidean GCD (15) Heap (15) LeetCode - Hard (15) Majority (15) Number Theory (15) Rolling Hash (15) Tree Traversal (15) Brute Force (14) Bucket Sort (14) DP - Knapsack (14) DP - Probability (14) Difficult (14) Fast Power Algorithm (14) Pattern (14) Prefix Sum (14) TreeSet (14) Algorithm Videos (13) Amazon Interview (13) Basic Algorithm (13) Codechef (13) Combination (13) Computational Geometry (13) DP - Digit (13) LCA (13) LeetCode - DFS (13) Linked List (13) Long Increasing Sequence(LIS) (13) Math-Divisible (13) Reservoir Sampling (13) mitbbs (13) Algorithm - How To (12) Company - Microsoft (12) DP - Interval (12) DP - Multiple Relation (12) DP - Relation Optimization (12) LeetCode - Classic (12) Level Order Traversal (12) Prime (12) Pruning (12) Reconstruct Tree (12) Thinking (12) X Sum (12) AOJ (11) Bit Mask (11) Company-Snapchat (11) DP - Space Optimization (11) Dequeue (11) Graph DFS (11) MinMax (11) Miscs (11) Princeton (11) Quick Sort (11) Stack - Tree (11) 尺取法 (11) 挑战程序设计竞赛 (11) Coin Change (10) DFS+Backtracking (10) Facebook Hacker Cup (10) Fast Slow Pointers (10) HackerRank Easy (10) Interval Tree (10) Limited Range (10) Matrix - Traverse (10) Monotone Queue (10) SPOJ (10) Starting Point (10) States (10) Stock (10) Theory (10) Tutorialhorizon (10) Kadane - Extended (9) Mathblog (9) Max-Min Flow (9) Maze (9) Median (9) O(32N) (9) Quick Select (9) Stack Overflow (9) System Design (9) Tree - Conversion (9) Use XOR (9) Book Notes (8) Company-Amazon (8) DFS+BFS (8) DP - States (8) Expression (8) Longest Common Subsequence(LCS) (8) One Pass (8) Quadtrees (8) Traversal Once (8) Trie - Suffix (8) 穷竭搜索 (8) Algorithm Problem List (7) All Sub (7) Catalan Number (7) Cycle (7) DP - Cases (7) Facebook Interview (7) Fibonacci Numbers (7) Flood fill (7) Game Nim (7) Graph BFS (7) HackerRank Difficult (7) Hackerearth (7) Inversion (7) Kadane’s Algorithm (7) Manacher (7) Morris Traversal (7) Multiple Data Structures (7) Normalized Key (7) O(XN) (7) Radix Sort (7) Recursion (7) Sampling (7) Suffix Array (7) Tech-Queries (7) Tree - Serialization (7) Tree DP (7) Trie - Bit (7) 蓝桥杯 (7) Algorithm - Brain Teaser (6) BFS - Priority Queue (6) BFS - Unusual (6) Classic Data Structure Impl (6) DP - 2D (6) DP - Monotone Queue (6) DP - Unusual (6) DP-Space Optimization (6) Dutch Flag (6) How To (6) Interviewstreet (6) Knapsack - MultiplePack (6) Local MinMax (6) MST (6) Minimum Spanning Tree (6) Number - Reach (6) Parentheses (6) Pre-Sum (6) Probability (6) Programming Pearls (6) Rabin-Karp (6) Reverse (6) Scan from right (6) Schedule (6) Stream (6) Subset Sum (6) TSP (6) Xpost (6) n00tc0d3r (6) reddit (6) AI (5) Abbreviation (5) Anagram (5) Art Of Programming-July (5) Assumption (5) Bellman Ford (5) Big Data (5) Code - Solid (5) Code Kata (5) Codility-lessons (5) Coding (5) Company - WMware (5) Convex Hull (5) Crazyforcode (5) DFS - Multiple (5) DFS+DP (5) DP - Multi-Dimension (5) DP-Multiple Relation (5) Eulerian Cycle (5) Graph - Unusual (5) Graph Cycle (5) Hash Strategy (5) Immutability (5) Java (5) LogN (5) Manhattan Distance (5) Matrix Chain Multiplication (5) N Queens (5) Pre-Sort: Index (5) Quick Partition (5) Quora (5) Randomized Algorithms (5) Resources (5) Robot (5) SPFA(Shortest Path Faster Algorithm) (5) Shuffle (5) Sieve of Eratosthenes (5) Strongly Connected Components (5) Subarray Sum (5) Sudoku (5) Suffix Tree (5) Swap (5) Threaded (5) Tree - Creation (5) Warshall Floyd (5) Word Search (5) jiuzhang (5)

Popular Posts