刷题笔记---8月
LCP40.心算挑战(贪心、排序)
cpp
class Solution {
public:
int maxmiumScore(vector<int>& cards, int cnt) {
//24.8.1
ranges::sort(cards, greater()); //从大到小排序
int s = reduce(cards.begin(), cards.begin()+cnt, 0);
if(s%2 == 0) return s;
auto replace_sum = [&](int x) -> int {
for(int i = cnt; i < cards.size(); i++) {
if(cards[i]%2 != x%2) {
return s-x+cards[i];
}
}
return 0;
};
int x = cards[cnt-1];
int ans = replace_sum(x);
cout << ans;
for(int i = cnt-2; i >= 0; i--) {
if(cards[i]%2 != x%2) {
ans = max(ans, replace_sum(cards[i]));
break;
}
}
return ans;
}
};
我感觉这压根不是简单题啊!想过很多办法:双指针、前缀和、dp,dp会超时。结果是个贪心题,话说贪心思路的确可以想到,但是要实现出来好像真的很麻烦!
946.验证栈序列(栈、模拟)
cpp
class Solution {
public:
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
stack<int> st;
int j = 0;
for(int pu : pushed) {
st.push(pu);
while(!st.empty() && st.top() == popped[j]) {
st.pop();
j++;
}
}
return st.empty();
}
};
go
func validateStackSequences(pushed []int, popped []int) bool {
st := []int{}
j := 0
for _, val := range pushed {
st = append(st, val)
for len(st) > 0 && st[len(st)-1] == popped[j] {
st = st[:len(st)-1]
j++
}
}
if len(st) > 0 {
return false
} else {
return true
}
}
简单的栈的使用,主要是模拟题目的过程。
3128.直角三角形(数学)
cpp
class Solution {
public:
long long numberOfRightTriangles(vector<vector<int>>& grid) {
int n = grid[0].size();
vector<int> col_sum(n, -1); //提前减一
for(auto row : grid) {
for(int i = 0; i < n; i++) {
col_sum[i] += row[i];
}
}
long long ans = 0;
for(auto row : grid) {
int rowsum = reduce(row.begin(), row.end(), 0)-1; //提前减一
for(int j = 0; j < n; j++) {
if(row[j] == 1) ans += rowsum*col_sum[j];
}
}
return ans;
}
};
go
func numberOfRightTriangles(grid [][]int) int64 {
n := len(grid[0])
colSum := make([]int, n)
for _, row := range grid {
for j, x := range row {
colSum[j] += x
}
}
ans := 0
for _, row := range grid {
rowSum := -1 //提前减一
for _, x := range row {
rowSum += x
}
for j, x := range row {
if x == 1 {
ans += rowSum*(colSum[j]-1)
}
}
}
return int64(ans)
}
1021.删除最外层的括号(栈、计数法)
cpp
class Solution {
public:
string removeOuterParentheses(string s) {
int level = 0;
string ans;
for(auto c : s) {
if(c == ')') level--;
if(level) ans.push_back(c);
if(c == '(') level++;
}
return ans;
}
};
go
func removeOuterParentheses(s string) string {
level := 0
ans := []byte{}
for _, c := range s {
if c == ')' {
level--
}
if level > 0 {
ans = append(ans, byte(c))
}
if c == '(' {
level++
}
}
return string(ans)
}
涨见识的一道题,关键是要想到这个方法,已经是知道要用栈来写这一题,但是还是没办法解决,这种感觉太难受了,就是无论怎么硬想都想不出来。。。这里的level是用来计数的,第一次和为0就代表第一个原语,还有这里的顺序很重要!感觉就把这个当作模板来记忆吧!
1190.反转每对括号间的子串(栈)
cpp
class Solution {
public:
string reverseParentheses(string s) {
stack<string> st;
string str = "";
for(char c : s) {
if(c == '(') {
st.push(str);
str = "";
} else if(c == ')') {
reverse(str.begin(), str.end());
str = st.top()+str;
st.pop();
} else {
str.push_back(c);
}
}
return str;
}
};
go
func reverseParentheses(s string) string {
st := [][]byte{}
str := []byte{}
for _, c := range s {
if c == '(' {
st = append(st, str)
str = []byte{}
} else if c == ')' {
for j, n := 0, len(str); j < n-1-j; j++ {
str[j], str[n-1-j] = str[n-1-j], str[j]
}
str = append(st[len(st)-1], str...)
st = st[:len(st)-1]
} else {
str = append(str, byte(c))
}
}
return string(str)
}
看来还是大意了,以为栈的题目都是很简单的,现在来看好像其实并不如此,还是有很多题型和方法是需要总结的。对于这一题,和之前都有所不同,这里是将栈定义成了stack<string>
类型保存的每一层的str,当遇到'('就将str入栈并str更新为空,当遇到')'就将这一层的str反转并返回给上一层,这里也是复习到了reverse(nums.begin(), nums.end())
反转函数,然后就是对于go,是没有反转函数的,这样也复习到了对对称字符串的操作了,最后特别说明一行str = append(st[len(st)-1], str...)
,由于str是一个字符串,所以append中的str要写成str...
1003.检查替换后的词是否有效(栈)
自己写的版本
cpp
class Solution {
public:
bool isValid(string s) {
stack<char> st;
for(char ch : s) {
if(!st.empty() && ch == 'c') {
char t = st.top();
st.pop();
if(!st.empty() && t == 'b' && st.top() == 'a') st.pop();
else st.push(t), st.push(ch);
} else {
st.push(ch);
}
}
return st.empty();
}
};
思路就是将abc看成整体,所以只需要遇到c的时候再出栈就行,然后就是栈操作的注意事项,这一题wa了两次,主要就是错在了两次的栈判空操作上,由于需要取出c的前两位,所以就需要有两次的st判空操作,这样就能ac这一题了
go
func isValid(s string) bool {
st := []byte{}
for _, ch := range s {
if len(st) > 0 && ch == 'c' {
t := st[len(st)-1]
st = st[:len(st)-1]
if len(st) > 0 && t == 'b' && st[len(st)-1] == 'a' {
st = st[:len(st)-1]
} else {
st = append(st, byte(ch))
}
} else {
st = append(st, byte(ch))
}
}
if len(st) > 0 {
return false
} else {
return true
}
}
灵神版本
cpp
class Solution {
public:
bool isValid(string s) { // s 同时作为栈
int i = 0; // i-1 表示栈顶下标,i=0 表示栈为空
for (char c: s) {
if (c > 'a' && (i == 0 || c - s[--i] != 1))
return false;
if (c < 'c')
s[i++] = c; // 入栈
}
return i == 0;
}
};
真的太过于简洁了,很多东西都合并在一起了,不看题解文章真的有点难理解。。。
2216.美化数组的最少删除数
cpp
class Solution {
public:
int minDeletion(vector<int>& nums) {
int cnt = 0, n = nums.size();
for(int i = 0; i < n; i++) {
if((i-cnt)%2 == 0 && i+1 < n && nums[i] == nums[i+1]) cnt++;
}
return (n-cnt)%2 == 0 ? cnt : cnt+1;
}
};
go
func minDeletion(nums []int) int {
cnt, n := 0, len(nums)
for i := 0; i < n; i++ {
if (i-cnt)%2 == 0 && i+1 < n && nums[i] == nums[i+1] {
cnt++
}
}
if (n-cnt)%2 == 0 {
return cnt
} else {
return cnt+1
}
}
这是栈的题单中的一道题,但是我看不懂栈的写法,从评论区看到一个很牛逼的写法,关键就是找到这个下标i-cnt
,这一点我感觉很妙,也非常切合题目意思,删除一个元素之后其它元素向前移动,这个就是充分利用了下标的和变量之间的关系
1006.笨阶乘(栈)
cpp
class Solution {
public:
int clumsy(int n) {
//24.8.3
stack<int> st;
st.push(n);
n--;
int index = 0;
while(n > 0) {
cout << n;
if(index%4 == 0) st.top() *= n;
else if(index%4 == 1) st.top() /= n;
else if(index%4 == 2) st.push(n);
else if(index%4 == 3) st.push(-n);
index++;
n--;
}
int sum = 0;
while(!st.empty()) {
sum += st.top();
st.pop();
}
return sum;
}
};
go
func clumsy(n int) int {
st := []int{}
st = append(st, n)
n--
index := 0
for n > 0 {
if index%4 == 0 {
st[len(st)-1] *= n
} else if index%4 == 1 {
st[len(st)-1] /= n
} else if index%4 == 2 {
st = append(st, n)
} else if index%4 == 3 {
st = append(st, -n)
}
n--
index++
}
sum := 0
for len(st) > 0 {
sum += st[len(st)-1]
st = st[:len(st)-1]
}
return sum
}
栈的灵活运用,思考的时候想到了利用取余的方式判断乘除加减,就是对栈操作上还是有很多漏洞,这一题的思路就是遇到乘除就将栈顶元素进行运算,遇到加减就将n或-n入栈就行,唉,主要是没想到这一些栈操作。
224.基本计算器(栈)
cpp
class Solution {
public:
int calculate(string s) {
//24.8.3
stack<int> st; //保存每一层的符号位
int sign = 1;
st.push(sign);
int n = s.size(), i = 0, ans = 0;
while(i < n) {
if(s[i] == ' ') i++;
else if(s[i] == '+') sign = st.top(), i++;
else if(s[i] == '-') sign = -st.top(), i++;
else if(s[i] == '(') st.push(sign), i++;
else if(s[i] == ')') st.pop(), i++;
else {
long long num = 0;
while(i < n && s[i] >= '0' && s[i] <= '9') {
num = num*10+s[i]-'0';
i++;
}
ans += sign*num;
}
}
return ans;
}
};
go
func calculate(s string) int {
st := []int{}
st = append(st, 1)
i, n, ans, sign := 0, len(s), 0, 1
for i < n {
switch s[i] {
case ' ':
i++
case '+':
sign = st[len(st)-1]
i++
case '-':
sign = -st[len(st)-1]
i++
case '(':
st = append(st, sign)
i++
case ')':
st = st[:len(st)-1]
i++
default:
num := 0
for i < n && s[i] >= '0' && s[i] <= '9' {
num = num*10+int(s[i]-'0') //一定要有int()
i++
}
ans += sign*num
}
}
return ans
}
和上一题相似,都属于表达式解析类型的题目,当然这里仅限的符号加减和括号,还没有涉及到乘除运算。两题解法其实很相似,栈中都是保存的每一层的数据,这里保存的是当前层的符号位,上一题保存的是值,感觉对栈的应用层次还是挺低的,就是明确这一题是用栈来实现,但是就是写不出
1475.商品折扣后的最终价格(单调栈)
cpp
class Solution {
public:
vector<int> finalPrices(vector<int>& prices) {
int n = prices.size();
stack<int> st;
vector<int> ans(n);
for(int i = n-1; i >= 0; i--) {
while(!st.empty() && st.top() > prices[i]) st.pop();
ans[i] = st.empty() ? prices[i] : prices[i]-st.top();
st.push(prices[i]);
}
return ans;
}
};
go
func finalPrices(prices []int) []int {
st := []int{}
n := len(prices)
ans := make([]int, n)
for i := n-1; i >= 0; i-- {
for len(st) > 0 && st[len(st)-1] > prices[i] {
st = st[:len(st)-1]
}
if len(st) == 0 {
ans[i] = prices[i]
} else {
ans[i] = prices[i]-st[len(st)-1]
}
st = append(st, prices[i])
}
return ans
}
栈中维护的是右侧第一个小于prices[i]的值,自底向上是递增的一个栈,当栈空时表明当前prices[i]右侧没有小于它的值,每次循环结束后需要将当前prices[i]入栈
496.下一个更大元素I(单调栈、哈希表)
从右往左
cpp
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
int n1 = nums1.size(), n2 = nums2.size();
vector<int> ans(n1, -1); //找不到直接初始化为-1
unordered_map<int, int> hash; //nums1[i] -- i
for(int i = 0; i < n1; i++) {
hash[nums1[i]] = i;
}
stack<int> st;
for(int i = nums2.size()-1; i >= 0; i--) {
int x = nums2[i];
while(!st.empty() && x >= st.top()) st.pop();
if(!st.empty() && hash.contains(x)) ans[hash[x]] = st.top();
st.push(x);
}
return ans;
}
};
go
func nextGreaterElement(nums1 []int, nums2 []int) []int {
n1, n2 := len(nums1), len(nums2)
hash := make(map[int]int)
for i, x := range nums1 {
hash[x] = i
}
st := []int{}
ans := make([]int, n1)
//得把ans数组初始化为-1 否则要在if len(st) > 0处加个else判断
// for i := range ans {
// ans[i] = -1
// }
for i := n2-1; i >= 0; i-- {
x := nums2[i]
for len(st) > 0 && x >= st[len(st)-1] {
st = st[:len(st)-1]
}
if len(st) > 0 {
if j, ok := hash[x]; ok {
ans[j] = st[len(st)-1]
}
} else {
ans[hash[x]] = -1
}
st = append(st, x)
}
return ans
}
过了一天后感觉对单调栈有了新认识,之前做过单调栈的题,但是昨天做的时候忘掉的差不多了,怎么都想不清楚这个单调栈维护的过程。
从左往右
cpp
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
int n1 = nums1.size(), n2 = nums2.size();
vector<int> ans(n1, -1); //找不到直接初始化为-1
unordered_map<int, int> hash; //nums1[i] -- i
for(int i = 0; i < n1; i++) {
hash[nums1[i]] = i;
}
stack<int> st; //记录还没有找到下一个更大元素的栈
for(int x : nums2) {
while(!st.empty() && x > st.top()) {
ans[hash[st.top()]] = x;
st.pop();
}
if(hash.contains(x)) {
st.push(x);
}
}
return ans;
}
};
572.另一棵树的子树(二叉树)---100
cpp
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
if(p == nullptr || q == nullptr) return p == q;
return p->val == q->val && isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
bool isSubtree(TreeNode* root, TreeNode* subRoot) {
if(root == nullptr) return false;
return isSameTree(root, subRoot) || isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);
}
};
go
func isSameTree(p, q *TreeNode) bool {
if p == nil || q == nil {
return p == q
}
return p.Val == q.Val && isSameTree(p.Left, q.Left) && isSameTree(p.Right, q.Right)
}
func isSubtree(root *TreeNode, subRoot *TreeNode) bool {
if root == nil {
return false
}
return isSameTree(root, subRoot) || isSubtree(root.Left, subRoot) || isSubtree(root.Right, subRoot)
}
这一题和100关联了起来,调用了100的函数isSameTree用来判断p,q是否为相同的树,关键就是最后return的或逻辑,我感觉不是很好理解,我的理解是可能只要是root的一个子树满足于subtree相同就行了,所以是或的逻辑
1019.链表中的下一个更大节点
从右往左遍历
cpp
class Solution {
public:
vector<int> nextLargerNodes(ListNode* head) {
vector<int> t;
ListNode* p = head;
while(p) {
t.push_back(p->val);
p = p->next;
}
int n = t.size();
stack<int> st;
vector<int> ans(n);
for(int i = 0; i < n; i++) {
int x = t[i];
while(!st.empty() && x > t[st.top()]) {
ans[st.top()] = x;
st.pop();
}
//if(st.empty()) ans[i] = 0;
st.push(i);
}
return ans;
}
};
从左往右遍历
cpp
class Solution {
public:
vector<int> nextLargerNodes(ListNode* head) {
vector<int> t;
ListNode* p = head;
while(p) {
t.push_back(p->val);
p = p->next;
}
int n = t.size();
stack<int> st;
vector<int> ans(n);
for(int i = 0; i < n; i++) {
int x = t[i];
while(!st.empty() && x > t[st.top()]) {
ans[st.top()] = x;
st.pop();
}
//if(st.empty()) ans[i] = 0;
st.push(i);
}
return ans;
}
};
把问题化简之后就好写很多了!就是先遍历一遍链表,同时将链表的值存入数组t中,这样问题就变成了求数组中当前元素的下一个更大的值了
227.基本计算器II(栈)
cpp
class Solution {
public:
int calculate(string s) {
vector<int> st;
char preSign = '+';
long long num = 0, n = s.size();
for(int i = 0; i < n; i++) {
if(isdigit(s[i])) num = num*10+s[i]-'0';
if(!isdigit(s[i]) && s[i] != ' ' || i == n-1) {
switch(preSign) {
case '+':
st.push_back(num);
break;
case '-':
st.push_back(-num);
break;
case '*':
st.back() *= num;
break;
default:
st.back() /= num;
}
preSign = s[i];
num = 0;
}
}
return reduce(st.begin(), st.end(), 0);
}
};
这种求表达式计算的值主要难点就在于分类讨论加上栈的操作,把这两点处理好就能很好的写出这一类题了
856.括号的分数(栈)
cpp
class Solution {
public:
int scoreOfParentheses(string s) {
stack<int> st;
st.push(0);
for(char c : s) {
if(c == '(') st.push(0);
else {
int t = st.top();
st.pop();
st.top() += max(2*t, 1);
}
}
return st.top();
}
};
go
func scoreOfParentheses(s string) int {
st := []int{}
st = append(st, 0)
for _, c := range s {
if c == '(' {
st = append(st, 0)
} else {
t := st[len(st)-1]
st = st[:len(st)-1]
st[len(st)-1] += max(2*t, 1)
}
}
return st[len(st)-1]
}
真的模拟不出来啊!!!有一种有力使不出的感觉,一个劲的想模拟这个过程,但是就是无论怎么想都想不出来,题解使用一个max函数就将这个括号得分的情况分类讨论出来了,这真的很妙!
1209.删除字符串中的所有相邻重复项II(栈)
cpp
class Solution {
public:
string removeDuplicates(string s, int k) {
stack<int> cnt;
for(int i = 0; i < s.size(); i++) {
if(i == 0 || s[i] != s[i-1]) cnt.push(1);
else if(++cnt.top() == k) {
s.erase(i-k+1, k);
cnt.pop();
i -= k;
}
}
return s;
}
};
用栈来维护上一个字符出现的次数,回顾了一下字符串的操作,删除字符串中的子串。erase(a, b)
,参数a:下标的起始位置,参数b:删除子串的长度。
2211.统计道路上的碰撞次数(脑经急转弯)
cpp
class Solution {
public:
int countCollisions(string directions) {
int cntL = 0, cntR = 0, cntS = 0, n = directions.size();
int i = 0, j = n-1;
if(n == 1) return 0;
while(i < n && directions[i++] == 'L') cntL++;
while(j > 0 && directions[j--] == 'R') cntR++;
//cout << cntL << " " << cntR << " " << count(directions.begin(), directions.end(), 'S');
cntS = count(directions.begin(), directions.end(), 'S');
return n-cntL-cntR-cntS;
}
};
go
func countCollisions(directions string) int {
directions = strings.TrimLeft(directions, "L") //前缀向左的车不会发生碰撞---移除开头L
directions = strings.TrimRight(directions, "R") //后缀向右的车不会发生碰撞---移除末尾R
return len(directions)-strings.Count(directions, "S") //剩下非停止的车必然会碰撞
}
本来说是一道栈的题目,直接给题解区秒杀了,我的天呐,厉害实在是厉害
1046.最后一块石头的重量(堆)
cpp
class Solution {
public:
int lastStoneWeight(vector<int>& stones) {
priority_queue<int> pq;
for(int s : stones) pq.push(s);
while(pq.size() > 1) {
int a = pq.top();
pq.pop();
int b = pq.top();
pq.pop();
if(a > b) pq.push(a-b);
}
return pq.empty() ? 0 : pq.top();
}
};
这个就体现出了选择好的数据结构解题的重要性了。记住记住:优先队列定义默认是大根堆,也就是堆顶保存的是最大的元素,如果要定义小根堆那么需要加上greater<>
默顶大反g
2558.从数量最多的堆取走礼物(堆、模拟)
cpp
class Solution {
public:
long long pickGifts(vector<int>& gifts, int k) {
priority_queue<int> pq(gifts.begin(), gifts.end());
while(k--) {
int t = pq.top();
pq.pop();
pq.push(sqrt(t));
}
long long ans = 0;
while(!pq.empty()) {
ans += pq.top();
pq.pop();
}
return ans;
}
};
首先是要想到这一题要用堆来写,然后就模拟堆的过程就行了,堆和栈队列一样,在遍历的时候不能用迭代的方法来遍历,只能弹出判空来遍历这一类型的数据结构
636.函数的独占时间(栈)
cpp
typedef pair<int, int> PII;
class Solution {
public:
vector<int> exclusiveTime(int n, vector<string>& logs) {
vector<int> ans(n, 0);
stack<PII> st;
for(int i = 0; i < logs.size(); i++) {
vector<string> temp = split(logs[i], ':');
int id = stoi(temp[0]), t1 = stoi(temp[2]);
if(temp[1] == "start") st.push({id, t1});
else {
auto top = st.top();
st.pop();
int t2 = top.second;
ans[top.first] += t1-t2+1;/ans[1]=5-2+1=4
if(!st.empty()) ans[st.top().first] -= t1-t2+1;//ans[0]=6-0-(5-2+1)
}
}
return ans;
}
//手撕split函数
vector<string> split(string& s, char delimiter) {
vector<string> ans;
int left = 0, right = 0;
while(right < s.size()) {
if(s[right] == delimiter) {
ans.push_back(s.substr(left, right-left));
left = right+1;
}
right++;
}
ans.push_back(s.substr(left, right-left));
return ans;
}
};
首先要看懂这一题要有一点操作系统的知识,这样才好理解这一题的意思。这一个题解也是我从评论区中看到的我想学的一个题解,pair的应用和手撕split函数。感觉C++没有split函数就复杂了好多,像python和java都可以直接调用split函数可以简化很多步骤。这一题还有个难点就在于这个时间的计算上要对应这个ans的下标需要巧妙的搭配上。以示例一为计算结果的过程放在了代码旁边的注释上了
go
func exclusiveTime(n int, logs []string) []int {
ans := make([]int, n)
type pair struct{ idx, timestamp int }
st := []pair{}
for _, log := range logs {
sp := strings.Split(log, ":")
idx, _ := strconv.Atoi(sp[0])
timestamp, _ := strconv.Atoi(sp[2])
if sp[1][0] == 's' {
if len(st) > 0 {
ans[st[len(st)-1].idx] += timestamp - st[len(st)-1].timestamp
st[len(st)-1].timestamp = timestamp
}
st = append(st, pair{idx, timestamp})
} else {
p := st[len(st)-1]
st = st[:len(st)-1]
ans[p.idx] += timestamp - p.timestamp + 1
if len(st) > 0 {
st[len(st)-1].timestamp = timestamp + 1
}
}
}
return ans
}
go的计算方法和cpp的计算方法是不一样的,go的计算方法是改变了栈顶的time值,从而来计算ans的,而cpp是直接通过计算来算出ans的,相比来说go的写法更容易发现一些,cpp的计算方法需要发掘出数字之间的关系,想到需要一点时间。
2434.使用机器人打印字典序最小的字符串(栈、哈希表、贪心)
cpp
class Solution {
public:
string robotWithString(string s) {
string ans;
stack<char> st;
int cnt[26]{};
int mn = 0;
for(char c : s) cnt[c-'a']++;
for(char c : s) {
cnt[c-'a']--;
while(mn <= 25 && cnt[mn] == 0) mn++;
st.push(c);
while(!st.empty() && st.top()-'a' <= mn) {
ans += st.top();
st.pop();
}
}
return ans;
}
};
go
func robotWithString(s string) string {
ans := make([]byte, 0, len(s))
st := []byte{}
cnt := [26]int{}
for _, c := range s {
cnt[c-'a']++
}
mn := byte(0)
for _, c := range s {
cnt[c-'a']--
for mn < 26 && cnt[mn] == 0 {
mn++
}
st = append(st, byte(c))
for len(st) > 0 && st[len(st)-1]-'a' <= mn {
ans = append(ans, st[len(st)-1])
st = st[:len(st)-1]
}
}
return string(ans)
}
感觉这个题还是挺难的,如果纯粹的模拟这一题肯定是过不了的,主要是要想到这是一个栈的过程,然后在解题过程中如何展现出"维护剩余字母的最小字母",难点就在于此了。
735.小行星碰撞(栈、邻项消除)
cpp
class Solution {
public:
vector<int> asteroidCollision(vector<int>& asteroids) {
vector<int> st;
for(auto aster : asteroids) {
bool alive = true;
while(alive && aster < 0 && !st.empty() && st.back() > 0) {
alive = aster < -st.back();
if(st.back() <= -aster) st.pop_back();
}
if(alive) st.push_back(aster);
}
return st;
}
};
go
func asteroidCollision(asteroids []int) []int {
st := []int{}
for _, aster := range asteroids {
alive := true
for alive == true && aster < 0 && len(st) > 0 && st[len(st)-1] > 0 {
alive = aster < -st[len(st)-1];
if st[len(st)-1] <= -aster {
st = st[:len(st)-1]
}
}
if alive == true {
st = append(st, aster)
}
}
return st
}
自己写的屎山代码然后还超时,唉,这次官解都写的特别简单,其实感觉自己能明白这种意思,但是写出来就是没那意思。。。
690.员工的重要性(dfs、哈希表)
cpp
class Solution {
public:
int getImportance(vector<Employee*> employees, int id) {
unordered_map<int, Employee*> mp;
for(auto e : employees) mp[e->id] = e;
auto dfs = [&](auto&& dfs, int id) -> int {
auto e = mp[id];
int res = e->importance;
for(auto s : e->subordinates) {
res += dfs(dfs, s);
}
return res;
};
return dfs(dfs, id);
}
};
go
func getImportance(employees []*Employee, id int) int {
mp := make(map[int]*Employee, len(employees))
for _, e := range employees {
mp[e.Id] = e
}
var dfs func(int) int
dfs = func(id int) int {
e := mp[id]
res := e.Importance
for _, subid := range e.Subordinates {
res += dfs(subid)
}
return res
}
return dfs(id)
}
思考这一题的时候点还是到位了,但是写不到位,主要感觉还是dfs不熟练,很明显的一个树结构,加上数据范围不是很大,所以这一题用dfs很合理
2336.无限集中的最小数字(优先队列、有序集合、哈希表)
有序集合写法
cpp
class SmallestInfiniteSet {
public:
int thres = 1;
set<int> s;
SmallestInfiniteSet() {}
int popSmallest() {
if(s.empty()) {
int ans = thres++;
return ans;
}
auto ans = *s.begin();
s.erase(s.begin());
return ans;
}
void addBack(int num) {
if(num < thres) {
s.insert(num);
}
}
};
小根堆+哈希表
cpp
class SmallestInfiniteSet {
public:
vector<bool> vis;
priority_queue<int, vector<int>, greater<>> pq;
int idx;
SmallestInfiniteSet() : idx(1) {
vis.resize(1010, false);
}
int popSmallest() {
if(pq.empty()) {
int ans = idx++;
return ans;
}
int ans = pq.top();
pq.pop();
vis[ans] = false;
return ans;
}
void addBack(int num) {
if(num >= idx || vis[num]) return;
if(num == idx-1) idx--;
else {
pq.push(num);
vis[num] = true;
}
}
};
写这一题的时候有两个困扰我的点:1、如何表示这个无限长的正整数集合;2、对堆进行操作的时候如何进行判重的操作。看了题解之后其实就是这两点是答案的所在。这里用idx边界来表示这个正整数集合,使用小根堆+哈希表来达到去重的操作,在添加元素的时候就进行去重,这样就不用担心集合里出现重复的元素了。
2530.执行k次操作后的最大分数(堆、向上取整)
cpp
class Solution {
public:
long long maxKelements(vector<int>& nums, int k) {
long long ans = 0;
priority_queue<int> pq;
for(auto n : nums) pq.push(n);
while(k--) {
int t = pq.top();
//cout << t << endl;
ans += t;
pq.pop();
t = ceil((double)t/3);
pq.push(t);
}
return ans;
}
};
go
func maxKelements(nums []int, k int) int64 {
var ans int64
h := hp{nums}
heap.Init(&h) //原地堆化
for ; k > 0; k-- {
ans += int64(h.IntSlice[0]) //堆顶
h.IntSlice[0] = (h.IntSlice[0]+2)/3 //对堆顶进行操作
heap.Fix(&h, 0) //调整堆
}
return ans
}
type hp struct{ sort.IntSlice }
func (h hp) Less(i, j int) bool { return h.IntSlice[i] > h.IntSlice[j] } // 最大堆
func (hp) Push(any) {}
func (hp) Pop() (_ any) { return }
熟悉堆操作,就是简单的对堆的插入删除,调整堆的操作
3066.超过阈值的最少操作数II(堆、模拟)---1962、2208、2233
cpp
class Solution {
public:
int minOperations(vector<int>& nums, int k) {
int ans = 0;
priority_queue<long long, vector<long long>, greater<>> pq;
for(auto n :nums) pq.push((long long)n);
while(pq.top() < k) {
long long x = pq.top(); pq.pop();
long long y = pq.top(); pq.pop();
pq.push(2*x+y);
ans++;
}
return ans;
}
};
自己能顺利ac了,但是看了题解发现写的就是屎山代码,看到题解的简化版本就能顿悟的感觉怎么能不佩服!
3144.分割字符频率相等的最少子字符串(动态规划、记忆化搜索)
cpp
class Solution {
public:
int minimumSubstringsInPartition(string s) {
int n = s.size();
vector<int> memo(n, -1);
auto dfs = [&](auto&& dfs, int i) -> int {
if(i < 0) return 0;
int& res = memo[i];
if(res > 0) return res;
res = INT_MAX;
int cnt[26]{}, maxcnt = 0, k = 0;
for(int j = i; j >= 0; j--) {
k += cnt[s[j]-'a']++ == 0;
maxcnt = max(maxcnt, cnt[s[j]-'a']);
if(i-j+1 == k*maxcnt) {
res = min(res, dfs(dfs, j-1)+1);
}
}
return res;
};
return dfs(dfs, n-1);
}
};
转化成递推
cpp
class Solution {
public:
int minimumSubstringsInPartition(string s) {
int n = s.size();
vector<int> dp(n+1, INT_MAX);
dp[0] = 0;
for(int i = 0; i < n; i++) {
int cnt[26]{}, maxcnt = 0, k = 0;
for(int j = i; j >= 0; j--) {
k += cnt[s[j]-'a']++ == 0;
maxcnt = max(maxcnt, cnt[s[j]-'a']);
if(i-j+1 == k*maxcnt) {
dp[i+1] = min(dp[i+1], dp[j]+1);
}
}
}
return dp[n];
}
};
如何理解外层循环是从0开始的呢?可以这样理解,由于我求的是dp[n],所以我最后的结果是dp[n],如果我从后往前遍历的话会导致dp[n]早已赋值完毕了,这样我们求的dp[n]就不是答案了,所以正确的外层遍历顺序是从前往后遍历。
208.实现Trie(前缀树)
cpp
class Trie {
public:
bool isEnd;
Trie* next[26];
Trie() {
isEnd = false;
memset(next, 0, sizeof(next));
}
void insert(string word) {
Trie* node = this;
for(char c : word) {
if(node->next[c-'a'] == NULL) {
node->next[c-'a'] = new Trie();
}
node = node->next[c-'a'];
}
node->isEnd = true;
}
bool search(string word) {
Trie* node = this;
for(char c : word) {
node = node->next[c-'a'];
if(node == NULL) return false;
//node = node->next[c-'a'];
}
return node->isEnd;
}
bool startsWith(string prefix) {
Trie* node = this;
for(char c : prefix) {
node = node->next[c-'a'];
if(node == NULL) return false;
//node = node->next[c-'a'];
}
return true;
}
};
这个东西实现起来感觉有点像多叉树,准确来说应该是26叉树,在写的过程中insert函数我有不同的见解,就是和search一样,将node=node->next[c-'a']放在前面,但是这样是不对的,看样子是将node开辟了一个新空间,但是这里的node是个临时变量,其实就是用来遍历这个树的一个指针,所以在insert函数只能是node = node->next[c-'a'];
放在循环体末尾。
3153.所有数对中数位差之和(枚举右维护左、拆位)
cpp
class Solution {
public:
long long sumDigitDifferences(vector<int>& nums) {
vector<array<int, 10>> cnt(to_string(nums[0]).size()); //内层用vector或者array都行 行表示当前是哪一位 列表示当前这一位是哪个数字
long long ans = 0;
for(int i = 0; i < nums.size(); i++) {
for(int j = 0, x = nums[i]; x > 0; j++, x /= 10) {
ans += i-cnt[j][x%10]++; //找到数量关系i-cnt[j][x%10]很重要 下标i表示前面有i个数
}
}
return ans;
}
};
go
func sumDigitDifferences(nums []int) (ans int64) {
cnt := make([][10]int, len(strconv.Itoa(nums[0])))
for i, x := range nums {
for j := 0; x > 0; x /=10 {
ans += int64(i-cnt[j][x%10])
cnt[j][x%10]++
j++
}
}
return ans
}
做这一题的思路仅限于想到了要竖着看这些数,但是难在不知道如何实现上,关键是这个数组的定义要搞明白,主要是打消了固定的思维方式,这两层for循环并不是对应的cnt数组下标。当然发掘每个数位差之和这个等量关系也很重要,i-cnt[j] [x%10]
14.最长公共前缀(字符串)
cpp
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
string s = strs[0];
for(int i = 0; i < s.size(); i++) {
for(string str : strs) {
if(i == s.size() || s[i] != str[i]) {
return s.substr(0, i); //当下标超过str的长度 或者遇到了不同的字符
}
}
}
return s; //遍历完整个s了 s就是最大的那个公共前缀
}
};
go
func longestCommonPrefix(strs []string) string {
s := strs[0]
for i := range s {
for _, str := range strs {
if i == len(str) || str[i] != s[i] {
return s[:i]
}
}
}
return s
}
从灵神字典树题单中找的,结果是个简单题,我连暴力的写法都想不出,感觉大抵是费了。。。其实感觉和刚刚做过的拆位题很像,也都是要一个一个竖着看,只不过这里需要左对齐竖着看,从这两道题目来看啊,感觉就是熟悉了习惯的遍历方法,突然换一个遍历的方式就感觉很陌生,看来还是需要多适应适应。
3127.构造相同颜色的正方形(举证、枚举)
cpp
class Solution {
public:
bool canMakeSquare(vector<vector<char>>& grid) {
auto check = [&] (int i, int j) -> bool {
int cnt[2]{};
cnt[grid[i][j]&1]++;
cnt[grid[i+1][j]&1]++;
cnt[grid[i][j+1]&1]++;
cnt[grid[i+1][j+1]&1]++;
return cnt[0] != 2;
};
return check(0, 0) || check(0, 1) || check(1, 0) || check(1, 1);
}
};
go
func canMakeSquare(grid [][]byte) bool {
check := func(i, j int) bool {
cnt := make([]int, 2)
cnt[grid[i][j]&1]++
cnt[grid[i][j+1]&1]++
cnt[grid[i+1][j]&1]++
cnt[grid[i+1][j+1]&1]++
return cnt[0] != 2
}
return check(0, 0) || check(0, 1) || check(1, 0) || check(1, 1)
}
简单题简单做,这一题做出来后看题解也是相同的思路,只不过没有注意到的是这是一个3*3的一个举证,所以只需要枚举四个左上角就行了,写两个for循环确实显得比较臃肿
8月总结
求三角形个数
以3128为例,枚举三角形的中间,然后通过乘法原理的数学等式来累加直角三角形的个数ans+=(rowsum-1)*(colsum-1)
,主要是发现这个乘法原理吧,再就是对二维矩阵的操作
计数法和栈的应用
这一用法出现在1021,题目意思是要去除最外层的括号,这里就需要用到计数法,当计数第一次到0就代表第一层原语结束,循环体内的if条件的顺序十分重要!
对称字符串的操作
cpp
//C++
for(int i = 0, n = nums.size(); i < n-1-i; i++) {
swap(nums[i], nums[n-1-i]);
}
//Go
for j, n := 0, len(str); j < n-1-j; j++ {
str[j], str[n-1-j] = str[n-1-j], str[j]
}
发掘变量之间的关系
对于某些题中,当你发现了某些变量之间的等价关系之后解题就会变得非常简单,以2216为例,下标i-cnt
就非常符合题中"删除一个数,其它元素向前移动",这样就动态的表示出来了数组的下标
字符串和数字之间的转化
cpp
//字符串转数字 如果涉及到取模运算,那么在每位累加的时候取模就行
long long num = 0;
while(i < n && s[i] >= '0' && s[i] <= '9') {
num = num*10+s[i]-'0';
i++;
}
ans += sign*num;
//方法二 只适用于小数字,如果字符串非常长,就不适用
stoi("111111")
//数字转字符串
to_string(111111)
单调栈
有两种遍历方式:从左到右和从右到左,对于我自己来说,感觉从右到左遍历好像更好理解一点。总结一下这两天做的单调栈的题,有了全新的认识,这类题型感觉可以算是一种模板。两种遍历方式则栈的定义就会不同,下面是模板代码,以1475为例
cpp
//从右往左遍历
int n = prices.size();
stack<int> st; //栈定义为下一个更...的元素值(或下标)的候选项 需要从后往前遍历
vector<int> ans(n);
for(int i = n-1; i >= 0; i--) {
while(!st.empty() && ...) st.pop();
if(...) ans[i] ...
else ans[i] ...
st.push(...);
}
//从左往右遍历
int n = prices.size();
stack<int> st; //栈定义为还未被使用的元素值(或下标) 需要从前往后遍历
vector<int> ans(n);
for(int i = 0; i < n; i++) {
while(!st.empty() && ...) {
ans[i]...
st.pop();
}
if(...) ...
st.push(...);
}
一般来说从右往左遍历栈定义成元素值,从左往右遍历栈定义成下标
省略号的逻辑就是因题而异了,大致模板就是这样,只要将省略号的逻辑写对了这个题就写对了
手撕split函数
cpp
vector<string> split(string& s, char delimiter) {
vector<string> ans;
int left = 0, right = 0;
while(right < s.size()) {
if(s[right] == delimiter) {
ans.push_back(s.substr(left, right-left));
left = right+1;
}
right++;
}
ans.push_back(s.substr(left, right-left));//首尾操作
return ans;
}
去重的思路
哈希表(记忆化搜索,数组去重、堆去重)、set容器
向上取整的应用(ceil函数、+k-1)
cpp
cout << ceil(2.3); //3
cout << ceil(11/3); //3
cout << ceil()(double)11/3); //4
在调用这个函数的时候只需要注意一个点,那就是括号里面的数一定要是浮点数,以第二个式子为例,先计算10/3=3,然后再将3调入到ceil函数,那么就变成了ceil(3),自然就会输出3了而不是4,改正的方法是ceil((double)10/3)
还有一种写法就是在除k之前将val先加上k-1,这样也能达到向上取整的效果
cpp
cout << (11+2)/3; //4
前缀树、字典树(trie)模板---可以说成26叉树
cpp
class Trie {
public:
bool isEnd;
Trie* next[26];//或者vector<Trie*> node;这样定义
//Trie() : isEnd(false), node(26, null) {}或者这样初始化
Trie() {
isEnd = false;
memset(next, 0, sizeof(next));
}
void insert(string word) {
Trie* node = this;
for(char c : word) {
if(node->next[c-'a'] == NULL) {
node->next[c-'a'] = new Trie();
}
node = node->next[c-'a'];
}
node->isEnd = true;
}
bool search(string word) {
Trie* node = this;
for(char c : word) {
node = node->next[c-'a'];
if(node == NULL) return false;
//node = node->next[c-'a'];
}
return node->isEnd;
}
bool startsWith(string prefix) {
Trie* node = this;
for(char c : prefix) {
node = node->next[c-'a'];
if(node == NULL) return false;
//node = node->next[c-'a'];
}
return true;
}
};