diff --git a/README.md b/README.md index b0bece57..d0653064 100644 --- a/README.md +++ b/README.md @@ -47,12 +47,7 @@ * [数组:总结篇](https://mp.weixin.qq.com/s/LIfQFRJBH5ENTZpvixHEmg) * [字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA) * [字符串:简单的反转还不够!](https://mp.weixin.qq.com/s/XGSk1GyPWhfqj2g7Cb1Vgw) -* 精选链表相关的面试题 -* 精选字符串相关的面试题 -* 精选栈与队列相关的面试题 -* 精选二叉树相关的面试题 -* 精选递归与回溯面试题 - +* [字符串:替换空格](https://mp.weixin.qq.com/s/t0A9C44zgM-RysAQV3GZpg) (持续更新中....) diff --git a/pics/51.N皇后.png b/pics/51.N皇后.png new file mode 100644 index 00000000..6410647d Binary files /dev/null and b/pics/51.N皇后.png differ diff --git a/pics/51.N皇后1.png b/pics/51.N皇后1.png new file mode 100644 index 00000000..e6c9c72d Binary files /dev/null and b/pics/51.N皇后1.png differ diff --git a/problems/0001.两数之和.md b/problems/0001.两数之和.md index 91332d09..57070f18 100644 --- a/problems/0001.两数之和.md +++ b/problems/0001.两数之和.md @@ -55,7 +55,7 @@ public: if(iter != map.end()) { return {iter->second, i}; } - map.insert(nums[i], i); + map.insert(pair(nums[i], i)); } return {}; } diff --git a/problems/0051.N皇后.md b/problems/0051.N皇后.md index ca28c5de..900828d2 100644 --- a/problems/0051.N皇后.md +++ b/problems/0051.N皇后.md @@ -36,6 +36,49 @@ n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并 # 思路 +都知道n皇后问题是回溯算法解决的经典问题,但是用回溯解决多了 排列,组合,子集问题之后,遇到这种二位矩阵还会有点不知所措。 + +首先来看一下皇后们的约束条件: + +1. 不能同行 +2. 不能同列 +3. 不能同斜线 + + +确定完约束条件,来看看究竟要怎么去搜索皇后们的位置,其实搜索皇后的位置,可以抽象为一棵树。 + +下面我用一个3 * 3 的棋牌,如图: + + + +将搜索过程抽象为一颗树,如图: + + + + +从图中,可以看出,二维矩阵,其实矩阵的行,就是 这颗树的高度,矩阵的宽就是二叉树没一个节点孩子的宽度。 + + +那么我们用皇后们的约束条件,来回溯搜索这颗二叉树,**只要搜索到了树的叶子节点,说明就找到了皇后们的合理位置了。** + +我总结的回溯模板如下: + +``` +backtracking() { + if (终止条件) { + 存放结果; + } + + for (枚举同一个位置的所有可能性,可以想成节点孩子的数量) { + 递归,处理节点; + backtracking(); + 回溯,撤销处理结果 + } +} +``` + +那么按照这个模板不能写出如下代码: + # C++代码 ``` diff --git a/problems/0151.翻转字符串里的单词.md b/problems/0151.翻转字符串里的单词.md index 8f8454e4..35258a55 100644 --- a/problems/0151.翻转字符串里的单词.md +++ b/problems/0151.翻转字符串里的单词.md @@ -2,15 +2,41 @@ ## 题目地址 https://leetcode-cn.com/problems/reverse-words-in-a-string/ -## 思路 +> 综合考察字符串操作的好题。 -这道题目可以说是综合考察了字符串的多种操作。 +# 题目:151.翻转字符串里的单词 -那么问题来了,要不要使用split 和 reverse 等等库函数 +给定一个字符串,逐个翻转字符串中的每个单词。 -这道题目中很多同学使用库函数走捷径解题,其实这也无可厚非,如果这样做,一定要确保自己可以实现这些库函数的功能,别看 split 好像很简单,其实很多时候自己去实现的时候错漏百出的。 +示例 1: +输入: "the sky is blue" +输出: "blue is sky the" -解题思路: +示例 2: +输入: "  hello world!  " +输出: "world! hello" +解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 + +示例 3: +输入: "a good   example" +输出: "example good a" +解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 + + +# 思路 + +**这道题目可以说是综合考察了字符串的多种操作。** + + +一些同学会使用split库函数,分隔单词,然后定义一个新的string字符串,最后再把单词倒序相加,那么这道题题目就是一道水题了,失去了它的意义。 + +所以这里我还是提高一下本题的难度:**不要使用辅助空间,空间复杂度要求为O(1)。** + +不能使用辅助空间之后,那么只能在原字符串上下功夫了。 + +想一下,我们将整个字符串都反转过来,那么单词的顺序指定是倒序了,只不过单词本身也倒叙了,那么再把单词反转一下,单词不就正过来了。 + +所以解题思路如下: * 移除多余空格 * 将整个字符串反转 @@ -22,7 +48,86 @@ https://leetcode-cn.com/problems/reverse-words-in-a-string/ 这样我们就完成了翻转字符串里的单词。 -## 代码 +思路很明确了,我们说一说代码的实现细节,就拿移除多余空格来说,一些同学会上来写如下代码: + +``` +void removeExtraSpaces(string& s) { + for (int i = s.size() - 1; i > 0; i--) { + if (s[i] == s[i - 1] && s[i] == ' ') { + s.erase(s.begin() + i); + } + } + // 删除字符串最后面的空格 + if (s.size() > 0 && s[s.size() - 1] == ' ') { + s.erase(s.begin() + s.size() - 1); + } + // 删除字符串最前面的空格 + if (s.size() > 0 && s[0] == ' ') { + s.erase(s.begin()); + } +} +``` + +逻辑很简单,从前向后遍历,遇到空格了就erase。 + +如果不仔细琢磨一下erase的时间复杂读,还以为以上的代码是O(n)的时间复杂度呢。 + +想一下真正的时间复杂度是多少,一个erase本来就是O(n)的操作,erase实现原理题目:[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA),最优的算法来移除元素也要O(n)。 + +erase操作上面还套了一个for循环,那么以上代码移除冗余空格的代码时间复杂度为O(n^2)。 + +那么使用双指针法来去移除空格,最后resize(重新设置)一下字符串的大小,就可以做到O(n)的时间复杂度。 + +如果对这个操作比较生疏了,可以再看一下这篇文章:[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA)是如何移除元素的。 + +那么使用双指针来移除冗余空格代码如下: fastIndex走的快,slowIndex走的慢,最后slowIndex就标记着移除多余空格后新字符串的长度。 + +``` +void removeExtraSpaces(string& s) { + int slowIndex = 0, fastIndex = 0; // 定义快指针,慢指针 + // 去掉字符串前面的空格 + while (s.size() > 0 && fastIndex < s.size() && s[fastIndex] == ' ') { + fastIndex++; + } + for (; fastIndex < s.size(); fastIndex++) { + // 去掉字符串中间部分的冗余空格 + if (fastIndex - 1 > 0 + && s[fastIndex - 1] == s[fastIndex] + && s[fastIndex] == ' ') { + continue; + } else { + s[slowIndex++] = s[fastIndex]; + } + } + if (slowIndex - 1 > 0 && s[slowIndex - 1] == ' ') { // 去掉字符串末尾的空格 + s.resize(slowIndex - 1); + } else { + s.resize(slowIndex); // 重新设置字符串大小 + } +} +``` + +有的同学可能发现用erase来移除空格,在leetcode上性能也还行。主要是以下几点;: + +1. leetcode上的测试集里,字符串的长度不够长,如果足够长,性能差距会非常明显。 +2. leetcode的测程序耗时不是很准确的。 + +此时我们已经实现了removeExtraSpaces函数来移除冗余空格。 + +还做实现反转字符串的功能,支持反转字符串子区间,这个实现我们分别在[字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA)和[字符串:简单的反转还不够!](https://mp.weixin.qq.com/s/XGSk1GyPWhfqj2g7Cb1Vgw)里已经讲过了。 + +代码如下: + +``` +// 反转字符串s中左闭又闭的区间[start, end] +void reverse(string& s, int start, int end) { + for (int i = start, j = end; i < j; i++, j--) { + swap(s[i], s[j]); + } +} +``` + +## 本题C++整体代码 效率: @@ -33,13 +138,12 @@ class Solution { public: // 反转字符串s中左闭又闭的区间[start, end] void reverse(string& s, int start, int end) { - int offset = (end - start + 1) / 2; - for (int i = start, j = end; i < start + offset; i++, j--) { + for (int i = start, j = end; i < j; i++, j--) { swap(s[i], s[j]); } } - // 字符串去掉冗余的空格 - // 使用快慢指针发 + + // 移除冗余空格:使用双指针(快慢指针法)O(n)的算法 void removeExtraSpaces(string& s) { int slowIndex = 0, fastIndex = 0; // 定义快指针,慢指针 // 去掉字符串前面的空格 @@ -63,23 +167,6 @@ public: } } - // 费时间的写法 - void removeExtraSpaces1(string& s) { - for (int i = s.size() - 1; i > 0; i--) { - if (s[i] == s[i - 1] && s[i] == ' ') { - s.erase(s.begin() + i); - } - } - // 删除字符串最后面的空格 - if (s.size() > 0 && s[s.size() - 1] == ' ') { - s.erase(s.begin() + s.size() - 1); - } - // 删除字符串最前面的空格 - if (s.size() > 0 && s[0] == ' ') { - s.erase(s.begin()); - } - } - string reverseWords(string s) { removeExtraSpaces(s); // 去掉冗余空格 reverse(s, 0, s.size() - 1); // 将字符串全部反转