Greedy algorithm small solution

Table of contents

1. Concept

2. Selection sort

3. Balance String

4. The best time to buy and sell stocks

5. Jumping game

6. Coin change

7. Multi-machine scheduling problem

8. Activity selection

9. No overlapping interval


1. Concept

  • Greedy algorithm (also known as greedy algorithm) means that when solving a problem, always make the best choice at present. That is to say, without considering the overall optimality, what is made is a local optimal solution in a certain sense
  • Greedy choice means that the overall optimal solution of the problem to be sought can be achieved through a series of local optimal choices, namely greedy choice. This is the first basic element for a greedy algorithm to work
  • When the optimal solution of a problem contains the optimal solution of its sub-problems, the problem is said to have the optimal substructure property. The greedy strategy is used to obtain the optimal solution at every transformation. The optimal substructure property of the problem is the key characteristic that the problem can be solved by greedy algorithm. Every operation of the greedy algorithm has a direct impact on the result. The greedy algorithm makes a choice for the solution of each subproblem and cannot go back
  • The basic idea of ​​the greedy algorithm is to proceed step by step from an initial solution of the problem. According to a certain optimization measure, each step must ensure that a local optimal solution can be obtained. Only one data is considered in each step, and its selection should meet the conditions of local optimization. If the next data and part of the optimal solution are no longer a feasible solution, the data will not be added to the partial solution until all the data is enumerated, or the algorithm can no longer be added.
  • In practice, there are very few cases where the greedy algorithm is applicable. Generally, to analyze whether a problem is suitable for the greedy algorithm, you can first select several actual data under the problem for analysis, and then you can make a judgment

Problems with the greedy algorithm:

  • There is no guarantee that the final solution obtained is optimal
  • Problems that cannot be used to find the maximum or minimum
  • Only the range of feasible solutions satisfying certain constraints can be found

2. Selection sort

Difficulty: Easy 

Selection sort uses a greedy strategy. The greedy strategy it adopts is to select the minimum value from the unsorted data each time, and put the minimum value at the starting position of the unsorted data until the unsorted data is 0, then the sorting ends

#include <iostream>
void Swap(int* num1, int* num2) 
{
	if (num1 == num2) return;
	*num1 += (*num2);//a = a+b
	*num2 = (*num1) - (*num2);//b = a
	*num1 -= (*num2);
}
void SelectSort(int* arr, int size)
{
	for (int i = 0; i < size - 1; ++i) 
	{
		int minIndex = i;
		for (int j = i + 1; j < size; ++j)
		{
			if (arr[j] < arr[minIndex]) minIndex = j;
		}
		Swap(&arr[i], &arr[minIndex]);
	}
}
int main()
{
	int arr[] = { 10,8,6,20,9,77,32,91 };
	SelectSort(arr, sizeof(arr) / sizeof(int));
	for (int i = 0; i < sizeof(arr)/sizeof(int); ++i) {
		std::cout << arr[i] << " ";
	}
	return 0;
}

3. Balance String

Difficulty: Easy

Greedy strategy: Do not have nested balance, as long as the balance is reached, it will be divided immediately.
Therefore, a variable balance can be defined to change in different directions when encountering different characters. When the balance is 0 and the balance is reached, the number of divisions will be updated, such
as :
Scan the string s from left to right, balance - 1 when L is encountered, and balance + 1 when R is encountered. When the balance is 0, update the record ++count, if the last count == 0, it means that s only needs to be kept as it is, and return 1

class Solution {
public:
    int balancedStringSplit(string s) {
        int count = 0;
        int balance = 0;
        for (int i = 0; i < s.size(); ++i) {
            if (s[i] == 'L') --balance;
            if (s[i] == 'R') ++balance;
            if (balance == 0) ++count;
        }
        return count;
    }
};

4. The best time to buy and sell stocks

Difficulty: Medium

Consecutive rising trading days: Buying on the first day and selling on the last day make the most profit, which is equivalent to buying and selling every day. Consecutive
falling trading days: Do not buy and sell the most profitable, that is, you will not lose money.
Therefore, you can traverse the entire stock trading day price list, buy and sell on all rising trading days (make all profits), and not buy and sell on all falling trading days (never lose money)

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int ret = 0;
        for(int i = 1; i < prices.size(); ++i) {
            int profit = prices[i] - prices[i - 1];
            if(profit > 0) ret += profit;
        }
        return ret;
    }
};

