力扣638. 大礼包(回溯法+剪枝优化)

第八十八天 --- 力扣638. 大礼包

题目一

力扣:力扣638. 大礼包

在这里插入图片描述
在这里插入图片描述

思路:回溯法

1、我们先不考虑礼包,直接去买东西,就是物品数量*物品价格,但是现在有优惠了,允许你买礼包。同一种礼包可一直买,也可以不同种礼包搭配。
2、现在我们的需求在needs中存着,我有两种选择,直接购买,买一种礼包。
<1>先算出直接买用多少钱。
<2>在众多礼包中选择符合要求的一个礼包,因为很多礼包,就得遍历礼包集合。我买了这个礼包,减少了一定的需求,那么花的钱就是,这个礼包花的钱+完成剩下的需求用的钱(把新的需求传下去,当后面算完就能回溯回来一个值,这个就是回溯思想)
<3>读题,我们要找到花钱最少是多少,所以要在买礼包和直接买之间找一个最小值作为答案。
3、“2”中分析了一次决策的过程,我们想,因为礼包之间可以任意搭配,所以礼包的决策会有很多次。一次选一个礼包,就完成了一次决策,那么就更新需求数组,因为每一次决策过程都一致,所以DFS实现,一个函数是一次决策,最后就能暴力枚举出来所有可能。
4、如果上述过程没明白,自己画一棵选择树,大部分DFS+回溯的过程都能在选择树上分析的很清楚,所以只要树整明白了,那么这个题就拿下了。

代码

细节:一定要注意,如果礼包中卖的物品多余所求,则不能买,所以要加以判断。

无剪枝的回溯

1、无剪枝,考虑所有可能的礼包。

class Solution {
    
    
public:
	vector<int> price;
	vector<vector<int>> special;
	vector<int> needs;

	int buyBySpecialOffers(vector<int> n) {
    
    
		int ans = 0;//这层决策,要花的最少钱
		for (int i = 0; i < n.size(); i++) {
    
    
			ans += price[i] * n[i];//先考虑不买礼包
		}
		int size_special = special.size();
		for (int i = 0; i < size_special; i++) {
    
    //买礼包,枚举礼包
			vector<int> tmp(n);//复制一遍需求,因为每次都是假设买这个礼包,会改变需求,一旦这个礼包不能买,就会导致数据损失
			int size1 = special[i].size();
			int price = special[i][size1 - 1];//礼包价格
			bool is_valid = true;//礼包能不能买
			for (int j = 0; j < size1 - 1; j++) {
    
    
				if (special[i][j] > n[j]) {
    
    //v出现了提供物品数大于需求
					is_valid = false;//不能买了
					break;
				}
				tmp[j] -= special[i][j];//假设能买,更新需求于临时需求数组
			}
			if (is_valid) {
    
    
				price += buyBySpecialOffers(tmp);//利用回溯思想,本礼包钱,加上剩下需求的钱
				ans = min(ans, price);
			}
			else {
    
    
				continue;
			}
		}
		return ans;
	}

	int shoppingOffers(vector<int>& price, vector<vector<int>>& special, vector<int>& needs) {
    
    
		this->price = price;
		this->special = special;
		this->needs = needs;
		return buyBySpecialOffers(needs);
	}
};

代码已经通过力扣OJ测试
(多次测试得到最短时间为):
在这里插入图片描述

剪枝优化

1、buyBySpecialOffers回溯函数并未改变
2、商家不是慈善家,有很多坑人的礼包:
<1>礼包只收钱,不卖你物品。属于抢劫行为的礼包,直接干掉就行。(极端情况,也要考虑进去)
<2>黑心商家,买了礼包反而更贵

class Solution {
    
    
public:
	vector<int> price;
	vector<vector<int>> special;
	vector<int> needs;

	int buyBySpecialOffers(vector<int> n) {
    
    //注释同上,此函数没变
		int ans = 0;
		for (int i = 0; i < n.size(); i++) {
    
    
			ans += price[i] * n[i];
		}
		int size_special = special.size();
		for (int i = 0; i < size_special; i++) {
    
    
			vector<int> tmp(n);
			int size1 = special[i].size();
			int price = special[i][size1 - 1];
			bool is_valid = true;
			for (int j = 0; j < size1 - 1; j++) {
    
    
				if (special[i][j] > n[j]) {
    
    
					is_valid = false;
					break;
				}
				tmp[j] -= special[i][j];
			}
			if (is_valid) {
    
    
				price += buyBySpecialOffers(tmp);
				ans = min(ans, price);
			}
			else {
    
    
				continue;
			}
		}
		return ans;
	}

	int shoppingOffers(vector<int>& price, vector<vector<int>>& special, vector<int>& needs) {
    
    
		this->price = price;
		this->needs = needs;
		//把坑人的大礼包去掉(剪枝):一件物品没有还要钱;买了礼包更贵
		int n1 = price.size();//这块操作读题就行
		vector<vector<int>> tmp;
		for (vector<int> item : special) {
    
    //数据预处理
			int cnt = 0, cnt_price = 0;//分别是统计卖出物品数,和按照原价买多少钱
			for (int i = 0; i < n1; i++) {
    
    
				cnt += item[i];
				cnt_price += item[i] * price[i];
			}
			if (cnt > 0 && item[n1] < cnt_price) {
    
    
				tmp.push_back(item);//只保留有效礼包
			}
		}
		this->special = tmp;
		return buyBySpecialOffers(needs);
	}
};

代码已经通过力扣OJ测试
(多次测试得到最短时间为):
在这里插入图片描述
在这里插入图片描述

Sum Up

这种回溯思路其实就是暴力枚举所有可能的选取礼包方案,一定要注意题目约定的数据量大小,一般就是<=10^2这个数量级的时候,即小数据量的时候,这个方法才可以。数据量上1000就不要用这个算法了,就用DP。

猜你喜欢

转载自blog.csdn.net/qq_45678698/article/details/120934067
今日推荐