|
| 1 | +/** |
| 2 | + * @file |
| 3 | + * @brief [Word Break Problem](https://leetcode.com/problems/word-break/) |
| 4 | + * @details |
| 5 | + * Given a non-empty string s and a dictionary wordDict containing a list of |
| 6 | + * non-empty words, determine if s can be segmented into a space-separated |
| 7 | + * sequence of one or more dictionary words. |
| 8 | + * |
| 9 | + * Note: |
| 10 | + * The same word in the dictionary may be reused multiple times in the |
| 11 | + * segmentation. You may assume the dictionary does not contain duplicate words. |
| 12 | + * |
| 13 | + * Example 1: |
| 14 | + * Input: s = "leetcode", wordDict = ["leet", "code"] |
| 15 | + * Output: true |
| 16 | + * Explanation: Return true because "leetcode" can be segmented as "leet code". |
| 17 | + * |
| 18 | + * Example 2: |
| 19 | + * Input: s = "applepenapple", wordDict = ["apple", "pen"] |
| 20 | + * Output: true |
| 21 | + * Explanation: Return true because "applepenapple" can be segmented as "apple |
| 22 | + * pen apple". Note that you are allowed to reuse a dictionary word. |
| 23 | + * |
| 24 | + * Example 3: |
| 25 | + * Input: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"] |
| 26 | + * Output: false |
| 27 | + * |
| 28 | + * @author [Akshay Anand] (https://github.com/axayjha) |
| 29 | + */ |
| 30 | + |
| 31 | +#include <cassert> |
| 32 | +#include <climits> |
| 33 | +#include <iostream> |
| 34 | +#include <string> |
| 35 | +#include <unordered_set> |
| 36 | +#include <vector> |
| 37 | + |
| 38 | +/** |
| 39 | + * @namespace dynamic_programming |
| 40 | + * @brief Dynamic programming algorithms |
| 41 | + */ |
| 42 | +namespace dynamic_programming { |
| 43 | + |
| 44 | +/** |
| 45 | + * @namespace word_break |
| 46 | + * @brief Functions for [Word Break](https://leetcode.com/problems/word-break/) |
| 47 | + * problem |
| 48 | + */ |
| 49 | +namespace word_break { |
| 50 | + |
| 51 | +/** |
| 52 | + * @brief Function that checks if the string passed in param is present in |
| 53 | + * the the unordered_set passed |
| 54 | + * |
| 55 | + * @param str the string to be searched |
| 56 | + * @param strSet unordered set of string, that is to be looked into |
| 57 | + * @returns `true` if str is present in strSet |
| 58 | + * @returns `false` if str is not present in strSet |
| 59 | + */ |
| 60 | +bool exists(const std::string &str, |
| 61 | + const std::unordered_set<std::string> &strSet) { |
| 62 | + return strSet.find(str) != strSet.end(); |
| 63 | +} |
| 64 | + |
| 65 | +/** |
| 66 | + * @brief Function that checks if the string passed in param can be |
| 67 | + * segmented from position 'pos', and then correctly go on to segment the |
| 68 | + * rest of the string correctly as well to reach a solution |
| 69 | + * |
| 70 | + * @param s the complete string to be segmented |
| 71 | + * @param strSet unordered set of string, that is to be used as the |
| 72 | + * reference dictionary |
| 73 | + * @param pos the index value at which we will segment string and test |
| 74 | + * further if it is correctly segmented at pos |
| 75 | + * @param dp the vector to memoize solution for each position |
| 76 | + * @returns `true` if a valid solution/segmentation is possible by segmenting at |
| 77 | + * index pos |
| 78 | + * @returns `false` otherwise |
| 79 | + */ |
| 80 | +bool check(const std::string &s, const std::unordered_set<std::string> &strSet, |
| 81 | + int pos, std::vector<int> *dp) { |
| 82 | + if (pos == s.length()) { |
| 83 | + // if we have reached till the end of the string, means we have |
| 84 | + // segmented throughout correctly hence we have a solution, thus |
| 85 | + // returning true |
| 86 | + return true; |
| 87 | + } |
| 88 | + |
| 89 | + if (dp->at(pos) != INT_MAX) { |
| 90 | + // if dp[pos] is not INT_MAX, means we must have saved a solution |
| 91 | + // for the position pos; then return if the solution at pos is true |
| 92 | + // or not |
| 93 | + return dp->at(pos) == 1; |
| 94 | + } |
| 95 | + |
| 96 | + std::string wordTillNow = |
| 97 | + ""; // string to save the prefixes of word till different positons |
| 98 | + |
| 99 | + for (int i = pos; i < s.length(); i++) { |
| 100 | + // Loop starting from pos to end, to check valid set of |
| 101 | + // segmentations if any |
| 102 | + wordTillNow += |
| 103 | + std::string(1, s[i]); // storing the prefix till the position i |
| 104 | + |
| 105 | + // if the prefix till current position is present in the dictionary |
| 106 | + // and the remaining substring can also be segmented legally, then |
| 107 | + // set solution at position pos in the memo, and return true |
| 108 | + if (exists(wordTillNow, strSet) and check(s, strSet, i + 1, dp)) { |
| 109 | + dp->at(pos) = 1; |
| 110 | + return true; |
| 111 | + } |
| 112 | + } |
| 113 | + // if function has still not returned, then there must be no legal |
| 114 | + // segmentation possible after segmenting at pos |
| 115 | + dp->at(pos) = 0; // so set solution at pos as false |
| 116 | + return false; // and return no solution at position pos |
| 117 | +} |
| 118 | + |
| 119 | +/** |
| 120 | + * @brief Function that checks if the string passed in param can be |
| 121 | + * segmented into the strings present in the vector. |
| 122 | + * In others words, it checks if any permutation of strings in |
| 123 | + * the vector can be concatenated to form the final string. |
| 124 | + * |
| 125 | + * @param s the complete string to be segmented |
| 126 | + * @param wordDict a vector of words to be used as dictionary to look into |
| 127 | + * @returns `true` if s can be formed by a combination of strings present in |
| 128 | + * wordDict |
| 129 | + * @return `false` otherwise |
| 130 | + */ |
| 131 | +bool wordBreak(const std::string &s, const std::vector<std::string> &wordDict) { |
| 132 | + // unordered set to store words in the dictionary for constant time |
| 133 | + // search |
| 134 | + std::unordered_set<std::string> strSet; |
| 135 | + for (const auto &s : wordDict) { |
| 136 | + strSet.insert(s); |
| 137 | + } |
| 138 | + // a vector to be used for memoization, whose value at index i will |
| 139 | + // tell if the string s can be segmented (correctly) at position i. |
| 140 | + // initializing it with INT_MAX (which will denote no solution) |
| 141 | + std::vector<int> dp(s.length(), INT_MAX); |
| 142 | + |
| 143 | + // calling check method with position = 0, to check from left |
| 144 | + // from where can be start segmenting the complete string in correct |
| 145 | + // manner |
| 146 | + return check(s, strSet, 0, &dp); |
| 147 | +} |
| 148 | + |
| 149 | +} // namespace word_break |
| 150 | +} // namespace dynamic_programming |
| 151 | + |
| 152 | +/** |
| 153 | + * @brief Test implementations |
| 154 | + * @returns void |
| 155 | + */ |
| 156 | +static void test() { |
| 157 | + // the complete string |
| 158 | + const std::string s = "applepenapple"; |
| 159 | + // the dictionary to be used |
| 160 | + const std::vector<std::string> wordDict = {"apple", "pen"}; |
| 161 | + |
| 162 | + assert(dynamic_programming::word_break::wordBreak(s, wordDict)); |
| 163 | + |
| 164 | + // should return true, as applepenapple can be segmented as apple + pen + |
| 165 | + // apple |
| 166 | + std::cout << dynamic_programming::word_break::wordBreak(s, wordDict) |
| 167 | + << std::endl; |
| 168 | + std::cout << "Test implementation passed!\n"; |
| 169 | +} |
| 170 | +/** |
| 171 | + * @brief Main function |
| 172 | + * @returns 0 on exit |
| 173 | + */ |
| 174 | +int main() { |
| 175 | + test(); // call the test function :) |
| 176 | + |
| 177 | + // the complete string |
| 178 | + const std::string s = "applepenapple"; |
| 179 | + // the dictionary to be used |
| 180 | + const std::vector<std::string> wordDict = {"apple", "pen"}; |
| 181 | + |
| 182 | + // should return true, as applepenapple can be segmented as apple + pen + |
| 183 | + // apple |
| 184 | + std::cout << dynamic_programming::word_break::wordBreak(s, wordDict) |
| 185 | + << std::endl; |
| 186 | +} |
0 commit comments