5. Jumping game

Difficulty: Medium

For any position y in the array, how do we judge whether it is reachable? According to the description of the topic, as long as there is a position x, it can reach itself, and the maximum length of its jump is x + nums[x], this value is greater than or equal to y, that is, x+nums[x] ≥ y, then the position y is also can reach

Traverse each position in the array in turn, and maintain the furthest reachable position in real time. For the currently traversed position x, if it is within the range of the farthest reachable position, then it can reach the position by several jumps from the starting point, so the farthest reachable position can be updated with x+nums[x]

During the traversal process, if the farthest reachable position is greater than or equal to the last position in the array, it means that the last position is reachable, and you can directly return True. If the last position is still unreachable after traversal, return False

class Solution {
public:
    bool canJump(vector<int>& nums) {
        int maxIndex = 0;
        for(int i = 0; i < nums.size(); ++i) {
            if(i <= maxIndex) //i位置在最大范围内,说明i位置可以到达
            {
                maxIndex = max(maxIndex, i + nums[i]);//更新最大范围
                if(maxIndex >= nums.size() - 1) 
                    return true;//若最大范围包括最后一个位置,说明最后一个位置可以到达
            }
        }
        return false;
    }
};

6. Coin change

Difficulty: Easy 

Using the idea of ​​​​greedy algorithm, use as much banknotes as possible in each step. Do the same in everyday life. Values ​​have been arranged in ascending order in the program

#include <iostream>
#include <vector>
using namespace std;
int Solve(int money, vector<pair<int, int>>& moneyCount)
{
	int num = 0;
	for (int i = moneyCount.size() - 1; i >= 0; --i)//逆序:先使用面值大的钱币
	{
		int count = min(moneyCount[i].second, money / moneyCount[i].first);//需要的数量和拥有的数量中选择较小的
		money -= moneyCount[i].first * count;
		num += count;
	}
	if (money != 0) return -1;//找不开钱
	return num;
}
int main()
{
	//first:面值 second:数量
	vector<pair<int, int>> moneyCount = { {1,3},{2,1},{5,4},{10,3},{20,0},{50,1},{100,10} };
	int money = 0;
	cout << "请输入需要支付多少钱" << endl;
	cin >> money;

	int ret = Solve(money, moneyCount);
	if (ret == -1) cout << "No" << endl;
	else cout << ret << endl;
	return 0;
}

7. Multi-machine scheduling problem

Difficulty: Medium 

There is no effective solution to this problem (seeking the optimal solution), but a better approximation algorithm can be designed with a greedy selection strategy (seeking the suboptimal solution)

When n<=m, just assign jobs to each machine; when n>m, first sort n jobs from large to small, and then assign jobs to idle machines in this order. That is, from the remaining jobs, select the one that requires the longest processing time, and then select the one with the second longest processing time in turn, until all jobs are processed, or the machine can no longer process other jobs. If the job that requires the shortest processing time is assigned to an idle machine each time, then all other jobs may be processed and only the job that takes the longest time is left to be processed, which is bound to be less efficient

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Compare {
public:
	bool operator()(int x, int y) {
		return x > y;
	}
};
int GreedStrategy(vector<int>& works, vector<int>& machines) 
{
	//按作业时间从大到小排序
	sort(works.begin(), works.end(), Compare());
	//若作业数小于等于机器数,直接返回最大的作业时间即可
	if (works.size() <= machines.size()) return works[0];

	//从大到小为每个作业分配机器
	for (int i = 0; i < works.size(); ++i) {
		//假设选择第一个机器
		int minMachines = 0;
		int time = machines[minMachines];
		//从机器中选择作业时间最小的
		for (int j = 1; j < machines.size(); ++j) {
			if (time > machines[j]) {
				minMachines = j;
				time = machines[j];
			}
		}
		//将当前作业交给作业时间最小的机器
		machines[minMachines] += works[i];
	}

	//从所有机器中选择总共作业时间最长的
	int ret = machines[0];
	for (int i = 1; i < machines.size(); ++i) {
		ret = max(ret, machines[i]);
	}
	return ret;
}
int main()
{
	int n = 0, m = 0;
	cout << "请输入作业数和机器数" << endl;
	cin >> n >> m;

	vector<int> works(n);
	vector<int> machines(m, 0);//存储每台机器总共的作业时间
	cout << "请输入各个作业所需要的时间" << endl;
	for (int i = 0; i < n; ++i) {
		cin >> works[i];
	}

	cout << GreedStrategy(works, machines) << endl;
	return 0;
}

