Showing posts with label MinMax. Show all posts
Showing posts with label MinMax. Show all posts

LeetCode 774 - Minimize Max Distance to Gas Station


https://blog.csdn.net/magicbean2/article/details/79663323
On a horizontal number line, we have gas stations at positions stations[0], stations[1], ..., stations[N-1], where N = stations.length.

Now, we add K more gas stations so that D, the maximum distance between adjacent gas stations, is minimized.

Return the smallest possible value of D.

Example:

Input: stations = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], K = 9
Output: 0.500000
Note:

stations.length will be an integer in range [10, 2000].
stations[i] will be an integer in range [0, 10^8].
K will be an integer in range [1, 10^6].
Answers within 10^-6 of the true value will be accepted as correct.

X.
http://www.cnblogs.com/grandyang/p/8970057.html
这道题说给了我们n个加油站,两两之间相距不同的距离,然后我们可以在任意地方新加K个加油站,问能使得任意两个加油站之间的最大距离的最小值是多少。乍眼一看,感觉很绕,一会儿最大,一会儿最小的。其实我们可以换个场景,比如n个人站一队,每两个人之间距离不同,有的人之间距离可能很大,有的人可能挨得很近。我们现在需要再加入K个人到队列中,我们希望人与人之间的距离尽可能小,所以新人就应该加入到距离大的地方,然后问我们加入K个人后,求人与人之间的最大距离。这么一说,是不是清晰一点了呢。博主最开始看到这个加油站的题,以为跟之前那道Gas Station有关联,结果发现二者并没有什么关系,只不过公用了加油站这个场景而已。对于这道题,我们还是抽离出本质,就是数组插数问题。博主最先考虑的是用贪婪算法,就是先算出每两个数字之间的距离,然后我们每次往距离最大的那两个数字之间插入一个数字,这种想法看似正确,但是会跪在这样一个test case:
[10, 19, 25, 27, 56, 63, 70, 87, 96, 97],K = 3
其两两之间的距离为:
9,6,2,29,7,7,17,9,1
如果按照博主前面所说的方法,会先将29分开,变成两个14.5,然后会将17分开,变成两个8.5,还剩一个加油站,会将其中一个14.5分开,变成两个7.25。但是这样弄下来,最大的距离还是14.5,而实际上我们有更好的办法,我们用两个加油站将29三等分,会变成三个9.67,然后用剩下的一个去分17,得到两个8.5,此时最大距离就变成了9.67,这才是最优的解法。这说明了博主那种图样图森破的贪婪算法并不work,缺乏对Hard题目的尊重。
后来看了官方解答贴中的解法,发现用DP会内存超标MLE,用堆会时间超标TLE。其实这道题的正确解法是用二分搜索法,博主第一反应是,还有这种操作!!??就是有这种操作!!!这道题使用的二分搜索法是博主归纳总结帖LeetCode Binary Search Summary 二分搜索法小结中的第四种,即二分法的判定条件不是简单的大小关系,而是可以抽离出子函数的情况,下面我们来看具体怎么弄。如果光说要用二分法来做,那么首先就要明确的是二分法用来查找什么,难道是用来查找要插入加油站的位置吗?很显然不是,其实是用来查找那个最小的任意两个加油站间的最大距离。这其实跟之前那道Kth Smallest Element in a Sorted Matrix非常的类似,那道题的二分搜索也是直接去折半定位所求的数,然后再来验证其是否真的符合题意。这道题也是类似的思路,题目中给了数字的范围[0, 10^8],那么二分查找的左右边界值就有了,又给了误差范围10^-6,那么只要right和left差值大于这个阈值,就继续循环。我们折半计算出来的mid就是一个candidate,我们要去验证个candidate是否符合题意。验证的方法其实也不难,我们计算每两个加油站之间的距离,如果此距离大于candidate,则计数器累加1,如果大于candidate距离的个数小于等于k,则说明我们的candidate偏大了,那么right赋值为mid;反之若大于candidate距离的个数大于k,则说明我们的candidate偏小了,那么left赋值为mid。最后left和right都会收敛为所要求的最小的任意两个加油站间的最大距离,是不是很神奇呀
http://hehejun.blogspot.com/2018/02/leetcodeminimize-max-distance-to-gas.html
和Split Array Largest Sum一样,此题也有binary search的解法。因为,我们确定目标答案target在区间(0, max(stations[i] - stations[i - 1])]中,并且给定y,我们可以在O(n)的时间里确定其是否为target的上/下界:
  • 假设区间长len,让区间满足子区间的最大值不超过y需要的额外加油站的数目为ceil(len / y),那么我们只需要算出满足y的条件下需要的加油站的总数sum,如果sum <= k,代表我们还可以用更多的加油站让最大区间更小,所以y是target的上界,target <= y
  • 如果sum > k,代表我们需要减少额外的加油站,这样y是target的上界,target >= y
