• LeetCode 139 单词拆分:字符串s能否分割为字符串数组words(wordDict)中字符串的组合?(某未来公司面试题目)


    本文持续更新地址:https://haoqchen.site/2018/11/08/LeetCode139/

    做了公司的面试题目后,上网一找。。发现竟然是Leetcode的题换了个外衣。。。~~~~~

    顺便把变态的II也做了,Leetcode 140 单词拆分II: 字符串s在字典wordDict中有多少种拆分方法。

    题目描述

    设计一个函数 WordBreak:
    + 传入参数 s:待分隔的字符串。
    + 传入参数 words:可以使用的字符串数组。
    + 返回值:一个 bool 值,表示是否存在这样的分割。是的话返回 true,否则返回 false。

    提示:
    字母区分大小写。
    所有字符串只会包含字母 A - Z 和 a - z。
    words 中的字符串都可以在拆分时使用多次。

    示例

    (1)
    s = "TuSimpleCoLtd"
    words = ["Co", "Ltd", "Ha", "TuSimple"]
    返回值为 true。(可以分割为"TuSimple" + "Co" + "Ltd")

    (2)
    s = "TusenCoLtd"
    words = ["Co", "Ltd", "Ha", "TuSimple"]
    返回值为 false。

    代码(千辛万苦AC)

    class Solution {
    public:
        bool wordBreak(string s, vector<string>& wordDict) {
            vector<vector<int>> words_map;
            words_map.resize(52);
            bool* has_break = new bool[s.size()];//创建一个与s同长度的bool类型变量,用于存储以前在某个位置上是否已经进行过分割,比如在[5]这里进行过分割,然后[5]后面没能成功分割,那么以后遇到在[5]这里的分割就可以直接跳过了,没有这个会超时。
            memset(has_break, false, sizeof(bool)*s.size());
            for (int i = 0; i < wordDict.size(); ++i){//以首字母为键值构建自己的map
                char word = wordDict[i][0];
                words_map[letter2int(word)].push_back(i);
            }
            for (int i = 0; i < 52; ++i){//对map按照字符串大小进行从大到小排序,目的是想先用长的字符串进行分割,可以一定程度上节省时间,后来加入了has_break其实这里不用也行
                sort(words_map[i].begin(), words_map[i].end(), [&wordDict](int a, int b)->bool{ return (wordDict[a].size() > wordDict[b].size()); });
            }
            bool can_break = dfs(s, wordDict, words_map, has_break);
            delete[] has_break;
            return can_break;
        }
    private:
        int letter2int(char _letter)//字母转map键值,先'a-z'再'A-Z'
        {
            if (_letter >= 'a' && _letter <= 'z')
                return (_letter - 'a');
            return (_letter - 'A' + 26);
        }
        
        bool dfs(const string& _s, const vector<string>& _words, vector<vector<int>>& _words_map, bool* _has_break)
        {
            if (_s.size() == 0)
                return true;
            bool can_break = true;
            for (auto index : _words_map[letter2int(_s[0])]){
                int i = 0;
                if((_words[index].size() > _s.size()) || (_has_break[_words[index].size() - 1]) )//如果字典字符串比原字符串大,以及已经在该位置进行过分割,直接跳过
                    continue;
                for (; i < _words[index].size(); ++i){//比较所有首字母相同的words
                    if (_s[i] ^ _words[index][i]){//利用异或比较首字母相同的word与s是否相同
                        can_break = false;
                        break;
                    }
                }
                if (can_break){
                    _has_break[_words[index].size() - 1] = true;//在某个位置上可以切割,记录下来
                    string substr = _s.substr(_words[index].size());
                    bool* sub_has_break = _has_break + _words[index].size();
                    if (dfs(substr, _words, _words_map, sub_has_break))//如果可以切割则dfs迭代
                        return true;//成功则返回,否则继续下一个word的判断
                }
                can_break = true;
            }
            return false;
        }
    };
    
    int main(int argc, char** argv)
    {
        Solution obj;
        string s = "abcd";
        vector<string> words;
        words.push_back("a");
        words.push_back("abc");
        words.push_back("b");
        words.push_back("cd");
        if (obj.wordBreak(s, words))
            cout << "True" << endl;
        else
            cout << "False" << endl;
        return 0;
    }

    思路:

    由于只有可能是字母,字母数量只有52个,所以构建map可以减小比较的时间。这是建立在words中没有重复的word假设之上的,如果words中存在重复的word,则直接将整个word作为键值或者构建排序二叉树会更好一点。然后就是基本的DFS问题了。简单的DFS会遇到一个问题,比如"a" "aa" "aaa",其实前两个加起来是等于第三个的,如果前两个合起来之后在[3]这里进行了切割得到的后续子字符串是不能成功切割的,其实第三个也不用试了,浪费时间,比如下面这种情况。博主就出现了超时的情况。这里引入一个bool型的数组,用于记录我们之前已经在哪个位置进行过切割,而且后续不行的。

    "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab" ["a","aa","aaa","aaaa","aaaaa","aaaaaa","aaaaaaa","aaaaaaaa","aaaaaaaaa","aaaaaaaaaa"]

    然后发现大佬十来句就解决了。。。。感受到了恐惧

    class Solution {
    public:
        bool wordBreak(string s, vector<string>& wordDict) {
            vector<bool> dp(s.size()+1,false);
            dp[0]=true;
            for(int i=1; i<=s.size(); i++){
                for(auto& p : wordDict){
                     int len = p.size();
                    if( i<len || !dp[i-len] || s[i-1] != p[len-1] )
                        continue;
                    if( s.substr(i-len, len) == p ){
                        dp[i]=true;
                    }
                }
            }
            return dp[s.size()];
        }
    };

    但是大佬的代码在字典非常多的情况下的时间复杂度会偏高。

  • 相关阅读:
    Java日期时间API系列1-----Jdk7及以前的日期时间类
    Redis设置密码,保护数据安全
    Java SE 8 并发增强
    Java8并发教程:Threads和Executors
    MySQL5.7增量备份恢复全实战
    HashSet HashMap 源码阅读笔记
    操作系统知识点
    Window批处理命令
    JS典型题
    SiteMesh装饰模式
  • 原文地址:https://www.cnblogs.com/HaoQChen/p/11048602.html
Copyright © 2020-2023  润新知