LeetCode刷题C++实录
刷题实录视频地址:https://space.bilibili.com/1102086181
1. 两数之和
题目
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
提示:
2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
只会存在一个有效答案
视频
LeetCode刷题实录——C++,1. 两数之和,哈希表
代码
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> hashtable; // 建立一个不排序的字典
for(int i=0; i < nums.size(); i++){
auto it = hashtable.find(target - nums[i]);
// 这里是查找target-nums[i],减去得到的如果在字典里面就已经找到了要找的两个下标了,返回
if(it != hashtable.end()){
// 说明找到了
return {it->second, i};
}
// 能执行到这里说明没找到,将数组的值作为键,数组的下标作为值加入到字典中
hashtable[nums[i]] = i;
}
return {}; //全都找不到,只好返回空了
}
};
121. 买卖股票的最佳时机
题目
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例 2:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
提示:
1 <= prices.length <= 105
0 <= prices[i] <= 104
视频
LeetCode刷题实录——C++,121. 买卖股票的最佳时机,贪心算法
代码
class Solution {
public:
int maxProfit(vector<int>& prices) {
// 刚才的方法是两次遍历,实际上可以优化为一次遍历,采用的是贪心的思路
// 选两个变量,分别记录当前最大值和最小值
int minn=10005;
// vector<int> maxns(prices.size());//maxns[i]用来记录从第1位往后的最大值
// for(int i=prices.size()-1; i >= 0; i--){
// maxn = max(maxn, prices[i]);
// maxns[i] = maxn;
// }
int ans=0;
// for(int i=0; i < prices.size(); i++){
// ans = max(maxns[i] - prices[i], ans);
// }
for(auto p:prices){
ans = max(p - minn, ans); // 比较出最大的值
minn = min(p, minn);// 找目前出现过的最小值
}
return ans;
}
};
382. 链表随机节点
题目
给你一个单链表,随机选择链表的一个节点,并返回相应的节点值。每个节点 被选中的概率一样 。
实现 Solution 类:
Solution(ListNode head) 使用整数数组初始化对象。
int getRandom() 从链表中随机选择一个节点并返回该节点的值。链表中所有节点被选中的概率相等。
示例:
输入
[“Solution”, “getRandom”, “getRandom”, “getRandom”, “getRandom”, “getRandom”]
[[[1, 2, 3]], [], [], [], [], []]
输出
[null, 1, 3, 2, 2, 3]
解释
Solution solution = new Solution([1, 2, 3]);
solution.getRandom(); // 返回 1
solution.getRandom(); // 返回 3
solution.getRandom(); // 返回 2
solution.getRandom(); // 返回 2
solution.getRandom(); // 返回 3
// getRandom() 方法应随机返回 1、2、3中的一个,每个元素被返回的概率相等。
提示:
链表中的节点数在范围 [1, 104] 内
-104 <= Node.val <= 104
至多调用 getRandom 方法 104 次
进阶:
如果链表非常大且长度未知,该怎么处理?
你能否在不使用额外空间的情况下解决此问题?
视频
LeetCode刷题实录——C++,382. 链表随机节点,水塘抽样
代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
ListNode *head;
public:
Solution(ListNode* head) {
this->head = head;
}
int getRandom() {
int i=1, ans=0;
for(auto node = head; node; node = node->next){
if(rand() % i == 0){
// 这里就是水塘抽样法
ans = node->val;
}
++i;
}
return ans;
}
};
/**
* Your Solution object will be instantiated and called as such:
* Solution* obj = new Solution(head);
* int param_1 = obj->getRandom();
*/
622. 设计循环队列
题目
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
MyCircularQueue(k): 构造器,设置队列长度为 k 。
Front: 从队首获取元素。如果队列为空,返回 -1 。
Rear: 获取队尾元素。如果队列为空,返回 -1 。
enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty(): 检查循环队列是否为空。
isFull(): 检查循环队列是否已满。
示例:
MyCircularQueue circularQueue = new MyCircularQueue(3); // 设置长度为 3
circularQueue.enQueue(1); // 返回 true
circularQueue.enQueue(2); // 返回 true
circularQueue.enQueue(3); // 返回 true
circularQueue.enQueue(4); // 返回 false,队列已满
circularQueue.Rear(); // 返回 3
circularQueue.isFull(); // 返回 true
circularQueue.deQueue(); // 返回 true
circularQueue.enQueue(4); // 返回 true
circularQueue.Rear(); // 返回 4
提示:
所有的值都在 0 至 1000 的范围内;
操作数将在 1 至 1000 的范围内;
请不要使用内置的队列库。
视频
LeetCode刷题实录——C++,622. 设计循环队列,数据结构题
代码
class MyCircularQueue {
public:
MyCircularQueue(int k) {
Que = vector<int> (k);
front = rear = 0;
tag = 0;
max_size = k;
}
bool enQueue(int value) {
if(!isFull()){
Que[rear] = value;
rear = (rear + 1) % max_size;
tag = 1;
return true;
}
return false;
}
bool deQueue() {
if(!isEmpty()){
front = (front + 1) % max_size;
tag = 0;
return true;
}
return false;
}
int Front() {
if(!isEmpty()){
return Que[front];
}
return -1;
}
int Rear() {
if(!isEmpty()){
return Que[(rear-1+max_size) % max_size]; //注意rear的位置是队尾的后一个位置
}
return -1;
}
// 我们设front = rear 且 tag=0为队列空
bool isEmpty() {
return front == rear && tag == 0;
}
// 设front = rear 且 tag=1为队满,这里跟严书上不一样,因为严书队列是空一位的,这里我要占满
bool isFull() {
return front == rear && tag == 1;
}
private:
vector<int> Que;
int front;
int rear;
int max_size;
bool tag;//为了充分利用存储空间,设置的一个标记,tag的实质是记录上一个入队操作还是出队操作,0为出队,1为入队,空的时候只能是因为初始化和出队导致的,所以tag为0front和rear相等队列为空
};
/**
* Your MyCircularQueue object will be instantiated and called as such:
* MyCircularQueue* obj = new MyCircularQueue(k);
* bool param_1 = obj->enQueue(value);
* bool param_2 = obj->deQueue();
* int param_3 = obj->Front();
* int param_4 = obj->Rear();
* bool param_5 = obj->isEmpty();
* bool param_6 = obj->isFull();
*/
623. 在二叉树中增加一行
题目
给定一个二叉树的根 root 和两个整数 val 和 depth ,在给定的深度 depth 处添加一个值为 val 的节点行。
注意,根节点 root 位于深度 1 。
加法规则如下:
给定整数 depth,对于深度为 depth - 1 的每个非空树节点 cur ,创建两个值为 val 的树节点作为 cur 的左子树根和右子树根。
cur 原来的左子树应该是新的左子树根的左子树。
cur 原来的右子树应该是新的右子树根的右子树。
如果 depth == 1 意味着 depth - 1 根本没有深度,那么创建一个树节点,值 val 作为整个原始树的新根,而原始树就是新根的左子树。
示例 1:
输入: root = [4,2,6,3,1,5], val = 1, depth = 2
输出: [4,1,1,2,null,null,6,3,1,5]
示例 2:
输入: root = [4,2,null,3,1], val = 1, depth = 3
输出: [4,2,null,1,1,3,null,null,1]
提示:
节点数在 [1, 104] 范围内
树的深度在 [1, 104]范围内
-100 <= Node.val <= 100
-105 <= val <= 105
1 <= depth <= the depth of tree + 1
视频
LeetCode刷题实录——C++,623. 在二叉树中增加一行,深度优先搜索
代码
/**
* 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:
TreeNode* addOneRow(TreeNode* root, int val, int depth) {
// 深度优先搜索
if(root == nullptr){
return nullptr;
}
if(depth == 1){
return new TreeNode(val, root, nullptr);
}else if(depth == 2){
TreeNode * leftNode = new TreeNode(val, root->left, nullptr);
TreeNode * rightNode = new TreeNode(val, nullptr, root->right);
root->left = leftNode;
root->right = rightNode;
return root;
}else{
root->left = addOneRow(root->left, val, depth-1);
root->right = addOneRow(root->right, val, depth-1);
return root;
}
}
};
640. 求解方程
题目
求解一个给定的方程,将x以字符串 “x=#value” 的形式返回。该方程仅包含 ‘+’ , ‘-’ 操作,变量 x 和其对应系数。
如果方程没有解,请返回 “No solution” 。如果方程有无限解,则返回 “Infinite solutions” 。
题目保证,如果方程中只有一个解,则 ‘x’ 的值是一个整数。
示例 1:
输入: equation = “x+5-3+x=6+x-2”
输出: “x=2”
示例 2:
输入: equation = “x=x”
输出: “Infinite solutions”
示例 3:
输入: equation = “2x=x”
输出: “x=0”
提示:
3 <= equation.length <= 1000
equation 只有一个 ‘=’.
equation 方程由整数组成,其绝对值在 [0, 100] 范围内,不含前导零和变量 ‘x’ 。
代码
class Solution {
public:
string solveEquation(string equation) {
// 我们要统计四个数
// 左侧x的系数,右侧x的系数,左侧常数,右侧常数
// 根据等式的规律,我们可以将右侧的x移到左侧,左侧的常数移到右侧
// 这样就只用记录左侧x系数和右侧常数
bool isequ=false;//记录是否经过等号
bool isadd=true;//记录加减号
int xn=0, cn=0;
int i=0, length=equation.size();
while(i < length){
//cout<<xn<<endl;
if(i < length && equation[i]=='x'){
if(!isequ){
if(isadd){
xn++;
}else{
xn--;
}
}else{
if(isadd){
xn--;
}else{
xn++;
}
}
i++;
}
if(i < length && equation[i]=='+'){
isadd=true;
i++;
}
if(i < length && equation[i]=='-'){
isadd=false;
i++;
}
if(i < length && equation[i]=='='){
isequ=true;
isadd=true;
i++;
}
int cnt=0;
while(i < length && equation[i] >= '0' && equation[i] <= '9'){
cnt *= 10;
cnt +=equation[i]-'0';
i++;
}
if(i < length && equation[i]=='x' && i > 0 && equation[i-1] >= '0' && equation[i-1] <= '9'){
if(!isequ){
if(isadd){
xn +=cnt ;
}else{
xn -= cnt;
}
}else{
if(isadd){
xn -= cnt;
}else{
xn += cnt;
}
}
i++;
}else if(i <= length && cnt){
cout<<cn<<endl;
if(!isequ){
if(isadd){
cn -=cnt ;
}else{
cn += cnt;
}
}else{
if(isadd){
cn += cnt;
}else{
cn -= cnt;
}
}
}
}
cout<<xn<<" "<<cn<<endl;
if(xn == 0 && cn == 0){
return "Infinite solutions";
}
//cout<<xn<<" "<<cn<<endl;
if(xn == 0 && cn){
return "No solution";
}
return "x="+to_string(cn / xn);
}
};
761. 特殊的二进制序列(分治算法)
题目
特殊的二进制序列是具有以下两个性质的二进制序列:
0 的数量与 1 的数量相等。
二进制序列的每一个前缀码中 1 的数量要大于等于 0 的数量。
给定一个特殊的二进制序列 S,以字符串形式表示。定义一个操作 为首先选择 S 的两个连续且非空的特殊的子串,然后将它们交换。(两个子串为连续的当且仅当第一个子串的最后一个字符恰好为第二个子串的第一个字符的前一个字符。)
在任意次数的操作之后,交换后的字符串按照字典序排列的最大的结果是什么?
示例 1:
输入: S = “11011000”
输出: “11100100”
解释:
将子串 “10” (在S[1]出现) 和 “1100” (在S[3]出现)进行交换。
这是在进行若干次操作后按字典序排列最大的结果。
说明:
S 的长度不超过 50。
S 保证为一个满足上述定义的特殊 的二进制序列。
视频
LeetCode刷题实录——C++,761. 特殊的二进制序列,分治
代码
class Solution {
public:
string makeLargestSpecial(string s) {
// 分治法,我们发现要满足情况,每个特殊二进制序列都是1开头,0结尾的(1的个数>=0的个数)
// 那么我们就可以去掉两端1和0,只看中间
// 我们可以将一个特殊的二进制序列分为多个特殊二进制子序列,我们将子序列排序再重新组成序列即为最大值
int cnt=0,left=0;
vector<string> subs;
for(int i=0; i < s.size(); i++){
if(s[i]=='1'){
cnt++;
}else{
cnt--;
if(cnt==0){
subs.push_back("1" + makeLargestSpecial(s.substr(left + 1, i - left - 1)) + "0");
left = i + 1;
}
}
}
sort(subs.begin(), subs.end(), greater<string>{});
string ans = accumulate(subs.begin(), subs.end(), string(""));
return ans;
}
};
899. 有序队列
题目
给定一个字符串 s 和一个整数 k 。你可以从 s 的前 k 个字母中选择一个,并把它加到字符串的末尾。
返回 在应用上述步骤的任意数量的移动后,字典上最小的字符串 。
示例 1:
输入:s = “cba”, k = 1
输出:“acb”
解释:
在第一步中,我们将第一个字符(“c”)移动到最后,获得字符串 “bac”。
在第二步中,我们将第一个字符(“b”)移动到最后,获得最终结果 “acb”。
示例 2:
输入:s = “baaca”, k = 3
输出:“aaabc”
解释:
在第一步中,我们将第一个字符(“b”)移动到最后,获得字符串 “aacab”。
在第二步中,我们将第三个字符(“c”)移动到最后,获得最终结果 “aaabc”。
提示:
1 <= k <= S.length <= 1000
s 只由小写字母组成。
视频
LeetCode刷题实录——C++,899. 有序队列,贪心算法
代码
class Solution {
public:
string orderlyQueue(string s, int k) {
// 分为2种情况,当k==1时,可以将s看成一个队列,首位出队列,然后进队列
// k > 1时,根据示例可以看到,总能向着字典最小的方向发展,因为操作数可以是无限的,根据贪心算法的思维,可以认为总有一种操作可以使得他们成为最小字典,即按照从小到大排序
if(k==1){
string ans = s;
int n = s.size();
for(int i=1; i < n; i++){
s.push_back(s[0]);
s = s.substr(1);
if(s < ans){
ans = s;
}
}
return ans;
}else{
sort(s.begin(), s.end());
return s;
}
}
};
952. 按公因数计算最大组件大小
题目
给定一个由不同正整数的组成的非空数组 nums ,考虑下面的图:
有 nums.length 个节点,按从 nums[0] 到 nums[nums.length - 1] 标记;
只有当 nums[i] 和 nums[j] 共用一个大于 1 的公因数时,nums[i] 和 nums[j]之间才有一条边。
返回 图中最大连通组件的大小 。
示例 1:
输入:nums = [4,6,15,35]
输出:4
示例 2:
输入:nums = [20,50,9,63]
输出:2
示例 3:
输入:nums = [2,3,6,7,4,12,21,39]
输出:8
提示:
1 <= nums.length <= 2 * 104
1 <= nums[i] <= 105
nums 中所有值都 不同
视频
LeetCode刷题实录——C++,952. 按公因数计算最大组件大小,并查集
代码
// 本题是一个并查集的问题,因为并查集是一个规范化的特定的问题,为了规范化代码,这里采用我采用官方的代码进行讲解
// 建立并查集类
class UnionFind{
public:
UnionFind(int n){
parent = vector<int>(n); // 用来表示并查集的链接关系,因为并查集可能是很多叉的树的结构,用树表示会很麻烦,很占内存,如果按照图来表示更会占不必要的内存,但是每个子树根节点是唯一的,我们用parent[i]来表示结点i的根节点
rank = vector<int>(n); // rank[i]代表的是第i位联通组件的大小
for(int i=0; i < n; i++){
// 初始值每个结点的根节点都为他们自己
parent[i] = i;
}
}
void uni(int x, int y){
// 将两个结点归为一个集合
// 要想将两个结点归为一个集合就应该将他们两个结点所在的两个集合归为一个集合,那么我们只需要找到他们各自的根节点,再将根节点合并即可
int rootx = find(x);
int rooty = find(y);
// 找到根节点之后就可以合并了
if(rootx != rooty){
if(rank[rootx] > rank[rooty]){
parent[rooty] = rootx;
}else if(rank[rootx] < rank[rooty]){
parent[rootx] = rooty;
}else{
parent[rooty] = rootx;
rank[rootx]++;
}
//思路就是小的归为大的,就像是收购
}
}
int find(int x){
// 查找根节点过程
if(parent[x] != x){
// 根节点上面一定是没有根节点的,在并查集中就是指向自己的结点
parent[x] = find(parent[x]);//这一块是在压缩路径,方便后面的查找能够直接找到根节点而不用随着路径再走一遍,这里是用的递归的做法,也可以使用循环的做法,网上有很多,不再赘述
}
return parent[x];
}
private:
vector<int> parent;
vector<int> rank;
};
class Solution {
public:
int largestComponentSize(vector<int>& nums) {
// 接下来才正式开始
int m=*max_element(nums.begin(), nums.end()); // 获取最大值
UnionFind uf(m+1);
for(int num:nums){
// 这两个循环就相当于开地图炮了,只要是范围内的全作为一个集合
for(int i=2; i * i <= num; i++){
if(num % i == 0){
uf.uni(num, i);
uf.uni(num, num / i);
}
}
}
vector<int> counts(m+1);
int ans=0;
for(int num:nums){
int root = uf.find(num);
counts[root]++;
ans = max(ans, counts[root]);
}
return ans;
}
};
//最后总结一下,本题的思路就是将每个数拆分,原来的数属于拆分的公约数的集合
1161. 最大层内元素和
题目
给你一个二叉树的根节点 root。设根节点位于二叉树的第 1 层,而根节点的子节点位于第 2 层,依此类推。
请返回层内元素之和 最大 的那几层(可能只有一层)的层号,并返回其中 最小 的那个。
示例 1:
输入:root = [1,7,0,7,-8,null,null]
输出:2
解释:
第 1 层各元素之和为 1,
第 2 层各元素之和为 7 + 0 = 7,
第 3 层各元素之和为 7 + -8 = -1,
所以我们返回第 2 层的层号,它的层内元素之和最大。
示例 2:
输入:root = [989,null,10250,98693,-89388,null,null,null,-32127]
输出:2
提示:
树中的节点数在 [1, 104]范围内
-105 <= Node.val <= 105
视频
LeetCode刷题实录——C++,1161. 最大层内元素和,广度优先搜索
代码
/**
* 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) {}
* };
*/
//很明显是一个层序遍历的问题,只不过层序遍历时需要记录层的信息
struct QNode{
TreeNode* root;
int level;
};
class Solution {
public:
int maxLevelSum(TreeNode* root) {
queue<QNode> qu;
int max_level = 1;
long long max_sum = root->val;
if(root->left){
QNode qn;
qn.root = root->left;
qn.level = 2;
qu.push(qn);
}
if(root->right){
QNode qn;
qn.root = root->right;
qn.level = 2;
qu.push(qn);
}
int cur_level = 1;
long long cur_sum = 0;
while(!qu.empty()){
QNode qn = qu.front();
qu.pop();
if(qn.level != cur_level){
cur_level = qn.level;
cur_sum = 0;
}
cur_sum += qn.root->val;
QNode qt = qu.front();
if(cur_sum > max_sum && qt.level != cur_level){
max_sum = cur_sum;
max_level = cur_level;
}
if(qn.root->left){
QNode n1;
n1.root = qn.root->left;
n1.level = qn.level + 1;
qu.push(n1);
}
if(qn.root->right){
QNode n1;
n1.root = qn.root->right;
n1.level = qn.level + 1;
qu.push(n1);
}
}
return max_level;
}
};
1184. 公交站间的距离
题目
环形公交路线上有 n 个站,按次序从 0 到 n - 1 进行编号。我们已知每一对相邻公交站之间的距离,distance[i] 表示编号为 i 的车站和编号为 (i + 1) % n 的车站之间的距离。
环线上的公交车都可以按顺时针和逆时针的方向行驶。
返回乘客从出发点 start 到目的地 destination 之间的最短距离。
示例 1:
输入:distance = [1,2,3,4], start = 0, destination = 1
输出:1
解释:公交站 0 和 1 之间的距离是 1 或 9,最小值是 1。
示例 2:
输入:distance = [1,2,3,4], start = 0, destination = 2
输出:3
解释:公交站 0 和 2 之间的距离是 3 或 7,最小值是 3。
示例 3:
输入:distance = [1,2,3,4], start = 0, destination = 3
输出:4
解释:公交站 0 和 3 之间的距离是 6 或 4,最小值是 4。
提示:
1 <= n <= 10^4
distance.length == n
0 <= start, destination < n
0 <= distance[i] <= 10^4
视频
LeetCode刷题实录——C++,accumulate函数的使用,1184. 公交站间的距离
代码
class Solution {
public:
int distanceBetweenBusStops(vector<int>& distance, int start, int destination) {
// 本题刚开始我以为是一个最短路径问题,但实际没必要那么负责,虽然使用最短路径也可以做出来
// 我们的思路是从开始到结束顺时针求一次距离,再逆时针求一次距离,然后取最短距离
// 因为距离是累加的,所以我们可以使用C++中的accumulate函数,其中有四个参数
// 前两个参数为累加范围,第三个参数表示从哪一位开始,第四个可以省略,默认为累加
if(start > destination){
swap(start, destination);
}
return min(accumulate(distance.begin()+start, distance.begin()+destination, 0),
accumulate(distance.begin(), distance.begin()+start, 0)+
accumulate(distance.begin() + destination, distance.end(), 0)
); // 首先是顺时针
// 然后是逆时针
// 逆时针可以看作是start向begin累加+destination向end累加
}
};
1302. 层数最深叶子节点的和
题目
给你一棵二叉树的根节点 root ,请你返回 层数最深的叶子节点的和 。
示例 1:
输入:root = [1,2,3,4,5,null,6,7,null,null,null,null,8]
输出:15
示例 2:
输入:root = [6,7,8,2,7,1,3,9,null,1,4,null,null,null,5]
输出:19
提示:
树中节点数目在范围 [1, 104] 之间。
1 <= Node.val <= 100
代码
/**
* 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 {
// 首先是深度优先搜索的做法
private:
int maxLevel = 0;//我们把根节点的层数看作第1层,初始时最大的层数视为0层
int sumn = 0; // 第0层结点的和设为0
public:
int deepestLeavesSum(TreeNode* root) {
dfs(root, 1);
return sumn;
}
void dfs(TreeNode * root, int level){
if(root == nullptr){
return;
}
if(level > maxLevel){
// 有了更深的层就更新最深的层和结点的和
maxLevel = level;
sumn = root->val;
}else if(level == maxLevel){
// 层数和最深层相同就加上去
sumn += root->val;
}
dfs(root->left, level+1);
dfs(root->right, level+1);
}
};
/**
* 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 deepestLeavesSum(TreeNode* root) {
int sumn = 0;
queue<TreeNode*> qu;
qu.emplace(root);// 这里的push在C++11后可以换成emplace
while(!qu.empty()){
sumn = 0;//每到新的一层就要重置sumn,因为到了更深的一层说明之前的层数不是最新的
// 接下来的和广度优先搜索不一样了
// 接下来要每次遍历一层的结点,而不是一个结点,用来区分层数
int size = qu.size();
for(int i=0; i < size; i++){
TreeNode* node = qu.front();
qu.pop();
sumn += node->val;
if(node->left != nullptr){
qu.emplace(node->left);
}
if(node->right != nullptr){
qu.emplace(node->right);
}
}
}
return sumn;
}
};
1374. 生成每种字符都是奇数个的字符串
题目
给你一个整数 n,请你返回一个含 n 个字符的字符串,其中每种字符在该字符串中都恰好出现 奇数次 。
返回的字符串必须只含小写英文字母。如果存在多个满足题目要求的字符串,则返回其中任意一个即可。
示例 1:
输入:n = 4
输出:“pppz”
解释:“pppz” 是一个满足题目要求的字符串,因为 ‘p’ 出现 3 次,且 ‘z’ 出现 1 次。当然,还有很多其他字符串也满足题目要求,比如:“ohhh” 和 “love”。
示例 2:
输入:n = 2
输出:“xy”
解释:“xy” 是一个满足题目要求的字符串,因为 ‘x’ 和 ‘y’ 各出现 1 次。当然,还有很多其他字符串也满足题目要求,比如:“ag” 和 “ur”。
示例 3:
输入:n = 7
输出:“holasss”
提示:
1 <= n <= 500
视频
LeetCode刷题实录——C++,贪心算法,1374. 生成每种字符都是奇数个的字符串
代码
class Solution {
public:
string generateTheString(int n) {
// 贪心算法,思路是找到距离n最近的奇数i,n减去奇数i,字符串加上最近的奇数i,知道n减为0为止
string ans="";
char ch='a'; // 起始字符
while(n){
for(int i=n; i >= 1; i--){
if(i % 2 != 0){
int k = i;
while(k--){
ans += ch;
}
ch = ch + 1;
n -= i;
break;
}
}
}
return ans;
}
};
1403. 非递增顺序的最小子序列
题目
给你一个数组 nums,请你从中抽取一个子序列,满足该子序列的元素之和 严格 大于未包含在该子序列中的各元素之和。
如果存在多个解决方案,只需返回 长度最小 的子序列。如果仍然有多个解决方案,则返回 元素之和最大 的子序列。
与子数组不同的地方在于,「数组的子序列」不强调元素在原数组中的连续性,也就是说,它可以通过从数组中分离一些(也可能不分离)元素得到。
注意,题目数据保证满足所有约束条件的解决方案是 唯一 的。同时,返回的答案应当按 非递增顺序 排列。
示例 1:
输入:nums = [4,3,10,9,8]
输出:[10,9]
解释:子序列 [10,9] 和 [10,8] 是最小的、满足元素之和大于其他各元素之和的子序列。但是 [10,9] 的元素之和最大。
示例 2:
输入:nums = [4,4,7,6,7]
输出:[7,7,6]
解释:子序列 [7,7] 的和为 14 ,不严格大于剩下的其他元素之和(14 = 4 + 4 + 6)。因此,[7,6,7] 是满足题意的最小子序列。注意,元素按非递增顺序返回。
示例 3:
输入:nums = [6]
输出:[6]
提示:
1 <= nums.length <= 500
1 <= nums[i] <= 100
视频
LeetCode刷题实录——C++,1403. 非递增顺序的最小子序列,贪心算法
代码
class Solution {
public:
vector<int> minSubsequence(vector<int>& nums) {
// 看起来很麻烦的题干,但是我们根据贪心算法的思路可以看出,只需要依次去除剩余的数组中的最大值即可
int num_sum = accumulate(nums.begin(), nums.end(), 0);
sort(nums.begin(), nums.end());
vector<int> ans;
int tmp = 0; //用来保存当前取出的数的序列和
for(int i=nums.size()-1; i >= 0; i--){
tmp += nums[i];
ans.push_back(nums[i]);
if(tmp > num_sum - tmp){
break;
}
}
return ans;
}
};
1408. 数组中的字符串匹配
题目
给你一个字符串数组 words ,数组中的每个字符串都可以看作是一个单词。请你按 任意 顺序返回 words 中是其他单词的子字符串的所有单词。
如果你可以删除 words[j] 最左侧和/或最右侧的若干字符得到 word[i] ,那么字符串 words[i] 就是 words[j] 的一个子字符串。
示例 1:
输入:words = [“mass”,“as”,“hero”,“superhero”]
输出:[“as”,“hero”]
解释:“as” 是 “mass” 的子字符串,“hero” 是 “superhero” 的子字符串。
[“hero”,“as”] 也是有效的答案。
示例 2:
输入:words = [“leetcode”,“et”,“code”]
输出:[“et”,“code”]
解释:“et” 和 “code” 都是 “leetcode” 的子字符串。
示例 3:
输入:words = [“blue”,“green”,“bu”]
输出:[]
提示:
1 <= words.length <= 100
1 <= words[i].length <= 30
words[i] 仅包含小写英文字母。
题目数据 保证 每个 words[i] 都是独一无二的。
视频
LeetCode刷题实录——C++,KMP算法,1408. 数组中的字符串匹配
代码
class KMP{//这里是建立一个KMP的类
public:
KMP(string t){
// 初始化的时候就要初始T和获取next数组
T = t;
getnextval();
}
void getnextval(){
// 获取next数组
next = vector<int> (T.size()+1); // 这个地方为什么要+1,是为了防止出现T是空串的情况导致越界
int i=0, j=-1; // 刚开始的时候就是要向后差一位的,并且next[0]=-1;
next[0] = -1;//如果上面不加1,空串这里会越界
while(i < T.size()-1){
if(j==-1||T[i]==T[j]){
// 两种情况,如果j==-1说明第一处就不匹配,那么模式串和主串都要向后一位
// 如果当前字符相等,也要一起移动,但是这两个是不一样的移动,j==-1是初始情况或者j=next[j]被赋值后成为的-1
++i;++j;
// 这里还可以进行修改
if(T[i] == T[j]){
next[i] = next[j]; // 即如果i,j位相等,移动到j会依然不匹配,所以直接移动到j位置不匹配时应该移动到的位置
}else{
next[i] = j;
}
}else{
j = next[j];
}
}
}
int index(string s){
// 这边就是KMP的匹配过程了
int i=0, j=0;
int slen = s.size();
int tlen = T.size();
while((i < slen) && (j < tlen)){
if((j==-1)|| (s[i]==T[j])){
++i;++j;
}else{
j = next[j];
}
}
if(j >= tlen){
return i-tlen;
}
return -1;
}
private:
vector<int> next;
string T;
};
// 开始写的时候忘记录屏了
class Solution {
public:
vector<string> stringMatching(vector<string>& words) {
vector<string> ans;
for(int i=0; i < words.size(); i++){
for(int j=0; j < words.size(); j++){
if(i != j){
// cout<<i<<j<<endl;
KMP T(words[i]);
int idx = T.index(words[j]);
// 遍历判断能否匹配上即可
if(idx!=-1){
ans.push_back(words[i]);
break;
}
}
}
}
return ans;
}
};
1413. 逐步求和得到正数的最小值
题目
给你一个整数数组 nums 。你可以选定任意的 正数 startValue 作为初始值。
你需要从左到右遍历 nums 数组,并将 startValue 依次累加上 nums 数组中的值。
请你在确保累加和始终大于等于 1 的前提下,选出一个最小的 正数 作为 startValue 。
示例 1:
输入:nums = [-3,2,-3,4,2]
输出:5
解释:如果你选择 startValue = 4,在第三次累加时,和小于 1 。
累加求和
startValue = 4 | startValue = 5 | nums
(4 -3 ) = 1 | (5 -3 ) = 2 | -3
(1 +2 ) = 3 | (2 +2 ) = 4 | 2
(3 -3 ) = 0 | (4 -3 ) = 1 | -3
(0 +4 ) = 4 | (1 +4 ) = 5 | 4
(4 +2 ) = 6 | (5 +2 ) = 7 | 2
示例 2:
输入:nums = [1,2]
输出:1
解释:最小的 startValue 需要是正数。
示例 3:
输入:nums = [1,-2,-3]
输出:5
提示:
1 <= nums.length <= 100
-100 <= nums[i] <= 100
视频
LeetCode刷题实录——C++, 1413. 逐步求和得到正数的最小值,累加
代码
class Solution {
public:
int minStartValue(vector<int>& nums) {
// 数组从左向右累加,并求最小值即可
int cur=0, minn=0;
for(int n:nums){
cur += n;
minn = min(cur, minn);
}
return -minn + 1;
}
};
1656. 设计有序流
题目
有 n 个 (id, value) 对,其中 id 是 1 到 n 之间的一个整数,value 是一个字符串。不存在 id 相同的两个 (id, value) 对。
设计一个流,以 任意 顺序获取 n 个 (id, value) 对,并在多次调用时 按 id 递增的顺序 返回一些值。
实现 OrderedStream 类:
OrderedStream(int n) 构造一个能接收 n 个值的流,并将当前指针 ptr 设为 1 。
String[] insert(int id, String value) 向流中存储新的 (id, value) 对。存储后:
如果流存储有 id = ptr 的 (id, value) 对,则找出从 id = ptr 开始的 最长 id 连续递增序列 ,并 按顺序 返回与这些 id 关联的值的列表。然后,将 ptr 更新为最后那个 id + 1 。
否则,返回一个空列表。
示例:
输入
[“OrderedStream”, “insert”, “insert”, “insert”, “insert”, “insert”]
[[5], [3, “ccccc”], [1, “aaaaa”], [2, “bbbbb”], [5, “eeeee”], [4, “ddddd”]]
输出
[null, [], [“aaaaa”], [“bbbbb”, “ccccc”], [], [“ddddd”, “eeeee”]]
解释
OrderedStream os= new OrderedStream(5);
os.insert(3, “ccccc”); // 插入 (3, “ccccc”),返回 []
os.insert(1, “aaaaa”); // 插入 (1, “aaaaa”),返回 [“aaaaa”]
os.insert(2, “bbbbb”); // 插入 (2, “bbbbb”),返回 [“bbbbb”, “ccccc”]
os.insert(5, “eeeee”); // 插入 (5, “eeeee”),返回 []
os.insert(4, “ddddd”); // 插入 (4, “ddddd”),返回 [“ddddd”, “eeeee”]
提示:
1 <= n <= 1000
1 <= id <= n
value.length == 5
value 仅由小写字母组成
每次调用 insert 都会使用一个唯一的 id
恰好调用 n 次 insert
视频
LeetCode刷题实录——C++,1656. 设计有序流, vector
代码
class OrderedStream {
public:
OrderedStream(int n) {
str = 1;
stream = vector<string>(n+1);
length = n;
}
vector<string> insert(int idKey, string value) {
stream[idKey] = value;
// 按照键值赋值
// 接下来是返回列表
vector<string> ans;
while(str <= length && !stream[str].empty()){
// 当指针不超过最大长度并且指针作为键值的值不为空(说明已经赋值过)时,加入到返回列表中
ans.push_back(stream[str++]);
}
return ans;
}
private:
vector<string> stream;
int str;
int length;
};
/**
* Your OrderedStream object will be instantiated and called as such:
* OrderedStream* obj = new OrderedStream(n);
* vector<string> param_1 = obj->insert(idKey,value);
*/
剑指 Offer II 115. 重建序列
题目
给定一个长度为 n 的整数数组 nums ,其中 nums 是范围为 [1,n] 的整数的排列。还提供了一个 2D 整数数组 sequences ,其中 sequences[i] 是 nums 的子序列。
检查 nums 是否是唯一的最短 超序列 。最短 超序列 是 长度最短 的序列,并且所有序列 sequences[i] 都是它的子序列。对于给定的数组 sequences ,可能存在多个有效的 超序列 。
例如,对于 sequences = [[1,2],[1,3]] ,有两个最短的 超序列 ,[1,2,3] 和 [1,3,2] 。
而对于 sequences = [[1,2],[1,3],[1,2,3]] ,唯一可能的最短 超序列 是 [1,2,3] 。[1,2,3,4] 是可能的超序列,但不是最短的。
如果 nums 是序列的唯一最短 超序列 ,则返回 true ,否则返回 false 。
子序列 是一个可以通过从另一个序列中删除一些元素或不删除任何元素,而不改变其余元素的顺序的序列。
示例 1:
输入:nums = [1,2,3], sequences = [[1,2],[1,3]]
输出:false
解释:有两种可能的超序列:[1,2,3]和[1,3,2]。
序列 [1,2] 是[1,2,3]和[1,3,2]的子序列。
序列 [1,3] 是[1,2,3]和[1,3,2]的子序列。
因为 nums 不是唯一最短的超序列,所以返回false。
示例 2:
输入:nums = [1,2,3], sequences = [[1,2]]
输出:false
解释:最短可能的超序列为 [1,2]。
序列 [1,2] 是它的子序列:[1,2]。
因为 nums 不是最短的超序列,所以返回false。
示例 3:
输入:nums = [1,2,3], sequences = [[1,2],[1,3],[2,3]]
输出:true
解释:最短可能的超序列为[1,2,3]。
序列 [1,2] 是它的一个子序列:[1,2,3]。
序列 [1,3] 是它的一个子序列:[1,2,3]。
序列 [2,3] 是它的一个子序列:[1,2,3]。
因为 nums 是唯一最短的超序列,所以返回true。
提示:
n == nums.length
1 <= n <= 104
nums 是 [1, n] 范围内所有整数的排列
1 <= sequences.length <= 104
1 <= sequences[i].length <= 104
1 <= sum(sequences[i].length) <= 105
1 <= sequences[i][j] <= n
sequences 的所有数组都是 唯一 的
sequences[i] 是 nums 的一个子序列
视频
LeetCode刷题实录——C++,拓扑排序,剑指 Offer II 115. 重建序列
代码
class Solution {
public:
bool sequenceReconstruction(vector<int>& nums, vector<vector<int>>& sequences) {
// 前两个示例对于解题帮助不大,我们来观察示例3
// 很明显这里sequences确定的是数字出现的顺序,那么我们很容易想到拓扑排序
// 所以问题就变成了右侧的序列能不能完好表示nums
int n = nums.size(); //
vector<int> indegrees(n+1); // 用来存储1-n结点的入度
vector<unordered_set<int>> graph(n+1); // 用来存储sequences组成的有向图
for(auto &sequence : sequences){
for(int i=1; i < sequence.size(); i++){
// 将sequence中先后顺序入图, next对应的入度+1
int prev = sequence[i-1], next = sequence[i];
if(!graph[prev].count(next)){
// if是为了防止sequence中有重复
graph[prev].emplace(next); //emplace是c11的特性,也可以insert
indegrees[next]++;
}
}
}
queue<int> qu;
for(int i=1; i <= n; i++){
if(indegrees[i] == 0){
// 选择入度为0的入队
qu.emplace(i); //也可以用push
}
}
while(!qu.empty()){
if(qu.size() > 1){
// 存在了两个及以上的入度为0的数说明他不能被唯一确定,因为这几个的先后顺序可互换
return false;
}
int num = qu.front();
qu.pop();
for(int next:graph[num]){
indegrees[next]--;
// 之前的结点出队了,所以他所指向的数入度-1
if(indegrees[next] == 0){
qu.emplace(next);
}
}
}
return true;//如果没有提前结束,那么为真
}
};
End
感谢大家的支持,将来会陆续更新