注意上下界都是double类型,我们设定误差的范围e,当上下界误差小于e的话我们终止即可。时间复杂度O(n * log(max(stations[i] - stations[i - 1]))),代码如下:

https://www.jiuzhang.com/solution/minimize-max-distance-to-gas-station/
二分"最大距离", 对每个 mid 统计可以添加的最少的加油站, 如果 > k 说明 mid 太小需要增大 (增大 mid 才可能添加更少的加油站)
    public double minmaxGasDist(int[] stations, int k) {
        // Write your code here
        double start = 0, end = stations[stations.length - 1] - stations[0];
        double eps = 1e-6;
        
        while (start + eps < end) {
            double mid = (start + end) / 2;
            int count = checkStations(stations, mid);
            if (count > k) {
                // minimum stations added is > K
                // so we nned to increase the mid
                start = mid;
            } else {
                // minimum stations added is <= K
                // so we need to narrow the mid
                end = mid;
            }
        }
        return start;
    }
    
    // get the minimum stations count at max distance 'dist'
    private int checkStations(int[] stations, double dist) {
        int count = 0;
        for (int i = 1; i < stations.length; i++) {
            count += (int) ((stations[i] - stations[i - 1]) / dist);
            // for example, 10 / 3, at least put 3 stations
            // to satisfy the condition: max distance is 'dist'
        }
        return count;
    }
  • 1
https://www.acwing.com/solution/leetcode/content/220/
  public double minmaxGasDist(int[] stations, int K) {
    double s = 0, e = 10e9;
    while (e - s > 1e-8) {
      double mid = (s + e) / 2;
      if (checkMin(stations, mid, K) < 0)
        s = mid;
      else
        e = mid;
    }
    return s;
  }

  private int checkMin(int[] stations, double dist, int k) {
    for (int i = 1; i < stations.length; i++) {
      double gap = (double) (stations[i] - stations[i - 1]);
      if (gap > dist) {
        k -= (int) (gap / dist);
        if (gap % dist == 0)
          k--;
      }
    }
    return k;

  }
https://chenboxie.wordpress.com/2018/06/24/leetcode-774-minimize-max-distance-to-gas-station/
I -- Construct a candidate solution
To construct a candidate solution, we need to understand first what the desired solution is. The problem description requires we output the minimum value of the maximum distance between adjacent gas stations. This value is nothing more than a non-negative double value, since the distance between any adjacent stations cannot be negative and in general it should be of double type. Therefore our candidate solution should also be a non-negative double value.

II -- Search space formed by all the candidate solutions
Let max be the maximum value of the distances between any adjacent stations without adding those additional stations, then our desired solution should lie within the range [0, max]. This is because adding extra stations (and placing them at proper positions) can only reduce the maximum distance between any adjacent stations. Therefore our search space will be [0, max] (or the upper bound should be at least max).

III -- Verify a given candidate solution
This is the key part of this trial and error algorithm. So given a candidate double value d, how do we determine if it can be the minimum value of the maximum distance between any adjacent stations after adding K extra stations? The answer is by counting.
If d can be the minimum value of the maximum distance between any adjacent stations after adding K extra stations, then the following two conditions should be true at the same time:
  1. The total number of stations we can add between all adjacent stations cannot go beyond K. In other words, assume we added cnt_i stations in between the pair of adjacent stations (i, i+1), then we should have sum(cnt_i) <= K.
  2. For each pair of adjacent stations (i, i+1), the minimum value of the maximum distance between adjacent stations after adding cnt_i additional stations cannot go beyond d. If the original distance between station i and i+1 is d_i = stations[i+1] - stations[i], then after adding cnt_i additional stations, the minimum value of maximum distance between any adjacent stations we can obtain is d_i / (cnt_i + 1), which is achieved by placing those stations evenly in between stations i and i+1. Therefore we require: d_i / (cnt_i + 1) <= d, which is equivalent to cnt_i >= (d_i/d - 1).
