当前位置:   article > 正文

算法——贪心

算法——贪心

「贪心的本质是选择每一阶段的局部最优,从而达到全局最优」

贪心无套路

1. 分发饼干

贪心策略:

(1)局部最优就是大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的小孩

(2)局部最优就是小饼干喂给胃口小的,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的小孩

代码实现:

  1. // 冒泡排序
  2. void sort(int *a, int len) {
  3. int flag = 1;
  4. for (int i = len - 1; flag && i > 0; i--) {
  5. flag = 0;
  6. for (int j = 0; j < i; j++) {
  7. if (a[j] > a[j + 1]) {
  8. flag = 1;
  9. int temp = a[j];
  10. a[j] = a[j + 1];
  11. a[j + 1] = temp;
  12. }
  13. }
  14. }
  15. }
  16. int findContentChildren(int *g, int gSize, int *s, int sSize) {
  17. sort(g, gSize);
  18. sort(s, sSize);
  19. int j = 0;
  20. int sum = 0;
  21. for (int i = 0; i < gSize; i++) {
  22. while (j < sSize && s[j] < g[i]) {
  23. j++;
  24. }
  25. if (j < sSize) {
  26. sum++;
  27. j++;
  28. }
  29. }
  30. return sum;
  31. }

2. 摆动序列

贪心策略:

「局部最优:删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰值」

「整体最优:整个序列有最多的局部峰值,从而达到最长摆动序列」

「实际操作上,其实连删除的操作都不用做,因为题目要求的是最长摆动子序列的长度,所以只需要统计数组的峰值数量就可以了(相当于是删除单一坡度上的节点,然后统计长度)」

代码实现:

  1. int wiggleMaxLength(int *nums, int numsSize) {
  2. if (numsSize <= 1) {
  3. return numsSize;
  4. }
  5. int now = 0; // 当前一对峰值
  6. int pre = 0; // 前一对峰值
  7. int result = 1; // 记录峰值个数,序列默认序列最右边有一个峰值
  8. for (int i = 1; i < numsSize; i++) {
  9. now = nums[i] - nums[i - 1];
  10. // 出现峰值
  11. if ((now > 0 && pre <= 0) || (pre >= 0 && now < 0)) {
  12. result++;
  13. pre = now;
  14. }
  15. }
  16. return result;
  17. }

3. 最大子数组和

暴力解法:超时

  1. int maxSubArray(int *nums, int numsSize) {
  2. int result = INT32_MIN;
  3. int count = 0;
  4. for (int i = 0; i < numsSize; i++) { // 设置起始位置
  5. count = 0;
  6. for (int j = i; j < numsSize; j++) { // 每次从起始位置i开始遍历寻找最大值
  7. count += nums[j];
  8. result = count > result ? count : result;
  9. }
  10. }
  11. return result;
  12. }

动规五步曲:

  • 确定dp数组(dp table)以及下标的含义

dp[i]:以 i 结尾的最大连续子序列和为dp[i]

  • 确定递推公式(容斥原理)

dp[i]只有两个方向可以推出来:

  • dp[i - 1] + nums[i],即:nums[i]加入当前连续子序列和

  • nums[i],即:从头开始计算当前连续子序列和

一定是取最大的,所以dp[i] = max(dp[i - 1] + nums[i], nums[i]);

  1. #define max(a, b) ((a) > (b) ? (a) : (b))
  2. int maxSubArray(int *nums, int numsSize) {
  3. if (numsSize == 0) { // 特殊情况
  4. return 0;
  5. }
  6. int dp[numsSize];
  7. dp[0] = nums[0];
  8. int result = dp[0];
  9. for (int i = 1; i < numsSize; i++) {
  10. dp[i] = max(dp[i - 1] + nums[i], nums[i]); // 状态转移方程
  11. result = max(result, dp[i]); // result 保存dp[i]的最大值
  12. }
  13. return result;
  14. }

贪心策略:

局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小

全局最优:选取最大“连续和”

  1. int maxSubArray(int *nums, int numsSize) {
  2. int result = INT32_MIN;
  3. int count = 0;
  4. for (int i = 0; i < numsSize; i++) {
  5. count += nums[i];
  6. if (count > result) { // 取区间累计的最大值(相当于不断确定最大子序终止位置)
  7. result = count;
  8. }
  9. if (count <= 0) {
  10. count = 0; // 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和
  11. }
  12. }
  13. return result;
  14. }

4. 买卖股票的最佳时机||

贪心策略:

「局部最优:收集每天的正利润,全局最优:求得最大利润」

  1. int maxProfit(int *prices, int pricesSize) {
  2. int minProfit = prices[0];
  3. int result = 0;
  4. for (int i = 1; i < pricesSize; i++) {
  5. if (prices[i] > minProfit) {
  6. result += prices[i] - minProfit;
  7. minProfit = prices[i];
  8. }
  9. if (prices[i] < minProfit) {
  10. minProfit = prices[i];
  11. }
  12. }
  13. return result;
  14. }

5. 跳跃游戏

贪心策略:

