• LeetCode 图解 | 30.串联所有单词的子串


    点击上方蓝字设为星标

    下面开始今天的学习~

    今天分享的题目来源于 LeetCode 上 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"]
    输出:[]
    

    题目解析

    此题还是从散列表入手,假设输入字符串s:“suanwusuanfa”,要求匹配的单词组 words:{"su", "an", "fa"}。单词组words每一个单词的长度都相同,可以把单词看成一个关键字,字符串里的随机两个连续的字符也看成一个关键字。

    但如何将字符串划分多个关键字呢?

    因为单词组 words 的单词长度都是相同的,单词的长度是 2,可以作为两次遍历:

    第一次遍历的时候,字符串 s 可以划分为{"su", "an", "wu", "su", "an", "fa"};

    第二次遍历的时候,字符串 s 可以划分为{"s", "ua", "nw", "us", "ua", "nf", "a"},只有一个字符的关键字可以直接排除掉。

    回头看题目描述要求,“注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序”。

    所以,单词组words:{"su", "an", "fa"}的长度是3,要求字符串依次遍历时,有连续三个关键字是和单词组words匹配上的。

    那如何去匹配呢?可以设置两个散列表,散列表匹配散列表,或者控制条件判断 count 是否等于散列表(单词组)的数组长度。

    创建一个散列表,统计单词的个数。

    words = ["su","an","fa"]
    散列表:统计单词个数
    [su:1]
    [an:1]
    [fa:1]
    
    

    创建 start 和 end 下标,用在字符串 s 上,可以看作是滑动窗口,这个滑动窗口的长度是可变化的,从 0 开始,同时创建 start 和 end 俩下标之间关键字(两个连续的字符)的统计个数 count ,默认为 0 。

    因为单词的长度为 2 ,end 下标每一次的偏移量都是 2 。

    滑动窗口创建新的散列表 window_map ,用于保存 start 下标和 end 下标之间的关键字。

    移动 end 的下标,截取 start 和 end 俩下标的关键字 word ,去和单词组的散列表 map 比较,如果散列表 map 包含这个单词 word ,则将 word保存到 window_map 的键,值 +1。

    字符串截取单词

    如果散列表不包含这个单词 word ,意味着 start 下标和 end 下标截取的字串并不匹配单词组 words ,则将 start 下标移到 end 下标的位置,count 清零,window_map 散列表也清空。

    start = end

    还有更巧妙的一点,散列表匹配散列表,那么可以要求 window_map 的关键字的值不能大于 map 同一关键字的个数。

    看示例 2:

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

    如果 window_map 已经保存了 good 关键字的值变为 2 ,但是单词组map的关键字 good 却只统计了一个,所以要移动 start 的下标,直到 window_map 里的 good 关键字的值变为 1,部分代码如下:

    while (window_map.get(word) > map.get(word)) {
        String sub_temp = s.substring(start, start + word_len);
        window_map.put(sub_temp, window_map.get(sub_temp) - 1);
        start += word_len;
        count--;
    }
    

    count 是统计 window_map 目前的数组长度,如果 count 等于 map 的数组长度,说明 window_map 的键值对和 map 相等的,如下图:

    俩散列表键值对都相等

    然后进行下一次的遍历,遍历次数直到超过一个单词的长度。

    第二轮,直到超出单词的长度

    动画描述

    参考代码

    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> result = new ArrayList<>();
        if (s == null || s == "" || words == null || words.length == 0) return result;
        // 创建一个散列表,将words统计到其中
        Map<String, Integer> map = new HashMap<>();
        for (String word : words) {
    //      if (map.containsKey(word)) map.put(word, map.get(word) + 1);
    //      else map.put(word,1);
            // 等价
            map.put(word, map.getOrDefault(word, 0) + 1);
        }
        // 滑动窗口
        int word_len = words[0].length();
        for (int i = 0; i < word_len; i++) {
            int start = i, end = i, count = 0;
            // 创建一个散列表
            Map<String, Integer> window_map = new HashMap<>();
            while (end + word_len <= s.length()) {
                // 截取字符串s里的单词
                String word = s.substring(end, end + word_len);
                end += word_len;
                if (!map.containsKey(word)) { // map 不存在这个word
                    count = 0;
                    start = end;
                    window_map.clear();
                } else {
                    window_map.put(word, window_map.getOrDefault(word, 0) + 1);
                    count++;
                    while (window_map.get(word) > map.get(word)) {
                        String sub_temp = s.substring(start, start + word_len);
                        window_map.put(sub_temp, window_map.get(sub_temp) - 1);
                        start += word_len;
                        count--;
                    }
                    if (count == words.length) result.add(start);
                }
            }
        }
        return result;
    }
    

    复杂度分析

    看上面动画视频可以看出来,end 下标遍历两次,但是偏移量长度是 2,元素个数 n 乘以两次遍历,又除以偏移量长度,所以时间复杂度是 O(n) 。

  • 相关阅读:
    Java中Bitmap的实现
    链接备用
    91家纺网,利用cookies登录
    selenium验证码pic处理代码,以91家纺网为例
    91家纺网,登录代码
    91家纺网,模拟浏览器登录
    91家纺网,models
    91家纺网,setting文件
    91家纺网,更新
    91家纺网,更新
  • 原文地址:https://www.cnblogs.com/csnd/p/16675014.html
Copyright © 2020-2023  润新知