So you can see that the two conditions are actually “contradictory” to each other: to meet condition 1, we want cnt_i to be as small as possible; but to meet condition 2, we’d like it to be as large as possible. So in practice, we can alway choose the set of smallest cnt_i‘s that satisfy the second condition and double check if they also meet the first condition. If they do, then d will set an upper limit on the final minimum value obtainable; otherwise d will set a lower limit on this minimum value.
This verification algorithm runs at O(n), where n is the length of the stations array. This is acceptable if we can walk the search space very efficiently (which can be done at the order of O(log(max/step)), with step = 10^-6). In particular, this is much faster than the straightforward O(Klogn) solution where we add the stations one by one in a greedy manner (i.e., always reduce the current maximum distance first), given that K could be orders of magnitude larger than n (note this greedy algorithm can be optimized to run at O(nlogn), see wannacry89’s post here).
https://blog.csdn.net/u014688145/article/details/79195114
首先求出每个station之间的距离,考虑如下问题:两个station为[1, 9],gap为8。要插入一个station使得最大的最小,显然插入后应该为[1, 5, 9],最大间隔为4。举个反例,如果插入后为[1, 6, 9], [1, 3, 9],它们的最大间隔分别为5, 6,明显不是最小。从这里可以看出,对于插入k个station使得最大的最小的唯一办法是均分。
换个思路,如果我们假设知道了答案会怎么样?因为知道了最大间隔,所以如果目前的两个station之间的gap没有符合最大间隔的约束,那么我们就必须添加新的station来让它们符合最大间隔的约束,这样一来,对于每个gap我们是能够求得需要添加station的个数。如果需求数<=K,说明我们还可以进一步减小最大间隔,直到需求数>K。

  public double minmaxGasDist(int[] stations, int K) {
    int n = stations.length;
    double[] gap = new double[n - 1];
    for (int i = 0; i < n - 1; ++i) {
      gap[i] = stations[i + 1] - stations[i];
    }
    double lf = 0;
    double rt = Integer.MAX_VALUE;
    double eps = 1e-7;
    while (Math.abs(rt - lf) > eps) {
      double mid = (lf + rt) / 2;
      if (check(gap, mid, K)) {
        rt = mid;
      } else {
        lf = mid;
      }
    }
    return lf;
  }

  boolean check(double[] gap, double mid, int K) {
    int count = 0;
    for (int i = 0; i < gap.length; ++i) {
      count += (int) (gap[i] / mid);
    }
    return count <= K;

  }

X. DP
http://hehejun.blogspot.com/2018/02/leetcodeminimize-max-distance-to-gas.html
这道题和Split Array Largest Sum十分类似。首先我们也可以考虑区间DP的解法。对于前i个interval(i + 1个加油站),我们额外增加j个加油站,相邻加油站间距的最大值为x,那么在在所有可能中x的最小值我们用DP[i][j]表示。我们有递推公式:

  • dp[i][j] = min(max(dp[i - 1][j - k], (stations[i] - stations[i - 1]) / (k + 1)))
  • base case: dp[i][0] = max(stations[1] - stations[0], ..., stations[i] - stations[i - 1])
公式中的(stations[i] - stations[i - 1]) / (k + 1)可以这么理解: 把一个interval用k个节点分为k + 1的子区间,那么minimize子区间最大值的方法就是吧interval等分。假设输入为n个加油站的位置,我们额外增加k个,算法时间复杂度和空间复杂度均为O(n * k)。代码如下:
    double minmaxGasDist(vector<int>& stations, int K) {
        int len = stations.size();
        //dp[i][j] represnets minimum max distance when adding j new gas stations to first i intervals
        //dp[i][j] = min(max(dp[i - 1][j - k], (stations[i] - stations[i - 1]) / (k + 1)))
        //base case: dp[i][0] = max(stations[0], ..., stations[i - 1]), dp[0][j] = 0
        vector<vector<double>> dp(len, vector<double>(K + 1, 0));
        for (int i = 1; i < len; ++i)dp[i][0] = max(dp[i - 1][0], static_cast<double>(stations[i] - stations[i - 1]));
        for (int i = 1; i < len; ++i)
        {
            for (int j = 1; j <= K; ++j)
            {
                dp[i][j] = numeric_limits<double>::max();
                for (int k = 0; k <= j; ++k)
                {
                    dp[i][j] = min(max(dp[i - 1][j - k], (stations[i] - stations[i - 1]) / (k + 1.0)), dp[i][j]);
                }
            }
        }
        return dp[len - 1][K];
    }

