• LeetCode 30


    一、问题描述

    Description:

    You are given a string, s, and a list of words, words, that are all of the same length. Find all starting indices of substring(s) in s that is a concatenation of each word in words exactly once and without any intervening characters.

    For example:

    • s: "barfoothefoobarman"
    • words: ["foo", "bar"]

    You should return the indices: [0,9].
    (order does not matter).

    给定一个字符串 s 与一个单词集合 words

    假设 str 是由集合 words 中的所有单词拼接(无序且每个单词仅出现一次)而成的字符串,求 str 在字符串 s 中匹配到的所有起始位置。


    二、解题报告

    解法一:暴力枚举

    枚举字符串 s 的每个起始位置 0inlen ,判断从当前位置开始的子串部分能不能由集合 words 里面的单词拼接而成。

    判断方法:从起始位置 i 开始时,依次判断接下来的每个单词是否在集合中,如果单词在集合中,就从集合中删除该单词。这里用一个 hash map 来保存单词,这样可以在O(1)时间内判断单词是否在集合中。

    class Solution {
    public:
        vector<int> findSubstring(string s, vector<string>& words) {
            int word_len = words[0].size();
            int len = words.size()*word_len;
            int n = s.size();
            unordered_map<string, int> m;              // hash_map<单词,出现的次数>
            for(int i=0; i<words.size(); ++i)
            {
                if(m.count(words[i]) == 0)             // 注意count方法的使用
                    m.insert(make_pair(words[i], 1));
                else
                    ++m[words[i]];
            }
    
            vector<int> index;
            for(int i=0; i<=n-len; ++i)                // 枚举起始位置
            {
                if(isContain(s.substr(i,len), m, word_len))
                    index.push_back(i);
            }
            return index;
        }
    
        /**
         * 判断子串s是否由集合words里面的单词拼接而成
         */
        bool isContain(string s, unordered_map<string,int> m, int word_len) {
            int i;
            for(i=0; i<s.size(); i+=word_len) {
                string subs = s.substr(i, word_len);
                unordered_map<string,int>::iterator it = m.find(subs);
                if(it!=m.end() && it->second>0)
                    --(it->second);
                else
                    break;
            }
    
            if(i==s.size())
                return true;
            else
                return false;
        }
    };

    假设 n 是字符串的长度,m 是所有单词拼接后的长度,则时间复杂度是 O(nm)


    解法二:滑动窗口

    本解法的思路和《LeetCode 3 - Longest Substring Without Repeating Characters》差不多,都用了一种滑动窗口的方法。因为单词都是定长的,所以本质上和单个字符一样。

    比如s = "a1b2c3a1d4",words=["a1", "b2", "c3", "d4"]

    窗口最开始为空:

    • a1在集合中,加入窗口 【a1】b2c3a1d4

    • b2在集合中,加入窗口 【a1b2】c3a1d4

    • c3在集合中,加入窗口 【a1b2c3】a1d4

    • a1在集合中,但前面a1已经出现了一次,达到了次数上限,不能再加入窗口。此时只需要把窗口向右移动一个单词:a1【b2c3a1】d4

    • d4在集合中,加入窗口a1【b2c3a1d4】此时找到了一个匹配。

    class Solution {
    public:
        vector<int> findSubstring(string s, vector<string>& words) {
            int word_len = words[0].size();
            int len = words.size()*word_len;
            int n = s.size();
            unordered_map<string, int> m;              // <单词,出现的次数>
            for(int i=0; i<words.size(); ++i)
            {
                if(m.count(words[i]) == 0)             // 注意count方法的使用
                    m.insert(make_pair(words[i], 1));
                else
                    ++m[words[i]];
            }
    
            vector<int> index;
            for(int i=0; i<word_len; ++i)
            {
                unordered_map<string, int> win;
                int left = i;                           // 窗口左边沿
                int count = 0;                          // 窗口中的单词数目
                for(int right=i; right<=n-word_len; right+=word_len)
                {
                    string word = s.substr(right, word_len);
                    if(m.find(word) != m.end())         // 在集合中
                    {
                        if(win.find(word) == win.end()) // 不在窗口中,即添加
                            win[word] = 1;
                        else
                            ++win[word];                // 在窗口中
    
                        if(win[word] <= m[word])        // 还没出现相应的次数
                            ++count;
                        else  
                        {
                            // 单词在集合中,但是它已经在窗口中出现了相应的次数,不应该加入窗口
                            // 此时把窗口起始位置想左移动到,该单词第一次出现的位置的下一个单词位置
                            for(int k=left;  ; k+=word_len)
                            {
                                string temp = s.substr(k, word_len);
                                --win[temp];
                                if(temp == word)
                                {
                                    left = k + word_len;
                                    break;
                                }
                                --count;
                            }
                        }
    
                        if(count == words.size())
                            index.push_back(left);
                    }
                    else                                // 不在集合中,从窗口右侧重新开始
                    {
                        left = right + word_len;
                        win.clear();
                        count = 0;
                    }
                }
            }
    
            return index;
        }
    };

    此解法的时间复杂度是O(nk)n 是字符串的长度,k 是单词的长度。

    由于 k 是个常数,所以这是一个线性算法。[参考]





    LeetCode答案源代码:https://github.com/SongLee24/LeetCode


  • 相关阅读:
    NS网络仿真,小白起步版,双节点之间的模拟仿真(基于UDP和CBR流)
    Linux学习,ACL权限管理
    SQL中的注释语句
    C#连接SQL Server数据库小贴士
    C#重写ToString
    C#控制台应用程序之选课系统
    浅谈C、C++及其区别、兼容与不兼容
    C++之客户消费积分管理系统
    A*算法
    HTML标签列表总览
  • 原文地址:https://www.cnblogs.com/songlee/p/5738036.html
Copyright © 2020-2023  润新知