二叉树的所有路径
给你一个二叉树的根节点root,按任意顺序,返回从根节点到叶子节点的路径。
深度优先搜索
- 如果当前节点不是叶子节点,则在当前的路径末尾添加该节点,并继续递归遍历节点的每一个孩子节点。
- 如果当前节点是叶子节点,则在当前路径末尾添加该节点后我们就得到了一条从根节点到叶子节点的路径,将该路径加入到答案即可。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
void dfs(TreeNode*root, vector<string> &ret, string path){
if(root != nullptr){
path += to_string(root->val);
if(root->left == nullptr && root->right == nullptr){
ret.push_back(path);
}else{
path += "->";
dfs(root->left, ret, path);
dfs(root->right, ret, path);
}
}
}
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> ret;
dfs(root, ret, "");
return ret;
}
};
合并数组中的最大元素
给一个下标从0开始,由正整数组成的数组nums。
可以在数组上执行下述操作任意次:
贪心+倒序遍历数组
题目中的一次替换删除操作,相当于将两个相邻并且非递减的数字进行求和合并。
经过若干次这样的合并,整个数组的和是不变的。
合并后数组中的每个元素,都是原数组的某个子数组的和,并且这些子数组拼接起来能构成整个原数组。
为了使数组的最大值最大,我们可以贪心地尽可能多的合并,直到整个数组都不能进行合并。
尽可能先合并靠后的数字,使其能尽快大,才能合并前面的数字。
第一个错误的版本
你是产品经理,目前正在带领一个团队开发新的产品。
产品的最新版本没有通过质量检测,由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
二分查找
因为题目要求尽可能减少调用检查接口的次数,所以不能对每个版本都调用检查接口,而是应该将调用检查接口的次数降到最低。
当一个版本为正确版本,则该版本之前所有的版本均为正确版本,当一个版本为错误版本,则该版本之后的所有版本均为错误版本。我们可以利用这个性质进行二分查找。
矩阵中移动的最大次数
广度优先搜索
首先把行坐标加入到集合中,作为出发点(第一列中的任一单元格出发)。
对于每一个单元格,找到下一个列的相邻单元格,并判断是否严格大于当前单元格。
如果是,说明可以移动到达。
把所有可到达的单元格坐标加到集合中,用于下一轮的搜索。
当到达最后一列或者集合为空,搜索结束,返回矩阵中移动的最大次数。
最小高度树
树是一个无向图,其中任何两个顶点只通过一条路径连接。
换句话说,一个任何没有简单环路的连通图都是一棵树。
给一颗包含n个节点的树,标记为0到n-1,给定数字n和一个有n-1条无向边的edges列表(每一个边都是一对标签),其中edges[i] = [ai,bi]表示树中节点ai和bi之间存在一条无向边。
区域和检索——数组不可变
前缀和
给定一个整数数组nums,处理以下类型的多个查询。
最朴素的想法是存储数组nums的值,每次调用sumRange时,通过循环的方式计算和,一共计算j-i+1个元素的和。
由于每次检索的时间和检索的下标范围有关,因此检索的时间复杂度较高,如果检索次数较多,则会超出时间限制。
可以计算数组nums在下标j和下标i-1的前缀和,然后计算两个前缀和的差。
统计桌面上的不同数字
给你一个正整数n,开始时,放在桌面上。
在109天内,每天都要执行下述步骤:
- 对于出现在桌面上的数字x,找出符合1<=i<=n,且满足x%i==1的所有数字i,然后将这些数字放在桌面上。
每天都对桌面上已出现的数字进行遍历,对于当前遍历的数字x。
数组的相对排序
class Solution {
public:
vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
vector<int>res;
vector<int>res2;
int cn=0;;
for(int i=0; i<arr2.size(); i++){
cn = count(arr1.begin(),arr1.end(),arr2[i]);
while(cn--){
res.push_back(arr2[i]);
}
}
for(int i=0; i<arr1.size(); i++){
if(find(arr2.begin(), arr2.end(), arr1[i]) == arr2.end()){
res2.push_back(arr1[i]);
}
}
sort(res2.begin(),res2.end());
for(int i=0; i<res2.size(); i++){
res.push_back(res2[i]);
}
return res;
}
};
两数之和IV——输入二叉搜索树
深度优先搜索+哈希表
使用深度优先搜索的方式遍历整棵树,用哈希表记录遍历过的节点的值。
对于一个值为x的节点,检查哈希表中是否存在k-x即可。如果存在,就找到;否则,就把x放入到哈希表中。
class Solution{
public:
unordered_set<int> hashTable;
bool findTarget(TreeNode *root, int k){
if(root == nullptr){
return false;
}
if(hashTable.count(k-root->val)){
return true;
}
hashTable.insert(root->val);
return findTarget(root->left,k) || findTarget(root->right,k);
}
};
按摩师
每次预约服务之间要有休息时间,不能接受相邻的预约。
给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回总的分钟数。
动态规划
定义dp[i][0]为前i个预约,第i个预约不接的最长预约时间。
dp[i][1]表示前i个预约,第i个预约接的最长预约时间。
从前往后计算dp值,假设已经计算出前i-1个dp值,考虑计算dp[i][0/1]的方案。
dp[i][0]由于这个状态下第i个预约是不接的,所以第i-1个预约接或不接都可以
dp[i][0] = max(dp[i-1][0],dp[i-1][1])
dp[i][1] = dp[i-1][0]+nums[i]
最后答案为max(dp[n][0],dp[n][1])
再看转移方程,计算dp[i][0/1]时,只与前一个状态有关,因此不用开数组,只用两个变量存储即可。
整理字符串
从左到右扫描字符串s的每个字符。
扫描过程中,维护当前整理好的字符串,记为ret。当扫描到字符ch时,有两种情况:
- 字符ch与字符串ret的最后一个字符互为同一个字母的大小写,此时弹出ret。
- 否则两个都保留
主要元素
Boyer-Moore投票算法
每一轮投票过程中,从数组中删除两个不同的元素,直到投票过程无法继续,此时数组为空或者数组中剩下的元素都相等。
数组中的字符串匹配
给一个字符串数组words,数组中的每个字符串都可以看做是一个单词。按任意顺序返回words是其它单词的子字符串的所有单词。
逐步求和得到正数的最小值
给一个整数数组nums。可以选定任意的正数startValue作为初始值。
需要从左到右遍历nums数组,并将startValue依次累加上nums数组中的值。
要确保和大于等于1,选出一个最小的正数作为startValue。
暴力求解
class Solution {
public:
int minStartValue(vector<int>& nums) {
int startValue = 1;
while(1){
int sum = startValue;
for(int num:nums){
sum += num;
if(sum < 1){
break;
}
}
if(sum < 1){
startValue++;
}else{
break;
}
}
return startValue;
}
};
贪心
要保证所有的累加和accSum满足accSum + startValue ≥1,只要保证累加和的最小值accSumMin满足accSumMin+startValue≥1,那么startValue的最小值即可取1-accSumMin。
class Solution {
public:
int minStartValue(vector<int>& nums) {
int minSum = nums[0];
int sum = nums[0];
for(int i=1; i<nums.size(); i++){
sum += nums[i];
if(sum < minSum){
minSum = sum;
}
}
if(minSum < 0){
return 1-minSum;
}
return 1;
}
};
分割字符串的最大得分
两次遍历
暴力求解,对于每个分割点遍历整个字符串计算分割字符串得分。
可以换一个角度思考,如果分割点从左到右移动一位,则位于原分割点处的字符从右子字符串移除并添加到左字符串中,根据该字符串的值更新分割字符串得分。
正整数和负整数的最大计数
由于数组呈现非递减顺序,因此可通过二分查找定位第一个数值大于等于0的位置pos1,以及第一个数值大于等于1的下标pos2.