X.
首先要从离散的点抽象出Interval的概念,其实已有的gas station就是一个个Interval。更为关键的是,这些interval可以视为永远不变,唯一变化的就是他们里边可能插入多少个K。基于这些interval,我们每次选择插入K的时候,只要选择插入当前所有interval中dist最大的那个,一直插入直到它比第二大小为止。插完所有的K,我们就得到了最后的解。
有了以上的理解,一个priority_queue就可以轻易的解决问题,用addK()去update当前interval里的value,然后把update完的top重新插回priority_queue。解法复杂度为O(klogn)
http://fyou.me/blog/2018/05/09/minimize-max-distance-to-gas-station/
首先要从离散的点抽象出Interval的概念,其实已有的gas station就是一个个Interval。更为关键的是,这些interval可以视为永远不变,唯一变化的就是他们里边可能插入多少个K。基于这些interval,我们每次选择插入K的时候,只要选择插入当前所有interval中dist最大的那个,一直插入直到它比第二大小为止。插完所有的K,我们就得到了最后的解。
有了以上的理解,一个priority_queue就可以轻易的解决问题,用addK()去update当前interval里的value,然后把update完的top重新插回priority_queue。解法复杂度为O(klogn)
首先要从离散的点抽象出Interval的概念,其实已有的gas station就是一个个Interval。更为关键的是,这些interval可以视为永远不变,唯一变化的就是他们里边可能插入多少个K。基于这些interval,我们每次选择插入K的时候,只要选择插入当前所有interval中dist最大的那个,一直插入直到它比第二大小为止。插完所有的K,我们就得到了最后的解。

有了以上的理解,一个priority_queue就可以轻易的解决问题,用addK()去update当前interval里的value,然后把update完的top重新插回priority_queue。解法复杂度为O(klogn)
这题的难点在于正确理解题意并抽象化后找出算法,关键点在于:

由于最早给定的gas station,随着K的插入,变化的并不是interval,而是每个interval内部的dist。
只关注最后的最大距离,并不关心每个gas station插在了哪里。
struct Interval {
    int length, k, dist;

    Interval(int i) : length(i), k(1), dist(i) {}

    bool operator< (const Interval& other) const {
        return dist < other.dist;
    }

    void addK() {
        k++;
        dist = length/k;
    }
};

priority_queue<Interval> buildQueue(const vector<int>& v) {
    vector<int> ret(v.size());
    adjacent_difference(v.begin(), v.end(), ret.begin());
    ret.erase(ret.begin());
    return priority_queue<Interval>(ret.begin(), ret.end());
}

int minMaxDist(const vector<int>& v, int k) {
    auto pq = buildQueue(v);
    int maxDist = (v.back() - v.front())/(k+1); // just for optimization

    while(k) {
        Interval top = pq.top();
        pq.pop();

        while(pq.top() < top || top.dist > maxDist) {
            top.addK();
            k--;
        }
        pq.push(top);
    }

    return pq.top().dist;
}





首先要从离散的点抽象出Interval的概念,其实已有的gas station就是一个个Interval。更为关键的是,这些interval可以视为永远不变,唯一变化的就是他们里边可能插入多少个K。基于这些interval,我们每次选择插入K的时候,只要选择插入当前所有interval中dist最大的那个,一直插入直到它比第二大小为止。插完所有的K,我们就得到了最后的解。




有了以上的理解,一个priority_queue就可以轻易的解决问题,用addK()去update当前interval里的value,然后把update完的top重新插回priority_queue。解法复杂度为O(klogn)
首先要从离散的点抽象出Interval的概念,其实已有的gas station就是一个个Interval。更为关键的是,这些interval可以视为永远不变,唯一变化的就是他们里边可能插入多少个K。基于这些interval,我们每次选择插入K的时候,只要选择插入当前所有interval中dist最大的那个,一直插入直到它比第二大小为止。插完所有的K,我们就得到了最后的解。
有了以上的理解,一个priority_queue就可以轻易的解决问题,用addK()去update当前interval里的value,然后把update完的top重新插回priority_queue。解法复杂度为O(klogn)

