30. 串联所有单词的子串

30. 串联所有单词的子串

给定一个字符串 s 和一些 长度相同 的单词 words **。**找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。

注意子串要与 words 中的单词完全匹配,中间不能有其他字符 ,但不需要考虑 words 中单词串联的顺序。

示例 1:

输入:s = "barfoothefoobarman", words = ["foo","bar"]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 "barfoo" 和 "foobar" 。
输出的顺序不重要, [9,0] 也是有效答案。

示例 2:

输入:s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"]
输出:[]

示例 3:

输入:s = "barfoofoobarthefoobarman", words = ["bar","foo","the"]
输出:[6,9,12]

提示:

  • 1 <= s.length <= 104
  • s 由小写英文字母组成
  • 1 <= words.length <= 5000
  • 1 <= words[i].length <= 30
  • words[i] 由小写英文字母组成
class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        // list:返回的结果集
        List<Integer> list = new ArrayList<Integer>();
        // len:单词的固定长度
        // size:words数组的长度,根据这个长度去创建result数组。
        // sign:记录当前出现的单词的顺序,它是第几个出现的。
        // repeatNum:之前出现的第几个单词,在现在又出现了。
        // 计算result数组的和,就不用去循环遍历看看是否都齐全了。
        // repeatIndex:重复的下标
        int len = words[0].length(), size = words.length, sign = 0, repeatNum = 0, sum = 0, repeatIndex = 0;
        // result数组:下标和words数组下标一一对应,代表对应下标的单词是第几个出现的。
        // 如果全都出现了,则全为以,那么和就是len。
        // 如果有某个位置大于1,说明出现了重复。
        int[] result = new int[size];
        // appear:是否出现了words数组中的单词,repeat:是否出现了重复的单词。
        boolean appear, repeat;
        // i是分类,将一个words数组分为len条路取走。
        for(int i = 0; i < len; ++i){
            // 如果每个单词长度为3,那么就应该是以下的样子
            // 0->3->6->9
            // 1->4->7->10
            // 2->5->8->11
            // 因为j + len取的是开区间,所以要有等号
            for(int j = 0 + i; j + len <= s.length(); j = j + len){
                String word = s.substring(j, j + len);
                appear = false;
                repeat = false;
                // 从words数组中找到对应的单词下标
                for(int m = 0; m < size; ++m){
                    // 找到了对应的词语,但是这里存在麻烦,假如words中有两个一样的单词,就会出现都堆积到第一个单词的下标上
                    if(words[m].equals(word)){
                        int k = -1;
                        // 如果出现了重复,即代表之前已经在这个下标的位置上加过数字了。
                        // 那我就应该记录下result[m],这样子我就知道第几个出现的数字出现了重复。
                        // 接下来在后续操作中,小于这个单词出场顺序的直接归零,其他的减去当前的单词顺序即可。
                        if(result[m] > 0){
                            repeat = true;
                            repeatNum = result[m];
                            repeatIndex = m;
                            // 所以需要手动往后面去查询,如果有的话就撤销重复,否则就会提示重复。
                            for(k = 0; k < size; ++k){
                                if(words[k].equals(word)){
                                    // 不能写作repeatIndex = repeatNum < result[k] ? m : k;因为当出现了三个及以上的重复时,就会出现异常
                                    repeatIndex = repeatNum < result[k] ? repeatIndex : k;
                                    repeatNum = repeatNum < result[k] ? repeatNum : result[k];
                                    if(result[k] == 0) {
                                        repeat = false;
                                        break;
                                    }
                                }
                            }
                            // 说明到了结尾都没有找到新的对应字符存放的位置,那就是出现了重复。要修改的下标就是所有的重复字符中值最小的那个的下标。
                            if(k == size){
                                result[repeatIndex] = sign + 1;
                            }
                        }
                        // 即便是重复也需要修改出场顺序,这样子方便后面操作。
                        ++sign;
                        // k == -1,根本就没有进入重复。
                        // k != -1 && k != size 说明进入了重复,但是其中找到了新的空格填充,空格的下标就是k。
                        if(k == -1){
                            result[m] = sign;
                        }
                        if(k != -1 && k != size){
                            result[k] = sign;
                        }
                        // 不论出没出现重复都可以先加sum
                        ++sum;
                        appear = true;
                        break;
                    }
                }
                // 说明当前的单词没有在words数组中出现过,直接从下一个单词重新查询
                if(!appear){
                    sign = 0;
                    sum = 0;
                    for(int n = 0; n < size; ++n){
                        result[n] = 0;
                    }
                }
                // 说明单词出现了重复
                if(repeat){
                    sign = sign - repeatNum;
                    sum = sum - repeatNum;
                    for(int n = 0; n < size; ++n){
                        if(result[n] > repeatNum){
                            result[n] = result[n] - repeatNum;
                        } else {
                            result[n] = 0;
                        }
                    }
                }
                // 如果sum等于size了,说明已经齐全了
                if(sum == size){
                    // 因为当前的单词是最后一个出现的,所以要获取出现的第一个下标,只要减去((总单词数 - 1) * 固定长度)
                    list.add(j - len * (size - 1));
                    // 因为全到齐了,所以检测下一个单词的时候,只要把第一个出现的单词抹去就行了。
                    // 即原来是[5,4,3,2,1]的改变为[4,3,2,1,0]
                    --sign;
                    --sum;
                    for(int n = 0; n < size; ++n){
                        if(result[n] > 1){
                            result[n] = result[n] - 1;
                        } else {
                            result[n] = 0;
                        }
                    }
                }
            }
            sign = 0;
            sum = 0;
            for(int n = 0; n < size; ++n){
                result[n] = 0;
            }
        }
        return list;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值