leetcode——第139题——单词拆分

题目:
给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。

说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。

/*
本题有两种方法:
方法一:回溯法
与回溯算法中的  第131题 分割回文串  比较相似,
整体思路,枚举分割后的所有子串,判断是否在字典里出现过
时间复杂度 O(2^n),因为每个单词有两个状态,切割,或者不切割
空间复杂度:O(n),算法递归系统调用栈的空间

普通的回溯法 超时了,使用记忆数组进行优化,虽然时间复杂度没有发生改变,但是测试用例都可以通过

方法二:
字典中单词的个数 即为 物品的个数,字典中的每个单词可以等价为 物品的重量
字符串 s 即可以等价为 背包
单词能否组成字符串 s ,就是问物品能否把背包装满。
时间复杂度:O(n^3),因为substr返回子串的副本是O(n)的复杂度(这里的n是字符串 s 的长度)
空间复杂度:O(n)

动态规划五部曲:
1、确定 dp 数组以及下标的含义
dp[j] 字符串长度为 i 的话,dp[i] = true,表示可以拆分为一个或多个在字典中出现的单词

2、确定递推公式
如果确定 dp[j] 是 true,且[j, i]这个区间的子串出现在字典里,那么 dp[i] 一定是true.
所以递推公式是 if( [j,i] 这个区间的子串出现在字典里 && dp[j]=true ),那么 dp[i] = true

两个判断条件:
一个是[0,i]区间可以拆分为字典中的单词
一个是[i, j]区间可以拆分为字典中的单词

3、dp数组如何初始化
从递推公式中可以看出,dp[i]的状态依靠 dp[j] 是否为true,
那么 dp[0] 就是递归的根基,dp[0]一定要为 true,否则递归下去后面都是 false,就无意义了
那么 dp[0]=true 的意义是什么呢?    表示 如果字符串为空的话,说明出现在字典里
对于其他非 0 的下标,dp[i] 初始化为 false

4、确定遍历顺序
题目说的是可以拆分为 一个或者多个 在字典中出现的单词,所以是完全背包问题
对于完全背包,就需要考虑是求组合数还是排列数
如果是组合数就是 外层for遍历物品,内层for遍历背包容量
如果是排列数就是 外层for遍历背包,内层for遍历物品

本题是问是否出现,所以顺序不重要,因此是求组合数,因此 使用组合或者 排列 都可以的

但是由于本题的特殊性,是求子串,最好是先遍历背包,再遍历物品

因为:如果要是外层for循环遍历物品,内层for循环遍历背包的话,就需要把所有的子串先预先放在一个容器里,

因此:最终的遍历顺序为,先遍历背包,在遍历物品,正序遍历

*/

class Solution {
/************************方法一:基本回溯法,超时*********************************/
// private:
//     bool backtracking(const string& s, const unordered_set<string>& wordSet, int startIndex)
//     {
//         // 终止条件
//         if(startIndex == s.size())
//         {
//             return true;
//         }

//         for(int i = startIndex; i < s.size(); ++i)
//         {
//             // 切割字符串,需要学习!!!!!!!!加深记忆
//             string word = s.substr(startIndex, i - startIndex + 1);
//             // 这一句,判断 哈希表 中是否出现过 word 这个词
//             if(wordSet.find(word) != wordSet.end() && backtracking(s, wordSet, i + 1))
//             {
//                 // 这里注意是把两个条件写一块了,要么再多写一个 if 判断,
//                 // 因为如果 backtracking 为 false 并不需要返回 false,
//                 return true;
//             }
//         }
//         return false;
//     }
// public:
//     bool wordBreak(string s, vector<string>& wordDict) 
//     {
//         // 将字典数组 存放在哈希表中,便于查找
//         unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
//         return backtracking(s, wordSet, 0);
//     }

// /************************方法二:记忆化递归,优化后不超时了*********************************/
// // 优化:使用数组保存一下递归过程中的计算结果,也就是使用 memory 数组保存每次计算的以 startIndex 起始的结果
// // 如果 memory[startIndex] 里已经被赋值了,直接用 memory[startIndex] 的结果
// private:
//     vector<int> memory;
//     bool backtracking(const string& s, const unordered_set<string>& wordSet, int startIndex)
//     {
//         // 终止条件
//         if(startIndex == s.size())
//         {
//             return true;
//         }

//         // 如果 memory[stratIndex] 不是初始值,则直接使用 memory[startIndex] 的结果
//         if(memory[startIndex] != -1)    return memory[startIndex];
//         for(int i = startIndex; i < s.size(); ++i)
//         {
//             // 切割字符串,需要学习!!!!!!!!加深记忆
//             string word = s.substr(startIndex, i - startIndex + 1);
//             // 这一句,判断 哈希表 中是否出现过 word 这个词
//             if(wordSet.find(word) != wordSet.end() && backtracking(s, wordSet, i + 1))
//             {
//                 // 这里注意是把两个条件写一块了,要么再多写一个 if 判断,
//                 // 因为如果 backtracking 为 false 并不需要返回 false,
//                 memory[startIndex] = 1;     // 记录以 startIndex 开始的子串是可以被拆为字典中的一个词
//                 return true;
//             }
//         }
//             memory[startIndex] = 0;     // 记录以 startIndex 开始的子串是不可以拆分为字典中的一个词的
//         return false;
//     }
// public:
//     bool wordBreak(string s, vector<string>& wordDict) 
//     {
//         // 将字典数组 存放在哈希表中,便于查找
//         unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
//         memory = vector<int>(s.size(), -1);     // -1 表示初始化状态
//         return backtracking(s, wordSet, 0);
//     }

/***************************方法三:转化为 dp 的背包问题**********************************/
public:
    bool wordBreak(string s, vector<string>& wordDict) 
    {
        unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
        vector<bool> dp(s.size() + 1, false);
        dp[0] = true;
        // 先遍历背包
        for(int j = 1; j <= s.size(); j++)
        {
            // 后遍历物品
            for(int i = 0; i < j; i++)
            {
                // substr 的两个参数,一个是 起始位置,一个是截取的个数
                string word = s.substr(i, j - i);
                // 两个判断条件:一个是[i, j]区间可以拆分为字典中的单词, 
                // 一个是[0,i]区间可以拆分为字典中的单词,满足这两个条件则表明[0, j]是可以是字典中的单词
                if(wordSet.find(word) != wordSet.end() && dp[i])
                {
                    dp[j] = true;
                }
            }
         }
        return dp[s.size()];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值