[LeetCode] 猜单词

350 阅读2分钟

这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战

题目

843. 猜猜这个单词

函数签名:

    public void findSecretWord(String[] wordlist, Master master) {
        
    }

分析

题目长的一笔,看看大概是个什么意思(设某个词为X,答案为Y):

  • 答案在wordlist里。

  • 调用master.guess(X),可以得知X有多少个位置上的字母和Y相同。

    例如:Y = acckzz , X = abcczz , guess(X) = 4,自己mock代码如下:

    class Master{
        String word;
    
        public Master(String word) {
            this.word = word;
        }
    
        public int guess(String cmp){
            int l = 6;
            int cnt = 0;
            for (int i = 0; i < l; i++) {
                if(word.charAt(i) == cmp.charAt(i)) cnt++;
            }
            return cnt;
        }
    }
    
  • 最多只能调用10次guess方法。

如何求解?划分到每一步试试。

假设某一次选取的单词为X,guess(X)= 3。

那么答案必然在和X有3次准确匹配的内容中,此时找出和X有3个准确匹配的集合为List[X]。

随后,从list[X]中取出和上一次的list[X]中有的数,逐步缩小可选的范围。

重复这个过程,看起来是可行的。

那么,为了快速地完成这个过程,就得提前做好和第i个字符有n次精确匹配的数组,方便我们在判断的时候不必反复计算。

    public void findSecretWord(String[] wordlist, Master master) {
        int n = wordlist.length;
        //dp[i][j] 表示:wordlist的i和wordlist的j,有多少个位置上的单词相同
        int[][] dp = new int[n][n];
        //由于dp[i][j] 等于dp[j][i] ,因此只要计算一半,另一半map过去就可以了
        for (int i = 0; i < wordlist.length; i++) {
            for (int j = i+1;j < wordlist.length;j++){
                dp[i][j] = matchLength(wordlist[i],wordlist[j]);
            }
            dp[i][i] = 6;
        }

        //map
        for(int i = 0; i <wordlist.length; i++){
            for(int j = 0; j< i; j++) dp[i][j] = dp[j][i];
        }
    //...
    }

public int matchLength(String a,String b){
    int cnt = 0;
    for (int i = 0; i < 6; i++) {
        if(a.charAt(i) == b.charAt(i)) cnt++;
    }
    return cnt;
}

这样子在后续的过程中,就可以通过这个dp数组,来快速判断和n相同的数有多少。

这一步也可以用map来做,但是这样子的话空间复杂度过大了一些。

剩下的代码就是更新可选取的内容了:

//上面的...补上这些代码
Set<Integer> candidates = null;
int curSelect = 0,times = 0;
while(true){
    times++;
    int cnt = master.guess(wordlist[curSelect]);
    System.out.println("guess word:"+wordlist[curSelect]);
    if(cnt==6) {
        System.out.println(times);
        return;
    }
    Set<Integer> cur = new HashSet<>();
    int[] ints = dp[curSelect];
    for (int i = 0; i < ints.length; i++) {
        if (ints[i] == cnt && (null == candidates ||candidates.contains(i))) {
            cur.add(i);
            curSelect = i;
        }
    }
    candidates = cur;
}

结果:

执行用时:3 ms, 在所有 Java 提交中击败了51.69%的用户

内存消耗:36.5 MB, 在所有 Java 提交中击败了57.63%的用户

优化

有没有可以改进的地方?

当然是有了,由于我们是提前做的数组初始化,由于只有10次,其实如果数组大小>10,那么必然有不会被访问到的数。那么,可以懒加载这个数组,只是在某些0的位置上需要提前做一点工作。