131.分割回文串
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。
示例:
输入: "aab"
输出:
[
["aa","b"],
["a","a","b"]
]
class Solution {
public:
vector<vector<string>> partition(string s) {
vector<string> temp;
vector<vector<string>>ret;
DFS(s, temp, ret, 0);
return ret;
}
void DFS(string s, vector<string> &temp, vector<vector<string>> &ret, int pos)
{
if(s.size() == pos)
{
ret.push_back(temp);
return;
}
for(int i = pos; i <s.size();i++)
{
if(isPalindrome(s, pos, i))
{
temp.push_back(s.substr(pos, i-pos+1));
DFS(s, temp, ret, i+1);
temp.pop_back();
}
}
}
bool isPalindrome(string s, int start, int end)
{
while(start < end)
{
if(s[start] != s[end])
return false;
start++;
end--;
}
return true;
}
};
139.单词拆分
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
- 拆分时可以重复使用字典中的单词。
- 你可以假设字典中没有重复的单词。
示例 1:
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。
示例 2:
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 applepenapple 可以被拆分成 apple pen apple
注意你可以重复使用字典中的单词。
示例 3:
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false
第一种方法递归,并将不在字典中的string保存在set中防止重复访问
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> dict;
for(int i = 0; i < wordDict.size(); i++)
dict.insert(wordDict[i]);
unordered_set<string> fail;
return helper(s, dict, fail);
}
bool helper(string s, unordered_set<string> & wordDict, unordered_set<string> &fail)
{
if(s.length() == 0)
return true;
for(int j = 1; j <= s.length();j++)
{
string first = s.substr(0, j);
if(wordDict.find(first) != wordDict.end())
{
string second = s.substr(j);
if(fail.find(second) == fail.end())
{
if(helper(second, wordDict, fail))
return true;
else
fail.insert(second);
}
}
}
return false;
}
};
第二种方法:动态规划法,字符串分为两部分s.substr(0,j),另一部分s.substr(j, i-j),如果s.substr(j,i-j)是字典中的单词且dp[j]是true,那么dp[i]也是true
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
int n = s.length();
vector<bool>dp(n+1, false);
dp[0] = true;
for(int i = 1; i <=n;i++)
for(int j = 0;j < i; j++)
{
if(find(wordDict.begin(), wordDict.end(), s.substr(j, i-j)) != wordDict.end())
{
if(dp[j])
{
dp[i] = true;
break;
}
}
}
return dp[n];
}
};
140.单词拆分 II
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中。返回所有这些可能的句子。
说明:
- 分隔时可以重复使用字典中的单词。
- 你可以假设字典中没有重复的单词。
示例 1:
输入: s = "catsanddog
" wordDict =["cat", "cats", "and", "sand", "dog"]
输出:[ "cats and dog", "cat sand dog" ]
示例 2:
输入:
s = "pineapplepenapple"
wordDict = ["apple", "pen", "applepen", "pine", "pineapple"]
输出:
[
"pine apple pen apple",
"pineapple pen apple",
"pine applepen apple"
]
解释: 注意你可以重复使用字典中的单词。
示例 3:
输入:
s = "catsandog"
wordDict = ["cats", "dog", "sand", "and", "cat"]
输出:
[]
第一种方法:递归回溯,超时了
class Solution {
public:
vector<string> wordBreak(string s, vector<string>& wordDict) {
vector<string> ret;
dfs(s, "", wordDict, ret);
return ret;
}
void dfs(string s, string str, vector<string> &wordDict, vector<string> &ret)
{
if(s.length() == 0)
{
ret.push_back(str);
return;
}
for(int i = 1; i <= s.size();i++)
{
if(find(wordDict.begin(), wordDict.end(), s.substr(0, i)) != wordDict.end())
{
string temp = str.empty() ? s.substr(0, i) : str+ " " + s.substr(0, i);
dfs(s.substr(i), temp, wordDict, ret);
}
}
}
};
第二种方法:记忆递归
class Solution {
public:
vector<string> wordBreak(string s, vector<string>& wordDict) {
unordered_map<string, vector<string>> map;//map记录前面的字符串wordbreak的结果
return dfs(s, wordDict, map);
}
vector<string> dfs(string s, vector<string> &wordDict, unordered_map<string, vector<string>> &map)
{
if(map.count(s))
return map[s];
if(s.length() == 0)
return {""};
vector<string> ret;
for(string word : wordDict)
{
if(s.length() < word.length() || s.substr(0, word.length()) != word)
continue;
vector<string> rem = dfs(s.substr(word.length()), wordDict, map);
for(int i = 0; i < rem.size(); i++)
{
string temp = word + (rem[i].empty() ? "" : " ") + rem[i];
ret.push_back(temp);
}
}
return map[s] = ret;
}
};
208.实现 Trie (前缀树)
实现一个 Trie (前缀树),包含 insert
, search
, 和 startsWith
这三个操作。
示例:
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple"); // 返回 true
trie.search("app"); // 返回 false
trie.startsWith("app"); // 返回 true
trie.insert("app");
trie.search("app"); // 返回 true
说明:
- 你可以假设所有的输入都是由小写字母
a-z
构成的。 - 保证所有输入均为非空字符串。
Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
它有3个基本性质:
- 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
- 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
- 每个节点的所有子节点包含的字符都不相同。
class TrieNode{
public:
TrieNode *child[26];
bool isWord;
TrieNode()
{
isWord = false;
for(auto &a : child)
a = NULL;
}
};
class Trie {
private:
TrieNode *root;
public:
/** Initialize your data structure here. */
Trie() {
root = new TrieNode();
}
/** Inserts a word into the trie. */
void insert(string word) {
TrieNode *p = root;
for(int i = 0; i < word.length();i++)
{
int n = word[i]-'a';
if(!p->child[n])
p->child[n] = new TrieNode();
p = p->child[n];
}
p->isWord = true;
}
/** Returns if the word is in the trie. */
bool search(string word) {
TrieNode *p = root;
for(int i = 0; i < word.length();i++)
{
int n = word[i]-'a';
if(!p->child[n])
return false;
p = p->child[n];
}
return p->isWord;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
bool startsWith(string prefix) {
TrieNode *p = root;
for(int i = 0; i < prefix.length();i++)
{
int n = prefix[i]-'a';
if(!p->child[n])
return false;
p = p->child[n];
}
return true;
}
};
/**
* Your Trie object will be instantiated and called as such:
* Trie* obj = new Trie();
* obj->insert(word);
* bool param_2 = obj->search(word);
* bool param_3 = obj->startsWith(prefix);
*/
212.单词搜索 II
给定一个二维网格 board 和一个字典中的单词列表 words,找出所有同时在二维网格和字典中出现的单词。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。
示例:
输入: words =["oath","pea","eat","rain"]
and board = [ ['o','a','a','n'], ['e','t','a','e'], ['i','h','k','r'], ['i','f','l','v'] ] 输出:["eat","oath"]
说明:
你可以假设所有输入都由小写字母 a-z
组成。
提示:
- 你需要优化回溯算法以通过更大数据量的测试。你能否早点停止回溯?
- 如果当前单词不存在于所有单词的前缀中,则可以立即停止回溯。什么样的数据结构可以有效地执行这样的操作?散列表是否可行?为什么? 前缀树如何?如果你想学习如何实现一个基本的前缀树,请先查看这个问题: 实现Trie(前缀树)。
第一种方法:递归回溯,超时了
class Solution {
public:
vector<string> findWords(vector<vector<char> >& board, vector<string>& words) {
vector<string> ret;
if(board.size() == 0 || board[0].size() == 0 || words.size() == 0)
return ret;
int m = board.size();
int n = board[0].size();
vector<vector<bool> >visit(m, vector<bool>(n, false));
for(int i = 0; i < words.size();i++)
{
string s = words[i];
for(int j = 0; j < m; j++)
for(int k = 0; k < n; k++)
{
if(dfs(board, s, 0, j, k, visit))
{
if(find(ret.begin(), ret.end(), s) == ret.end())
ret.push_back(s);
}
}
}
return ret;
}
bool dfs(vector<vector<char> >& board, string s, int i, int x, int y, vector<vector<bool> >&visit)
{
if(s.length() == i)
return true;
if(x < 0 || y < 0 || x >= board.size() || y >= board[0].size() || visit[x][y] || s[i] != board[x][y])
return false;
visit[x][y] = true;
bool result = dfs(board, s, i+1, x-1, y, visit) || dfs(board, s, i+1, x, y-1, visit) || dfs(board, s, i+1, x+1, y, visit)
|| dfs(board, s, i+1, x, y+1, visit);
visit[x][y] = false;
return result;
}
};
第二种方法使用前缀树
class TrieNode{
public:
TrieNode *child[26];
string str;
TrieNode()
{
str = "";
for(auto &a : child)
a = NULL;
}
};
class Trie {
public:
TrieNode *root;
public:
/** Initialize your data structure here. */
Trie() {
root = new TrieNode();
}
/** Inserts a word into the trie. */
void insert(string word) {
TrieNode *p = root;
for(int i = 0; i < word.length();i++)
{
int n = word[i]-'a';
if(!p->child[n])
p->child[n] = new TrieNode();
p = p->child[n];
}
p->str = word;
}
};
class Solution {
public:
vector<string> findWords(vector<vector<char> >& board, vector<string>& words) {
vector<string> ret;
if(board.size() == 0 || board[0].size() == 0 || words.size() == 0)
return ret;
int m = board.size();
int n = board[0].size();
Trie T;
vector<vector<bool> >visit(m, vector<bool>(n, false));
for(string word : words)
T.insert(word);
for(int i = 0; i < m; i++)
for(int j = 0; j < n; j++)
{
dfs(board, i, j, visit, T.root, ret);
}
return ret;
}
void dfs(vector<vector<char> >& board, int x, int y, vector<vector<bool> > &visit, TrieNode *p, vector<string> &ret)
{
if(!p->str.empty())
{
ret.push_back(p->str);
p->str.clear();
}
if(x < 0 || y < 0 || x >= board.size() || y >= board[0].size() || !p->child[board[x][y]-'a'] || visit[x][y])
return;
visit[x][y] = true;
p = p->child[board[x][y]-'a'];
dfs(board, x-1, y, visit, p, ret);
dfs(board, x+1, y, visit, p, ret);
dfs(board, x, y-1, visit, p, ret);
dfs(board, x, y+1, visit, p, ret);
visit[x][y] = false;
}
};
242.有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的一个字母异位词。
示例 1:
输入: s = "anagram", t = "nagaram"
输出: true
示例 2:
输入: s = "rat", t = "car"
输出: false
说明:
你可以假设字符串只包含小写字母。
class Solution {
public:
bool isAnagram(string s, string t) {
if(s.size() != t.size())
return false;
unordered_map<char, int>map;
for(int i = 0;i < s.length();i++)
{
map[s[i]]++;
map[t[i]]--;
}
for(auto iter = map.begin(); iter != map.end(); iter++)
if(iter->second != 0)
return false;
return true;
}
};
387.字符串中的第一个唯一字符
给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。
案例:
s = "leetcode"
返回 0.
s = "loveleetcode",
返回 2.
class Solution {
public:
int firstUniqChar(string s) {
unordered_map<char, int>temp;
for(int i = 0; i < s.length(); i++)
temp[s[i]]++;
for(int j = 0; j < s.length(); j++)
if(temp[s[j]] == 1)
return j;
return -1;
}
};
75.颜色分类
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
注意:
不能使用代码库中的排序函数来解决这道题。
示例:
输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]
进阶:
- 一个直观的解决方案是使用计数排序的两趟扫描算法。
首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。 - 你能想出一个仅使用常数空间的一趟扫描算法吗?
class Solution {
public:
void sortColors(vector<int>& nums) {
int n = nums.size();
int head = 0, tail = n-1;
for(int i = 0; i < n;)
{
if(i > tail)
break;
if(nums[i] == 2)
{
swap(nums[i], nums[tail]);
tail--;
}
else if(nums[i] == 0)
{
swap(nums[i], nums[head]);
i++;
head++;
}
else
i++;
}
}
};
152.乘积最大子序列
给定一个整数数组 nums
,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。
示例 1:
输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例 2:
输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
第一种方法:暴力求解,时间O(n2)
class Solution {
public:
int maxProduct(vector<int>& nums) {
int n = nums.size();
int multiply;
int max = INT_MIN;
for(int i = 0; i < n; i++)
{
multiply = 1;
for(int j = i; j < n; j++)
{
multiply = multiply * nums[j];
if(multiply > max)
max = multiply;
}
}
return max;
}
};
384.打乱数组
打乱一个没有重复元素的数组。
示例:
// 以数字集合 1, 2 和 3 初始化数组。 int[] nums = {1,2,3}; Solution solution = new Solution(nums); // 打乱数组 [1,2,3] 并返回结果。任何 [1,2,3]的排列返回的概率应该相同。 solution.shuffle(); // 重设数组到它的初始状态[1,2,3]。 solution.reset(); // 随机返回数组[1,2,3]打乱后的结果。 solution.shuffle();
思路:看到reset函数的作用是重置到初始状态,我们需要一个数组来保存原来的数组。
其次,就是shuffle函数,打乱顺序。我们需要一个数组来进行打乱,对于数组中的每一项都有可能与其他项进行交换,至于对哪一项进行交换我们可以使用rand函数来产生随机数,这就保证了等可能性。
class Solution {
private:
vector<int> copy;
public:
Solution(vector<int>& nums) {
copy = nums;
}
/** Resets the array to its original configuration and return it. */
vector<int> reset() {
return copy;
}
/** Returns a random shuffling of the array. */
vector<int> shuffle() {
vector<int> cur(copy);
for(int i = cur.size() - 1; i >= 0;i--)
{
int pos = rand() % (i + 1);
swap(cur[pos], cur[i]);
}
return cur;
}
};
/**
* Your Solution object will be instantiated and called as such:
* Solution* obj = new Solution(nums);
* vector<int> param_1 = obj->reset();
* vector<int> param_2 = obj->shuffle();
*/
350.两个数组的交集 II
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入: nums1 = [1,2,2,1], nums2 = [2,2] 输出: [2,2]
示例 2:
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4] 输出: [4,9]
说明:
- 输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。
- 我们可以不考虑输出结果的顺序。
进阶:
- 如果给定的数组已经排好序呢?你将如何优化你的算法?
- 如果 nums1 的大小比 nums2 小很多,哪种方法更优?
- 如果 nums2 的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
int n1 = nums1.size(), n2 = nums2.size();
vector<int> ret;
if(n1 == 0 || n2 == 0)
return ret;
unordered_map<int, int> nums1_count;
for(int i = 0; i < n1;i++)
{
if(nums1_count.find(nums1[i]) == nums1_count.end())
nums1_count.insert(make_pair(nums1[i], 1));
else
nums1_count[nums1[i]]++;
}
for(int i = 0; i < n2; i++)
{
if(nums1_count[nums2[i]] > 0)
{
ret.push_back(nums2[i]);
nums1_count[nums2[i]]--;
}
}
return ret;
}
};
334.递增的三元子序列
给定一个未排序的数组,判断这个数组中是否存在长度为 3 的递增子序列。
数学表达式如下:
如果存在这样的 i, j, k, 且满足 0 ≤ i < j < k ≤ n-1,
使得 arr[i] < arr[j] < arr[k] ,返回 true ; 否则返回 false 。
说明: 要求算法的时间复杂度为 O(n),空间复杂度为 O(1) 。
示例 1:
输入: [1,2,3,4,5] 输出: true
示例 2:
输入: [5,4,3,2,1] 输出: false
思路一: dp[i] = max(dp[i], dp[j] + 1);(0 <= j <i , nums[j] < nums[i]),时间和空间复杂度都是O(n)
class Solution {
public:
bool increasingTriplet(vector<int>& nums) {
int n = nums.size();
if(n <= 2)
return false;
vector<int>dp(n, 1);
for(int i = 0; i < n; i++)
for(int j = 0; j < i; j++)
{
if(nums[j] < nums[i])
dp[i] = max(dp[i], dp[j]+1);
if(dp[i] >= 3)
return true;
}
return false;
}
};
思路二:使用m1和m2保存第二和第三大的两个数,如果有值大于这两个,说明有三个连续的递增元素,空间复杂度为O(1)
class Solution {
public:
bool increasingTriplet(vector<int>& nums) {
int n = nums.size();
if(n <= 2)
return false;
int m1 = INT_MAX, m2 = INT_MAX;
for(int i = 0; i < n; i++)
{
if(m1 >= nums[i])
m1 = nums[i];
else if(m2 >= nums[i])
m2 = nums[i];
else
return true;
}
return false;
}
};
238.除自身以外数组的乘积
给定长度为 n 的整数数组 nums
,其中 n > 1,返回输出数组 output
,其中 output[i]
等于 nums
中除 nums[i]
之外其余各元素的乘积。
示例:
输入:[1,2,3,4]
输出:[24,12,8,6]
说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。
进阶:
你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)
思路:从前往后,从后往前保存两个累乘数组
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
vector<int> left;
vector<int> right;
vector<int> ret;
int multiply = 1;
int n = nums.size();
for(int i = 0; i < n; i++)
{
multiply = multiply * nums[i];
left.push_back(multiply);
}
multiply = 1;
for(int i = n-1; i >= 0; i--)
{
multiply = multiply * nums[i];
right.push_back(multiply);
}
ret.push_back(right[n-2]);
for(int i = 1; i < nums.size()-1;i++)
{
multiply = left[i-1] * right[n-2-i];
ret.push_back(multiply);
}
ret.push_back(left[n-2]);
return ret;
}
};