有时候人们会用重复写一些字母来表示额外的感受,比如 “hello” -> “heeellooo”, “hi” -> “hiii”。我们将相邻字母都相同的一串字符定义为相同字母组,例如:“h”, “eee”, “ll”, “ooo”

对于一个给定的字符串 S ,如果另一个单词能够通过将一些字母组扩张从而使其和 S 相同,我们将这个单词定义为可扩张的(stretchy)。扩张操作定义如下:选择一个字母组(包含字母 c ),然后往其中添加相同的字母 c 使其长度达到 3 或以上

例如,以 “hello” 为例,我们可以对字母组 “o” 扩张得到 “hellooo”,但是无法以同样的方法得到 “helloo” 因为字母组 “oo” 长度小于 3。此外,我们可以进行另一种扩张 “ll” -> “lllll” 以获得 “helllllooo”。如果 S = “helllllooo”,那么查询词 “hello” 是可扩张的,因为可以对它执行这两种扩张操作使得 query = “hello” -> “hellooo” -> “helllllooo” = 输入一组查询单词,输出其中可扩张的单词数量

示例

输入
S = “heeellooo”
words = [“hello”, “hi”, “helo”]
输出:
解释
我们能通过扩张 “hello” 的 “e” 和 “o” 来得到 “heeellooo”
我们不能通过扩张 “helo” 来得到 “heeellooo” 因为 “ll” 的长度小于 3 

提示
0 <= len(S) <= 100
0 <= len(words) <= 100
0 <= len(words[i]) <= 100
S 和所有在 words 中的单词都只由小写字母组成

思路

如果s能由word扩展而来,则遵守以下的判断原则:

  1. word中不同字符的相对顺序,和s中这些字符的相对顺序保持一致,也就是说,word中出现的字符必然出现在s中。比如hello可以扩展成heeellooo,因为h、e、l、o四个字符的相对顺序在两个字符串中保持一致。

  2. word中相同同字符连续出现的次数repeatWc,必然要小于或等于该字符在s中连续出现的次数repeatSc,同时,repeatSc至少为3。比如helo不可以扩展成heeellooo,虽然h、e、l、o四个字符的相对顺序在两个字符串中保持一致,但此时l字符在word中的repeatWc为1,而l字符在s中的repeatSc为2,虽然满足了repeatWc<=repeatSc的原则,但是repeatSc小于3,所以无法扩展成s字符。

维护双指针i和j,在循环中分别遍历s中的每一个字符s[i]和word中的每一个字符w[j],并进行如下判断:

  • 确保每一次循环中,s[i]和w[j]都相等,原因是能够扩展情况下,w[i]对应的不同字符相对顺序一定和s中这些字符的相对顺序保持一致。如果不相等,表明起码有一个字符在二者的相对顺序中不一致,不能够进行扩展。
  • s[i]和w[j]相等的情况下,通过指针不断向后统计当前s[i]和w[j]连续重复出现的个数repeatSc和repeatWc,判断repeatSc和repeatWc的大小。
  • 当任意一个指针到达字符串末尾,在循环结束后判断双指针是否均完成对各自字符串中每个字符的搜寻,只有双方都到达末尾,才能证明之前的字符都经过循环中的判断,能够完成扩展转换。

思路递推

