/*
从前往后遍历数组,若发现空的位置,则判断前后是否已经有花,若没有则安放即可
*/
class Solution {
public:
bool canPlaceFlowers(vector<int>& flowerbed, int n) {
for (int i = 0; i < flowerbed.size(); i++)
if (flowerbed[i] == 0) {
if ((i == 0 || flowerbed[i - 1] == 0) &&
(i == flowerbed.size() - 1 || flowerbed[i + 1] == 0)) {
flowerbed[i] = 1;
n--;
}
}
return n <= 0;
}
};
/*
给定一个01数组,其中没有两个连续的1,判断是否能再添加n个1使得所有的1还是不相邻,以01数组中连续两个1(不是相邻,也没有相邻)为一段,这一段中能填多少个1取决于中间有多少个0,可看出规律,如果是内部的k个0的话,可以填(k-1)/2,如果是有一边是边界上的0的话,k/2
如果两边都是边界的0的话 (k+1)/2,所以要扫描每一段有多少个连续的0可以用双指针算法
*/
class Solution {
public:
bool canPlaceFlowers(vector<int>& flowerbed, int n) {
if (!n) return true;
int res = 0;
for (int i = 0; i < flowerbed.size(); i ++ ) {
if (flowerbed[i]) continue;
int j = i;//此时i是第一个0,前面要么没有要么是1
while (j < flowerbed.size() && flowerbed[j]==0) j ++ ;
int k = j - i - 1;//去掉i,j两个边界之后连续的0的个数
if (i==0) k ++ ;//如果左边是边界的话
if (j == flowerbed.size()) k ++ ;//如果右边是边界的话
res += k / 2;
if (res >= n) return true;
i = j;
}
return false;
}
};
LeetCode 606. 根据二叉树创建字符串
class Solution {
public:
/*
根节点(左子树)(右子树)
*/
string ans;
string tree2str(TreeNode* t) {
dfs(t);
return ans;
}
void dfs(TreeNode* t) {
if (!t) return;
ans += to_string(t->val);
if (t->left || t->right) {
ans += '(';//当前节点只要有子节点,左子树必须要建
dfs(t->left);
ans += ')';
}
if (t->right) {
ans += '(';//当前节点只要有右节点,右子树才能建
dfs(t->right);
ans += ')';
}
}
};
LeetCode 609. 在系统中查找重复文件
LeetCode 611. 有效三角形的个数
class Solution {
public:
/*
(枚举+线性扫描) O(n^2)
将整个数组从小到大排序。
在排序后的数组上,从前向后枚举三角形的两个较短的边 nums[i] 和 nums[j],较短的边不能为 0。
在确定了最短的边 nums[i] 后,令 k = i + 1,然后再枚举次短的边 nums[j] 后,不断向后更新 k,使得 nums[i] + nums[j] <= nums[k]。由于 nums[j] 单调递增,故 k 也会单调递增。最终对于每次枚举的 nums[j],累计的答案个数就是 k - j - 1。
时间复杂度
数组排序时间复杂度为 O(nlogn),枚举最短边的时间复杂度为 O(n)。确定了最短边之后,每个值最多只会遍历两次,故总时间复杂度为 O(n^2)。
*/
int triangleNumber(vector<int>& nums) {
sort(nums.begin(), nums.end());
int res = 0;
for (int i = 0; i < nums.size(); i ++ )
for (int j = i - 1, k = 0; j > 0 && k < j; j -- ) {
while (k < j && nums[k] <= nums[i] - nums[j]) k ++ ;
res += j - k;
}
return res;
}
};
class Solution {
public:
/*
使用出现次数最多的任务去构造循环节,可以证明总是可以在循环间隙节中将非最频繁任务插完并且不违反规则。
先找出现次数最多的字母A,最多次数mc,如果只安排A类任务,就是第一行先放一个A,然后空n个位置,然后第二行再放一个A,然后再空n个位置...最后第mc行再放一个A,总共放mc个A,所以只考虑A的情况需要(mc-1)(n+1)+1个位置
如果另外一类任务B也出现了mc次,每一行内部只能放一个B,这样的话两类任务总共需要(mc-1)(n+1)+2个位置
所以如果总共有k类任务(k个字母)均出现mc次,总共需要(mc-1)(n+1)+k个位置
考虑边界情况,如果k>n怎么办,也就是n个空位放不下这些字母,其次剩余的次数没这么多的字母怎么排
对于其余字母来说,出现次数必然<mc,也就是前mc-1行的一整列一定能放完某一类出现次数小于mc的字母,然后这一列剩余的空位再继续排下一类字母,并且一定不会再同一行排到两个同类的字母,因为这样的话出现次数必然大于mc-1(这种字母是倒着排)
对于某些类的字母出现次数大于n的情况,直接在每一行末尾加空位即可
*/
int leastInterval(vector<char>& tasks, int n) {
unordered_map<char, int> hash;
for (auto c: tasks) hash[c] ++ ;
int maxc = 0, cnt = 0;
for (auto [k, v]: hash) maxc = max(maxc, v);
for (auto [k, v]: hash)
if (maxc == v)
cnt ++ ;
return max((int)tasks.size(), (maxc - 1) * (n + 1) + cnt);//后者是有空位的情况
}
};
LeetCode 622. 设计循环队列
LeetCode 623. 在二叉树中增加一行
class Solution {
public:
TreeNode* addOneRow(TreeNode* root, int v, int d) {
if (d == 1) {//特判
auto cur = new TreeNode(v);
cur->left = root;
return cur;
}
queue<TreeNode*> q;
q.push(root);
for (int i = 0; i < d - 2; i ++ ) {//到达第d-1层,走d-2步
for (int j = q.size(); j; j -- ) {
auto t = q.front();
q.pop();
if (t->left) q.push(t->left);
if (t->right) q.push(t->right);
}
}
while (q.size()) {
auto t = q.front();
q.pop();
auto left = new TreeNode(v), right = new TreeNode(v);
left->left = t->left, right->right = t->right;
t->left = left, t->right = right;
}
return root;
}
};
/*
(递归,模拟) O(n)
若 d 为 1,则直接按照题目描述做即可。
否则,从根结点开始遍历,到 d - 1 层时,新创建两个结点,并初始化结点值,左右儿子为当前遍历结点的左右儿子。
时间复杂度
每个结点最多遍历一次,故时间复杂度为 O(n)。
*/
class Solution {
public:
void dfs(TreeNode *r, int cur_d, int v, int d) {
if (r == NULL)
return;
if (cur_d == d - 1) {
TreeNode *new_left = new TreeNode(v);
TreeNode *new_right = new TreeNode(v);
new_left -> left = r -> left;
new_right -> right = r -> right;
r -> left = new_left;
r -> right = new_right;
return;
}
dfs(r -> left, cur_d + 1, v, d);
dfs(r -> right, cur_d + 1, v, d);
}
TreeNode* addOneRow(TreeNode* root, int v, int d) {
if (d == 1) {
TreeNode *new_root = new TreeNode(v);
new_root -> left = root;
return new_root;
}
dfs(root, 1, v, d);
return root;
}
};
/*
三个数的最大乘积,必然是三个最大的数的乘积或者两个最小的(负)数与最大数的乘积。
线性扫描或者排序找出最大的三个数和最小的两个数即可。
*/
class Solution {
public:
int maximumProduct(vector<int>& nums) {
//假设max1 > max2 > max3
int max1 = INT_MIN;
int max2 = INT_MIN;
int max3 = INT_MIN;
//假设min1 < min2
int min1 = INT_MAX;
int min2 = INT_MAX;
for (int a : nums) {
if (a > max1) {
max3 = max2;
max2 = max1;
max1 = a;
} else if (a > max2) {
max3 = max2;
max2 = a;
} else if (a > max3) {
max3 = a;
}
if (a < min1) {
min2 = min1;
min1 = a;
} else if (a < min2) {
min2 = a;
}
}
return max(min1 * min2 * max1, max1 * max2 * max3);
}
};
LeetCode 633. 平方数之和
class Solution {
public:
bool judgeSquareSum(int c) {
// for (long long i = 0; i * i <= c; i ++ ) {
// int j = c - i * i;
// int r = sqrt(j);
// if (r * r == j) return true;
// }
// return false;
// 强行枚举
// for (int a = 0; a <= sqrt(c); ++a){
// int b = sqrt(c - a * a);
// if (a * a + b * b == c)
// return true;
// }
// return false;
// 双平方和定理,一个自然数能被表示为两个平方数之和,数学,费马双平方和定理 O(n^0.5)
// 当且仅当它的质因数分解中,模4余3的质数次方数均为偶数
// for (int i = 2; i <= c / i; ++i){
// int cnt = 0;
// while (c % i == 0){
// c /= i;
// ++cnt;
// }
// if (i % 4 == 3 && cnt % 2 != 0)
// return false;
// }
// if (c % 4 == 3)
// return false;
// return true;
//双指针
long i = 0, j = sqrt(c);
while (i <= j)
if (i * i + j * j == c) return true;
else if (i * i + j * j > c) j --;
else i ++;
return false;
}
};
LeetCode 636. 函数的独占时间
class Solution {
public:
/*
用栈来维护当前函数调研递归序列
*/
vector<int> exclusiveTime(int n, vector<string>& logs) {
vector<int> res(n);
stack<int> stk;//维护当前执行了哪个函数
int last;// 维护上一个事件的开始时间
for (auto& log: logs) {
int x = log.find(':'), y = log.substr(x + 1).find(':') + x + 1;
int id = stoi(log.substr(0, x)), ts = stoi(log.substr(y + 1));
if (log.substr(x + 1, y - x - 1) == "start") {
if (stk.size()) res[stk.top()] += ts - last;
//比如时刻1到3在执行函数A,时刻3新开了一个函数B,则3-1这段时间是函数A的独占时间
stk.push(id);//更新栈顶元素
last = ts;
} else {
/*
注意对于开始和结束两个事件要特判一下,比如题中所给例子,函数1在时刻 2 开始,结束于时刻 5,函数 0 再次在时刻 6 开始执行,那么从时刻5到6这一段时间仍然在执行函数1,所以在这一段要加的独占时间是5-2+1==4
*/
res[stk.top()] += ts - last + 1;
stk.pop();
last = ts + 1;
}
}
return res;
}
};
LeetCode 637. 二叉树的层平均值
class Solution {
public:
vector<double> averageOfLevels(TreeNode* root) {
vector<double> res;
if (!root) return res;
queue<TreeNode*> q;
q.push(root);
while (q.size()) {
int sz = q.size();
double sum = 0;
for (int i = 0; i < sz; i ++ ) {
auto t = q.front();
q.pop();
sum += t->val;
if (t->left) q.push(t->left);
if (t->right) q.push(t->right);
}
res.push_back(sum / sz);
}
return res;
}
};
LeetCode 640. 求解方程
class Solution {
public:
pair<int, int> work(string str) {//返回两个系数
int a = 0, b = 0;//a表示x的系数,b表示常数项
if (str[0] != '+' && str[0] != '-') str = '+' + str;//补上+,方便处理
for (int i = 0; i < str.size(); i ++ ) {//从前往后枚举
int j = i + 1;//此时i表示正负号,从下一位开始
while (j < str.size() && isdigit(str[j])) j ++ ;
//跳出循环时,一般来说从i+1到j-1这一段是数字
int c = 1;//如果数字不存在的话就是1
if (i + 1 <= j - 1) c = stoi(str.substr(i + 1, j - 1 - i));//如果存在,把这一小段数字字符变为数字
if (str[i] == '-') c = -c;//判断正负,是否需要取反
if (j < str.size() && str[j] == 'x') {//判断当前是常数项还是一次项
//由于要保证每次进入循环时,i要指向正负号,所以i要更新为当前项的最后一位,这样循环内+1之后才满足
a += c;
i = j;//指向一次项的x
} else {
b += c;
i = j - 1;//指向常数项的最后一位数字
}
}
return {a, b};
}
/*
先将左边整理成ax+b的形式,右边整理成cx+d的形式 得到(a-c)x=d-b->Ax=B
如果A==0
B==0:无穷解
B不等于0:无解
否则 x=B/A
*/
string solveEquation(string equation) {
int k = equation.find('=');//先找到"="的位置
//把等号两边合并同类项
auto left = work(equation.substr(0, k)), right = work(equation.substr(k + 1));
int a = left.first - right.first, b = right.second - left.second;
if (!a) {
if (!b) return "Infinite solutions";
else return "No solution";
}
return "x=" + to_string(b / a);
}
};
LeetCode 643. 子数组最大平均数 I
/*
对每个长度为k的子区间求平均数,滑动窗口扫描长度为k的区间即可
*/
class Solution {
public:
double findMaxAverage(vector<int>& nums, int k) {
double res = -1e5;
for (int i = 0, j = 0, s = 0; i < nums.size(); i ++ ) {
s += nums[i];
if (i - j + 1 > k) s -= nums[j ++ ];
if (i >= k - 1) res = max(res, s / (double)k);
}
return res;
}
};
LeetCode 645. 错误的集合
class Solution {
public:
/*
数组中1个数出现了0次,1个数出现了两次,其他均只出现1次,且范围在1到n之间
注意要先返回重复的,再返回缺失的
(线性扫描) O(n)
利用原数组进行线性扫描。扫描过程中,将 nums[abs(nums[i]) - 1] 取相反数。若扫描过程中发现 nums[abs(nums[i]) - 1] 已经是负数了,就不再将其置负数,同时说明 abs(nums[i]) - 1 是重复的。
最后再扫描数组中,若发现 nums[i] 是正数,则说明 i + 1 是丢失的数。
注意数组中第i个数的下标是i-1
*/
vector<int> findErrorNums(vector<int>& nums) {
vector<int> res(2);
for (auto x: nums) {
int k = abs(x);//取绝对值是因为当前遍历到的这个数可能已经被取反了
if (nums[k - 1] < 0)//如果第k个数以及取反,说明第数字k是重复的那个数
res[0] = k;
nums[k - 1] *= -1;
}
//循环结束后,数组中只有两个数为正数,一个是重复数k对于的第k个数,它被取反两次,一个数缺失的数,被取反0次
for (int i = 0; i < nums.size(); i ++ ) {
if (nums[i] > 0 && i + 1 != res[0]) {//下标i对应的数是i+1
res[1] = i + 1;
}
}
return res;
}
};
/*
异或位运算 timeO(n)
1.
将原数组和辅助数组[1, 2, ...n]所有元素进行异或,可知其中等于dup的有3个,mis有1个(在辅助数组中),其余元素各有2个。在异或运算过程中,成对出现的都会抵消为0(异或运算的先后顺序不影响,故可当做相等元素各自运算)。最后的结果就是 dup ^ mis
2.
根据dup和mis最后不同的那一位去把两个数组分类,其中一类中3个dup,其余元素成对出现;另一部分有1个mis,其余元素成对出现。对于这两部分,各自将自身所有元素按位异或^,发现结果正好一个是dup,一个是mis。
3.
区分谁重复,谁缺失,先返回重复的
*/
class Solution {
public:
vector<int> findErrorNums(vector<int>& nums) {
int xorr = 0, n = nums.size();//0与任何数异或都为数自身,所以以0为基元素
for (int num: nums)
xorr ^= num;
for (int i = 1; i <= n; ++i)
xorr ^= i;
//循环结束后,xorr为dup^mis
//lowbit操作得到最后xorr最后一位1,遍历所有元素,将各自类中的数全部异或
int lowbit = xorr & -xorr, a = 0, b = 0;
for (int num: nums)
if (num & lowbit)
a ^= num;
else
b ^= num;
for (int i = 1; i <= n; ++i)
if (i & lowbit)
a ^= i;
else
b ^= i;
//上面两个循环结束后,a和b中一个数dup,一个是mis
//然后再区别谁是dup,谁是mis,先返回dup
for (int num: nums)
if (num == a)
return {a, b};
else if (num == b)
return {b, a};
return {-1, -1};
}
};
LeetCode 646. 最长数对链
class Solution {
public:
int findLongestChain(vector<vector<int>>& pairs) {
sort(pairs.begin(), pairs.end(), [](vector<int>& a, vector<int>& b){
return a[1] < b[1];//按照第二个元素从小到大排序,或者说按照区间右端点从小到大排序
});
int res = 1, ed = pairs[0][1];
for (auto& p: pairs) {
if (p[0] > ed) {
res ++ ;
ed = p[1];
}
}
return res;
}
};
LeetCode 647. 回文子串
/*
统计所给字符串所有的子串中,回文子串的数量,这道题直接枚举即可,因为输入的字符串长度不会超过1000
将所有回文串根据中心点分为若干类,以及要分奇数偶数情况
时间复杂度O(n^2)
*/
class Solution {
public:
int countSubstrings(string s) {
int res = 0;
for (int i = 0; i < s.size(); i ++ ) {
// 枚举长度为奇数的情况
for (int j = i, k = i; j >= 0 && k < s.size(); j --, k ++ ) {//枚举回文串一半长度
if (s[j] != s[k]) break;
res ++ ;
}
// 偶数情况
for (int j = i, k = i + 1; j >= 0 && k < s.size(); j --, k ++ ) {
if (s[j] != s[k]) break;
res ++ ;
}
}
return res;
}
};
LeetCode 649. Dota2 参议院
/*
贪心做法: O(n)
这题贪心的思路很简单,就是轮到每个还有用权利的人时,那么他肯定是尽量去禁止最近的当前轮还没有表决的对方选手的权利,如果没有这样的话,那么就从已经行使过权利的对方选手选择一个人禁止他在剩下轮的权力,如果还没有的话,那么说明已经没有敌方选手了,直接获胜。直接模拟的话代码就会比较丑陋,但是很容易理解:这里我们使用字符O代表当前选手被out禁止权利。
*/
class Solution {
public:
string predictPartyVictory(string senate) {
int n = senate.size();
while(true)
{
for(int i = 0 ; i < n ; i ++)
{
int k = i + 1;
if(senate[i] == 'R')
{
while(k < n && (senate[k] == 'R' || senate[k] == 'O'))
k ++;
if(k < n) senate[k] = 'O';
else
{
k = 0;
while(k < i && (senate[k] == 'R' || senate[k] == 'O'))
k ++;
if(k < i) senate[k] = 'O';
else return "Radiant";
}
}else if(senate[i] == 'D')
{
while(k < n && (senate[k] == 'D' || senate[k] == 'O'))
k ++;
if(k < n) senate[k] = 'O';
else
{
k = 0;
while(k < i && (senate[k] == 'D' || senate[k] == 'O'))
k ++;
if(k < i) senate[k] = 'O';
else return "Dire";
}
}
}
}
return "";
}
};