整体战略
学习新知识,掌握思路独立编写出一种代码就ok,不要钻进去非得把代码随想录中的所有思路都啃下来编写出来,节约时间掌握知识最重要。 对于章节末端难度比较大的题目,只要整体思路OK,能独立完成一套解决问题的代码就很好,其他的思路了解即可,重在对于知识的掌握,题目就是来加深对于知识的理解的,不要陷入到做题的弯弯绕绕技巧中去,这些可以放在二刷巩固提高的时候搞。
涉及动态规划的代码讲解先跳过,等到下一章节自然就会了。
9.17日任务:分发饼干
方法一:
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
//贪心算法第一道题
//整体感受,和回溯法相近的地方就是,先分析题目找到解决问题的思路,然后用代码去实现思路就可以了。
//本质上代码不是问题,重要的是思路。
//贪心算法,核心理论就是通过不断寻找局部最优最后堆叠成全局最优
//我的思路,小饼干首先满足胃口小的孩子,如果大饼干满足胃口小的孩子,那么小饼干肯定是浪费了;或者大饼干优先满足胃口大的孩子,这样的话每个饼干都能找到合适归宿
//1.尝试小饼干满足胃口小的孩子
//先从小到大排序
sort(g.begin(), g.end());
sort(s.begin(), s.end());
int gIndex{0};
for (int i = 0; i < s.size(); i++) {
if (s[i] >= g[gIndex]) gIndex++;
if (gIndex == g.size()) break;
}
return gIndex;
}
};
方法二:
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
//贪心算法第一道题
//整体感受,和回溯法相近的地方就是,先分析题目找到解决问题的思路,然后用代码去实现思路就可以了。
//本质上代码不是问题,重要的是思路。
//贪心算法,核心理论就是通过不断寻找局部最优最后堆叠成全局最优
//我的思路,小饼干首先满足胃口小的孩子,如果大饼干满足胃口小的孩子,那么小饼干肯定是浪费了;或者大饼干优先满足胃口大的孩子,这样的话每个饼干都能找到合适归宿
//1.尝试小饼干满足胃口小的孩子
//大孩子找大饼干
if (s.size() == 0) return 0;
sort(g.begin(), g.end());
sort(s.begin(), s.end());
int result{0};
int sIndex = s.size() - 1;
for (int i = g.size() - 1; i >= 0; i--) {
if (s[sIndex] >= g[i]) {
result++;
sIndex--;
}
if (sIndex < 0) break;
}
return result;
}
};
方法一改良:非常简洁
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
//贪心算法第一道题
//整体感受,和回溯法相近的地方就是,先分析题目找到解决问题的思路,然后用代码去实现思路就可以了。
//本质上代码不是问题,重要的是思路。
//贪心算法,核心理论就是通过不断寻找局部最优最后堆叠成全局最优
//我的思路,小饼干首先满足胃口小的孩子,如果大饼干满足胃口小的孩子,那么小饼干肯定是浪费了;或者大饼干优先满足胃口大的孩子,这样的话每个饼干都能找到合适归宿
//1.尝试小饼干满足胃口小的孩子
//先从小到大排序
sort(g.begin(), g.end());
sort(s.begin(), s.end());
int gIndex{0};
for (int i = 0; i < s.size() && gIndex < g.size(); i++) {
if (s[i] >= g[gIndex]) gIndex++;
}
return gIndex;
}
};
9.18-9.19日任务:摆动序列
9.18日状态很差,没有进度。
调整:9.19日开始,贪心算法和动态规划交替刷,刷完就是图论和单调栈,后面的题目难度相对就比较高了。
可以先浅浅思考一下,再去看代码随想录解析,然后自己再理清楚,之后写代码,问题越是难的时候越不要囫囵吞枣,囫囵吞枣最后可能就变成纯纯背题,对于自己没有任何提升,必须始终保持自己思维的独立性,实事求是,加油。
看了代码随想录贪心思路,独立编写程序,并没有完全独立编写,这道题目很难想,最后的代码虽然很简单,但是中间的思路逻辑梳理起来非常麻烦难想,属于那种比较难想的题目,我倾向于半理解,半背记。
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
//看了代码随想录的贪心算法思路,独立编写代码
//贪心算法,通过搜寻局部最优值,最终搜索到全局最优值,如果生活中找不到其他反例,则可以尝试贪心算法思路
//基础思路:
//情况一:直接找波峰波谷再加上前后两端的元素就ok,这是不存在平坡的情况,代码考虑起来也最简单
//情况二:考虑平坡,有上升再平坡再下降这种波动的平坡,也有在单调坡中出现平坡的情况。
//怎么说呢?这种题目不好想,看了参考思路也只能跟着参考思路走,只能说体会一部分,背一部分,慢慢体会吧,整体思路是代码随想录给出的参考思路
//题目给出nums的长度大于等于1,不考虑空数组的情况
if (nums.size() <= 1) return nums.size();
int result = 1;//默认最右侧节点要加上
int prediff = 0;//前一对差值为0
int curdiff = 0;
for (int i = 1; i < nums.size(); i++) {
curdiff = nums[i] - nums[i - 1];
if (prediff <= 0 && curdiff > 0 || prediff >= 0 && curdiff < 0) {
result++;
prediff = curdiff;
}
}
return result;
}
};
第二种方法用的是动态规划,因为还没有刷到动态规划,理解不足,先搁置,后面理解更深入之后再来回看。
针对第一种解法的贪心思路:

