文章目录
二叉树相关
965. 单值二叉树
965. 单值二叉树
要求每个节点值相同,相同返回true
,反之返回false
二叉树解决方法,遍历框架:
void traverse(root){
if(root == null) return;
//前
traverse(root.left);
//中
traverse(root.right);
//后
}
定义两个全局变量,bool flag = true
记录合规性,一旦出现false
即返回;int val
记录根节点的值与其他节点比较。
/**
* 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:
bool flag = true;
int val;
bool isUnivalTree(TreeNode* root) {
if(root == nullptr) return true;
val = root->val;
traverse(root);
return flag;
}
void traverse(TreeNode* root){
// 发现节点为空或者flag== false的情况,即return
if(root == nullptr || !flag) return;
if(root->val != val) {
flag = false;
return;
}
traverse(root->left);
traverse(root->right);
}
};
993. 二叉树的堂兄弟节点
在二叉树中,根节点位于深度 0 处,每个深度为 k 的节点的子节点位于深度 k+1 处。
如果二叉树的两个节点深度相同,但 父节点不同 ,则它们是一对堂兄弟节点。
我们给出了具有唯一值的二叉树的根节点 root ,以及树中两个不同节点的值 x 和 y 。
只有与值 x 和 y 对应的节点是堂兄弟节点时,才返回 true 。否则,返回 false。
题解:
二叉树问题无非就是“遍历” 和“分治”
这个问题应该使用遍历方法,确定返回true
的条件为
x
和y
的父节点是不同的x
和y
应位于同一层,即两者的深度相同。
所以,在遍历找到x
和y
的同时将其父节点信息和深度信息一同记录
二叉树遍历的框架为
void traverse(root,参数){
if(root == null) return;
//前
traverse(root.left);
//中
traverse(root.right);
//后
}
此题中需要找到深度和父节点,参数中需要深度信息depth
和父节点TreeNode* parent
。
另外为找到x
和y
的位置,要比较root->val
所以在前序遍历位置写入。
解答如下:
/**
* 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:
//定义几个全局变量
//父节点、深度,x、y的值
TreeNode* parentX = nullptr;
TreeNode* parentY = nullptr;
int depthX = 0, depthY = 0;
int x, y;
bool isCousins(TreeNode* root, int x, int y) {
this->x = x;
this->y = y;
traverse(root, 0,nullptr);
//父节点不同且深度相同即符合条件
if(parentX != parentY && depthY == depthX){
return true;
}
return false;
}
//输入 root 当前深度,父节点,记录相应值
void traverse(TreeNode* root, int depth, TreeNode* parent){
if(root == nullptr) return;
//前序位置
if(root->val == x) {
//记录x 深度和父节点
parentX = parent;
depthX = depth;
}
if(root->val == y){
//记录y
parentY = parent;
depthY = depth;
}
traverse(root->left, depth+1, root);
traverse(root->right, depth+1, root);
}
};
250 统计同值子树
题目描述:
给定一个二叉树,统计该二叉树数值相同的子树个数。
同值子树是指该子树的所有节点都拥有相同的数值。
因为要得到子树的相关信息,利用后序遍历得到子树的信息。
/**
* 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:
int res = 0;
int countUnivalSubtrees(TreeNode* root) {
// 先保证父节点不为空
if(root == nullptr) return 0;
getUnivalue(root);
return res;
}
// 输入一个节点
// 如果是同值子树 返回true
// 不是同值子树返回false
bool getUnivalue(TreeNode* root){
if(root == nullptr) return true;
bool left = getUnivalue(root->left);
bool right = getUnivalue(root->right);
//后序遍历位置
// 已经得到子树信息
if(root->left && root->val != root->left->val){
return false;
}
if(root->right && root->val != root->right->val){
return false;
}
if(right && left){
res++;
return true;
}
return false;
}
};
括号题目相关
1541. 平衡括号字符串的最少插入次数
给你一个括号字符串 s ,它只包含字符 ‘(’ 和 ‘)’ 。一个括号字符串被称为平衡的当它满足:
- 任何左括号 ‘(’ 必须对应两个连续的右括号 ‘))’ 。
- 左括号 ‘(’ 必须在对应的连续两个右括号 ‘))’ 之前。
比方说 “())”, “())(())))” 和 “(())())))” 都是平衡的, “)()”, “()))” 和 “(()))” 都是不平衡的。
你可以在任意位置插入字符 ‘(’ 和 ‘)’ 使字符串平衡。
请你返回让 s 平衡的最少插入次数。
题解:
这个问题主要在处理细节上,用need
表示需要的右括号数量,res
表示需要的左括号数量,返回结果:res + need
。
处理的细节:
- 遍历至左括号,按规则右括号
need += 2
,但要检查此时是否为奇数。奇数情况说明添加左括号即可,使need--;res++;
。 - 遍历至右括号,按规则
need--
,但也要检查need == -1
,-1时说明缺少左括号,使res++
同时need= 1
。
class Solution {
public:
int minInsertions(string s) {
//need代表需要几个右括号
// res 表示过程中要加的左括号
//返回res+need
int need = 0;
int res = 0;
for(char c : s){
if(c == '('){
need += 2;
if(need % 2 == 1){
//单数情况下,得添加左括号,另need--
need--;
res++;
}
}
if(c == ')'){
need--;
if(need == -1){
res++;
need = 1;
}
}
}
return need + res;
}
};
二分搜索的泛化使用
框架
一般的二分搜索是求有序数组中的元素
泛化场景下,一般存在一个f(x)
的单调函数,求其中的左右边界。
代码框架如下:
// 函数 f 是关于自变量 x 的单调函数
int f(int x) {
// ...
}
// 主函数,在 f(x) == target 的约束下求 x 的最值
int solution(int[] nums, int target) {
if (nums.length == 0) return -1;
// 问自己:自变量 x 的最小值是多少?
int left = ...;
// 问自己:自变量 x 的最大值是多少?
int right = ... + 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (f(mid) == target) {
// 问自己:题目是求左边界还是右边界?
// ...
} else if (f(mid) < target) {
// 问自己:怎么让 f(x) 大一点?
// ...
} else if (f(mid) > target) {
// 问自己:怎么让 f(x) 小一点?
// ...
}
}
return left;
}
875. 爱吃香蕉的珂珂
珂珂喜欢吃香蕉。这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 H 小时后回来。
珂珂可以决定她吃香蕉的速度 K (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 K 根。如果这堆香蕉少于 K 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。
珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。
返回她可以在 H 小时内吃掉所有香蕉的最小速度 K(K 为整数)。
此题就存在一个单调递减的f(x)
函数,x
是吃香蕉的速度,f(x)
为所用的时间。
寻找的是f(x) = H
的最小x
值
代码如下:
class Solution {
public:
int minEatingSpeed(vector<int>& piles, int h) {
int lo = 1;//最小值一小时一根香蕉
int hi = 1000000000 + 1;//最大值,题目限定的最大值+1
// int hi = 1; //或者求最大值,因为一次只会选择一堆吃
// for(int banna : piles){
// hi = max(hi, banna);
// }
while(lo < hi){
int mid = lo + (hi - lo) / 2;
//求解的是最小速度,求得是左边界,返回lo
if(f(piles, mid) <= h){
hi = mid;//f(x)小了,要让x变小,让右边界缩
}else{
// 加1是因为mid已经计算不等于h,所以舍去
lo = mid+1;//f(x)大了,要让x变大,让左边界缩,
}
}
return lo;
}
//二分法需要列出f(x),
// 定义速度为x,需要f(x)小时吃完香蕉
int f(vector<int>& piles, int x){
int hours = 0;
for(int i = 0; i < piles.size(); ++i){
//一次选择一堆,计算每一堆的时间
hours += piles[i] / x;
if(piles[i] % x != 0){
hours++;//无法整除,说明堆中剩下的不足x个,加1小时
}
}
return hours;
}
};
1011. 在 D 天内送达包裹的能力
传送带上的包裹必须在 days 天内从一个港口运送到另一个港口。
传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量(weights)的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。
返回能在 days 天内将传送带上的所有包裹送达的船的最低运载能力。
还是按照上面的套路,f(x)
是载重量为x
时完成运输的天数,还是单调递减。
处理f(x)
的细节上略有不同,但二分搜索的框架仍不变
class Solution {
public:
int shipWithinDays(vector<int>& weights, int days) {
int lo = 1;
int hi = 1;
// 因为是传送带可以一直运输,重量是每个包裹的重量且不可拆分
// 最小lo应是最大包裹重量,包裹不能拆,不然运不走
// 最大hi应是所有包裹重量,一天运走
for(auto w : weights){
lo = max(lo, w);
hi += w;
}
// 求能够运完的最小速度,求得是左边界,返回lo
while(lo < hi){
int mid = lo + (hi - lo) / 2;
if(f(weights, mid) <= days){
hi = mid;
}else{
lo = mid+1;
}
}
return lo;
}
// 二分法求极值
// 首先定义f(x):运载能力在x时,需要f(x)天运送完成
int f(vector<int>& weights, int x){
int days = 0;
// 前提:只要船没有装满就可以一直装
for(int i = 0; i < weights.size(); ){
int cap = x;
while(i < weights.size()){
if(cap < weights[i]) break;
// 装不下这个包裹了,跳出while,天数加1,cap重新计算
else cap -= weights[i];
i++;
}
days++;
}
return days;
}
};
前缀和问题
应用前缀和的场景,前缀和主要适用的场景是原始数组不会被修改的情况下,频繁查询某个区间的累加和。
差分数组
与前缀和思想非常类似的算法技巧「差分数组」,但和前缀和有所不同,差分数组的主要适用场景是频繁对原始数组的某个区间的元素进行增减。
一般场景:
给定一个数组,要求在[i1, j1]内加val1,在[i2, j2]内加val2,在[i3, j3]内加val3…求最后的数组
常规思路会按照模拟去频繁访问数组修改,效率较低。
差分数组工具类:
class Difference {
private:
//差分数组
vector<int> diff;
public:
//构造差分数组
void difference(vector<int> nums){
if(nums.size() > 0){
diff.resize(nums.size());
diff[0] = nums[0];
for(int i = 1; i < nums.size(); i++){
diff[i] = nums[i] - nums[i-1];
}
}
}
//给[i,j]的闭区间上加上val,val可以是负数
void increament(int i, int j, int val){
diff[i] += val;
if(j + 1 < diff.size()){
diff[j+1] -= val;
}
}
//返回结果数组
vector<int> getResult(){
vector<int> ans(diff.size());
ans[0] = diff[0];
for(int i = 1; i < diff.size(); i++){
ans[i] = diff[i]+ ans[i-1];
}
return ans;
}
};
1109. 航班预订统计
1109. 航班预订统计
这里有 n 个航班,它们分别从 1 到 n 进行编号。
有一份航班预订表 bookings ,表中第 i 条预订记录 bookings[i] = [firsti, lasti, seatsi] 意味着在从 firsti 到 lasti (包含 firsti 和 lasti )的 每个航班 上预订了 seatsi 个座位。
请你返回一个长度为 n 的数组 answer,里面的元素是每个航班预定的座位总数。
vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {
//初始化
vector<int> nums(n,0);
// 新建差分工具对象
Difference df = Difference();
df.difference(nums);
/*
i = books[0]-1;
j = books[1]-1;
val = books[2];
*/
for(auto books : bookings){
df.increament(books[0]-1, books[1]-1, books[2]);
}
return df.getResult();
}
370.区间加法
370.区间加法
假设你有一个长度为 n 的数组,初始情况下所有的数字均为 0,你将会被给出 k 个更新的操作。
其中,每个操作会被表示为一个三元组:[startIndex, endIndex, inc],你需要将子数组 A[startIndex … endIndex](包括 startIndex 和 endIndex)增加 inc。
请你返回 k 次操作后的数组。
vector<int> getModifiedArray(int length, vector<vector<int>>& updates) {
// 初始化数组
vector<int> nums(length, 0);
// df工具类对象
Difference df = Difference();
df.difference(nums);
/*
i = update[0]
j = update[1]
val = update[2]
*/
for(auto update : updates){
df.increament(update[0],update[1], update[2]);
}
return df.getResult();
}
1094. 拼车
假设你是一位顺风车司机,车上最初有 capacity 个空座位可以用来载客。由于道路的限制,车 只能 向一个方向行驶(也就是说,不允许掉头或改变方向,你可以将其想象为一个向量)。
这儿有一份乘客行程计划表 trips[][],其中 trips[i] = [num_passengers, start_location, end_location] 包含了第 i 组乘客的行程信息:
必须接送的乘客数量;
乘客的上车地点;
以及乘客的下车地点。
这些给出的地点位置是从你的 初始 出发位置向前行驶到这些地点所需的距离(它们一定在你的行驶方向上)。
请你根据给出的行程计划表和车子的座位数,来判断你的车是否可以顺利完成接送所有乘客的任务(当且仅当你可以在所有给定的行程中接送所有乘客时,返回 true,否则请返回 false)。
bool carPooling(vector<vector<int>>& trips, int capacity) {
// 初始化数组
vector<int> nums(1001, 0);
// 差分工具对象
Difference df = Difference();
df.difference(nums);
/*
i = trip[1] 该站上车
j = trip[2]-1 该站就下车了,所以不计入人数
val = trip[0] 人数
*/
for(auto trip : trips){
df.increament(trip[1],trip[2]-1, trip[0]);
}
vector<int> ans = df.getResult();
// 判断结果
for(int a : ans){
if(a > capacity){
return false;
}
}
return true;
}