03.数组中的重复数字
使用unordered_set 容器,容器特性:不再以键值对的形式存储数据,而是直接存储数据的值;容器内部存储的各个元素的值都互不相等,且不能被修改;不会对内部存储的数据进行排序。
默认构造函数创建空的 unordered_set 容器
std::unordered_set<std::string> uset;
其他常用方法:
begin() 返回指向容器中第一个元素的正向迭代器。
end() 返回指向容器中最后一个元素之后位置的正向迭代器。
empty() 若容器为空,则返回 true;否则 false。
size() 返回当前容器中存有元素的个数。
emplace() 向容器中添加新元素,效率比 insert() 方法高。
insert() 向容器中添加新元素。
erase() 删除指定元素。
find(key) 查找以值为 key 的元素,如果找到,则返回一个指向该元素的正向迭代器;反之,则返回一个指向容器中最后一个元素之后位置的迭代器(如果 end() 方法返回的迭代器)。
uset.find(key) == uset.end(), 说明uset中没有key元素
04.二维数组中的查找
左分支比根小,右分支比根大。
从(0, m-1)开始与目标值比较,目标值大则往右:i++;目标值小则往左:j–;直到到达(n-1,0)
05.替换空格
string的大小和容量
string s;
s.size();
s.length(); //二者执行效果相同
先遍历字符串统计空格个数,扩充string长度
s.resize(length); //将字符串s的长度扩充为length
双指针法,i指向新长度末尾,j指向旧长度末尾,从后向前遍历
s[j]是空格,i的位置填充0、2、%,否则s[i] = s[j]
if(s[j] == ' '){
s[i] = '0'; //s[i] = "0"这种写法报错了,不知道为什么,只能单引号
s[--i] = '2';
s[--i] = '%';
}
06.从尾到头打印链表
- 从头到尾遍历并输出到vector,然后翻转
- 递归
vector<int> reverse(ListNode* node, vector<int>& a){
if(node == NULL) return a;
reverse(node -> next, a);
a.push_back(node->val);
return a;
}
vector<int> reversePrint(ListNode* head) {
vector<int> result;
reverse(head, result);
return result;
}
官方题解还有用栈的方法,但是又要遍历链表压入栈,又要把元素依次出栈,感觉没必要,对于这道题来说太复杂了,但是好像突然理解了栈和递归的关系
07.重建二叉树
- 数组为空,结束递归
- preorder[0]作为根节点
- 在inorder中找到根节点的值所在位置,作为切割点
- 根据切割点将inorder切割为左右子树的中序遍历
- 根据左右子树中序遍历的长度,将preorder切割为左右子树的前序遍历
- 分别递归构建左右子树
vector对象初始化的方法:
圆括号用来构造vector对象,例如用来说明容量和初始值;花括号用来列表初始化vector对象,花括号内的值即为元素初始值的列表
vector<T> v1 //v1是一个空vector,它潜在的元素是T类型的,执行默认初始化
vecotr<T> v2(v1) //v2中包含有v1所有元素的副本
vector<T> v2 = v1 //等价于v2(v1)
vector<T> v3(n, val) //v3包含了n个重复元素,且每个元素的值都是val
vector<T> v4(n) //v4包含了n个重复执行了值初始化的对象,即v4长度为n,初值为数据类型T默认的初始值,对于int即初值均为0
vector<T> v5{a,b,c,...} //v5包含了初始值个数的元素,每个元素被赋予相应的初始值
vector<T> v5 = {a,b,c,...} //等价于 v5{a,b,c,...}
10.斐波那契数列
直接递归会超时
动态规划:转移方程dp[i] = dp[i -1] + dp[i - 2]
所以每次只需要记录dp[i -1]和dp[i - 2]的值,在设置一个中间值tmp,来每次传递新值
还要记得取模
int n_0 = 0;
int n_1 = 1;
for(int i = 0; i < n ; i++){
int sum = (n_0 + n_1) % 1000000007;
n_0 = n_1;
n_1 = sum;
}
return n_0; //从0开始的斐波那契数列
这道题通过率低真的很有道理,处处是坑。
- 青蛙跳台阶问题是从1开始的数列
int n_0 = 1;
int n_1 = 1;
for(int i = 0; i < n; i++){
int sum = (n_0 + n_1) % 1000000007;
n_0 = n_1;
n_1 = sum;
}
return n_0;
11.旋转数组的最小数字
不再是递增的时候就是最小数字。
循环时向前比较,这样 i 不容易溢出,同时这样循环需要提前判断输入数组的长度是否为1,是则直接返回。
int minArray(vector<int>& numbers) {
if(numbers.size() == 1) return numbers[0];
int i = 1;
while(i < numbers.size()){
if(numbers[i] >= numbers[i - 1]) i++;
else break;
}
if(i == numbers.size()) return numbers[0]; //没有旋转,整个数组符合递增序列
else return numbers[i];
}
12.矩阵的路径
- 递归函数的参数和返回值:返回值为布尔值res,记录是否匹配;参数包括递归的元素在矩阵中的位置i,j,当前目标字符在word中的索引k
- 终止条件:
返回false:行列越界;矩阵元素与目标字符不匹配;当前元素已经在上一层递归访问过(可以与上一个条件重合,因为在下述递归逻辑中将访问的元素设为了空字符)
返回true:k = word.length() - 1,即全部匹配成功 - 递归逻辑:
- 标记当前元素:将word[i][j]修改为空字符,代表已经访问过,避免重复访问
- 搜索下一单元格:向上下左右进行四层递归,结果使用或连接(只要找到一条可行路径就返回),将该结果记录到res
- 还原当前元素:将word[i][j]还原为初始值
class Solution {
public:
int rows,cols;
bool traversal(vector<vector<char>>& board, string word, int i, int j, int k){
if(i >= rows || i < 0 || j < 0 || j >= cols) return false;
if(board[i][j] != word[k]) return false;
if(k == word.length() - 1) return true;
//这里其实不需要tmp,因为没有在上面三个if下结束该次递归,说明此时word[k] == board[i][j]
char tmp = board[i][j];
board[i][j] = '\0';
bool result = (traversal(board, word, i - 1, j, k + 1) || //上
traversal(board, word, i + 1, j, k + 1) || //下
traversal(board, word, i, j - 1, k + 1) || //左
traversal(board, word, i, j + 1, k + 1)); //右
board[i][j] = tmp; //这里board[i][j] = word[k]即可
return result;
}
bool exist(vector<vector<char>>& board, string word) {
rows = board.size();
cols = board[0].size();
for(int i = 0;i < rows; i++){
for(int j = 0; j < cols; j++){
//从word第一个字符开始进入递归,与board[i][j]遍历进行比较
if(traversal(board, word, i, j, 0)) return true;
}
}
return false;
}
};