刷题笔记—8月
LCP40.心算挑战(贪心、排序)
class Solution {
public:int maxmiumScore(vector<int>& cards, int cnt) {//24.8.1ranges::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.验证栈序列(栈、模拟)
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();}
};
func validateStackSequences(pushed []int, popped []int) bool {st := []int{}j := 0for _, 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.直角三角形(数学)
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;}
};
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 := 0for _, 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.删除最外层的括号(栈、计数法)
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;}
};
func removeOuterParentheses(s string) string {level := 0ans := []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.反转每对括号间的子串(栈)
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;}
};
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.检查替换后的词是否有效(栈)
自己写的版本
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这一题了
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}
}
灵神版本
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.美化数组的最少删除数
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;}
};
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.笨阶乘(栈)
class Solution {
public:int clumsy(int n) {//24.8.3stack<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;}
};
func clumsy(n int) int {st := []int{}st = append(st, n)n--index := 0for 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 := 0for len(st) > 0 {sum += st[len(st)-1]st = st[:len(st)-1]}return sum
}
栈的灵活运用,思考的时候想到了利用取余的方式判断乘除加减,就是对栈操作上还是有很多漏洞,这一题的思路就是遇到乘除就将栈顶元素进行运算,遇到加减就将n或-n入栈就行,唉,主要是没想到这一些栈操作。
224.基本计算器(栈)
class Solution {
public:int calculate(string s) {//24.8.3stack<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;}
};
func calculate(s string) int {st := []int{}st = append(st, 1)i, n, ans, sign := 0, len(s), 0, 1for 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 := 0for i < n && s[i] >= '0' && s[i] <= '9' {num = num*10+int(s[i]-'0') //一定要有int()i++}ans += sign*num}}return ans
}
和上一题相似,都属于表达式解析类型的题目,当然这里仅限的符号加减和括号,还没有涉及到乘除运算。两题解法其实很相似,栈中都是保存的每一层的数据,这里保存的是当前层的符号位,上一题保存的是值,感觉对栈的应用层次还是挺低的,就是明确这一题是用栈来实现,但是就是写不出
1475.商品折扣后的最终价格(单调栈)
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;}
};
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(单调栈、哈希表)
从右往左
class Solution {
public:vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {int n1 = nums1.size(), n2 = nums2.size();vector<int> ans(n1, -1); //找不到直接初始化为-1unordered_map<int, int> hash; //nums1[i] -- ifor(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;}
};
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
}
过了一天后感觉对单调栈有了新认识,之前做过单调栈的题,但是昨天做的时候忘掉的差不多了,怎么都想不清楚这个单调栈维护的过程。
从左往右
class Solution {
public:vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {int n1 = nums1.size(), n2 = nums2.size();vector<int> ans(n1, -1); //找不到直接初始化为-1unordered_map<int, int> hash; //nums1[i] -- ifor(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
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);}
};
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.链表中的下一个更大节点
从右往左遍历
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;}
};
从左往右遍历
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(栈)
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.括号的分数(栈)
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();}
};
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(栈)
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.统计道路上的碰撞次数(脑经急转弯)
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;}
};
func countCollisions(directions string) int {directions = strings.TrimLeft(directions, "L") //前缀向左的车不会发生碰撞---移除开头Ldirections = strings.TrimRight(directions, "R") //后缀向右的车不会发生碰撞---移除末尾Rreturn len(directions)-strings.Count(directions, "S") //剩下非停止的车必然会碰撞
}
本来说是一道栈的题目,直接给题解区秒杀了,我的天呐,厉害实在是厉害
1046.最后一块石头的重量(堆)
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.从数量最多的堆取走礼物(堆、模拟)
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.函数的独占时间(栈)
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=4if(!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的下标需要巧妙的搭配上。以示例一为计算结果的过程放在了代码旁边的注释上了
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].timestampst[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 + 1if len(st) > 0 {st[len(st)-1].timestamp = timestamp + 1}}}return ans
}
go的计算方法和cpp的计算方法是不一样的,go的计算方法是改变了栈顶的time值,从而来计算ans的,而cpp是直接通过计算来算出ans的,相比来说go的写法更容易发现一些,cpp的计算方法需要发掘出数字之间的关系,想到需要一点时间。
2434.使用机器人打印字典序最小的字符串(栈、哈希表、贪心)
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;}
};
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.小行星碰撞(栈、邻项消除)
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;}};
func asteroidCollision(asteroids []int) []int {st := []int{}for _, aster := range asteroids {alive := truefor 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、哈希表)
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);}
};
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) intdfs = func(id int) int {e := mp[id]res := e.Importancefor _, subid := range e.Subordinates {res += dfs(subid)}return res}return dfs(id)
}
思考这一题的时候点还是到位了,但是写不到位,主要感觉还是dfs不熟练,很明显的一个树结构,加上数据范围不是很大,所以这一题用dfs很合理
2336.无限集中的最小数字(优先队列、有序集合、哈希表)
有序集合写法
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);}}
};
小根堆+哈希表
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次操作后的最大分数(堆、向上取整)
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;}
};
func maxKelements(nums []int, k int) int64 {var ans int64h := 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
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.分割字符频率相等的最少子字符串(动态规划、记忆化搜索)
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);}
};
转化成递推
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(前缀树)
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.所有数对中数位差之和(枚举右维护左、拆位)
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;}
};
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.最长公共前缀(字符串)
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就是最大的那个公共前缀}
};
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.构造相同颜色的正方形(举证、枚举)
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);}
};
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条件的顺序十分重要!
对称字符串的操作
//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
就非常符合题中“删除一个数,其它元素向前移动”,这样就动态的表示出来了数组的下标
字符串和数字之间的转化
//字符串转数字 如果涉及到取模运算,那么在每位累加的时候取模就行
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为例
//从右往左遍历
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函数
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)
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,这样也能达到向上取整的效果
cout << (11+2)/3; //4
前缀树、字典树(trie)模板—可以说成26叉树
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;}
};