一往直前,贪心法
2.2.1:最少花费硬币数量问题
问题叙述: 有1元,5元,10元,50元,100元,500元的硬币各c1,c5,c10,c50,c100枚。现在要用这些硬币来支付A元,最少需要多少枚硬币,假定本题至少存在一种支付方案。
//在已有的硬币金额及数量 支付 指定人民币,获取其最小花费金币数
//思路策略:尽量选大硬币来支付
// 1. 使用coinDenominations保存硬币面额
// 2. 循环递减硬币面额, 每次循环选择 金币面额数量与花费钱数所能花费的金币面额数量的最小值(int payCoinCount=min(coinMap.at(coinDenominations.at(i)),payRMB/coinDenominations.at(i));),并累加于统计金币数minCoinCount,payRMB减去其花费金币面额和数量乘积
int GetMinCoinCountPayFor(map<int,int> &coinMap,int payRMB)
{
vector<int> coinDenominations;
for_each(coinMap.begin(),coinMap.end(),[&coinDenominations](const pair<int,int> &coinInfo){ coinDenominations.push_back(coinInfo.first);});
sort(coinDenominations.begin(),coinDenominations.end());
int minCoinCount=0;
for(decltype(coinDenominations.size()) i=coinDenominations.size()-1;i>=0;--i){
int payCoinCount=min(coinMap.at(coinDenominations.at(i)),payRMB/coinDenominations.at(i));
payRMB-=payCoinCount*coinDenominations.at(i);
minCoinCount+=payCoinCount;
if(payRMB==0)
break;
}
return minCoinCount;
}
int main()
{
map<int,int> coinMap{{1,200},
{5,10},
{10,200},
{100,2}};
int payRMB=241;
cout<<GetMinCoinCountPayFor(coinMap,payRMB);
}
2.2.2 区间调度问题(一往无前,贪心法)
问题叙述:有n项工作,每项工作分别在si 时间开始,在 ti 时间结束。对于每项工作,你都可以选择参与与否。如果选择了参与,那么自始自终都必须全程参与。 此外,参与工作的时间段不能重叠(即便是开始的瞬间和结束的瞬间也是不允许的)
//分析区间调度问题:在容易想到的策略中再次选择正确的规则
//在可选的工作中(于目前已选工作都不重叠的工作)中,每次都选取开始时间最早的工作
//(1)在可选的工作中,每次都选择结束时间最早的工作
// (2) 在可选的工作中,每次都选取用时最短的工作。
//(3)在可选的工作中,每次都选取与最少可选工作有重叠的工作。
// |----1---| (0,2)
// |----2----| (1,3)
// |----3----| (4,6)
// |------4--------| (5,9)
int GetMaxWorkCount(int n, const vector<int> &s,const vector<int> &t){
//设置数组添加其 工作,将其工作结束时间作为关键字,开始时间为值存入。
vector<pair<int,int>> workVec;
workVec.reserve(s.size());
for (decltype(s.size()) i = 0; i !=s.size(); ++i) {
pair<int,int> workPair={t.at(i),s.at(i)};
workVec.push_back(workPair);
}
//根据策略1对数组排序,结束时间早晚排序
sort(workVec.begin(),workVec.end());
int maxWorkCount=0;
int curWorkOverTime=0;
//如果 选择的工作开始时间大于当前工作的结束时间,即可选择并记录
for (int j = 0; j !=workVec.size(); ++j) {
if(workVec.at(j).second>curWorkOverTime)
{
++maxWorkCount;
curWorkOverTime=workVec.at(j).first;
}
}
return maxWorkCount;
}
2.2.3 字典序最小问题
问题描述:给定长度为N的字符串S,要构造一个长度为N的字符串T,起始,T为空串,随后反复进行下列任意操作。
- 从S 的头部删除一个字符,加到T的尾部
- 从S 的尾部删除一个字符,加到T的尾部
目标是要构造字典序最小的字符串T:
//贪心算法 :字典序最小问题
//从头部删除元素到加到T的尾部
//头部大添加头元素,尾部大添加尾部元素,一样大,获取倒序反转的S,若倒序的反转的大于等于正序,选择正序
void GetMinDictionaryOrderStr(const string &S,string &T){
int l=0,r=S.size()-1;
while(l!=r)
{
if(S.at(l)>S.at(r)) { T+=S.at(r); --r ;}
else if(S.at(l) < S.at(r)) { T+=S.at(l); ++l;}
else{
string aStr(S.begin()+l,S.begin()+r+1);
string bStr(aStr.rbegin(),aStr.rend());
if(aStr>bStr) {
T += S.at(r);
--r;
} else{
T+=S.at(l);
++l;
}
}
}
T+=S.at(l);
}
2.2.4 其他例题 (Saruman‘s Army)
问题描述:直线上 有N个点,点 i 的位置是 Xi 。从这 N 个点中选择若干个,给他们加上标记。对每一个点,其距离为 R 以内的区域里必须有带有标记的点(自己本身带有标记的点,可以认为与其距离为 0 的地方有一个带有标记的点)。在满足这个条件的情况下,希望能为尽可能少的点添加标记。请问至少要有多少点被加上标记?
//解法思路: *------*----------*----------*----------*-*-*-
// | r |
// | r |
// | |
//从上图我们可以看出,从设置的起始点+r的范围内的最后一个点被作为标记点,从标记点递推 r 范围外的第一个点被作为 下一个起始点,我们使用while循环递推这种规则即可
int GetMinLabelPointCount(const vector<int> &posVec,int R)
{
if(posVec.size()<=1) return posVec.size();
auto N=posVec.size();
decltype(N) i=0;
int minLabelPointCount=0;
while(i!=N){
int startPos=posVec.at(i);
while(i!=N && posVec.at(i)<=startPos+R) ++i;
int labelPos=posVec.at(i-1);
while(i!=N && posVec.at(i) <= labelPos+R ) ++i;
++minLabelPointCount;
}
return minLabelPointCount;
}
2.2.4 其他例题 (Fence Repair)
问题描述:农夫约翰为了修理栅栏,要将一块很长的木板切割成 N 块。准备切成的木板长度为L1,L2…LN, 未切割前木板的长度恰好未切割后木板长度的总和。 每次切断木板时 ,需要的开销为这块木板的长度。 例如长度为 21 的木板要切成长度为 5 ,8 ,8 的三块木板。长 21 的木板切成长为 13 和 8 的板时,开销为 21 . 再将长度 为 13 的板切成长度为 5 和 8 的板 时,开销是 13,于是开销就是 34,请求出按照木板要求将木板切割完最小的开销是多少。
//思路: 二叉树的子节点 * 其深度 = 总消耗
// 消耗最小的话:使子节点最小的板长为深度最大的子节点,由于木板是一分为二,可以理解为二叉树
// 这样的话就是: 每次筛选最短板和次短板 并合二为一,每次累加其和值 ,直到仅仅剩下最后一根木板即可。
// 15的木板 分成 {3,4,5,1,2}
//思路:
// 3
/*第一步 { 1,2 { 暂定} } /\ 合并后 n=4 ans=3
1 2
6
/ \
第二步 {3,3 {暂定}} 3 3 合并后 n=3 ans=3+6=9
/ \
1 2
第三步 {4,5,{6}} 合并后 n=2 ans=3+6+9=18
6 9
/ \ / \
3 3 4 5
/ \
1 2
最后一步 {6,9}
15 合并后n=1 ans=3+6+9+15=33 退出循环
/ \
6 9
/ \ / \
3 3 4 5
/ \
1 2
*/
int GetMinCostVigor(vector<int> &woodVec){
long long ans=0;
int N=woodVec.size();
//直到计算出木板为1块时为止
while(N>1){ //选择搜索比全排序更快
//直到计算出最短的板mii1和次短板mii2
int mii1=0,mii2=1;
if(woodVec.at(mii1) > woodVec.at(mii2)) swap(mii1,mii2);
for (int i = 2; i !=N ; ++i) {
if(woodVec.at(i) < woodVec.at(mii1)){
mii2 = mii1;
mii1 = i;
} else if(woodVec.at(i) < woodVec.at(mii2))
{
mii2=i;
}
}
//将两块板合并
auto t = woodVec.at(mii1)+woodVec.at(mii2);
ans+=t;
if(mii1 == N-1) swap(mii1,mii2);
woodVec.at(mii1) = t;
woodVec.at(mii2)=woodVec.at(N-1);
--N;
}
return ans;
}