学习材料声明
所有知识点都来自互联网,进行总结和梳理,侵权必删。
引用来源:基本参考代码随想录的刷题顺序和讲解。
理论基础
贪心是什么?
局部最优导向全局最优。
但是,没有套路。
如何证明?数学归纳法。
习题
1|455. 分发饼干
我的思路就是先排序,最小胃口的先满足。
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
sort(g.begin(), g.end());
sort(s.begin(), s.end());
int sum=0;
int i=0, j=0;
for(; i<g.size(); i++){
for(; j<s.size(); j++){
if(g[i] <= s[j]){
sum++;
s[j] = 0;
break;
}
}
}
return sum;
}
};
而代码随想录的一些文字我没看懂。
2|376. 摆动序列
想不到。
这题的解法图好有趣。
贪心解法(不是很理解。)
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
if((nums.size()==1)||(nums.size()==2 && nums[0]==nums[2])){
return 1;
}
if(nums.size()==2 && nums[0]!=nums[2]){
return 2;
}
int curDiff=0, preDiff=0, ans=1;
for(int i=0; i<nums.size()-1; i++){
curDiff = nums[i+1] - nums[i];
if ((preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)) {
ans++;
preDiff = curDiff;
}
}
return ans;
}
};
3|53. 最大子数组和
这会让我想到动态规划。动态规划代码如下:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n = nums.size();
int dp[n+1];
memset(dp, 0, sizeof(dp));
dp[0] = nums[0];
int ma=dp[0];
for(int i=1; i<n; i++){
dp[i] = max(nums[i], dp[i-1]+nums[i]);
if(ma<dp[i]){
ma = dp[i];
}
}
for(int i=0; i<n; i++){
cout<<dp[i]<<" ";
}
return ma;
}
};
贪心思路:记录count,如果count值为负就立马归零。因为这样肯定会拉低总和。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
//贪心算法
int result = INT32_MIN;
int count = 0;
for (int i = 0; i < nums.size(); i++) {
count += nums[i];
if (count > result) { // 取区间累计的最大值(相当于不断确定最大子序终止位置)
result = count;
}
if (count <= 0) count = 0; // 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和
}
return result;
}
};
----------------------------------------------------------------------------2023年10月29日----------------------------------------------------------
4|122. 买卖股票的最佳时机 II
这不就是动态规划的状态转移嘛?嘿嘿,让我大展拳脚的时候到了。艾府!!!
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
int dp[n+1][2];
memset(dp, 0, sizeof(dp));
dp[0][0] = 0;//第一天未持有
dp[0][1] = -prices[0];//第一天持有
for(int i=1; i<n; i++){
dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i]);
dp[i][1] = max(dp[i-1][1], dp[i-1][0]-prices[i]);
}
return max(dp[n-1][0], dp[n-1][1]);
}
};
贪心思路:贪心的思路真的好难想。利润拆解:
class Solution {
public int maxProfit(int[] prices) {
int ans = 0;
int n = prices.length;
for(int i=1; i<n; i++){
ans += Math.max(prices[i]-prices[i-1], 0);
}
return ans;
}
}
5|55. 跳跃游戏
第一想法就是回溯。哇,我是不是脑子不灵光,想不到贪心的规则。超时了。
class Solution {
boolean flag=false;
int n;
public void backTracing(int[] nums, int startIndex){
if(startIndex == n-1){
flag = true;
return;
}
for(int i=startIndex+1; i<=nums[startIndex]+startIndex&&i<n; i++){
System.out.print(startIndex + " " + i + "\n");
if(flag){
break;
}
backTracing(nums, i);
}
}
public boolean canJump(int[] nums) {
n = nums.length;
backTracing(nums, 0);
return flag;
}
}
贪心思路:记录覆盖范围。
class Solution {
public:
bool canJump(vector<int>& nums) {
int cover = 0;
if (nums.size() == 1) return true; // 只有一个元素,就是能达到
for (int i = 0; i <= cover; i++) { // 注意这里是小于等于cover
cover = max(i + nums[i], cover);
cout<<cover<<" ";
if (cover >= nums.size() - 1) return true; // 说明可以覆盖到终点了
}
return false;
}
};
6|45. 跳跃游戏 II
我觉得这很像动态规划。依旧超时。
class Solution {
public:
int jump(vector<int>& nums) {
int n = nums.size();
int dp[n+1];
memset(dp, 10001, sizeof(dp));
dp[0] = 0;
for(int i=0; i<n; i++){
for(int j=i+1; j<n&&j<=i+nums[i]; j++){
dp[j] = min(dp[i]+1, dp[j]);
for(int i=0; i<n; i++){
cout<<dp[i]<<" ";
}
cout<<endl;
}
}
return dp[n-1];
}
};
贪心算法,依旧是覆盖范围,不过是第几步可覆盖范围。
//代码来自代码随想录
class Solution {
public:
int jump(vector<int>& nums) {
if (nums.size() == 1) return 0;
int curDistance = 0; // 当前覆盖最远距离下标
int ans = 0; // 记录走的最大步数
int nextDistance = 0; // 下一步覆盖最远距离下标
for (int i = 0; i < nums.size(); i++) {
nextDistance = max(nums[i] + i, nextDistance); // 更新下一步覆盖最远距离下标
if (i == curDistance) { // 遇到当前覆盖最远距离下标
ans++; // 需要走下一步
curDistance = nextDistance; // 更新当前覆盖最远距离下标(相当于加油了)
if (nextDistance >= nums.size() - 1) break; // 当前覆盖最远距到达集合终点,不用做ans++操作了,直接结束
}
}
return ans;
}
};
----------------------------------------------------------------------------2023年10月30日----------------------------------------------------------
7|1005. K 次取反后最大化的数组和
每次都取数组里最小的进行取反。O(knlogn)
class Solution {
public:
int largestSumAfterKNegations(vector<int>& nums, int k) {
for(int i=1; i<=k; i++){
sort(nums.begin(), nums.end());
nums[0] = -1*nums[0];
}
int sum=0;
for(int i=0; i<nums.size(); i++){
sum += nums[i];
}
return sum;
}
};
真正的贪心思路,负数变正,正数最小变负(来回变换)。
class Solution {
static bool cmp(int a, int b) {
return abs(a) > abs(b);
}
public:
int largestSumAfterKNegations(vector<int>& A, int K) {
sort(A.begin(), A.end(), cmp); // 第一步,从大到小
for (int i = 0; i < A.size(); i++) { // 第二步,负数变正
if (A[i] < 0 && K > 0) {
A[i] *= -1;
K--;
}
}
if (K % 2 == 1) A[A.size() - 1] *= -1; // 第三步
int result = 0;
for (int a : A) result += a; // 第四步
return result;
}
};
8|134. 加油站
思路超时了。
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int n=gas.size(), curgas=0, maxgasIdx=0, sumgas, sumcost;
int flag[n], ans=-1;
for(int i=0; i<n; i++){
if(gas[i]<cost[i]){
continue;
}
curgas = 0;
ans = i;
for(int j=0; j<n; j++){
if(curgas+gas[(i+j)%n]==cost[(i+j)%n]){
curgas = 0;
continue;
}
if(curgas+gas[(i+j)%n]<cost[(i+j)%n]){
ans = -1;
break;
}
curgas += (gas[(i+j)%n]-cost[(i+j)%n]);
}
if(ans!=-1){
break;
}
}
return ans;
}
};
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int curSum = 0;
int totalSum = 0;
int start = 0;
for (int i = 0; i < gas.size(); i++) {
curSum += gas[i] - cost[i];
totalSum += gas[i] - cost[i];
if (curSum < 0) { // 当前累加rest[i]和 curSum一旦小于0
start = i + 1; // 起始位置更新为i+1
curSum = 0; // curSum从0开始
}
}
if (totalSum < 0) return -1; // 说明怎么走都不可能跑一圈了
return start;
}
};
9|135. 分发糖果
思路:进行分发,直至分发数满足要求,但是超时。
class Solution {
public:
int candy(vector<int>& ratings) {
int ans=2, sum=ratings.size(), n=ratings.size();
int candyN[n+1];
memset(candyN, 0, sizeof(candyN));
while(1){
for(int i=0; i<n-1; i++){
if(ratings[i]<ratings[i+1]){
if(candyN[i+1]<=candyN[i]){
sum += (candyN[i] + 1 - candyN[i+1]);
candyN[i+1]=candyN[i]+1;
}
}
if(ratings[i]>ratings[i+1]){
if(candyN[i+1]>=candyN[i]){
sum += (candyN[i+1] + 1 - candyN[i]);
candyN[i]=candyN[i+1]+1;
}
}
}
if(ans==sum){
break;
}
ans = sum;
}
return ans;
}
};
贪心思路,从前往后一遍,从后往前一遍就好了。满足比左边大的多拿,满足比右边大的多拿,那么就满足比两边大的多拿。
class Solution {
public:
int candy(vector<int>& ratings) {
vector<int> candyVec(ratings.size(), 1);
// 从前向后
for (int i = 1; i < ratings.size(); i++) {
if (ratings[i] > ratings[i - 1]) candyVec[i] = candyVec[i - 1] + 1;
}
// 从后向前
for (int i = ratings.size() - 2; i >= 0; i--) {
if (ratings[i] > ratings[i + 1] ) {
candyVec[i] = max(candyVec[i], candyVec[i + 1] + 1);
}
}
// 统计结果
int result = 0;
for (int i = 0; i < candyVec.size(); i++) result += candyVec[i];
return result;
}
};
----------------------------------------------------------------------------2023年10月31日----------------------------------------------------------
10|435. 无重叠区间
想着学习上一篇左不重叠,右不重叠。没想到,超时了。
class Solution {
public:
static bool cmp(vector<int> a, vector<int> b){
return a[0]<b[0];
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end(), cmp);
int n = intervals.size();
int sum = 0;
for(int i=1; i<intervals.size(); i++){
if(intervals[i][0]<intervals[i-1][0]){
intervals.erase(intervals.begin()+i);
i=i-1;
}else if(intervals[i][0]==intervals[i-1][0]){
if(intervals[i][1]>intervals[i-1][1]){
intervals.erase(intervals.begin()+i);
i=i-1;
}else{
intervals.erase(intervals.begin()+i-1);
i=i-1;
}
}
}
for(int j=1; j<intervals.size(); j++){
if(intervals[j][0]<intervals[j-1][1]){
if(intervals[j][1]>intervals[j-1][1]){
intervals.erase(intervals.begin()+j);
j=j-1;
}else{
intervals.erase(intervals.begin()+j-1);
j=j-1;
}
}
}
return n-intervals.size();
}
};
其实完全没有必要左右。尽管还是超时。
class Solution {
public:
static bool cmp(vector<int> a, vector<int> b){
return a[0]<b[0];
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end(), cmp);
int n = intervals.size();
int sum = 0;
for(int i=1; i<intervals.size(); i++){
if(intervals[i][0]<intervals[i-1][1]){
if(intervals[i][1]>intervals[i-1][1]){
intervals.erase(intervals.begin()+i);
i=i-1;
}else{
intervals.erase(intervals.begin()+i-1);
i=i-1;
}
}
}
return n-intervals.size();
}
};
代码随想录的方法,记录分割点。
class Solution {
public:
static bool cmp (const vector<int>& a, const vector<int>& b) {
return a[0] < b[0]; // 改为左边界排序
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
if (intervals.size() == 0) return 0;
sort(intervals.begin(), intervals.end(), cmp);
int count = 0; // 注意这里从0开始,因为是记录重叠区间
int end = intervals[0][1]; // 记录区间分割点
for (int i = 1; i < intervals.size(); i++) {
if (intervals[i][0] >= end) end = intervals[i][1]; // 无重叠的情况
else { // 重叠情况
end = min(end, intervals[i][1]);
count++;
}
}
return count;
}
};
11|763. 划分字母区间
完全想不到怎么做。
仔细想想,还是能找到区间重叠的影子的。统计每一个字母出现的起始位置和结束位置,作为一个区间。那么求这样多个区间里,重叠覆盖之后的区间长度。
class Solution {
public:
static bool cmp (const vector<int>& a, const vector<int>& b) {
return a[0] < b[0]; // 改为左边界排序
}
vector<int> partitionLabels(string s) {
vector<int> ans;
int n = s.length();
int labels[26][2];
memset(labels, -1, sizeof(labels));
for(int i=0; i<n; i++){
if(labels[s[i]-'a'][0] == -1){
labels[s[i]-'a'][0]=i;
}
labels[s[i]-'a'][1] = i;
}
vector<vector<int>> nums;
vector<int> temp;
for(int i=0, j=0; i<26; i++){
if(labels[i][0]!=-1){
temp.push_back(labels[i][0]);
temp.push_back(labels[i][1]);
nums.push_back(temp);
temp.clear();
j++;
}
}
sort(nums.begin(), nums.end(), cmp);
int mi=nums[0][0], ma=nums[0][1];
for(int i=0; i<nums.size(); i++){
if(nums[i][0]<mi&&nums[i][1]<ma&&nums[i][1]>mi){
mi=nums[i][0];
}
if(nums[i][1]>ma&&nums[i][0]>mi&&nums[i][0]<ma){
ma=nums[i][1];
}
if(nums[i][0]>ma){
ans.push_back(ma-mi+1);
mi=nums[i][0];
ma=nums[i][1];
}
}
ans.push_back(ma-mi+1);
return ans;
}
};
代码随想录里的方法:
class Solution {
public:
vector<int> partitionLabels(string S) {
int hash[27] = {0}; // i为字符,hash[i]为字符出现的最后位置
for (int i = 0; i < S.size(); i++) { // 统计每一个字符最后出现的位置
hash[S[i] - 'a'] = i;
}
vector<int> result;
int left = 0;
int right = 0;
for (int i = 0; i < S.size(); i++) {
right = max(right, hash[S[i] - 'a']); // 找到字符出现的最远边界
if (i == right) {
result.push_back(right - left + 1);
left = i + 1;
}
}
return result;
}
};
12|56. 合并区间
这一题就是上一题解法的一部分。。
class Solution {
public:
static bool cmp (const vector<int>& a, const vector<int>& b) {
return a[0] < b[0]; // 改为左边界排序
}
vector<vector<int>> merge(vector<vector<int>>& intervals) {
vector<int> temp;
vector<vector<int>> ans;
sort(intervals.begin(), intervals.end(), cmp);
int mi=intervals[0][0], ma=intervals[0][1];
for(int i=0; i<intervals.size(); i++){
if(intervals[i][0]<mi&&intervals[i][1]<=ma&&intervals[i][1]>=mi){
mi=intervals[i][0];
}
if(intervals[i][1]>ma&&intervals[i][0]>=mi&&intervals[i][0]<=ma){
ma=intervals[i][1];
}
if(intervals[i][0]>ma){
temp.push_back(mi);
temp.push_back(ma);
ans.push_back(temp);
temp.clear();
mi=intervals[i][0];
ma=intervals[i][1];
}
}
temp.push_back(mi);
temp.push_back(ma);
ans.push_back(temp);
return ans;
}
};
13|452. 用最少数量的箭引爆气球
看起来与前两题是同样的方法。但还是有所不同,这个算是求交集,上述两题求并集。
class Solution {
public:
static bool cmp (const vector<int>& a, const vector<int>& b) {
return a[0] < b[0]; // 改为左边界排序
}
int findMinArrowShots(vector<vector<int>>& points) {
vector<int> temp;
vector<vector<int>> ans;
sort(points.begin(), points.end(), cmp);
int mi=points[0][0], ma=points[0][1], res=0;
for(int i=0; i<points.size(); i++){
if(points[i][0]<=ma){
mi=points[i][0];
ma=min(points[i][1], ma);
}else{
res++;
mi=points[i][0];
ma=points[i][1];
}
}
res++;
return res;
}
};
----------------------------------------------------------------------------2023年11月01日----------------------------------------------------------
13|738.单调递增的数字
根据错误不断调试的结果。
class Solution {
public:
int monotoneIncreasingDigits(int n) {
vector<int> nums;
int temp=n;
int res=0;
if(n==0){
return 0;
}
while(temp){
nums.push_back(temp%10);
temp /= 10;
}
for(int i=nums.size()-1; i>=0; i--){
cout<<nums[i]<<" ";
}
cout<<endl;
if(nums.size()==1){
return n;
}
for(int i=0, j=0; i<nums.size()-1; i++){
if(nums[i]<nums[i+1]){
nums[i]=9;
nums[i+1] = (nums[i+1]-1)%10;
j=i-1;
while(j>=0){
if(nums[j]!=9){
nums[j] = 9;
}else{
break;
}
j--;
}
}
}
for(int i=nums.size()-1; i>=0; i--){
cout<<nums[i]<<" ";
res += (nums[i]*pow(10, i));
}
cout<<endl;
return res;
}
};
代码随想录的方法是一旦一个数变成9,之后的所有数字都要变成9。那么就记录最后一个变成9的位置就好。
class Solution {
public:
int monotoneIncreasingDigits(int N) {
string strNum = to_string(N);
// flag用来标记赋值9从哪里开始
// 设置为这个默认值,为了防止第二个for循环在flag没有被赋值的情况下执行
int flag = strNum.size();
for (int i = strNum.size() - 1; i > 0; i--) {
if (strNum[i - 1] > strNum[i] ) {
flag = i;
strNum[i - 1]--;
}
}
for (int i = flag; i < strNum.size(); i++) {
strNum[i] = '9';
}
return stoi(strNum);
}
};
14|968.监控二叉树
----------------------------------------------------------------------------2023年11月02日----------------------------------------------------------