关于贪心算法,左神网课中总结的其实是没有定式的,也就是说,我们一般是通过尝试不同的贪心策略来获得正确的贪心策略,而通过对数器的方法来验证贪心策略的选择是否正确。
其实还是要多刷相关的题目,感觉正确的贪心策略如果面试的时候遇到的话没见过的题目应该是想不出来的 - -
直接上题目了
1.给定一个二维数组,其中每个元素的第一个元素代表会议的开始时间,第二个元素代表会议的结束时间,会议室一个时间段只能安排一个会议,问最多能安排多少个会议
int test01(std::vector<std::vector<int>>& vec) {
if (vec.size() <= 1) {
return vec.size();
}
std::sort(vec.begin(), vec.end(), [](const vector<int>& v1, const vector<int>& v2) { return v1[1] < v2[1]; });
int res = 0;
int deadline = 0;
for (int i = 0; i < vec.size(); i++) {
if (vec[i][0] >= deadline) {
++res;
deadline = vec[i][1];
}
}
return res;
}
2.给定一个字符串string,只能由“x”和“。”组成,X表示墙不能放灯,。表示居民点可以放灯,一扇灯可以照亮三个位置,返回照亮所有位置的最小灯的个数
int test02(const string& str) {
if (str.size() == 0) return 0;
int index = 0;
int count = 0;
while (index < str.size()) {
if (str[index] == 'X') {
++index;
}
else {
if (index + 1 < str.size() && str[index + 1] == 'X') {
++count;
index += 2;
}
else if (index + 1 < str.size() && str[index + 1] == '.'){
count++;
if (index + 2 < str.size() && str[index + 2] == 'X') {
index += 2;
}
else {
index += 3;
}
}
}
}
return count;
}
3.切金条问题(可以直接搜题目描述)
int test03(vector<int>& vec) {
std::priority_queue<int, vector<int>> que;
for (int i = 0; i < vec.size(); i++) {
que.push(vec[i]);
}
int sum = 0;
int cur = 0;
while (que.size() > 1) {
int tmp1 = que.top();
que.pop();
int tmp2 = que.top();
que.pop();
sum += (tmp1 + tmp2);
que.push(tmp1 + tmp2);
}
return sum;
}
4.给定项目的花费和代价,以及自己的初始资金w以及最大能选择的项目个数k,求最大盈利
class Cmp1 {
bool operator()(vector<int> v1, vector<int> v2) {
return v1[0] < v2[0];
}
};
class Cmp2 {
bool operator()(vector<int> v1, vector<int> v2) {
return v1[1] > v2[1];
}
};
int test04(const vector<vector<int>>& vec, int w, int k) {
std::priority_queue < vector<int>, vector<vector<int>>, Cmp1> minConst;
std::priority_queue<vector<int>, vector<vector<int>>, Cmp2> maxPriority;
for (int i = 0; i < vec.size(); i++) {
minConst.push(vec[i]);
}
for (int i = 0; i < k; i++) {
while (!minConst.empty() && minConst.top()[0] <= w) {
maxPriority.push(minConst.top());
minConst.pop();
}
if (maxPriority.empty()) return w;
w += maxPriority.top()[1];
maxPriority.pop();
}
return w;
}
5.并查集基本结构
并查集的作用:用于处理一些不相交集合的合并及查询问题(即所谓的并、查)。比如说,我们可以用并查集来判断一个森林中有几棵树、某个节点是否属于某棵树等。
从名字上来看,并查集中最重要的操作就是并和查,也就是下面给定代码中的findParent操作和Union操作。
下面先给出并查集的基本数据结构:
class UNfind {
public:
UNfind(std::vector<int>);
bool isSameSet(int,int);
int findParent(int);
void unionUN(int,int);
int value;
//结点集合
std::unordered_map<int, int> nodes;
//结点的最终汇集点集合
std::unordered_map<int, int> parents;
//整个并查集中最终汇集点的记录
std::unordered_map<int, int> sizeMap;
};
具体操作实现如下所示:
UNfind::UNfind(std::vector<int> vec) {
for (int i = 0; i < vec.size(); i++) {
nodes[vec[i]] = vec[i];
parents[vec[i]] = vec[i];
sizeMap[vec[i]] = 1;
}
}
int UNfind::findParent(int cur) {
std::stack<int> path;
while (cur != parents[cur]) {
path.push(cur);
cur = parents[cur];
}
while (!path.empty()) {
//将沿途所有的结点的父节点改为此时的cur点
parents[path.top()] = cur;
path.pop();
}
return cur;
}
bool UNfind::isSameSet(int a,int b) {
if (nodes.find(a) == nodes.end() || nodes.find(b) == nodes.end()) {
return false;
}
return findParent(a) == findParent(b);
}
void UNfind::unionUN(int a,int b) {
if (nodes.find(a) == nodes.end() || nodes.find(b) == nodes.end()) {
return;
}
int aHead = findParent(a);
int bHead = findParent(b);
if (aHead != bHead) {
//直接小规模的结点集合挂在大规模的结点集合上
int aSetSize = sizeMap[aHead];
int bSetSize = sizeMap[bHead];
int big = aSetSize >= bSetSize ? aHead : bHead;
int small = big == aHead ? bHead : aHead;
parents[small] = big;
sizeMap[big] = aSetSize + bSetSize;
sizeMap.erase(bHead);
}
}
6.图的基本结构
基本的数据结构如下所示:
class Node;
class Edge {
public:
int weight;
Node* from;
Node* to;
Edge(int weight, Node* from, Node* to) {
this->weight = weight;
this->from = from;
this->to = to;
}
};
class Node {
public:
int value;
int in;
int out;
std::vector<Node*> nexts;
std::vector<Edge*> edges;
Node(int value) {
this->value = value;
in = 0;
out = 0;
}
};
class Graph {
public:
std::unordered_map<int, Node*> nodes;
std::unordered_set<Edge*> edges;
};
6.1 图的bfs
//1.对图进行宽度优先遍历
void test01_bfs(Node* node) {
if (node == nullptr) {
return;
}
queue<Node*> que;
unordered_set<Node*> set;
que.push(node);
set.insert(node);
while (!que.empty()) {
Node* cur = que.front();
que.pop();
cout << cur->value << endl;
for (auto& n : cur->nexts) {
if (set.find(n) != set.end()) {
set.insert(n);
que.push(n);
}
}
}
6.2 图的dfs
void test01_dfs(Node* node) {
if (node == nullptr) return;
std::stack<Node*> sta;
std::unordered_set<Node*> set;
sta.push(node);
set.insert(node);
cout << node->value << endl;
while (!sta.empty()) {
Node* tmp = sta.top();
sta.pop();
for (auto& n : tmp->nexts) {
if (set.find(n) == set.end()) {
sta.push(tmp);
set.insert(n);
cout << n->value << endl;
break;
}
}
}
}
6.3 图的拓扑排序算法
void test03(Graph* graph) {
//图的拓扑排序算法 一直删掉其中入度为0的点
//key代表某个node value代表剩余的入度
if (graph == nullptr) return;
std::unordered_map<Node*, int > map;
std::queue<Node*> que;
for (auto& m : graph->nodes) {
map[m.second] = m.second->in;
if (m.second->in == 0) {
que.push(m.second);
}
}
while (!que.empty()) {
Node* cur = que.front();
que.pop();
cout << cur->value << endl;
for (auto next : cur->nexts) {
map[next] = next->in--;
if (map[next] == 0) {
que.push(next);
}
}
}
}
7.最小生成树
7.1 kruskalMST算法
//思路:首先是我们通过优先队列来存储所有的边,将每个边按照从小到大的顺序排列。
//然后使用并查集结构,假如优先队列的对头元素也就是这条边的左右两侧不属于同一个集合,那么我们就要这个边,否则就不要。
class Cmp {
bool operator()(const Edge* e1, const Edge* e2) {
return e1->weight < e2->weight;
}
};
set<Edge*> test04(Graph* graph) {
UnionFind unionFind;
UnionFind.makeSets(graph->nodes);
priority_queue<Edge*, vector<Edge*>, Cmp> priQue;
for (Edge* e : graph->edges) {
priQue.push(e);
}
set<Edge*> result;
while (!priQue.empty()) {
Edge* edg = priQue.top();
priQue.pop();
if (unionFind.isSameSet(edg->from, edg->to)) {
result.insert(edg);
unionFind.unionFunc(edg->from, edg->to);
}
}
return result;
}
7.2 PrimTree算法
std::set<Edge*> test05(Graph* graph) {
//解锁的边进入小根堆
priority_queue<Edge*, vector<Edge*>, Cmp> priQue;
//哪些点被解锁了
unordered_set<Node*> set;
//已经考虑过的边
unordered_set<Edge*> set2;
std::set<Edge*> result;//依次挑选的边
//从无向图随便挑一个点
for (auto& node : graph->nodes) {
Node* tmp = node.second;
if (set.find(tmp) == set.end()) {
set.insert(tmp);
//解锁的点的所有边 放到小根堆里去
for (auto& e : tmp->edges) {
if (set2.find(e) == set2.end()) {
priQue.push(e);
set2.insert(e);
}
}
while (!priQue.empty())
{
//从边的小根堆中跳出一个最小的边
Edge* edge = priQue.top();
priQue.pop();
//to方向的点
Node* toNode = edge->to;
//同方向的点不在集合的时候,说明我们可以考虑这个带你
if (set.find(toNode) == set.end()) {
set.insert(toNode);
result.insert(edge);
for (Edge* nextEdge : toNode->edges) {
if (set2.find(nextEdge) == set2.end()) {
priQue.push(nextEdge);
set2.insert(nextEdge);
}
}
}
}
}
break;
}
return result;
}
8.dijkstra算法
Node* getMinDistanceAndUnSelectNode(unordered_map<Node*, int> map, unordered_set<Node*> set) {
Node* minNode = nullptr;
int minDistance = INT_MAX;
for (const auto& m : map) {
Node* tmp = m.first;
int num = m.second;
if (set.find(tmp) == set.end() && num < minDistance) {
minNode = tmp;
minDistance = num;
}
}
return minNode;
}
unordered_map<Node*,int> test01(Node* from) {
unordered_map<Node*, int> map;
unordered_set<Node*> set;
map[from] = 0;
Node* minNode = getMinDistanceAndUnSelectNode(map, set);
while (minNode != nullptr) {
int distance = map[minNode];
for (const auto& n : minNode->vec) {
Node* to = n->to;
if (map.find(to) == map.end()) {
map[to] = distance + n->weight;
}
else {
map[to] = std::min(map[to], distance + n->weight);
}
}
set.insert(minNode);
minNode = getMinDistanceAndUnSelectNode(map, set);
}
return map;
}