9.20日任务:最大子序和
思路跟随代码随想录的思路,非常有趣精简。解释起来不太好描述,二刷的时候想不出来就去看代码随想录的参考思路吧。
根据代码随想录的思路独立编写代码,耗时半个多小时,出了点问题在visualstudio上debug解决了。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
//看了代码随想录的思路,独立编写
//大概思路描述:搜索的逻辑和暴力解法的搜索逻辑是一致的,不过不需要像暴力搜索那样把每一种情况都进行记录比对
//一个子序列,从前往后数,第一个数如果是负值,那这个数已经不能作为最大和的起点;同样的,如果一段数的和为负值,那么这段数已经不适合作为最大和数组的前半部分
int result = INT_MIN;
for (int i = 0; i < nums.size(); i++) {
//先考虑处理第一个数是负值的情况
if (nums[i] < 0) {
result = result >= nums[i] ? result : nums[i];
continue;
}
//再考虑不是负值的情况
int tempSum = 0;
for (int j = i; j < nums.size(); j++) {
tempSum += nums[j];
//考虑一小段序列和为负数的情况
if (tempSum < 0) {
i = j;
break;
}
else result = result >= tempSum ? result : tempSum;
if (j == nums.size() - 1) return result;
}
}
return result;
}
};
我的代码是双层循环,复杂度不好看
代码随想录给出的是单层循环,尝试自己编写单层循环.
这个单层循环其实和我的双层循环执行过程是一致的,但是这个单层循环对于问题的理解就更为透彻了,值得二刷,思路非常巧妙。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
//看了代码随想录的思路,独立编写
//尝试用单层循环解
int result = INT_MIN;
int temp = 0;
for (int i = 0; i < nums.size(); i++) {
temp += nums[i];
if (temp > result) result = temp;
if (temp < 0) temp = 0;
}
return result;
}
};
9.20日第二道题:买卖股票的最佳时机 II
利润分解,很睿智