首先要从离散的点抽象出Interval的概念,其实已有的gas station就是一个个Interval。更为关键的是,这些interval可以视为永远不变,唯一变化的就是他们里边可能插入多少个K。基于这些interval,我们每次选择插入K的时候,只要选择插入当前所有interval中dist最大的那个,一直插入直到它比第二大小为止。插完所有的K,我们就得到了最后的解。
有了以上的理解,一个priority_queue就可以轻易的解决问题,用addK()去update当前interval里的value,然后把update完的top重新插回priority_queue。解法复杂度为O(klogn)
首先要从离散的点抽象出Interval的概念,其实已有的gas station就是一个个Interval。更为关键的是,这些interval可以视为永远不变,唯一变化的就是他们里边可能插入多少个K。基于这些interval,我们每次选择插入K的时候,只要选择插入当前所有interval中dist最大的那个,一直插入直到它比第二大小为止。插完所有的K,我们就得到了最后的解。
有了以上的理解,一个priority_queue就可以轻易的解决问题,用addK()去update当前interval里的value,然后把update完的top重新插回priority_queue。解法复杂度为O(klogn)
首先要从离散的点抽象出Interval的概念,其实已有的gas station就是一个个Interval。更为关键的是,这些interval可以视为永远不变,唯一变化的就是他们里边可能插入多少个K。基于这些interval,我们每次选择插入K的时候,只要选择插入当前所有interval中dist最大的那个,一直插入直到它比第二大小为止。插完所有的K,我们就得到了最后的解。
有了以上的理解,一个priority_queue就可以轻易的解决问题,用addK()去update当前interval里的value,然后把update完的top重新插回priority_queue。解法复杂度为O(klogn)
首先要从离散的点抽象出Interval的概念,其实已有的gas station就是一个个Interval。更为关键的是,这些interval可以视为永远不变,唯一变化的就是他们里边可能插入多少个K。基于这些interval,我们每次选择插入K的时候,只要选择插入当前所有interval中dist最大的那个,一直插入直到它比第二大小为止。插完所有的K,我们就得到了最后的解。
有了以上的理解,一个priority_queue就可以轻易的解决问题,用addK()去update当前interval里的value,然后把update完的top重新插回priority_queue。解法复杂度为O(klogn)
首先要从离散的点抽象出Interval的概念,其实已有的gas station就是一个个Interval。更为关键的是,这些interval可以视为永远不变,唯一变化的就是他们里边可能插入多少个K。基于这些interval,我们每次选择插入K的时候,只要选择插入当前所有interval中dist最大的那个,一直插入直到它比第二大小为止。插完所有的K,我们就得到了最后的解。
有了以上的理解,一个priority_queue就可以轻易的解决问题,用addK()去update当前interval里的value,然后把update完的top重新插回priority_queue。解法复杂度为O(klo
1、采用优先队列:采用优先队列的思路比较简单:就是每次找出gap最大的一个区间,然后新加入一个station,直到所有的k个station都被加入,此时最大的gap即为所求。在实现上我们采用优先队列,使得每次最大的gap总是出现在队首。这种方法的空间复杂度是O(n),时间复杂度是O(klogn),其中k是要加入的新station的个数,n是原有的station个数。这种方法应该没毛病,但是还是没有通过所有的数据测试。
    double minmaxGasDist(vector<int>& stations, int K) {
        priority_queue<vector<double>> pq;     // {avg_distance, count}
        for (int i = 0; i + 1 < stations.size(); ++i) {
            pq.push({stations[i + 1] - stations[i], 1});
        }
        for (int i = 0; i < K; ++i) {
            vector<double> vec = pq.top();
            pq.pop();
            vec[0] = vec[0] * vec[1] / (vec[1] + 1);
            vec[1] += 1.0;
            pq.push(vec);
        }
        return pq.top()[0];
    }


首先要从离散的点抽象出Interval的概念,其实已有的gas station就是一个个Interval。更为关键的是,这些interval可以视为永远不变,唯一变化的就是他们里边可能插入多少个K。基于这些interval,我们每次选择插入K的时候,只要选择插入当前所有interval中dist最大的那个,一直插入直到它比第二大小为止。插完所有的K,我们就得到了最后的解。
有了以上的理解,一个priority_queue就可以轻易的解决问题,用addK()去update当前interval里的value,然后把update完的top重新插回priority_queue。解法复杂度为O(klogn)

LeetCode 843 - Guess the Word


https://leetcode.com/problems/guess-the-word/
This problem is an interactive problem new to the LeetCode platform.
We are given a word list of unique words, each word is 6 letters long, and one word in this list is chosen as secret.
You may call master.guess(word) to guess a word.  The guessed word should have type string and must be from the original list with 6 lowercase letters.
This function returns an integer type, representing the number of exact matches (value and position) of your guess to the secret word.  Also, if your guess is not in the given wordlist, it will return -1 instead.
For each test case, you have 10 guesses to guess the word. At the end of any number of calls, if you have made 10 or less calls to master.guess and at least one of these guesses was the secret, you pass the testcase.
Besides the example test case below, there will be 5 additional test cases, each with 100 words in the word list.  The letters of each word in those testcases were chosen independently at random from 'a' to 'z', such that every word in the given word lists is unique.
Example 1:
Input: secret = "acckzz", wordlist = ["acckzz","ccbazz","eiowzz","abcczz"]

Explanation:

master.guess("aaaaaa") returns -1, because "aaaaaa" is not in wordlist.
master.guess("acckzz") returns 6, because "acckzz" is secret and has all 6 matches.
master.guess("ccbazz") returns 3, because "ccbazz" has 3 matches.
master.guess("eiowzz") returns 2, because "eiowzz" has 2 matches.
master.guess("abcczz") returns 4, because "abcczz" has 4 matches.

We made 5 calls to master.guess and one of them was the secret, so we pass the test case.
X.  random choose and exclude not match words
https://blog.csdn.net/u011439455/article/details/80482508
https://www.jianshu.com/p/b77ae5d669b1
基于贪心策略,我们应该要利用好系统的反馈机制,避免我们浪费查询机会。我们的目标应该是提高每次的 matches 值,即每次都要淘汰词表里不等于matches的单词,缩小我们枚举范围。
每次查询之后,如果matches不等于6,我们虽然还不知道"secret"。但我们知道哪些一定不是"secret",进而缩小范围,逼近答案。

    int match(const string a, const string b){
        int ans = 0;
        
        for(int i = 0;i<a.length(); i++){
            if(a[i] == b[i]) ++ans;
        }
        
        return ans;
    }
    
    void shrinkWordList(vector<string>& wordlits, const string guessWord, const int matches){
        vector<string> tmp;
        
        
        for(string word : wordlits){
            int m = match(word, guessWord);
            if(m == matches){
                tmp.push_back(word);
            }
        }
        
        wordlits = tmp;
    }
    
    void findSecretWord(vector<string>& wordlist, Master& master) {
        
        string target = wordlist[random() % wordlist.size()];
        int Count = 10;
        while(Count--){
            int matches = master.guess(target);
            
            shrinkWordList(wordlist, target, matches);
            
            target = wordlist[random() % wordlist.size()];
        }
        
    }
The description emphasize that the wordlist is generated randomly and it's does have a reason.
There is no solution that can guarantee to find a secret word in 10 tries. If I make up a test case with wordlist like ["aaaaaa", "bbbbbb" ...., "zzzzzz"], it need 26 tries to find the secret.
So 10 tries is just a limit to test reasonable solution. And this problem is more than finding right output for given input, it's more about a strategy.
Intuition:
Take a word from wordlist and guess it.
Get the matches of this word
Update our wordlist and keep only the same matches to our guess.
For example we guess "aaaaaa" and get matches x = 3, we keep the words with exactly 3 a.
Also we need to know the matches between two words, so a sub function as following will be helpful.
    public int match(String a, String b) {
        int matches = 0;
        for (int i = 0; i < a.length(); ++i) if (a.charAt(i) == b.charAt(i)) matches ++;
        return matches;
    }
This process is straight forward.
However, the key point is, which word should we guess from all of the wordlist?
First of all, I guessed the first word from wordlist.
Unfortunately, I didn't get a lucky pass.
This problem has only 5 test cases but they are good.
But I didn't give up this idea. All words are generated randomly.
So why not we also guess a random word and let it be whatever will be.
So here it is this idea and it can get accepted.
    public void findSecretWord(String[] wordlist, Master master) {
        for (int i = 0, x = 0; i < 10 && x < 6; ++i) {
            String guess = wordlist[new Random().nextInt(wordlist.length)];
            x = master.guess(guess);
            List<String> wordlist2 = new ArrayList<>();
            for (String w : wordlist)
                if (match(guess, w) == x)
                    wordlist2.add(w);
            wordlist = wordlist2.toArray(new String[wordlist2.size()]);
        }
    }
I said I could get accepted but not for sure. In fact it has 80% rate to get accepted.
Now we want to find a solution that improve this rate. We should guess a word that can minimum our worst case.
Generally, we will get 0 matches and wordlist size reduce slowly.
So we compare each two words and for each word, we note how many 0 matches it gets.
Then we guess the word with minimum 0 matches.
So even in most cases we get 0 match from master, it's still the best word that we can guess.
Because the wordlist will reduce at minimum as possible.
Time Complexity and Result Analyse
To be more convincing, I test each approach with 1000 test cases.
For the random approach, time complexity O(N), average 6.5 guess, worst case 14 guess.
For the minimax approach, time complexity O(N^2), average 5.5 guess, worst case 10 guess.


    public void findSecretWord(String[] wordlist, Master master) {
        for (int i = 0, x = 0; i < 10 && x < 6; ++i) {
            HashMap<String, Integer> count = new HashMap<>();
            for (String w1 : wordlist)
                for (String w2 : wordlist)
                    if (match(w1, w2) == 0)
                        count.put(w1, count.getOrDefault(w1 , 0) + 1);
            Pair<String, Integer> minimax = new Pair<>("", 1000);
            for (String w : wordlist)
                if (count.getOrDefault(w, 0) < minimax.getValue())
                    minimax = new Pair<>(w, count.getOrDefault(w, 0));
            x = master.guess(minimax.getKey());
            List<String> wordlist2 = new ArrayList<String>();
            for (String w : wordlist)
                if (match(minimax.getKey(), w) == x)
                    wordlist2.add(w);
            wordlist = wordlist2.toArray(new String[0]);
        }
    }
X.
https://www.cnblogs.com/lightwindy/p/9795777.html
这是一个Leetcode平台新型的交互式问题
给定一个不重复的词表,里面都是只有6个小写字母的单词。然后,系统会随机选定一个单词作为 "secret"
你可以调用系统提供的API master.guess(word) 来查询word 是否就是 "secret"。
这个API返回的参数是个整型,表示查询的匹配程度。
对于每个测试用例,你有10次查询机会。如果你能在10次以内的查询中找出 "secret" 则判定你通过用例。
任何绕过判定的做法都会被视为非法。
解法:Random Guess and Minimax Guess with Comparison
X. http://hehejun.blogspot.com/2018/11/leetcodeguess-word.html
这道题是猜数字的游戏,通常这类题目我们仍然是运用minimax的思路,对于所有可能成为答案的candidates:

  • 对于每一个数,我们要算其在所有match可能(0-5)下,能eliminate的最少数量,我们称其为这个pick的score。考虑最坏的情况,这就是min
  • 那么对于所有可能pick的数,我们取score最大的。在所有可选的范围内选择最优的,这就是max
我们需要preprocess建立一个match的表,维护了每两个string match的情况。然后对于每一个可能的pick,和match数m,我们计算其可以排除掉的数,即如果string和pick的match数不为m,那么肯定不是当前条件的答案的候选,所以排除。时间复杂度O(N^2 * log N),每一次pick的过程是O(N^2),每次应该pick之后至少应该能排除一半,所以是log N,
Approach #1: Minimax with Heuristic [Accepted]
We can guess that having less words in the word list is generally better. If the data is random, we can reason this is often the case.
Now let's use the strategy of making the guess that minimizes the maximum possible size of the resulting word list. If we started with N words in our word list, we can iterate through all possibilities for what the secret could be.
Algorithm
Store H[i][j] as the number of matches of wordlist[i] and wordlist[j]. For each guess that hasn't been guessed before, do a minimax as described above, taking the guess that gives us the smallest group that might occur.
  • Time Complexity: O(N^2 \log N), where N is the number of words, and assuming their length is O(1). Each call to solve is O(N^2), and the number of calls is bounded by O(\log N).
  • Space Complexity: O(N^2).

  int[][] H;

  public void findSecretWord(String[] wordlist, Master master) {
    int N = wordlist.length;
    H = new int[N][N];
    for (int i = 0; i < N; ++i)
      for (int j = i; j < N; ++j) {
        int match = 0;
        for (int k = 0; k < 6; ++k)
          if (wordlist[i].charAt(k) == wordlist[j].charAt(k))
            match++;
        H[i][j] = H[j][i] = match;
      }

    List<Integer> possible = new ArrayList();
    List<Integer> path = new ArrayList();
    for (int i = 0; i < N; ++i)
      possible.add(i);

    while (!possible.isEmpty()) {
      int guess = solve(possible, path);
      int matches = master.guess(wordlist[guess]);
      if (matches == wordlist[0].length())
        return;
      List<Integer> possible2 = new ArrayList();
      for (Integer j : possible)
        if (H[guess][j] == matches)
          possible2.add(j);
      possible = possible2;
      path.add(guess);
    }

  }

  public int solve(List<Integer> possible, List<Integer> path) {
    if (possible.size() <= 2)
      return possible.get(0);
    List<Integer> ansgrp = possible;
    int ansguess = -1;

    for (int guess = 0; guess < H.length; ++guess) {
      if (!path.contains(guess)) {
        ArrayList<Integer>[] groups = new ArrayList[7];
        for (int i = 0; i < 7; ++i)
          groups[i] = new ArrayList<Integer>();
        for (Integer j : possible)
          if (j != guess) {
            groups[H[guess][j]].add(j);
          }

        ArrayList<Integer> maxgroup = groups[0];
        for (int i = 0; i < 7; ++i)
          if (groups[i].size() > maxgroup.size())
            maxgroup = groups[i];

        if (maxgroup.size() < ansgrp.size()) {
          ansgrp = maxgroup;
          ansguess = guess;
        }
      }
    }

    return ansguess;
  }

https://leetcode.com/problems/guess-the-word/discuss/134251/Optimal-MinMax-Solution-(%2B-extra-challenging-test-cases)

X.
https://www.cnblogs.com/haoweizh/p/10202516.html
思考下为什么这个算法会失败,直观上来说,我们希望每次剪枝都能删除尽可能多的字符串,而随机挑选字符串的策略并不能满足要求,所以我以第一个字符为目标,在第一个字符出现频率最高的所有字符串中随机挑选一个字符串,然后再进行迭代,这样就可以保证每次都能删掉尽可能多的字符串。不过这道题我还有个疑问,我们是否能够保证在十次之内必定能找到目标字符串,能否用数学方式证明这个结论是否正确。
11     int getMatch(string &a,string &b){
12         int count = 0;
13         for(int i = 0;i != a.size();++i){
14             if(a[i] == b[i])
15                 count++;
16         }
17         return count;
18     }
19     char getMost(vector<string>& words){
20         vector<int> v(26,0);
21         int maximal = 0;
22         char result;
23         for(string s:words)
24             v[s[0]-'a']++;
25         for(int i = 0;i != 26;++i){
26             if(v[i] > maximal){
27                 maximal = v[i];
28                 result = 'a'+i;
29             }
30         }
31         return result;
32     }
33     void findSecretWord(vector<string>& wordlist, Master& master) {
34         char c = getMost(wordlist);
35         vector<string> vec(wordlist.begin(),wordlist.end());
36         string str;
37         for(string s:wordlist){
38             if(s[0] == c){
39                 str = s;
40                 break;
41             }
42         }
43         while(true){
44             vector<string> t;
45             int match = master.guess(str);
46             if(match == 6) return;
47             for(int i = 0;i != vec.size();++i){
48                 if(getMatch(str,vec[i]) == match && str != vec[i])
49                     t.push_back(vec[i]);
50             }
51             c = getMost(t);
52             for(string s:t){
53                 if(s[0] == c){
54                     str = s;
55                     break;
56                 }
57             }
58             vec = t;
59         }
60     }

https://docs.google.com/document/d/1qxA2wps0IhVRWULulQ55W4SGPMu2AE5MkBB37h8Dr58/edit#heading=h.gsbh7qxi4gdo

Guess word改版

给一个矩阵,在里面找一个gift,不知道gift坐标,然后每次pick一个点,告诉你是更近了还是更远了还是距离不变, 距离是曼哈顿距离,要求用最少的猜测次数找到gift

思路:KD tree算法


0
1
2
3
4
0





1





2





3

gift



4





从(0,0)开始,先二分列坐标,看(0, 4),如果04比00近,说明gift在j = 2的右边矩阵中,如果04比00远,在j = 2的左边矩阵中,如果相等,说明gift在j=2这条线上
一直二分到 l == r找到gift所在列

然后二分行坐标,直到找到gift

参考代码:
Provider: null
   public int guess(int i1, int j1, int i2, int j2) {
    int x = 3;
    int y = 1;
    int dist1 = Math.abs(i1 - x) + Math.abs(j1 - y);
    int dist2 = Math.abs(i2 - x) + Math.abs(j2 - y);
    return dist1 - dist2;
   }
   private void guessGift(int rows, int cols) {
    int left = 0, right = cols - 1;
    while (left < right) {
    int res = guess(0, left, 0, right);
    int mid = left + (right - left) / 2;
    if(res == 0) {
    left = mid;
    break;
    } else if(res < 0) {
    right = mid - 1;
    } else {
    left = mid + 1;
    }
    }
    int top = 0, bot = rows - 1;
    while (top < bot) {
    int mid = top + (bot - top) / 2;
    int res = guess(top, left, bot, left);
    if(res == 0) {
    break;
    } else if(res < 0) {
    bot = mid  - 1;
    } else {
    top = mid + 1;
    }
    }
    System.out.println(top + ", " + left);

   }

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