LeetCode-问题2178-拆分成最多数目的正偶数之和

文章讲述了如何解决LeetCode中的一道问题,即找到将一个数分解为正偶数之和的方案,要求元素不重复且数量最多。作者首先尝试使用回溯法,但发现超时。然后意识到贪心算法更为适合,通过从小到大依次添加偶数来达到目标,证明了这种方法的正确性并给出了优化后的解决方案。
摘要由CSDN通过智能技术生成

题目链接

https://leetcode.cn/problems/maximum-split-of-positive-even-integers/

解答过程

这个题目乍一看有点唬人,翻译一下就是把一个数做偶数之和分解,要求不能有重复元素,可能有多种分解方式,求包含元素最多的分解方式包含的元素个数。显然,如果目标值是奇数,那么解不存在。如果是偶数,那么怎么计算包含元素最多的分解方式包含的元素个数呢?

从题目要求到题目示例,我很直接就想起来回溯法…,说起来也逻辑也很顺,回溯法找出所有可能的分解方式,然后取包含元素最多的那种就可以了。吭哧吭哧开始写,

	public List<Long> maximumEvenSplit(long finalSum) {
		if (finalSum % 2 != 0) {
			return Collections.emptyList();
		}

		List<List<Long>> splits = new ArrayList<>();
		List<Long> split = new ArrayList<>();
		backtrack(splits, split, 0, finalSum);

		if (splits.isEmpty()) {
			return Collections.emptyList();
		}

		int maxSize = 0;
		int maxIdx = -1;
		for (int i = 0; i < splits.size(); i++) {
			split = splits.get(i);
			if (split.size() > maxSize) {
				maxSize = split.size();
				maxIdx = i;
			}
		}

		return splits.get(maxIdx);
	}

	private void backtrack(List<List<Long>> splits, List<Long> split, long last, long remain) {
		if (remain == 0) {
			splits.add(new ArrayList<>(split));
			return;
		}

		if (last > remain) {
			return;
		}

		long next = last + 2;
		while (next <= remain) {
			split.add(next);
			backtrack(splits, split, next, remain - next);
			split.remove(split.size() - 1);

			next += 2;
		}
	}

到这里还挺洋洋自得呢,虽然挺长时间没写回溯题目了,但还比较手顺。结果提交以后不通过,超时。

其实本地自测的时候就隐约觉得不对劲,比如,一个并不大的目标值28,可能的分解方式已经很多了,但其实第一个回溯出的结果就是满足题目要求的。我想在回溯过程中加一个escape,回溯出第一个结果就结束,但总觉得这种处理方式怪怪的。无奈看了一眼官方题解,额,果然犯了方向性的错误,这个题目更适合于贪心算法。

想一下,其实也很好理解,为了使分解后的元素数尽量多,那么就应该尽量用比较小的数,所以就应该2、4、6、8、…这样挨个累加,到最接近目标值时,把与目标值最后的那点差距加到最后一个元素上即可。为什么这样做是正确的呢?反正法。设定上述方式得到的分解方式包含的元素个数为n,如果n不是最优结果,那么必然存在包含元素数为n+1的分解方式,根据我们的分解逻辑,n+1的这种分解方式必然大于目标值了。因此得证。

	public List<Long> maximumEvenSplit(long finalSum) {
		if (finalSum % 2 != 0) {
			return Collections.emptyList();
		}

		List<Long> split = new ArrayList<>();
		long val = 2;
		long remain = finalSum;
		while (val <= remain) {
			split.add(val);
			remain -= val;
			val += 2;
		}

		int size = split.size();
		long last = split.get(size - 1);
		split.set(size - 1, last + remain);

		return split;
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值