class Solution {
public:
/*
如果我们当前遇到的数字比上一个数字要小的话,肯定是删除上一个数字比较划算。我们最多能删除k个字符。所以我们使用一个单调栈来存储每一个字符,如果当前读进来的数字比前一个数字小,我们就将栈顶元素出栈,直至出栈了k个字符或者栈顶元素已经比当前元素还小。这样在我们删除k个元素后,栈中元素就是剩下的数字啦。这时候我们需要考虑的就是删除前导0和空栈的情况啦。字符串有push和pop操作,所以我们可以直接用字符串来模拟栈,效果是一样的。
*/
string removeKdigits(string num, int k) {
k = min(k, (int)num.size());
string res;
for (auto c: num) {
while (k && res.size() && res.back() > c) {
k -- ;
res.pop_back();
}
res += c;
}
while (k -- ) res.pop_back();
k = 0;
while (k < res.size() && res[k] == '0') k ++ ;
if (k == res.size()) res += '0';
return res.substr(k);
}
};
LeetCode 404. 左叶子之和
/*
主要是树的遍历要熟悉 ,然后加上判断是否是叶子和左节点
*/
class Solution {
public:
int ans = 0;
void dfs(TreeNode* root,int isLeft){
if(root==NULL) return;
if(root->left==NULL && root->right == NULL && isLeft){//判断当前点是否又是左节点又是叶子结点
ans += root->val; return;
}
dfs(root->left,1);
dfs(root->right,0);
}
int sumOfLeftLeaves(TreeNode* root) {
if(root == NULL) return 0;
dfs(root,0);
return ans;
}
};
LeetCode 405. 数字转换为十六进制数
class Solution {
public:
string toHex(int num) {
/*
不用管补码的问题,计算机就是这样操作的,只需要转换即可
C++ 将num转为unsigned类型,即可进行逻辑右移,此时对于负数而言,进行右移时,左侧添加的是0,而不是int类型的符号位。
转换就是把32位分为8组,每组4位,就是从0000到1111,把它转化为0123456789abcdef中的一个字母即可
*/
if(num == 0) return "0";
string ans = "";
unsigned num2 = num;
string s = "0123456789abcdef";
while(num2 != 0){
ans = s[num2 & 15] + ans;
num2 >>= 4;
}
return ans;
}
};
LeetCode 406. 根据身高重建队列
class Solution {
public:
int n;
vector<int> tr;//定义树状数组
int lowbit(int x) {//树状数组的两个函数,默写即可
return x & -x;
}
void add(int x, int v) {
for (int i = x; i <= n; i += lowbit(i)) tr[i] += v;
}
int query(int x) {//求前缀和,以上三个是树状数组的模板
int res = 0;
for (int i = x; i; i -= lowbit(i)) res += tr[i];
return res;
}
/*
先考虑身高较矮的人,对于身高相同的人来说,先考虑k最大的
首先双关键字排序,先按照身高从小到大排序,然后对于相同身高的人来说,按照次序从大到小排
然后对于当前的这个人来说,从前往后找到第一个空位使得当前空位的前面有k个空位,
这一步暴力来做的话是O(n^2),首先要在一个序列中找到满足某种条件的第一个元素,可以二分
二分时要求当前点左边有多少空位,也就是前面有多少个0或1,前缀和可以用树状数组
可以用二分+树状数组来做
所以时间复杂度可以优化到log^2n
*/
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
n = people.size();//人数
tr.resize(n + 1);//下标从1开始
sort(people.begin(), people.end(), [](vector<int>a, vector<int>b) {
//双关键字排序,首先对于身高不相等的人,按照身高从小到大排序
if (a[0] != b[0]) return a[0] < b[0];
//其次对于身高相等的人,按照k从大到小排序
return a[1] > b[1];
});
vector<vector<int>> res(n);//记录答案 res初始化长度
for (auto p: people) {//从前往后扫描
int h = p[0], k = p[1];
int l = 1, r = n;//树状数组下标从1开始
while (l < r) {
int mid = l + r >> 1;
//求一下从1到mid中有多少个0,等价于总和mid减去1的个数
if (mid - query(mid) >= k + 1) r = mid;
else l = mid + 1;
}
res[r - 1] = p;//-1是因为res的下标从0开始
add(r, 1);//当前位置放数之后,记得+1
}
return res;
}
};
LeetCode 407. 接雨水 II
/*
这道题思路类似于 LeetCode 42. Trapping Rain Water 中的双指针算法,对于每个点我们可以找到上下左右四个方向的最大值(也就是这四个格子的最终高度),然后当中的最小值减去当前柱子的高度就是盛水量
f(i,j)表示每个格子最终的高度
f(i,j)=max( min( f(i-1,j) , f(i,j+1) , f(i+1,j) , f(i,j-1) ) , h(i,j) )
很像动态规划,但格子之间的状态相互依赖,没有拓扑序,不能循环来求
所以用图论去求,对于边界来说,这些格子最终的状态是确定的,就是自身高度f(i,j)=h(i,j)
推导时类似于dijkstra,每次从所有当前已经求出答案的状态里面找到一个权值最小的状态,用它去更新周围未被更新的状态
证明:假设最终f(i,j)=max( f(i,j+1) , h(i,j) )
如果另外三个格子都在堆里,那没什么说的,f(i,j+1)是最小的
如果有格子不在堆里,也就是没有被更新,那它一定大于等于当前堆里的最小值,由f(i,j)>=f(i,j+1)可推出
所以如果当前堆里最小值是f(i,j+1) ,由它去更新未被更新的f(i,j)时,不管f(i,j)周围四个格子是否全部都在堆中,
f(i,j+1)必然都是这个四个格子的最小值,因此这样更新是正确的
这样类似于dijstra,但区别在于dijkstra中,第一次从堆中出来时,这个状态是真实值,这道题是入堆的时候就是真实值
但思路是类似的
每个点只会插入弹出堆一次,总共有mn个点,每次插入与弹出最多需要O(logmn)的时间,因此总时间复杂度为O(mnlogmn)
*/
class Solution {
public:
struct Cell {//定义一个结构体来存堆里面的每个元素
int h, x, y;
bool operator< (const Cell& t) const {//由于要用到堆,所以重载小于号
return h > t.h;//默认大根堆,所以改一下符号
}
};
int trapRainWater(vector<vector<int>>& h) {
if (h.empty() || h[0].empty()) return 0;//边界条件
int n = h.size(), m = h[0].size();
priority_queue<Cell> heap;
vector<vector<bool>> st(n, vector<bool>(m));//判重数组避免格子被重复搜索
for (int i = 0; i < n; i ++ ) {//先把左右边界的点放入堆中
st[i][0] = st[i][m - 1] = true;
heap.push({h[i][0], i, 0});
heap.push({h[i][m - 1], i, m - 1});
}
for (int i = 1; i + 1 < m; i ++ ) {//再把上下边界的点放入堆中
st[0][i] = st[n - 1][i] = true;
heap.push({h[0][i], 0, i});
heap.push({h[n - 1][i], n - 1, i});
}
int res = 0;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
while (heap.size()) {//当堆不空时
auto t = heap.top();//取出堆顶元素
heap.pop();
res += t.h - h[t.x][t.y];//加上这个堆顶元素最终容纳的雨水
for (int i = 0; i < 4; i ++ ) {//枚举四个方向
int x = t.x + dx[i], y = t.y + dy[i];
if (x >= 0 && x < n && y >= 0 && y < m && !st[x][y]) {
heap.push({max(h[x][y], t.h), x, y});
st[x][y] = true;
}
}
}
return res;
}
};
LeetCode 409. 最长回文串
/*
回文串要求除了中心元素之外,其他任何元素都是左右对称
对于每个字母,它在字符串中出现k次,对于它而言,最多可以拿出来k/2对出来放到最长回文串中,它用的字符个数是k/2*2
最后再看一下如果有一个字母出现奇数次的话,则可以在回文串中间再加一个字母
所以先统计每个字母出现次数,哈希表存储,如果这个字母出现k次的话,这个字母能被用k/2*2个,遍历到最后,看有没有字母剩余,如果有剩余,则可以在字符串中心再加一个字母
*/
class Solution {
public:
int longestPalindrome(string s) {
unordered_map<char, int> hash;
for (auto c: s) hash[c] ++ ;
int res = 0;
for (auto [a, k]: hash) res += k / 2 * 2;
if (res < s.size()) res ++ ;
return res;
}
};
LeetCode 412. Fizz Buzz
class Solution {
public:
vector<string> fizzBuzz(int n) {
vector<string> res;
for (int i = 1; i <= n; i ++ )
if (i % 3 == 0 && i % 5 == 0) res.push_back("FizzBuzz");
else if (i % 3 == 0) res.push_back("Fizz");
else if (i % 5 == 0) res.push_back("Buzz");
else res.push_back(to_string(i));
return res;
}
};
LeetCode 414. 第三大的数
class Solution {
public:
int thirdMax(vector<int>& nums) {
/*
注意第三大的数是指第三大且唯一出现的数 输入:[2, 2, 3, 1] 输出:1
本质上分情况讨论
从三个数记录前三大的值,遍历数组,更新三个变量,另外用一个变量记录整个数组一共有多少个最大值
另外这道题数据会出现负无穷,为了不处理边界情况,用long long
*/
long long INF = 1e10, a = -INF, b = -INF, c = -INF, s = 0;
for (auto x: nums) {
if (x > a) s ++, c = b, b = a, a = x;//注意更新顺序
else if (x < a && x > b) s ++, c = b, b = x;
else if (x < b && x > c) s ++, c = x;
//对于等于abc以及小于c的情况都没有处理,s记录的就是有几个最大值
}
if (s < 3) return a;
return c;
}
};
LeetCode 415. 字符串相加
class Solution {
public:
vector<int> add(vector<int>& A, vector<int>& B) {
vector<int> C;
for (int i = 0, t = 0; i < A.size() || i < B.size() || t; i ++ ) {
if (i < A.size()) t += A[i];
if (i < B.size()) t += B[i];
C.push_back(t % 10);
t /= 10;
}
return C;
}
string addStrings(string a, string b) {
vector<int> A, B;
for (int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');
for (int i = b.size() - 1; i >= 0; i -- ) B.push_back(b[i] - '0');
auto C = add(A, B);
string c;
for (int i = C.size() - 1; i >= 0; i -- ) c += to_string(C[i]);
return c;
}
};
class Solution {
public:
//方法3
int trap(vector<int>& height) {
/*
平面上给定n根柱子的高度,求这些柱子围成的区域,最多容纳多少雨水,这道题用到了单调栈,但与单调栈的一般用法并不相同,依次遍历每根柱子,存入单调栈中
*/
stack<int> stk;//单调栈,存所有柱子的编号
int res = 0;
for (int i = 0; i < height.size(); i ++ ) {//枚举所有柱子
int last = 0;//上一个pop掉的的栈顶元素的高度
while (stk.size() && height[stk.top()] <= height[i]) {
//每次加的雨水是当前枚举的柱子与当前栈顶柱子之间能容纳的雨水
res += (height[stk.top()] - last) * (i - stk.top() - 1);
last = height[stk.top()];
stk.pop();
}
if (stk.size()) res += (i - stk.top() - 1) * (height[i] - last);
stk.push(i);
}
return res;
}
};
LeetCode 419. 甲板上的战舰
class Solution {
public:
//对于每艘战舰只统计左上角即可,只要当前格子上方与左方都没有'X'的话,统计它
int countBattleships(vector<vector<char>>& board) {
int res = 0;
for (int i = 0; i < board.size(); i ++ )
for (int j = 0; j < board[i].size(); j ++ ) {
if (i > 0 && board[i - 1][j] == 'X') continue;
if (j > 0 && board[i][j - 1] == 'X') continue;
if (board[i][j] == 'X') res ++ ;
}
return res;
}
};
LeetCode 420. 强密码检验器
class Solution {
public:
int strongPasswordChecker(string s) {
int a = 0, b = 0, c = 0, n = s.size(), k = 0;
for (auto x: s) {
if (x >= '0' && x <= '9') a = 1;
else if (x >= 'a' && x <= 'z') b = 1;
else if (x >= 'A' && x <= 'Z') c = 1;
}
k = a + b + c;
if (n < 6) return max(6 - n, 3 - k);//字符数与种类数
else {
int p = 0, del = n - 20, res = del;
int d[3] = {0};
for (int i = 0; i < s.size(); i ++ ) {
int j = i;
while (j < s.size() && s[j] == s[i]) j ++ ;
int s = j - i;
i = j - 1;
p += s / 3;
if (s >= 3) d[s % 3] ++ ;
}
if (n <= 20) return max(p, 3 - k);
if (d[0] && del > 0) {
int t = min(d[0], del);
del -= t;
p -= t;
}
if (d[1] && del > 0) {
int t = min(d[1] * 2, del);
del -= t;
p -= t / 2;
}
if (p && del > 0) {
int t = min(p * 3, del);
p -= t / 3;
}
return res + max(p, 3 - k);
}
}
};
LeetCode 423. 从英文中重建数字
class Solution {
public:
/*
首先判断哪些单词出现的次数唯一确定。也就是看哪个单词有唯一独特的字母,比如只有"zero"有"z"
首先判断0有多少个,也就是z有多少个,然后把所有的zero删掉
然后可以发现"g"只在"eight"中有,所以可以判断有多少个8
然后h只在3中有,w只在2中有,x:6,u:4,f:5,o:1,s:7,最后是9
所以按照这个顺序判断即可
*/
string originalDigits(string s) {
string name[] = {
"zero", "one", "two", "three", "four", "five",
"six", "seven", "eight", "nine"
};
int ord[] = {0, 8, 3, 2, 6, 4, 5, 1, 7, 9};//判断顺序
unordered_map<char, int> cnt;//统计每个字母出现的次数
for (auto c: s) cnt[c] ++ ;
string res;//定义答案
for (int x: ord) {//按照顺序枚举每个单词
while (true) {//当前枚举的单词可能没出现,有可能出现很多次
bool flag = true;
for (auto c: name[x])
if (!cnt[c]) {
flag = false;
break;
}
if (flag) {
res += to_string(x);
for (auto c: name[x]) cnt[c] -- ;
}
else break;
}
}
sort(res.begin(), res.end());
return res;
}
};
LeetCode 424. 替换后的最长重复字符
class Solution {
public:
int characterReplacement(string s, int k) {
int len = s.size(), maxCnt = 0, res = 0;//maxcnt维护当前窗口出现次数最多的字符的出现次数
vector<int> c2cnt(26);//动态维护窗口中每个字符出现的次数
for (int i = 0, j = 0; i < len; i++){//遍历尾端点
maxCnt = max(maxCnt, ++c2cnt[s[i] - 'A']);//当前遍历字符出现次数加一,判断是否要更新最大出现次数
while (i - j + 1 - maxCnt > k)
--c2cnt[s[j++] - 'A'];//始终保证当前窗口不同的字母最多k个,这样替换之后就是全都一样
res = max(res, i - j + 1);
}
return res;
}
};
class Solution {
public:
int characterReplacement(string s, int k) {
int res = 0;
for (char c = 'A'; c <= 'Z'; c ++ ) {//先枚举目标字符
for (int i = 0, j = 0, cnt = 0; i < s.size(); i ++ ) {
//枚举尾字符i,找到最左边的j使得j到i之间不等于目标字符的字符数量小于等于k
if (s[i] == c) cnt ++ ;
while (i - j + 1 - cnt > k) {
if (s[j] == c) cnt -- ;
j ++ ;
}
res = max(res, i - j + 1);
}
}
return res;
}
};
LeetCode 429. N叉树的层序遍历
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
vector<vector<int>> res;
if (!root) return res;
queue<Node*> q;
q.push(root);
while (q.size()) {
int len = q.size();
vector<int> line;
while (len -- ) {
auto t = q.front();
q.pop();
line.push_back(t->val);
for (auto c: t->children) q.push(c);
}
res.push_back(line);
}
return res;
}
};
LeetCode 430. 扁平化多级双向链表
/*
// Definition for a Node.
class Node {
public:
int val;
Node* prev;
Node* next;
Node* child;
};
*/
/*
本质上是一个递归的过程,从前往后遍历每个节点,当遍历到某个有子节点的节点时,递归将其子节点全部展开成一条双链表,然后插到节点后面
*/
class Solution {
public:
Node* flatten(Node* head) {
auto res = dfs(head);//返回头结点和尾节点
return res[0];
}
vector<Node*> dfs(Node* head) {
if (!head) return {NULL, NULL};//边界情况
auto cur = head, tail = head;//前者表示当前遍历的节点,后者表示尾节点
while (cur) {
tail = cur;//更新tail,它是最后一个不为空的点
if (cur->child) {
auto t = dfs(cur->child);
cur->child = NULL;//清空child
t[1]->next = cur->next;//双链表接进来
if (cur->next) cur->next->prev = t[1];
cur->next = t[0];
t[0]->prev = cur;
cur = t[1]->next;
tail = t[1];//tail是最后一个不为空的点
} else {
cur = cur->next;
}
}
return {head, tail};
}
};
LeetCode 434. 字符串中的单词数
class Solution {
public:
//双指针,和LeetCode 485. 最大连续1的个数很像
int countSegments(string s) {
int res = 0;
for (int i = 0; i < s.size(); i ++ ) {
if (s[i] == ' ') continue;
int j = i + 1;
while (j < s.size() && s[j] != ' ') j ++ ;
res ++ ;
i = j;
}
return res;
}
};
LeetCode 435. 无重叠区间
/*
边界相邻不算有交集,可以求这个问题的对偶问题,给定一些区间,最多选出多少区间使得选出来的区间两两没有交集,这道题是一个经典的贪心题
在选择要保留区间时,区间的结尾十分重要:选择的区间结尾越小,余留给其它区间的空间 就越大,就越能保留更多的区间。因此,我们采取的贪心策略为,优先保留结尾小且不相交的区 间。
首先按照区间右端点从小到大排序,然后从左到右选择每个区间,能选就选如果当前区间与上一个区间有交集就不能选
证明贪心解==最优解,先证>=,再证明<=
*/
bool cmp (vector<int> &a, vector<int> &b){
return a[1] < b[1];
}
class Solution {
public:
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
int n = intervals.size();
sort(intervals.begin(), intervals.end(), cmp);
int cnt = 0, right_bound = 0;
for (int i = 0; i < n; ++i){
if (i == 0 || intervals[i][0] >= right_bound){
++cnt;
right_bound = intervals[i][1];
}
}
return n - cnt;
}
};
LeetCode 436. 寻找右区间
class Solution {
public:
/*
对于每个区间找到最靠左的右侧区间
先将所有区间按照左端点排序
二分
*/
vector<int> findRightInterval(vector<vector<int>>& q) {
int n = q.size();
for (int i = 0; i < n; i ++ ) q[i].push_back(i);//存下标
sort(q.begin(), q.end());
vector<int> res(n, -1);
for (auto& x: q) {
int l = 0, r = n - 1;
while (l < r) {
int mid = l + r >> 1;
if (q[mid][0] >= x[1]) r = mid;
else l = mid + 1;
}
if (q[r][0] >= x[1]) res[x[2]] = q[r][2];
}
return res;
}
};
LeetCode 437. 路径总和 III
class Solution {
public:
/*
对于一维的区间,可以用前缀和来做,要求以i为右端点的区间和为给定target值的区间个数,也就是求固定的s[i]-多少个s[j]==target,也就是求有多少个s[j]==s[i]-target,求从起始点到i中有多少个前缀和等于s[i]-target
类似思想,dfs遍历过程中,用一个哈希表动态维护从根节点到当前遍历的节点的路径上所有前缀和出现的次数
*/
unordered_map<int, int> cnt;//动态维护从根节点到当前遍历的节点的路径上所有前缀和出现的次数
int res = 0;
int pathSum(TreeNode* root, int sum) {
cnt[0] ++ ;
dfs(root, sum, 0);
return res;
}
void dfs(TreeNode* root, int sum, int cur) {
if (!root) return;
cur += root->val;//当前前缀和更新
res += cnt[cur - sum];//统计sj=si-T
cnt[cur] ++ ;//先统计再计算,否则出错
dfs(root->left, sum, cur), dfs(root->right, sum, cur);
cnt[cur] -- ;//回溯
}
};
LeetCode 438. 找到字符串中所有字母异位词
/*
双指针算法,用一个滑动窗口维护一个长度是m的子串,每次用一个哈希表维护这个子串里面每个字符出现的次数,很好维护,因为每次只会把窗口往后移动一位,每次新增一个字符,去掉一个字符,如何快速判断当前窗口每个字符出现次数与所给短串字符出现次数相同,用一个变量记录当前窗口中有多少种字符与所给短串出现的字符完全匹配
*/
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
unordered_map<char, int> cnt;
for (auto c: p) cnt[c] ++ ;
vector<int> res;
int tot = cnt.size();//有多少种字符待完全匹配
for (int i = 0, j = 0, satisfy = 0; i < s.size(); i ++ ) {//satisfy表示一共有多少种字符满足要求
/*哈希表最初只有所给短串出现的字符与对应的出现次数,遍历过程中,新增一个字符,出现次数-1,考虑这个新增的字符能否让某一种待匹配的字符完全匹配,也就是加入之后是否为0,如果需要去掉一个字符的话,这个字符的出现次数+1,需要考虑的是这个去掉的字符是否会影响某种已经完全匹配的字符,也就是去掉之前是否已经为0*/
if ( -- cnt[s[i]] == 0) satisfy ++ ;
if (i - j + 1 > p.size()) {
if (cnt[s[j]] == 0) satisfy -- ;
cnt[s[j ++ ]] ++ ;
}
//判断当前区间是否完全匹配
if (satisfy == tot) res.push_back(j);
}
return res;
}
};
class Solution {
public:
vector<int> findDuplicates(vector<int>& nums) {
vector<int> res;
for (auto x: nums) {
int k = abs(x);
nums[k-1] *= -1;
if (nums[k-1] > 0) res.push_back(k);
}
return res;
}
};
class Solution {
public:
vector<int> findDuplicates(vector<int>& nums) {
int n = nums.size();
for (int i = 0; i < nums.size(); ++i){
while (nums[nums[i] - 1] != nums[i])
swap(nums[nums[i] - 1], nums[i]);
}
vector<int> res;
for (int i = 0; i < nums.size(); ++i){
if (nums[i] != i + 1)
//nums[i]这个在数组中出现过的数还没有在正确的位置说明nums[i]本身重复,i+1是没出现过的数
res.push_back(nums[i]);
}
return res;
}
};
LeetCode 443. 压缩字符串
class Solution {
public:
/*
使用原地算法也就是不使用额外空间,或者使用空间是O(1)
*/
int compress(vector<char>& s) {
int k = 0;//答案
for (int i = 0; i < s.size(); i ++ ) {
int j = i + 1;//每次进入循环内部时,i指向每一段相同字符的第一个字符
while (j < s.size() && s[j] == s[i]) j ++ ;
//枚举i指向字符有多少个相同的,跳出循环时,比如aaab,i指向第一个a,j指向b
int len = j - i;//相同字符个数
s[k ++ ] = s[i];
if (len > 1) {//为了不使用额外空间,修改输入数组
int t = k;
while (len) {//len可能为12,这样数字要一位一位的放
s[t ++ ] = '0' + len % 10;//此时把len的最后一位变成字符形式后放入
len /= 10;
}
//放完之后要翻转
reverse(s.begin() + k, s.begin() + t);
k = t;//更新答案
}
i = j - 1;
}
return k;
}
};
LeetCode 445. 两数相加 II
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
/*
高精度问题,换成链表存储,以及要特别注意这道题是高位在前,低位在后存储,但是由于计算时候是先从低位开始加,所以可以先翻转链表,然后再计算,对于进阶思考,如果要不修改链表的话,可先开一个额外的数组或者说栈存下来
*/
class Solution {
public:
ListNode* reverse(ListNode* head) {
auto a = head, b = head->next;
while (b) {
auto c = b->next;
b->next = a;
a = b, b = c;
}
head->next = NULL;
return a;
}
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
l1 = reverse(l1), l2 = reverse(l2);
auto head = new ListNode(-1);
int t = 0;
while (l1 || l2 || t) {
if (l1) t += l1->val, l1 = l1->next;
if (l2) t += l2->val, l2 = l2->next;
auto cur = new ListNode(t % 10);
t /= 10;//要实现边插入边翻转
cur->next = head->next;
head->next = cur;
}
return head->next;
}
};
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
stack<int> st1,st2,res;
while(l1){st1.push(l1->val); l1 = l1->next;}
while(l2){st2.push(l2->val); l2 = l2->next;}
int cn = 0;
while(!st1.empty()||!st2.empty())
{
int x,y;
x = st1.empty()?0:st1.top();
y = st2.empty()?0:st2.top();
res.push((x + y + cn) % 10);
cn = (x + y + cn) / 10;
if(!st1.empty())st1.pop();
if(!st2.empty())st2.pop();
}
if(cn == 1)
res.push(1);
ListNode* dummy = new ListNode(0);
ListNode* cur = dummy;
while(!res.empty())
{
cur->next = new ListNode(res.top());
res.pop();
cur = cur->next;
}
return dummy->next;
}
LeetCode 448. 找到所有数组中消失的数字
/*
利用下标,把每个数都放在正确的位置上,也就是下标与数字一一对应,因为这道题的数据范围数1到n,所以数字i对应的第i个数的下标是i-1
让数组中出现的数都在正确的位置上
最后只要不在正确的位置上的数就是没出现过的
*/
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
int n = nums.size();
for (int i = 0; i < n; ++i){//遍历,只有当前遍历的数不在正确的位置上,就交换使得在正确的位置上
while (nums[nums[i] - 1] != nums[i])//while循环是尽可能的交换
swap(nums[nums[i] - 1], nums[i]);//数字nums[i]对应的第nums[i]个数的下标是nums[i]-1
}
vector<int> res;
for (int i = 0; i < n; ++i)
if (nums[i] != i + 1)//此时是i+1没出现过
res.push_back(i + 1);
return res;
}
};
/*
如果不考虑空间限制的话,可以开一个长度是n的数组标记每个数是否出现,然后遍历原数组记录每个数的出现次数,然后再遍历新开数组即可
要求不开额外空间的话可以原地修改原数组,使得原数组帮我们记录每个数是否出现过
由于所有数的范围都是1到n之间,所有只有数字x出现过,就把数组中第x个数(对应下标为x-1)变成负数即可
最后数组中的正数对应的数是没有出现过的
*/
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
for (auto x: nums) {
x = abs(x);
if (nums[x - 1] > 0) nums[x - 1] *= -1;
//这里是大于0的时候才变为负数,所以出现一次或两次的数最后都是负数
}
vector<int> res;
for (int i = 0; i < nums.size(); i ++ )
if (nums[i] > 0)
res.push_back(i + 1);
return res;
}
};
LeetCode 450. 删除二叉搜索树中的节点
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
del(root, key);
return root;
}
void del(TreeNode* &root, int key) {
if (!root) return;
if (key == root->val) {
if (!root->left && !root->right) root = NULL; // 子节点
else if (!root->left) root = root->right; // 只有右儿子
else if (!root->right) root = root->left; // 只有左儿子
else { // 左右儿子都有
auto p = root->right;
while (p->left) p = p->left; // 找后继
root->val = p->val;
del(root->right, p->val);
}
}
else if (key < root->val) del(root->left, key);
else del(root->right, key);
}
};
LeetCode 451. 根据字符出现频率排序
class Solution {
public:
/*
(哈希表 + 排序) O(LlogL)
用 unordered_map 统计每个字符的出现次数。
将每个字符的出现次数和字符本身构成 pair,加入到数组中。
对数组排序,然后从大到小组合为答案即可。
*/
string frequencySort(string s) {
unordered_map<char, int> hash;
for (auto &c : s)
hash[c]++;
vector<pair<int, char>> chs;
for (auto it = hash.begin(); it != hash.end(); it++)
chs.push_back({it -> second, it -> first});
sort(chs.begin(), chs.end());
reverse(chs.begin(), chs.end());
string ans = "";
for (auto &ch : chs)
ans += string(ch.first, ch.second);
return ans;
}
};
class Solution {
public:
/*
堆 或者 排序的思路 timeO(nlogn)
*/
string frequencySort(string s) {
unordered_map<char, int> c2cnt;
for (char c: s)
++c2cnt[c];
typedef pair<int, int> PII;
priority_queue<PII, vector<PII>> pq;
for (auto c_cnt: c2cnt)
pq.push({c_cnt.second, c_cnt.first});
string res;
while (!pq.empty()){
auto cnt_c = pq.top(); pq.pop();
res += string(cnt_c.first, cnt_c.second);
}
return res;
}
};
class Solution {
public:
/*
桶排序思路 timeO(n)
*/
string frequencySort(string s) {
unordered_map<char, int> c2cnt;
for (char c: s)
++c2cnt[c];
int n = s.size();
vector<string> buckets(n + 1, "");//下标0到n,下标为i表示出现i次的字符有哪些
for (auto &c_cnt: c2cnt){
char c = c_cnt.first;
int cnt = c_cnt.second;
buckets[cnt].push_back(c);
}
string res="";
for (int i = n; i >= 1; --i)
for (char c: buckets[i])
res += string(i, c);
return res;
}
};
LeetCode 452. 用最少数量的箭引爆气球
/*
这道题就是最少点覆盖问题,给定一些区间,至少放多少个点使得这些区间至少包含一个点,基础课模板题,经典贪心
先将所有区间按照右端点排序,从前往后扫描区间放点,每次看当前区间右端点能否放点
*/
bool cmp(vector<int> &a, vector<int> &b){
return a[1] < b[1];
}
class Solution {
public:
int findMinArrowShots(vector<vector<int>>& points) {
sort(points.begin(), points.end(), cmp);
int n = points.size(), res = 0, r = INT_MIN;
for (int i = 0; i < n; ++i){
if (i == 0 || points[i][0] > r){
++res;
r = points[i][1];
}
}
return res;
}
};
LeetCode 453. 最小移动次数使数组元素相等
class Solution {
public:
/*
因为我们最后使数组元素相等,所以我们关心的是数组元素相对的值,每次令 n - 1 个元素的值加 1,相当于对一个元素减 1
故只需要找到最小的元素值 min_x,让其他大于它的元素减少到 min_x。
具体地,让所有元素的总和 tot 减去 n * min_x。
*/
int minMoves(vector<int>& nums) {
int minv = INT_MAX;
for (auto x: nums) minv = min(minv, x);
int res = 0;
for (auto x: nums) res += x - minv;//遍历每个数与min的差
return res;
}
};
LeetCode 454. 四数相加 II
/*
先两重循环,用哈希表存储一下两个数组中任意两个元素的和有多少种组合
再两重循环查表,用空间换时间
时间复杂度O(n^2)
*/
class Solution {
public:
int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>&