以题目中的s=“heeellooo”,words = [“hello”, “hi”, “helo”]为例,遍历words中的每一个字符串word,分别判断是否能够完成扩展:

  • s=“heeellooo”,words=“hello”

    • 初始i = 0,j = 0,开始循环。
    • s[i] = ‘h’,w[j] = ‘h’,两个字符相等。
    • 找到各自字符连续重复出现的个数,此时repeatSc = 1,repeatWc = 1,双指针分别向后移动当前重复的次数,由于二者相等,不违背规则,进入下一次循环。
    • i = 1,j = 1,判断第二个字符。
    • s[i] = ‘e’,w[j] = ‘e’,两个字符相等。
    • 找到各自字符连续重复出现的个数,此时repeatSc = 3,repeatWc = 1,双指针分别向后移动当前重复的次数,根据规则:repeatSc大于repeatWc并且repeatSc至少为3,不违背规则,进入下一次循环。
    • i = 4,j = 2,判断第三个字符。
    • s[i] = ‘l’,w[j] = ‘l’,两个字符相等。
    • 找到各自字符连续重复出现的个数,此时repeatSc = 2,repeatWc = 2,双指针分别向后移动当前重复的次数,由于二者相等,不违背规则,进入下一次循环。
    • i = 6,j = 4,判断第四个字符。
    • s[i] = ‘o’,w[j] = ‘o’,两个字符相等。
    • 找到各自字符连续重复出现的个数,此时repeatSc = 3,repeatWc = 1,双指针分别向后移动当前重复的次数,根据规则:repeatSc大于repeatWc并且repeatSc至少为3,不违背规则,进入下一次循环。
    • 此时i到达s的最末尾,退出循环。
    • 此时i和j均到达各自字符串的最末尾,所以可以完成扩展转换。
  • s=“heeellooo”,words=“hi”

    • 初始i = 0,j = 0,开始循环。
    • s[i] = ‘h’,w[j] = ‘h’,两个字符相等。
    • 找到各自字符连续重复出现的个数,此时repeatSc = 1,repeatWc = 1,双指针分别向后移动当前重复的次数,由于二者相等,不违背规则,进入下一次循环。
    • i = 1,j = 1,判断第二个字符。
    • s[i] = ‘e’,w[j] = ‘i’,两个字符不相等,不满足相对顺序,不可以完成转换。
  • s=“heeellooo”,words=“helo”

    • 初始i = 0,j = 0,开始循环。
    • s[i] = ‘h’,w[j] = ‘h’,两个字符相等。
    • 找到各自字符连续重复出现的个数,此时repeatSc = 1,repeatWc = 1,双指针分别向后移动当前重复的次数,由于二者相等,不违背规则,进入下一次循环。
    • i = 1,j = 1,判断第二个字符。
    • s[i] = ‘e’,w[j] = ‘e’,两个字符相等。
    • 找到各自字符连续重复出现的个数,此时repeatSc = 3,repeatWc = 1,双指针分别向后移动当前重复的次数,根据规则:repeatSc大于repeatWc并且repeatSc至少为3,不违背规则,进入下一次循环。
    • i = 4,j = 2,判断第三个字符。
    • s[i] = ‘l’,w[j] = ‘l’,两个字符相等。
    • 找到各自字符连续重复出现的个数,此时repeatSc = 2,repeatWc = 1,双指针分别向后移动当前重复的次数,由于二者相等,根据规则:repeatSc大于repeatWc并且repeatSc至少为3,此时repeatSc = 2,并没有达到3,不满足规则,无法完成扩展转换。

代码实现

    public static int expressiveWords(String s, String[] words) {
        int result = 0;
        for (String word : words) {
            if (canExpand(s,word))
                result++;
        }
        return result;
    }

    /**
     * @Author huang.zh
     * @Description 判断字符串s是否能由word扩展而来
     * @Date 9:59 PM 2023/9/11
     * @Param [s, word]
     * @return
     **/
    private static boolean canExpand(String s,String word){
        //定义双指针,分别遍历s中的每个字符s[i]和word中的每个字符w[j]
        int i = 0,j = 0;
        int sLength = s.length(),wLength = word.length();

        while (i < sLength && j < wLength){
            // 如果s能由word扩展而来,则遵守以下的判断原则:
            // 1. word中不同字符的相对顺序,和s中这些字符的相对顺序保持一致,也就是说,word中出现的字符必然出现在s中。
            // 2. word中相同同字符连续出现的次数repeatWc,必然要小于或等于该字符在s中连续出现的次数repeatSc,同时,repeatSc至少为3
            if (s.charAt(i) != word.charAt(j))
                // word中出现的字符必然出现在s中,从首字母开始,每一次循环都需要保证s[i]和w[j]相等
                return false;
            // 此时两个字符相等,找出重复次数
            int repeatSc = 0;
            char repeatS = s.charAt(i);
            while (i < sLength && s.charAt(i) == repeatS){
                //找出字符s[i]连续出现的个数
                repeatSc++;
                i++;
            }
            int repeatWc = 0;
            char repeatW = word.charAt(j);
            while (j < wLength && word.charAt(j) == repeatW){
                //找出字符w[j]连续出现的个数
                repeatWc++;
                j++;
            }
            if (repeatSc < repeatWc || (repeatSc != repeatWc && repeatSc < 3))
                //word中相同同字符连续出现的次数repeatWc,必然要小于或等于该字符在s中连续出现的次数repeatSc,同时,repeatSc至少为3
                return false;
        }
        // 双指针各自走到尽头,代表每一个字符都完成判断
        return i == sLength && j == wLength;
    }

    public static void test(){
        System.out.println(expressiveWords("heeellooo",new String[]{"hello","hi","helo"}));
    }