class Solution {
public:
int maxProfit(vector<int>& prices) {
//代码随想录的利润分解思路非常睿智聪明
//P[3]-P[1] = P[3] - P[2] + P[2] - P[1] + P[1] - P[0]
//最后最大利润就是把每天是正利润的利润加起来
int result = 0;
for (int i = 1; i < prices.size(); i++) {
int dayprofit = prices[i] - prices[i - 1];
if (dayprofit > 0) result += dayprofit;
}
return result;
}
};
9.21日任务:跳跃游戏
跟着代码随想录的思路走,思路很巧妙,像脑筋急转弯似的,代码编写部分不是问题,这些题目全当是扩展思路了
class Solution {
public:
bool canJump(vector<int>& nums) {
//我的思路是错误的,现在按照代码随想录的思路来
int post = 0;
for(int i = 0; i < nums.size() && i <= post; i++) {
if (i + nums[i] > post) post = i + nums[i];
if (post >= nums.size() - 1) return true;
}
return false;
}
};
9.21日第二题: 跳跃游戏II
按照自己的思路独立编写完成,有了上一题的铺垫,这一题就比较好想出来了,这部分题目代码实现没有什么难度,主要都是思路上的一些东西
class Solution {
public:
int jump(vector<int>& nums) {
//这个按照我的思路来
//我跳的位置取决于该位置相比其他可以跳到的位置能够覆盖更远的区域,这就保证了我可以一直跳到最远的地方,最快到达终点位置
//题目保证可以达到nums[n-1],所以一定可以到达。
int jumpstart = 0;
int result = 0;
while (jumpstart < nums.size() - 1) {
//倘若一步可以跳到终点的话,就不用再考虑跳到哪个点覆盖范围更广的问题了
if (jumpstart + nums[jumpstart] >= nums.size() - 1) {
result++;
break;
}
//倘若一步无法跳到终点,就要考虑跳到哪个点后面覆盖的范围更广。
//记录可以覆盖的最远距离
int post = 0;
//记录下一步应该跳跃几步
int maxOffset = 0;
for (int i = 1; i <= nums[jumpstart] && i < nums.size() - jumpstart; i++) {
if (i + nums[jumpstart + i] > post) {
post = i + nums[jumpstart + i];
maxOffset = i;
}
}
//记录该跳到哪一个点
jumpstart += maxOffset;
result++;
}
return result;
}
};
9.21日第三题:K 次取反后最大化的数组和
出现了一些特殊的问题:
**仿函数中使用>=的时候出现了内存越界的问题,暂时还不知道什么原因。**在优快云中找到了问题答案,涉及到了一些底层代码,我暂时还没有解决这个问题的能力,总之以后要注意不要再尝试用等于(=)号了。
按照代码随想录自行编写
class Solution {
public:
//定义排序仿函数
static bool absoluteValueSort(int a, int b) {
if (abs(a) > abs(b)) return true;
else return false;
}
int largestSumAfterKNegations(vector<int>& nums, int k) {
//k次取反后最大化的数组和
//按照代码随想录的思路,首先把所有负数从绝对值最大的负数开始翻成正的,全部翻成正的之后,如果K还大于0,那么一直翻转最小的那个数
//第一步:按绝对值大小进行排序(由大到小),遍历将负值翻成正值,直到K用完
int result = 0;
sort(nums.begin(), nums.end(), absoluteValueSort);
for (int i = 0; i < nums.size() && k > 0; i++) {
if (nums[i] < 0) {
nums[i] = -nums[i];
k--;
}
}
if (k == 0) {
for (int i = 0; i < nums.size(); i++) {
result += nums[i];
}
} //直接求和
else {
sort(nums.begin(), nums.end());
while (k--) {
nums[0] = -nums[0];
}
for (int i = 0; i < nums.size(); i++) {
result += nums[i];
}
}
return result;
}
};
上面有点繁琐了,简化版
这里就是代码随想录的写法,太简洁了:
1.仿函数写法巨简洁
2.绝对值从大到小排序之后,不用再重新排序,因为全部变为非负数之后,末尾的数就是最小值
3.k%2 == 1 在思路上更精简了
class Solution {
public:
//定义排序仿函数
static bool absoluteValueSort(int a, int b) {
return abs(a) > abs(b);
}
int largestSumAfterKNegations(vector<int>& nums, int k) {
//k次取反后最大化的数组和
//按照代码随想录的思路,首先把所有负数从绝对值最大的负数开始翻成正的,全部翻成正的之后,如果K还大于0,那么一直翻转最小的那个数
//第一步:按绝对值大小进行排序(由大到小),遍历将负值翻成正值,直到K用完
int result = 0;
sort(nums.begin(), nums.end(), absoluteValueSort);
for (int i = 0; i < nums.size() && k > 0; i++) {
if (nums[i] < 0) {
nums[i] = -nums[i];
k--;
}
}
if (k % 2 == 1) nums[nums.size() - 1] *= -1;
for (int value : nums) result += value;
return result;
}
};
9月22日:加油站
跟着代码随想录的思路来,思路还是非常巧妙的。
思路比较绕,思路二更容易理解,思路一略难理解,但是看完代码还是可以理解,属于不通用但是挺开阔思路的一个方法。
第一种解法是暴力解法,不过运行过程中有一部分测试样例会超时,其实就是时间复杂度比较大。
直接按照代码随想录给出的贪心思路(二),根据想法独立编写代码
思路难点:如何理解gas[i] - cost[i]?
解:gas[i] - cost[i] > 0,说明如果将i站作为始发站,从i站到达i+1站之后还能剩下油;gas[i] - cost[i] = 0,说明如果将i站作为始发站,从i站到达i+1站之后刚好油用完;gas[i] - cost[i] < 0,说明如果将i站作为始发站,无法到达下一站。
下面代码的实现思路是:先找出可能的始发点,然后再确认该始发点是否能够真正完成一个轮回。
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
//自己首先想到的是暴力解法,暴力解法的细节描述下来就是:从第一个gas>cost的点开始,一直累加直到能够回到起始点
//但是复杂度比较高
//这里独立编写代码随想录的第二种方法,贪心方法
int resultIndex = 0;
int tempSum = 0;
for (int i = 0; i < gas.size(); i++) {
tempSum += gas[i] - cost[i];
if (tempSum < 0) {
tempSum = 0;
resultIndex = i + 1;
}
}
//上面代码确定出了可能存在的解的位置,下面是去验证这个解是否合理存在
tempSum = 0;
for (int i = resultIndex; i < gas.size() + resultIndex; i++) {
tempSum += gas[i % gas.size()] - cost[i % gas.size()];
if (tempSum < 0) return -1;
}
return resultIndex;
}
};
下面是代码随想录的写法,totalSum和tempSum放在一块,对这个问题的理解就更加深入了,非常巧妙。
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int resultIndex = 0;
int tempSum = 0;
int totalSum = 0;
for (int i = 0; i < gas.size(); i++) {
tempSum += gas[i] - cost[i];
totalSum += gas[i] - cost[i];
if (tempSum < 0) {
tempSum = 0;
resultIndex = i + 1;
}
}
//上面代码确定出了可能存在的解的位置,下面是去验证这个解是否合理存在
if (totalSum < 0) return -1;
return resultIndex;
}
};
9.23日任务:分发糖果
根据代码随想录的思路独立编写,理解起来有点复杂,我的代码注释里的解释到时候可以帮我回忆一下。
class Solution {
public:
int candy(vector<int>& ratings) {
//这是一道困难题目,全部按照代码随想录的思路来
//第一步,从左向右遍历,先确保当右孩子的评分高于左孩子,那么右孩子的糖果数得高于左孩子
vector<int> candyNumber(ratings.size(), 0);
candyNumber[0] = 1;
for (int i = 1; i < ratings.size(); i++) {
if (ratings[i] > ratings[i - 1]) candyNumber[i] = candyNumber[i - 1] + 1;
else candyNumber[i] = 1;
}
//经过了上面的代码,这下只要右边的孩子比左边孩子评分更高,那么右边孩子糖果数一定比左边孩子更高。
//但是上面代码的遍历对哪些candyNumber置1了呢?即rating[i]<=rating[i - 1];
//接下来需要解决左边孩子的评分大于等于右边孩子的情况(即右边孩子评分小于等于左边孩子的情况)(等于是无所谓的,大于小于是有所谓的)
//接下来解决左边比右边大的情况,从后往前遍历.为什么从后往前遍历呢?因为你上面的遍历决定了右孩子评分高,右孩子糖果多。从右向左遍历过程中,可能某个左孩子评分大于右孩子,但同时也大于左左孩子,这个时候这个左孩子可能存在糖果数+1的情况,这个糖果增多不会改变依然比左左孩子多的格局。但是你从左往右遍历的话,可能某个左孩子评分大于右孩子,但同时小于左左孩子评分,这个时候这个左孩子的糖果数加一可能会导致糖果数大于等于左左孩子的情况,这样子平衡就被破环了。
for (int i = ratings.size() - 2; i >= 0; i--) {
if (ratings[i] > ratings[i + 1]) candyNumber[i] = max(candyNumber[i], candyNumber[i + 1] + 1);
}
int resultSum = 0;
for (int value:candyNumber) {
resultSum += value;
}
return resultSum;
}
};
代码随想录中定义candyNumber数组的时候就全部置1了,而我置的0,后面遍历还涉及到幅值为1,反倒代码运
行时间变长了,还是对于C++的性能理解有点差了。
9.24日任务:柠檬水找零
按照自己的思路独立编写。记录当前手里5元的数量和10元的数量,用到贪心的部分就是当收到的钱是20元时,优先考虑用一张10元和一张5元找零,因为无论收到多少钱,5元都可以用来找零,但是用到10元机会比较少,这一步算是有点贪心算法的感觉。
class Solution {
public:
bool lemonadeChange(vector<int>& bills) {
//首先按照自己的10元5元各有多少张?20块钱不用算。若给的钱没有足够的10元,5元结账,应该就结束了
int fiveNum = 0;
int tenNum = 0;
for (int i = 0; i < bills.size(); i++){
if (bills[i] == 5) fiveNum++;
if (bills[i] == 10) {
if (fiveNum == 0) return false;
else fiveNum -= 1;
tenNum++;
}
//上面两种情况只需要考虑5元钱就可以了
//下面这种收到20元,既可以用3个5元找零,也可以一个10一个5找零,我觉得贪心的话就先把10元找零,多留5元,剩下全是5元的话都可以找零,但是剩下全是10元的话就会有局限性
if (bills[i] == 20) {
//有10元就找10元的
if (tenNum > 0) {
if (fiveNum > 0) {
fiveNum -= 1;
tenNum -= 1;
continue;
}
else return false;
}
else {
if (fiveNum / 3 >= 1) {
fiveNum -= 3;
continue;
}
else return false;
}
}
}
return true;
}
};
9月24日第二题:根据身高重建队列
根据代码随想录的思路自行编写代码,思路很巧妙,但不好想,当作拓展思路的题目了
class Solution {
public:
static bool compareFun(vector<int>& a, vector<int>& b) {
if (a[0] > b[0]) return true;
else if (a[0] == b[0]) {
if (a[1] < b[1]) return true;
}
return false;
}
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
//思考了一小会,感觉还是非常有难度的,完全没有的思路
//直接去看代码随想录的思路吧
//局部最优,进而推导出全局最优
//我还是完全没思路,还是看代码随想录的思路吧
//代码随想录提示该题和分发糖果那道题很像,两个维度先试着排序确定一个维度,之后再尝试确定另外一个维度,分开来看,而不是两个一起想,这样会顾此失彼
//看完代码随想录的思路了,先按照身高进行排列,身高相同则谁的k值大谁排在后面(这个很好想),接下来按照进行插队操作,只要根据k值插入到对应索引位置就好了(从前往后遍历),为什么从前往后遍历,假如前面的大数值插入到索引3这个位置,轮到后面小的数值插入的时候,无论是插入到索引3之前还是之后,都不会破坏前面的大数值的状态。但是从后往前遍历的话,则会打乱这种状态。思路很新颖,很难想,全当时拓展思维了。
//接下来自行编写代码
sort(people.begin(), people.end(), compareFun);
for (int i = 0; i < people.size(); i++) {
if (people[i][1] != i) {
vector<int> tempVec = people[i];
people.erase(people.begin() + i);
people.insert(people.begin() + tempVec[1], tempVec);
}
}
return people;
}
};
代码随想录的写法:仿函数写的非常简洁,我还得继续加深学习。
static bool compareFun(vector<int>& a, vector<int>& b) {
if (a[0] == b[0]) return a[1] < b[1];
return a[0] > b[0];
}
代码随想录使用了另一种插入方法,总之就是新建了数组。另外代码随想录还使用了链表这个容器。
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
sort (people.begin(), people.end(), cmp);
vector<vector<int>> que;
for (int i = 0; i < people.size(); i++) {
int position = people[i][1];
que.insert(que.begin() + position, people[i]);
}
return que;
}
9.24日反思
最近科研进度有些迷茫,科研老是进不去状态。搞科研坐在椅子上20分钟就痛不欲生,如坐针毡,刷题一个小时都能坐的住。本来计划每天固定时间点刷一道题,现在科研搞不进去,脑袋昏昏就想刷个题,一刷可能就刷多了,刷完题还是脑袋昏昏,甚至还把前面科研的思绪打断了,总之科研完全进不去状态。
试图调整:停止刷题一星期。把清醒的时间全部压给科研,状态再差也要硬磨,状态差的时候,连续10个小时总能注意力集中2-3个小时,磨蹭3个小时总能在第四个小时进入一会状态。如果安排刷题时间,磨蹭昏沉的时候总想着去搞简单的刷题,这样会打断科研状态,思考没法持续,是一种不由自主的逃避行为。当前搞科研毕业才是重中之重。
当科研有思路,反馈和进展相对顺利的时候,平衡科研,刷题以及其他事情是一件相对比较简单的事情,但是科研遇到瓶颈迷茫,停滞不前时,这种平衡就会出现失衡,需要及时调整。
先停止刷题一星期,把科研状态找回来。
一直坚持不见得是一件很合理明智的事情,学会调整,学会适度才是更要追求的。
目前不足存档:
1.C++各种容器的使用并不熟练,刷完题之后要刻意攻坚。
2.复杂度知识点需要补充,目前还没有投入时间。
3.八股的课程简单扫了一遍,具体的东西刷完题目之后集中攻坚。
停下脚步,10月1日之前把状态找回来。
9.25日任务:根据身高重建队列(续集)
简单分析了一下时间复杂度,时间复杂度课本上讲解的太简单了,真正实践还是要独立分析自己写的代码。后边的代码要结合GPT分析时间复杂度,不要贪图题目做的多,后面把时间复杂度的训练提上来。
科研状态今天恢复了,有些时候就是这么快,只花了一天。可能的原因:1.从一段小的感情困惑中清醒了。2.昨天导师说我该进行一次深入汇报了。外部压力互相影响,我活过来了。
9.26日任务:用最少数量的箭引爆气球
自己思考了一会,再结合代码随想录的思路独立编写代码,写法和参考代码不同,但是思路是一致的。
第一步:以线段的起始点自小到大排序。
第二步:判断是否交叉重叠。这里最最重要,最最容易忽略的就是必须不断更新重叠部分的最小右边界,考虑到这一点这道题就算是拿下了。
class Solution {
public:
static bool compare(vector<int>& a, vector<int>& b) {
return a[0] < b[0];
}
int findMinArrowShots(vector<vector<int>>& points) {
//我的思路,观察是否重叠。
//具体步骤:1.以气球直径第一个元素进行排序(排序算法复杂度为nlogn) 2.排序好之后判断前一个气球的第二个元素是否大于等于后一个气球的第一个元素,如果可以,就可以一块射掉
//先排序
sort(points.begin(), points.end(), compare);
//再遍历比较
int result = 0;
for (int i = 0; i < points.size(); i++) {
int tempcount = 0;
//需要不断更新最小右边界
int minRightEdge = points[i][1];
while(i + 1 + tempcount< points.size() && minRightEdge >= points[i + 1 + tempcount][0]) {
//更新最小右边界
minRightEdge = min(minRightEdge, points[i + 1 + tempcount][1]);
tempcount++;
}
//循环完之后,tempcount>0则说明可以一次射多个,tempcount=0则说明没有重合气球,一次只能射一个
//两种情况result都+1.
result++;
i += tempcount;
}
return result;
}
};
后期帮助自己回忆

