力扣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。

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JLU_LYM

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值