8. Activity selection

Difficulty: Medium 

Greedy strategy:

  • Every time the activity with the earliest start time is selected, the optimal solution cannot be obtained
  • Every time the activity with the shortest duration is selected, the optimal solution cannot be obtained
  • Each time the activity with the earliest end time is selected, the optimal solution can be obtained. Choose this way to leave as much time as possible for unscheduled activities. The activity set S shown in the figure below, in which the activities are sorted monotonically increasing according to the end time

 

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Compare
{
public:
	bool operator()(pair<int,int> p1, pair<int,int> p2) {
		return p1.second < p2.second;
	}
};
int GreedyActivitySelector(const vector<pair<int, int>>& act)
{
	int num = 1;//记录可以举行的活动的个数
	int i = 0;
	for (int j = 1; j < act.size(); ++j) {
		if (act[j].first >= act[i].second) {
			i = j;
			++num;
		}
	}
	return num;
}
int main()
{
	int number = 0;
	cin >> number;
	vector<pair<int, int>> act(number);
	for (int i = 0; i < act.size(); ++i) {
		cin >> act[i].first >> act[i].second;
	}

	//按照活动截止日期从小到大排序
	sort(act.begin(), act.end(), Compare());
	int ret = GreedyActivitySelector(act);
	cout << ret << endl;
	return 0;
}

9. No overlapping interval

Difficulty: Medium 

 method one

This question is very similar to the eighth question "Activity Selection". Find the maximum number of non-conflicting intervals, and then subtract the maximum value from the total number of intervals to get the minimum number of intervals to be removed.

class Solution {
public:
    static bool cmp(vector<int>& a, vector<int>& b) {
        return a[1] < b[1];
    }
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        std::sort(intervals.begin(), intervals.end(), cmp);
        int num = 1;
        int i = 0;
        for (int j = 1; j < intervals.size(); ++j) {
            if (intervals[j][0] >= intervals[i][1]) {
                i = j;
                ++num;
            }
        }
        return intervals.size() - num;
    }
};

Method Two

Sort the intervals according to the starting point, and the greedy algorithm can play a very good role.
Greedy strategy:
when considering the intervals according to the order of the starting point. Use a prev pointer to keep track of the interval just added to the final list

Case 1:
The two intervals currently considered do not overlap:
in this case, do not remove any intervals, assign prev to the following intervals, and the number of removed intervals remains unchanged

Case 2:
Two intervals overlap, and the end point of the latter interval is before the end point of the former interval.
In this case, you can simply use only the latter interval. Because the length of the latter interval is smaller, more space can be left to accommodate more intervals. Therefore, prev is updated to the current interval, the number of removed intervals + 1

Case 3:
Two intervals overlap, and the end point of the latter interval is after the end point of the previous interval
. In this case, we use a greedy strategy to deal with the problem and directly remove the latter interval. This can also leave more space to accommodate more intervals. Therefore, prev is unchanged, and the number of removed intervals + 1

bool cmp(const vector<int>& a, const vector<int>& b)
{
    //按起点递增排序
    return a[0] < b[0];
}
class Solution {
public:
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        if (intervals.size() == 0) {
            return 0;
        } 
        //按起点递增排序
        sort(intervals.begin(), intervals.end(), cmp);  

        int end = intervals[0][0], prev = 0, count = 0;
        for (int i = 1; i < intervals.size(); i++) 
        {
            if (intervals[prev][1] > intervals[i][0]) {//两个区间冲突
                if (intervals[prev][1] > intervals[i][1]) {
                    //情况2
                    prev = i;
                } 
                //情况3
                count++;
            } 
            else {
                //情况1
                prev = i;
            }
        } 
        return count;
    }
};

Guess you like

Origin blog.csdn.net/GG_Bruse/article/details/130001863