9月27日任务:无重叠区间
第一遍掌握一种写法即可,二刷再丰富思路。
按照自己的思路独立编写代码:
1.先排序
2.如果重叠那么就把右边界更远的那条线段删除掉(同时result+1),不重叠就把右边界更新为新线段的右边界,贪心算法。
class Solution {
public:
static bool compare(vector<int>& a, vector<int>& b) {
return a[0] < b[0];
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
sort(intervals.begin(),intervals.end(),compare);
int result = 0;
int minRightEdge = intervals[0][1];
for (int i = 1; i < intervals.size(); i++) {
if (minRightEdge > intervals[i][0]) {
if (minRightEdge > intervals[i][1]) minRightEdge = intervals[i][1];
result++;
}
else minRightEdge = intervals[i][1];
}
return result;
}
};
我的思路简单画个图:二刷时能想起来就直接写,想不起来就取看代码随想录的解析。


9月28日任务:划分字母区间
首先是按照自己的思路编写:
我的思路比较绕:
第一步:统计整个字符串每个字符的数量
第二步:遍历字符串,如果一个字符数量大于1,就把他压入到unordered_set容器中,同时该字符剩余数量减1,等他剩余数量为0的时候,就把他从容器中剔除。
核心思想:遍历过的字符以及这些字符的剩余数量都会被存入到容器中,字符剩余数量为0则会被移除容器。在这个过程中当unordered_set容器为空的时候,说明对于前面遍历过的字符,在后边的字符串中应该不会再出现了,这是就应该是分界点。
class Solution {
public:
vector<int> partitionLabels(string s) {
//尝试自己分析几分钟,按照我的思路的话:
//第一步:统计每个字母的数量(这个可以用长度为26的数组表示)
//第二步:进行遍历,遍历一个字母则把字母压入到一个容器中,等到这个字母数量为0,容器就把这个字母弹出,等到容器为空的时候,就是第一段字符串。所以多了一个问题就是这个字符串要压入到什么容器中呢?
//先完成第一步统计:
int stringCount[26] = {0};
for (int i = 0; i < s.size(); i++) {
stringCount[s[i] - 'a']++;
}
unordered_set<int> temp;
vector<int> result;
int tempCount = 0;
for (int i = 0; i < s.size(); i++) {
tempCount++;
stringCount[s[i] - 'a']--;
//还剩下余量则放入unordered_set中去
if (stringCount[s[i] - 'a'] > 0 && temp.find(s[i] - 'a') == temp.end()) {
temp.insert(s[i] - 'a');
}
else if (stringCount[s[i] - 'a'] == 0) {
if (temp.find(s[i] - 'a') != temp.end()) temp.erase(s[i] - 'a');
if (temp.empty()) {
result.push_back(tempCount);
tempCount = 0;
}
}
}
return result;
}
};
接下来是代码随想录的思路:
核心思路:
第一步:记录每一个字符最后一次出现的最远位置
第二步:遍历,记录遍历过的所有字符的最远位置,当遍历下标和这个最远位置一致时,说明已经达到了遍历过的所有字符的最远位置,说明遍历过的所有字符不会再在后面出现了,这是就是合理的分割点。
class Solution {
public:
vector<int> partitionLabels(string s) {
//接下来是代码随想录的思路
//记录一个字符最后一次出现的位置下标
vector<int> result;
vector<int> stringMaxIndex(26, 0);
//统计一个字符最后一次出现的位置下标
for (int i = 0; i < s.size(); i++) {
stringMaxIndex[s[i] - 'a'] = i;
}
int TraversaledMaxLoc = INT_MIN;
int tempCount = 0;
for (int i = 0; i < s.size(); i++) {
tempCount++;
TraversaledMaxLoc = max(TraversaledMaxLoc,stringMaxIndex[s[i] - 'a']);
if (i == TraversaledMaxLoc) {
result.push_back(tempCount);
tempCount = 0;
}
}
return result;
}
};
9.29日任务:合并区间
挺简单的,直接按照自己的思路解。先排序,然后合并
class Solution {
public:
static bool compare(vector<int>& a, vector<int>& b) {
return a[0] < b[0];
}
vector<vector<int>> merge(vector<vector<int>>& intervals) {
//按照自己的思路来
//先排序再合并
//第一步:排序
vector<vector<int>> result;
sort(intervals.begin(), intervals.end(), compare);
int mergeStart = intervals[0][0];
int mergeEnd = intervals[0][1];
for (int i = 1; i < intervals.size(); i++) {
if (intervals[i][0] <= mergeEnd) {
mergeEnd = max(mergeEnd, intervals[i][1]);
}
else {
result.push_back(vector<int>{mergeStart,mergeEnd});
mergeStart = intervals[i][0];
mergeEnd = intervals[i][1];
}
}
result.push_back(vector<int>{mergeStart,mergeEnd});
return result;
}
};
代码随想录中的写法:思路一致,不过降低了空间复杂度,更省时,当作扩展思路的写法
class Solution {
public:
static bool compare(vector<int>& a, vector<int>& b) {
return a[0] < b[0];
}
vector<vector<int>> merge(vector<vector<int>>& intervals) {
//按照自己的思路来
//先排序再合并
//第一步:排序
vector<vector<int>> result;
sort(intervals.begin(), intervals.end(), compare);
result.push_back(intervals[0]);
for (int i = 1; i < intervals.size(); i++) {
if (intervals[i][0] <= result.back()[1]) {
result.back()[1] = max(result.back()[1], intervals[i][1]);
}
else {
result.push_back(intervals[i]);
}
}
return result;
}
};
9月30日任务:单调递增的数字
完全按照自己的思路来:
第一步:把n的每一位映射到数组中去
第二步:从高位向低位遍历,找到第一个递减的位置,因为最后递减位后面的位都会被置为9.
第三步:处理从最高位到递减位的数值:递减位先减1,再与高位比较,如果是递减,那么递减位置为9,高位减1,一直向高位遍历。
class Solution {
public:
int monotoneIncreasingDigits(int n) {
//按照自己的分析:
//不断/10,%10,寻找n中不满足单调递增的最后一个位置,这个位置后面的数字应该都为9,前面的数字不变,自身减1,如果自身为0,再往前推就可以。
int result = 0;
//这道题n的取值范围为0-10^9
//可以通过一个数组来记录数字的每一位,从个位开始记录
vector<int> nMap;
if (n == 0) return 0;
while (n != 0) {
nMap.push_back(n % 10);
n = n / 10;
}
int decreaseIndex = -1;
//找到从高位到低位第一个递减转折点
for (int i = nMap.size() - 1 ; i >= 1; i--) {
if (nMap[i] > nMap[i - 1]) {
decreaseIndex = i;
break;
}
}
//对递减转折点前的数值进行操作
for (int i = decreaseIndex; i < nMap.size(); i++) {
if (i == decreaseIndex) nMap[i]--;
if (i + 1 < nMap.size() && nMap[i] < nMap[i + 1]) {
nMap[i] = 9;
nMap[i + 1]--;
}
}
//重新对数值进行计算
for (int i = nMap.size() - 1 ; i >= 0; i--) {
if (i >= decreaseIndex) result += nMap[i] * pow(10,i);
else result += 9 * pow(10,i);
}
return result;
}
};
代码随想录的思路:
代码随想录的思路在寻找decreaseIndex时是从低位向高位遍历,这样子顺利完成了对于高位数值的更改。
我的思路是从高位向低位遍历,这样子又得往回遍历重新修改数值,思维绕了一些。
代码的简洁与否一定程度上反映了思路的简洁与否。
class Solution {
public:
int monotoneIncreasingDigits(int n) {
//按照自己的分析:
//不断/10,%10,寻找n中不满足单调递增的最后一个位置,这个位置后面的数字应该都为9,前面的数字不变,自身减1,如果自身为0,再往前推就可以。
int result = 0;
//这道题n的取值范围为0-10^9
//可以通过一个数组来记录数字的每一位,从个位开始记录
vector<int> nMap;
if (n == 0) return 0;
while (n != 0) {
nMap.push_back(n % 10);
n = n / 10;
}
//初始化递减位,只要是一个负数就行。
int decreaseIndex = -1;
//代码随想录的思路比我的思路更上一层,可以和我的代码思路比较体会一下
for (int i = 1; i < nMap.size(); i++) {
if (nMap[i - 1] < nMap[i]) {
nMap[i]--;
decreaseIndex = i;
}
}
//重新对数值进行计算
for (int i = nMap.size() - 1 ; i >= 0; i--) {
if (i >= decreaseIndex) result += nMap[i] * pow(10,i);
else result += 9 * pow(10,i);
}
return result;
}
};
9月30日任务(当日第二题):监控二叉树
直接根据代码随想录的思路编写:
class Solution {
public:
int result = 0;
//返回值:0代表该节点无覆盖,1代表本节点有摄像头,2代表本结点有覆盖
int Traversal(TreeNode* root) {
//终止条件
if (root == nullptr) return 2;
//顺序执行
//左
int left = Traversal(root->left);
//右
int right = Traversal(root->right);
//中
//分不同情况:
//情况1:两个子节点处都有摄像头,此时中节点应该是有覆盖。
//情况2:两个子节点处都有覆盖,此时中节点应该是无覆盖。
//情况3: 两个子节点处都无覆盖以及只要有无覆盖的情况出现。即只要存在无覆盖的情况,此时中节点应该是插入摄像头
if (left == 1 && right == 1 || left == 1 && right ==2 || left == 2 && right == 1) return 2;
else if (left == 2 && right == 2) return 0;
else if (left == 0 && right == 0 || left == 0 && right == 1 || left == 0 && right == 2 || left == 1 && right == 0 || left == 2 && right == 0 ) {
result++;
return 1;
}
return 0;
}
int minCameraCover(TreeNode* root) {
//直接按照代码随想录的思路上
//大体思路就是从低到上,先给叶子节点父节点放个摄像头,然后隔两个节点放一个摄像头,直至到二叉树头结点
//两个难题:1.如何遍历2.如何每隔一个节点放置一个摄像头
//使用左右中后序遍历进行递归
//递归:输入参数:根节点,输出参数:该节点有无摄像头覆盖
if (root == nullptr) return 0;
if(Traversal(root) == 0) result += 1;
return result;
}
};
简化:
class Solution {
public:
int result = 0;
//返回值:0代表该节点无覆盖,1代表本节点有摄像头,2代表本结点有覆盖
int Traversal(TreeNode* root) {
//终止条件
if (root == nullptr) return 2;
//顺序执行
//左
int left = Traversal(root->left);
//右
int right = Traversal(root->right);
//中
//分不同情况:
//情况1:两个子节点处都有摄像头或者有一个摄像头,此时中节点应该是有覆盖。
//情况2:两个子节点处都有覆盖,此时中节点应该是无覆盖。
//情况3: 两个子节点处都无覆盖以及只要有无覆盖的情况出现。即只要存在无覆盖的情况,此时中节点应该是插入摄像头
if (left == 2 && right == 2) return 0;
else if (left == 0 || right == 0) {
//只要有无覆盖的节点,就得加一个摄像头
result++;
return 1;
}else return 2;
return 0;
}
int minCameraCover(TreeNode* root) {
//直接按照代码随想录的思路上
//大体思路就是从低到上,先给叶子节点父节点放个摄像头,然后隔两个节点放一个摄像头,直至到二叉树头结点
//两个难题:1.如何遍历2.如何每隔一个节点放置一个摄像头
//使用左右中后序遍历进行递归
//递归:输入参数:根节点,输出参数:该节点有无摄像头覆盖
if (root == nullptr) return 0;
if(Traversal(root) == 0) result += 1;
return result;
}
};
思路绘图如下:

773

被折叠的 条评论
为什么被折叠?