「贪心算法局部最优解:每次取最大跳跃步数(取最大覆盖范围),整体最优解:最后得到整体最大覆盖范围,看是否能到终点」

  1. bool canJump(int *nums, int numsSize) {
  2. int cover = 0;
  3. if (numsSize == 1) {
  4. return true;
  5. }
  6. for (int i = 0; i <= cover; i++) {
  7. cover = cover > i + nums[i] ? cover : i + nums[i];
  8. if (cover >= numsSize - 1) {
  9. return true;
  10. }
  11. }
  12. return false;
  13. }

6. 跳跃游戏||

贪心策略

局部最优:当前可移动距离尽可能多走,如果还没到终点,步数再加一。整体最优:一步尽可能多走,从而达到最小步数

  • 若当前覆盖最远距离下标不是是终点,步数就加一,还需要继续走。
  • 若当前覆盖最远距离下标就是是终点,步数不用加一,结束循环返回结果

  1. #define max(a, b) ((a) > (b) ? (a) : (b))
  2. int jump(int* nums, int numsSize) {
  3. if (numsSize == 1) {
  4. return 0;
  5. }
  6. int curDistance = 0; // 当前覆盖最远距离下标
  7. int ans = 0; // 记录走的最大步数
  8. int nextDistance = 0; // 下一步覆盖最远距离下标
  9. for (int i = 0; i < numsSize; i++) {
  10. nextDistance = max(nums[i] + i, nextDistance); // 更新下一步覆盖最远距离下标
  11. if (i == curDistance) { // 遇到当前覆盖最远距离下标
  12. if (curDistance != numsSize - 1) { // 如果当前覆盖最远距离下标不是终点
  13. ans++; // 需要走下一步
  14. curDistance = nextDistance; // 更新当前覆盖最远距离下标(相当于加油了)
  15. if (nextDistance >= numsSize - 1) { // 下一步的覆盖范围已经可以达到终点,结束循环
  16. break;
  17. }
  18. } else { // 当前覆盖最远距离下标是集合终点,不用做ans++操作了,直接结束
  19. break;
  20. }
  21. }
  22. }
  23. return ans;
  24. }

7. K次取反后最大化的数组和

贪心策略:

        局部最优:让绝对值大的负数变为正数,当前数值达到最大,整体最优:整个数组和达到最大

        如果将负数都转变为正数了,K依然大于0

        局部最优:只找数值最小的正整数进行反转,当前数值可以达到最大,全局最优:整个 数组和 达到最大

  1. int find_min(int *nums, int numsSize) {
  2. int min = nums[0];
  3. int ind = 0;
  4. for (int i = 1; i < numsSize; i++) {
  5. if (nums[i] < min) {
  6. min = nums[i];
  7. ind = i;
  8. }
  9. }
  10. return ind;
  11. }
  12. int numsSum(int *nums, int numsSize) {
  13. int sum = 0;
  14. for (int i = 0; i < numsSize; i++) {
  15. sum += nums[i];
  16. }
  17. return sum;
  18. }
  19. int largestSumAfterKNegations(int *nums, int numsSize, int k) {
  20. while (k > 0) {
  21. int min_ind = find_min(nums, numsSize);
  22. if (nums[min_ind] >= 0) {
  23. break;
  24. }
  25. if (nums[min_ind] < 0) {
  26. nums[min_ind] = -nums[min_ind];
  27. }
  28. k--;
  29. }
  30. if (k != 0 && k % 2) {
  31. int min_ind = find_min(nums, numsSize);
  32. nums[min_ind] = -nums[min_ind];
  33. }
  34. int sum = numsSum(nums, numsSize);
  35. return sum;
  36. }

8. 加油站

暴力解法:超时

  1. int canCompleteCircuit(int *gas, int gasSize, int *cost, int costSize) {
  2. for (int i = 0; i < costSize; i++) {
  3. int rest = gas[i] - cost[i]; // 记录剩余油量
  4. int index = (i + 1) % costSize;
  5. while (rest > 0 && index != i) { // 模拟以i为起点行驶一圈
  6. rest += gas[index] - cost[index];
  7. index = (index + 1) % costSize;
  8. }
  9. // 如果以i为起点跑一圈,剩余油量>=0,返回该起始位置
  10. if (rest >= 0 && index == i) {
  11. return i;
  12. }
  13. }
  14. return -1;
  15. }

贪心策略:

每个加油站的剩余量rest[i]为gas[i] - cost[i]

i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,起始位置从i+1算起,再从0计算curSum

局部最优:当前累加rest[j]的和curSum一旦小于0,起始位置至少要是j+1,因为从j开始一定不行。全局最优:找到可以跑一圈的起始位置」

  1. int canCompleteCircuit(int *gas, int gasSize, int *cost, int costSize) {
  2. int curSum = 0;
  3. int totalSum = 0;
  4. int start = 0;
  5. for (int i = 0; i < gasSize; i++) {
  6. curSum += gas[i] - cost[i];
  7. totalSum += gas[i] - cost[i];
  8. if (curSum < 0) {
  9. start = i + 1; // 起始位置更新为i+1
  10. curSum = 0; // curSum从0开始
  11. }
  12. }
  13. if (totalSum < 0) { // 说明怎么走都不可能跑一圈
  14. return -1;
  15. }
  16. return start;
  17. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/284754

推荐阅读
相关标签