代码随想录---贪心篇
系列文章目录
文章目录
- 系列文章目录
- 前言
- 贪心一般解题步骤
- 一、2025.5.14
- 1.学习内容
- 455. 分发饼干
- 2.复习内容
- 51. N 皇后
- 332. 重新安排行程
- 37. 解数独
- 70. 爬楼梯
- 二、2025.5.17(前两天写的都没保存)
- 1.学习内容
- 55. 跳跃游戏
- 2.复习内容
- 455. 分发饼干
- 376. 摆动序列
- 53. 最大子数组和
- 122. 买卖股票的最佳时机 II
- 2025.5.18
- 1.学习内容
- 1005. K 次取反后最大化的数组和
- 2.复习内容
- 19. 删除链表的倒数第 N 个结点
- 面试题 02.07. 链表相交
- 2025.5.19
- 1.学习内容
- 134. 加油站
- 2.复习内容
- 55. 跳跃游戏
- 45. 跳跃游戏 II
- 1005. K 次取反后最大化的数组和
- 2025.5.20
- 1.学习内容
- 135. 分发糖果
- 2.复习内容
- 134. 加油站
- 142. 环形链表 II
- 2025.5.21
- 1.学习内容
- 860. 柠檬水找零
- 406. 根据身高重建队列
- 2025.5.22
- 1.学习内容
- 452. 用最少数量的箭引爆气球
- 2025.5.23
- 1.学习内容
- 435. 无重叠区间
- 763. 划分字母区间
- 2025.5.25
- 1.学习内容
- 738. 单调递增的数字
- 968. 监控二叉树
- 2.复习内容
- 56. 合并区间
- 435. 无重叠区间
- 763. 划分字母区间
前言
贪心的本质是选择每一阶段的局部最优,从而达到全局最优。
贪心一般解题步骤
①将问题分解为若干个子问题
②找出适合的贪心策略
③求解每一个子问题的最优解
④将局部最优解堆叠成全局最优解
一、2025.5.14
1.学习内容
455. 分发饼干
int findContentChildren(vector<int>& g, vector<int>& s) {sort(g.begin(), g.end());sort(s.begin(), s.end());int index = s.size() - 1;int result = 0;//给胃口最大的孩子分配最大的饼干for (int i = g.size() - 1; i >= 0; i--){if (index >= 0 && s[index] >= g[i]){result++;index--;}}return result;}
int findContentChildren(vector<int>& g, vector<int>& s) {sort(g.begin(), g.end());sort(s.begin(), s.end());int index = 0;//最小的饼干满足胃口最小的孩子for (int i = 0; i < s.size(); i++){if (index < g.size() && s[i] >= g[index]) index++;}return index;}
2.复习内容
51. N 皇后
332. 重新安排行程
37. 解数独
70. 爬楼梯
二、2025.5.17(前两天写的都没保存)
1.学习内容
55. 跳跃游戏
//贪心:这道题主要考虑的就是覆盖范围,如果覆盖范围超过了nums的大小,那么就是true,反之就是false。bool canJump(vector<int>& nums) {int cover = 0;if (nums.size() <= 1) return true;for (int i = 0; i <= cover; i++){cover = max(i + nums[i], cover);if (cover >= nums.size()) return true;}return false;}
2.复习内容
455. 分发饼干
376. 摆动序列
//贪心int wiggleMaxLength(vector<int>& nums) {int preDiff = 0, curDiff = 0;//默认最右端为峰值或者谷值int result = 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)){result++;preDiff = curDiff;}}return result;}
//动态规划int wiggleMaxLength(vector<int>& nums) {//状态标识:i表示nums的第i个元素,j表示将该点作为0:波谷,1:波峰,dp[i][j]表示以i结尾的摆动序列的长度int dp[1010][2];//使用memset初始化二维数组的时候,只能将其值赋值为-1或0;memset(dp, 0, sizeof(dp));dp[0][0] = dp[0][1] = 1;// for (int i = 0; i < nums.size(); i++)// {// dp[i][0] = dp[i][1] = 1;// }for (int i = 1; i < nums.size(); i++){//也可以在初始化dp数组的时候,利用for循环将所有的值设为1;dp[i][0] = dp[i][1] = 1;for (int j = 0; j <= i; j++){if (nums[i] < nums[j]) dp[i][0] = max(dp[j][1] + 1, dp[i][0]);if (nums[i] > nums[j]) dp[i][1] = max(dp[j][0] + 1, dp[i][1]);}}return max(dp[nums.size() - 1][0], dp[nums.size() - 1][1]);}
53. 最大子数组和
//贪心int maxSubArray(vector<int>& nums) {int count = 0, result = nums[0];for (int i = 0; i < nums.size(); i++){count += nums[i];//更新result必须在count判断之前,因为如果全是负数的话,如果颠倒了这个顺序,结果就是0,而不是最大负数result = max(result, count);if (count < 0) count = 0;}return result;}
int maxSubArray(vector<int>& nums) {//动态规划if (nums.size() == 0) return 0;vector<int> dp(nums.size(), 0);dp[0] = nums[0];int result = dp[0];for (int i = 1; i < nums.size(); i++){//状态转移方程dp[i] = max(dp[i - 1] + nums[i], nums[i]);result = max(result, dp[i]);}return result;}
122. 买卖股票的最佳时机 II
//贪心:这道题最重要的一点就是知道利润是可以分解的://假如第 0 天买入,第 3 天卖出,那么利润为:prices[3] - prices[0]。//相当于(prices[3] - prices[2]) + (prices[2] - prices[1]) + (prices[1] - prices[0])。//此时就是把利润分解为每天为单位的维度,而不是从 0 天到第 3 天整体去考虑!int maxProfit(vector<int>& prices) {int result = 0;for (int i = 1; i < prices.size(); i++){result += max(0, prices[i] - prices[i - 1]);}return result;}
2025.5.18
1.学习内容
1005. K 次取反后最大化的数组和
//贪心:第一次贪心,将绝对值大的负值优先转变为正值;//第二次贪心:最后还有反转次数的话,就将最小的值反复反转,就会得到最大的和//要是先第一个贪心,就需要对按照绝对值的大小对nums进行排序,然后根据k值将负值转变为正值static bool cmp(const int &a, const int &b){return abs(a) > abs(b);}int largestSumAfterKNegations(vector<int>& nums, int k) {int result = 0;sort(nums.begin(), nums.end(), cmp);for (int i = 0; i < nums.size(); i++){if (nums[i] < 0 && k > 0){nums[i] *= -1;k--;}}//第二次贪心if (k % 2) nums[nums.size() - 1] *= -1;for (int x : nums) result += x;return result;}
2.复习内容
19. 删除链表的倒数第 N 个结点
//利用快慢节点来实现一次遍历从而删除节点的效果
ListNode* removeNthFromEnd(ListNode* head, int n) {ListNode* dummy_Head = new ListNode(0);dummy_Head -> next = head;ListNode* pre = dummy_Head;ListNode* cur = dummy_Head;while (n-- && pre != nullptr){pre = pre -> next;}while (pre -> next != nullptr){pre = pre -> next;cur = cur -> next; }cur -> next = cur -> next -> next;//一般不要直接返回head,而是返回dummy_Head -> next;return dummy_Head -> next;}
面试题 02.07. 链表相交
public:ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {int lenA = 0, lenB = 0;ListNode* curA = headA, *curB = headB;while (curA){lenA++;curA = curA -> next;}while (curB){lenB++;curB = curB -> next;}if (lenA < lenB){swap(lenA, lenB);swap(headA, headB);}curA = headA;curB = headB;int diff = lenA - lenB;while (diff--){curA = curA -> next;}while (curA){if (curA == curB) return curA;curA = curA -> next;curB = curB -> next;}return nullptr;}
2025.5.19
1.学习内容
134. 加油站
//暴力做法 (会超时)int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {for (int i = 0; i < gas.size(); i++){int rest = gas[i] - cost[i];int index = (i + 1) % gas.size();while (index != i && rest >= 0){rest += (gas[index] - cost[index]);index = (index + 1) % gas.size();}if (rest >= 0 && index == i) return i;}return -1;}
//贪心算法:只有考虑全局最优,没有考虑局部最优int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {int min = 0, sum = 0;for (int i = 0; i < cost.size(); i++){int rest = gas[i] - cost[i];sum += rest;if (sum < min) min = sum;}if (sum < 0) return -1;if (min >= 0) return 0;for (int i = cost.size() - 1; i >= 0; i--){int rest = gas[i] - cost[i];min += rest;if (min >= 0) return i;}return -1;}
//贪心思路:gas[i] - cost[i](i - j)的和如果为负数,代表从i-j这段距离走不通,因此就要从j + 1开始往后寻找//如果到最后总和为负值,那么就返回-1,反之就是我们之前定义的值。int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {int curSum = 0, totalSum = 0;int start = 0;for (int i = 0; i < gas.size(); i++){totalSum += gas[i] - cost[i];curSum += gas[i] - cost[i];if (curSum < 0){start = i + 1;curSum = 0;}}if (totalSum < 0) return -1;return start;}
2.复习内容
55. 跳跃游戏
45. 跳跃游戏 II
1005. K 次取反后最大化的数组和
2025.5.20
1.学习内容
135. 分发糖果
//本题我采用了两次贪心的策略://一次是从左到右遍历,只比较右边孩子评分比左边大的情况。//一次是从右到左遍历,只比较左边孩子评分比右边大的情况。//这样从局部最优推出了全局最优,即:相邻的孩子中,评分高的孩子获得更多的糖果。int candy(vector<int>& ratings) {//初始化数组vector<int> candies(ratings.size(), 1);//第一遍从左往右遍历for (int i = 1; i < ratings.size(); i++){if (ratings[i] > ratings[i - 1]) candies[i] = candies[i - 1] + 1;}//第二次遍历从右往左遍历,并且取candies[i]和(candies[i + 1] + 1/1)的最大值for (int i = ratings.size() - 2; i >= 0; i--){if (ratings[i + 1] < ratings[i]) candies[i] = max(candies[i + 1] + 1, candies[i]);}int sum = 0;for (int a : candies){sum += a;}return sum;}
2.复习内容
134. 加油站
142. 环形链表 II
ListNode *detectCycle(ListNode *head) {ListNode* slow = head, *fast = head;while (fast != nullptr && fast -> next != nullptr){slow = slow -> next;fast = fast -> next -> next;//快慢指针相遇:相遇的节点是环形内部的节点,但是这块需要找的是入环的第一个节点//但是我们根据(head - 入环的第一个节点)= (slow - 入环的第一个节点) + 环形内的节点个数 * n(n = 1,2,3...),那么让他们同时next,得到的第一个相同的节点就是ansif (slow == fast){ListNode *index1 = slow, *index2 = head;while (index1 != index2){index1 = index1 -> next;index2 = index2 -> next;}return index1;}}return nullptr;}
2025.5.21
1.学习内容
860. 柠檬水找零
//有如下三种情况://情况一:账单是5,直接收下。//情况二:账单是10,消耗一个5,增加一个10//情况三:账单是20,优先消耗一个10和一个5,如果不够,再消耗三个5//贪心的情况只要考虑第三种情况:因为美元10只能给账单20找零,而美元5可以给账单10和账单20找零,美元5更万能!//所以局部最优:遇到账单20,优先消耗美元10,完成本次找零。全局最优:完成全部账单的找零。bool lemonadeChange(vector<int>& bills) {int five = 0, ten = 0;for (int i = 0; i < bills.size(); i++){if (bills[i] == 5){five++;}else if (bills[i] == 10){if (five <= 0) return false;five--;ten++;}else{if (ten > 0 && five > 0){ten--;five--;}else if (five >= 3){five -= 3;}else return false;}}return true;}
406. 根据身高重建队列
//贪心://局部最优:优先按身高高的people的k来插入。插入操作过后的people满足队列属性//全局最优:最后都做完插入操作,整个队列满足题目队列属性static bool cmp (const vector<int>& a, const 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);//list底层使用链表实现的list<vector<int>> que;for (int i = 0; i < people.size(); i++){int index = people[i][1];//vector的insert操作是比较耗时的,因为如果插入后数组超过原数组大小,就会扩容,从而将数据拷贝被新的vector中,所以可以使用链表代替vector(iterator的用法我还是不是很熟悉,看看博客去)std::list<vector<int>>::iterator it = que.begin();while (index--){it++;}que.insert(it, people[i]);}return vector<vector<int>>(que.begin(), que.end());}
2025.5.22
1.学习内容
452. 用最少数量的箭引爆气球
//贪心:感觉和合并区间类似,比较好理解,对于这个问题,我刚开始一直有一个困惑:会不会前三个有交集,第三个和第四个有交集,从而不知道先射哪个,但是转念一想,好像按照这两种情况箭的数量没有什么区别,那就按照1好了,代码逻辑还清晰。static bool cmp(const vector<int>& a, const vector<int>& b){if (a[0] == b[0]) return a[1] < b[1];return a[0] < b[0];}int findMinArrowShots(vector<vector<int>>& points) {//最少需要一只箭int arrowNum = 1;sort(points.begin(), points.end(), cmp);for (int i = 1; i < points.size(); i++){if (points[i][0] > points[i - 1][1]) arrowNum++;else points[i][1] = min(points[i - 1][1], points[i][1]);}return arrowNum;}
2025.5.23
1.学习内容
435. 无重叠区间
static bool cmp(const vector<int>& a, const vector<int>& b){if (a[0] == b[0]) return a[1] < b[1];return a[0] < b[0];}int eraseOverlapIntervals(vector<vector<int>>& intervals) {sort (intervals.begin(), intervals.end(), cmp);int result = 1;for (int i = 1; i < intervals.size(); i++){if (intervals[i][0] < intervals[i - 1][1]){intervals[i][1] = min(intervals[i - 1][1], intervals[i][1]);}else result++;}return (intervals.size() - result);}
763. 划分字母区间
vector<int> partitionLabels(string s) {int hash[27];for (int i = 0; i < s.size(); i++){hash[s[i] - 'a'] = i;}int left = 0, right = 0;vector<int> result;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 = right + 1;}}return result;}
2025.5.25
1.学习内容
738. 单调递增的数字
//贪心int monotoneIncreasingDigits(int n) {string num_Str = to_string(n);//如果下面的count没有被赋值,那么第二个循环就不会执行int count = num_Str.size();for (int i = num_Str.size() - 2; i >= 0; i--){if (num_Str[i + 1] < num_Str[i]){count = i;//这段代码一定要放在这块,因为变9可以之后再操作,但是比较的话应该是和-1之后的数进行比较(其实按照思路来说,如果前面发现了非单调递增的,那么更新所有的数,但是更新这些数变成9貌似对后面的比较没有太大的影响,所以每次只要将i的数-1即可,这种对比较结果还是有影响的)num_Str[i]--;}}for (int i = num_Str.size() - 1; i > count; i--){num_Str[i] = '9';}return stoi(num_Str);}
968. 监控二叉树
int result = 0;//由于要从叶子节点往上遍历才能使用更少的摄像头,所以我们使用后序遍历//三种状态: 0:没有被覆盖, 1:有摄像头, 2:被覆盖int traversal(TreeNode* node){//如果节点为空,表示已经被覆盖if (node == nullptr) return 2;//迭代int left = traversal(node -> left);int right = traversal(node -> right);//处理逻辑//如果左右节点都已经被覆盖,那么该节点就应该是未被覆盖的情况if (left == 2 && right == 2) return 0;//如果左右节点有一个未被覆盖,那么该节点就应该安摄像头if (left == 0 || right == 0){result++;return 1;}//如果左右节点有一个有摄像头(因为有一个未被覆盖的情况已经在上面考虑过了)if (left == 1 || right == 1) return 2;//肯定不会有-1的情况return -1;}int minCameraCover(TreeNode* root) {if (root == nullptr) return 0;if (traversal(root) == 0) result++;return result;}