(PTA Advanced)1044.Shopping in Mars (区间查找,二分查找)

原题:https://pintia.cn/problem-sets/994805342720868352/problems/994805439202443264

在这里插入图片描述
Sample Input 1:
16 15
3 2 1 5 4 6 8 7 16 10 15 11 9 12 14 13

Sample Output 1:
1-5
4-6
7-8
11-11

Sample Input 2:
5 13
2 4 5 7 9

Sample Output 2:
2-4
4-5

题目大意

给定一数字序列,找到其中一连续段,使得这个连续段内的数字和恰好等于给定价格M;若不能找到恰
好等于,则找到让自己付出价格最少的一个子区间(该区间的总金额大于等于需支付价格),并求所有可能得结果。

方法一(暴力搜索)

假定输入数字长度为N,则 i i i 从 1 到 N, 每次找从 i i i 开始满足条件得最小长度,下标记作 j j j。最后,输出满足条件且差值最小的区间端点。

/* 1044 Shopping in Mars (25 分) */
// 暴力求解,复杂度 O(n^2)
#include<vector>
#include<iostream>

using namespace std;

const int inf = 100000000;

typedef struct node {
	int start;
	int end;
}node;

vector<int> list; // 钻石序列
int minlost = inf; // 当前最小差距
vector<node> ans;  // 答案序列
int main() {
	int n, m, i, j;
	scanf("%d%d", &n, &m);
	list.resize(n);
	for (i = 0; i < n; i++) scanf("%d", &list[i]);
	// 暴力求解
	for (i = 0; i < n; i++) {
		int sum = 0;
		for (j = i; j < n && sum < m; j++) sum += list[j]; // j 是序列的下一位
		if (sum - m < minlost && sum >= m) {
			ans.clear();
			ans.push_back({ i,j });
			minlost = sum - m;
		}
		else if (sum - m == minlost) ans.push_back({ i,j });
	}
	for (auto iter = ans.begin(); iter != ans.end(); iter++) {
		node tmp = *iter;
		printf("%d-%d\n", tmp.start + 1, tmp.end);
	}
	
	return 0;
}

由于涉及双重for循环,所以复杂度为 O ( n 2 ) O(n^{2}) O(n2),会有两个测试点超时。
在这里插入图片描述

改进(折半查找)

由于暴力搜索的复杂度为 O ( n 2 ) O(n^{2}) O(n2),所以考虑改为二分查找,其复杂度为 O ( l o g n ) O(logn) O(logn)
但是二分查找只适用于有序序列,所以考虑将输入的数字序列叠加起来,令数组sum[i]表示输入数字num[1]到num[i]的加和,这样,和序列即为非递减序列,然后利用二分查找搜索两个端点,具体做法使固定一个端点,再二分查找另一个端点,复杂度为 O ( n ∗ l o g n ) O(n * logn) O(nlogn)
注意:与普通的二分查找不一样的地方使,我们不是找到精确等于的地方,而是大于等于的位置

/* 1044 Shopping in Mars (25 分) */
// 拿空间换时间,遍历两次,复杂度为
#include<vector>
#include<iostream>

using namespace std;

const int inf = 100000000;
vector<int> sum; // 和序列
vector<int> ans; // 答案
int n, m;

void BinarySearch(int start, int& end, int& tmpsum) {
	int left = start, right = n; // 总所有start后的位置找一个
	while (left < right) {
		int mid = (left + right) / 2;
		// 表示[start,mid]区间的和
		if (sum[mid] - sum[start - 1] < m)  //终点扩大到右半段
			left = mid + 1;
		else right = mid; // 找大于等于的位置
	}
	end = right;
	tmpsum = sum[end] - sum[start - 1];
}
int main() {
	int i, j, tmp;
	scanf("%d%d", &n, &m);
	sum.resize(n + 1);
	for (i = 1; i <= n; i++) {
		scanf("%d", &sum[i]);
		sum[i] += sum[i - 1]; // sum[0] 初始为0
	}
	int minlost = inf; // 最小差距
	// 开始折半查找
	for (i = 1; i <= n; i++) { // 起点
		BinarySearch(i, j, tmp);
		if (tmp >= m) {
			if (tmp - m < minlost) {
				ans.clear();
				minlost = tmp - m;   
				ans.push_back(i); 
				ans.push_back(j);
			}
			else if (tmp - m == minlost) {
				ans.push_back(i);
				ans.push_back(j);
			}
		}
	}
	for (i = 0; i < ans.size(); i += 2)
		printf("%d-%d\n", ans[i], ans[i + 1]);

	system("pause");
	return 0;
}

最终通过测试:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值