关键点
1.map的遍历方式
map<int, int>occ0Num, occ1Num;
for (auto it = thetaSet.begin(); it != thetaSet.end(); ++it) {
num = num + occ0Num[*it] - occ1Num[*it];
auto nextIt = next(it); // 获取下一个迭代器
if (num >= maxNum && nextIt != thetaSet.end()) {
bestTheta = *nextIt; // 使用下一个元素的值
maxNum = num;
}
}
2.时间复杂度优化:减少重复计算
100分思路通过预处理(排序和计数)以及有效地更新正确预测的次数来避免了不必要的重复计算,显著提高了效率。与70分的暴力枚举方法相比,它将时间复杂度从 O ( n 2 ) O(n^2) O(n2) 降低到了 O ( n l o g n ) O(n log n) O(nlogn),这对于大数据集来说是一个重大的改进。
暴力枚举方法(70分思路):
-
这个方法通过对所有可能的阈值进行枚举来找到最佳阈值。对于每个可能的阈值,它遍历所有样本,计算以该阈值进行分类时的准确性(即正确预测的次数)。这需要两层嵌套循环:外层循环遍历所有可能的阈值,内层循环遍历所有样本来计算准确性。
- 时间复杂度: 如果有 n 个样本,则外层循环执行 n 次(因为阈值是从样本中选取的),内层循环也执行 n 次(每次都要检查所有样本)。因此,总时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
减少重复运算方法(100分思路):
-
首先,预先计算每个可能阈值对应的分类结果,这是通过对样本进行排序和统计每个特征值下真实结果为真和假的样本数量来实现的。接下来,它只遍历一次排序后的样本列表来初始化正确预测次数,然后遍历所有不同的特征值作为可能的阈值,而不是重新计算每个可能阈值的正确预测次数。这是通过更新一个累计计数来完成的,该计数在遍历过程中根据当前阈值下样本分类的变化而调整。
- 时间复杂度: 对所有样本排序的时间复杂度为 O ( n l o g n ) O(n log n) O(nlogn)。初始化正确预测次数的循环时间复杂度为 O(n)(单次遍历)。遍历不同的特征值(可能的阈值)来调整正确预测次数的循环也是 O ( n ) O(n) O(n),因为即使是在所有不同的特征值上迭代,这个数量也不会超过 n(在最坏情况下,所有特征值都不相同)。因此,整体时间复杂度是 O ( n l o g n + n ) O(n log n + n) O(nlogn+n),即 O ( n l o g n ) O(n log n) O(nlogn),这主要由排序决定。
解题思路
题目是关于找到一个最佳阈值(θ)来评价预测模型的性能。这个预测模型基于一个特征值(y)来预测是否达到某个结果,使用二分类的方式,即预测结果为真(1)或假(0)。评价的核心是找到一个阈值,使得当预测值与真实结果相等时的次数最多。代码的解题思路如下:
-
数据读取:读取样本数量以及每个样本的特征值(y)和真实结果(result)。同时,初始化两个映射(
occ0Num
和occ1Num
),它们分别记录每个特征值对应的结果为假(0)和真(1)的样本数量。还有一个集合(thetaSet
)来存储所有可能的阈值(即所有不同的特征值)。 -
预处理:对样本按照特征值(y)进行排序,确保后续处理有序进行。排序同时也方便后续确定阈值的选择。
-
初始化:初始化当前的最佳预测次数
num
为最小特征值作为阈值时的正确预测次数,这通过比较每个样本的特征值与最小特征值(作为初始阈值)来确定,并计算满足条件(预测等于真实结果)的样本数。 -
寻找最佳阈值:遍历所有可能的阈值(通过遍历每个不同的特征值)。对每个阈值,根据其将样本分为两类(大于等于阈值、小于阈值)的能力来调整正确预测的次数。每次迭代调整基于前一个阈值的正确次数,并考虑当前阈值导致的结果变化(即增加因特征值大于等于阈值且结果为假的样本数,减少因特征值小于阈值且结果为真的样本数)。每当找到更好的分割(即正确预测的次数增加),更新最佳阈值
bestTheta
和最大预测正确次数maxNum
。 -
输出最佳阈值:在完成所有可能的阈值检验后,输出能得到最多正确预测次数的阈值
bestTheta
。
完整代码
【100分思路-减少重复运算】
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <set>
using namespace std;
struct MyScore
{
int y;
bool result;
};
bool predict(int y, int theta) {
return y >= theta;
}
bool cmp(MyScore& a, MyScore& b) {
return a.y < b.y;
}
int n, bestTheta, maxNum, num;
map<int, int>occ0Num, occ1Num;
set<int>thetaSet;
int main() {
cin >> n;
vector<MyScore>list(n);
for (size_t i = 0; i < n; i++) {
cin >> list[i].y >> list[i].result;
auto it0 = occ0Num.find(list[i].y), it1 = occ1Num.find(list[i].y);;
if (it0 == occ0Num.end()) occ0Num[list[i].y] = 0;
if (it1 == occ1Num.end()) occ1Num[list[i].y] = 0;
if (list[i].result) occ1Num[list[i].y]++;
else occ0Num[list[i].y]++;
thetaSet.insert(list[i].y);
}
sort(list.begin(), list.end(), cmp);
for (auto& it : list) {
if (predict(it.y, list[0].y) == it.result) num++;
}
bestTheta = list[0].y, maxNum = num;
for (auto it = thetaSet.begin(); it != thetaSet.end(); ++it) {
num = num + occ0Num[*it] - occ1Num[*it];
auto nextIt = next(it); // 获取下一个迭代器
if (num >= maxNum && nextIt != thetaSet.end()) {
bestTheta = *nextIt; // 使用下一个元素的值
maxNum = num;
}
}
cout << bestTheta;
return 0;
}
【70分思路-暴力枚举】
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct MyScore
{
int y;
bool result;
};
bool predict(int y, int theta) {
return y >= theta;
}
bool cmp(MyScore& a, MyScore& b) {
return a.y > b.y;
}
int n, bestTheta, maxNum;
int main() {
cin >> n;
vector<MyScore>list(n);
for (size_t i = 0; i < n; i++)
{
cin >> list[i].y >> list[i].result;
}
sort(list.begin(), list.end(), cmp);
for (size_t i = 0; i < n; i++)
{
int theta = list[i].y, num = 0;
for (auto& it : list)
{
if (predict(it.y, theta) == it.result) num++;
}
if (num > maxNum)
{
bestTheta = theta;
maxNum = num;
}
}
cout << bestTheta;
return 0;
}