CSP-J 2023 入门级 第一轮 完善程序(1)
(1)(寻找被移除的元素)问题:原有长度为n+1、公差为1的等差升序数列;将数列输入到程序的数组时移除了一个元素,导致长度为n的升序数组可能不再连续,除非被移除的是第一个或最后一个元素。需要在数组不连续时,找出被移除的元素。
试补全程序。
#include
#include
using namespace std;
int find_missing(vector<int>& nums) {
int left = 0, right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] == mid + ①) {
②
} else {
③
}
}
return ④
}
int main() {
int n;
cin >> n;
vector<int> nums(n);
for(int i = 0; i < n; i++)cin >> nums[i];
int missing_number = find_missing(nums);
if (missing_number == ⑤) {
cout << "Sequence is consecutive" << endl;
} else {
cout << "Missing number is " << missing_number << endl;
}
return 0;
}
先看主函数
int main() {
int n;
cin >> n;
vector<int> nums(n);
for(int i = 0; i < n; i++)cin >> nums[i];
int missing_number = find_missing(nums);
if (missing_number == ⑤) {
cout << "Sequence is consecutive" << endl;
} else {
cout << "Missing number is " << missing_number << endl;
}
return 0;
}
输入n,输入n个元素保存在顺序表nums中,下标0~n-1。根据题目,等差数列公差为1,一共n+1个数字,如果被移除的不是nums[0]
,那么这n+1个数字最小值为nums[0]
,最大值应该为nums[0]+n
,在这n+1个数中移除一个数字。
调用find_missing函数,查找丢失的数字,赋值给missing_number。
如果丢失的数字是(5),那么输出“序列是连续的”,否则输出丢失的数字。
也就是说如果丢失的数字是n+1个数字中的第1个或最后一个,find_missing函数会返回(5)处应该填的值,此时整个序列就是连续的。
int find_missing(vector<int>& nums) {
int left = 0, right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] == mid + ①) {
②
} else {
③
}
}
return ④
}
而后看find_missing函数,传入是的nums的引用。看这一代码结构,有left, right, mid,自然是使用了二分查找算法。
设为nums的元素下标从0~nums.size()-1,先将left, right指向nums保存的序列的两端。
只要left < right
,每次循环取中点位置mid为mid = left+(right-left)/2
,该写法在数学意义上等价于mid = (left+right)/2
。
之所以写成
mid = left+(right-left)/2
,是因为这个写法可以保证即使left+right的值超过int可以表示的数值范围,仍能正确求出(left+right)/2
的值
在整数域二分查找算法中,如果取中点的写法为mid = (left+right)/2
,那么就是在求满足某一条件的最小值的最小下标。(反之,如果取中点的写法为mid = (left+right+1)/2
,那么就是在求满足某一条件的最大值的最大下标。)
该题要找的是缺失的数字,那么二分查找的条件就可以是“大于缺失数字”,二分查找后找到的是“大于缺失数字的最小值的最小下标”。
二分查找的过程中,要始终保持要寻找的值的下标在left~right范围之内。
right = mid
。left = mid+1
。设数字序列起始值为s,去掉的数字为s+m。
0 | 1 | 2 | … | m-1 | m | m+1 | … | n-1 | n | |
---|---|---|---|---|---|---|---|---|---|---|
原数字序列 | s | s+1 | s+2 | … | s+m-1 | s+m | s+m+1 | … | s+n-1 | s+n |
去掉m后的序列:nums | s | s+1 | s+2 | … | s+m-1 | s+m+1 | s+m+2 | … | s+n |
可以看到,在下标小于m的范围内,nums[i]
的值应该等于s+i
,下标大于等于m时,nums[i]
的值应该等于s+i-1
。
假设去掉的数字不是第一个数字s,起始数字s就是nums[0]
,nums[mid]
应该与mid+nums[0]
比较,(1)处应该填nums[0]
,选B。
nums[mid]
的值是mid+nums[0]
,那么mid一定小于缺失的数字,不满足条件“大于缺失数字”,应该让left = mid+1
,(2)处选A。nums[mid]
的值不是mid+nums[0]
,那么mid一定大于缺失的数字,满足条件“大于缺失数字”,应该让right = mid
,(3)处选C。while循环跳出后,left或right的值就是找到的是"大于缺失数字"的最小值的最小下标,该下标就是上表中的m,缺失的值应该是s+m
,也就是代码中的nums[0]+left
或nums[0]+right
,(4)处选A或B。
如果去掉的数字就是第一个数字s,或最后一个数字,整个序列是连续的序列,无论mid为何值,都满足nums[mid]==mid+nums[0]
,按照上述写法运行程序,会不断的改变left,而right不变,直到while循环跳出,此时left与right的值都为right的初始值nums.size()-1
,返回值应该为nums[0]+nums.size()-1
。
在主函数中,nums的长度nums.size()
就是n,因此当整个序列是连续序列时,find_missing函数的返回值missing_number就是nums[0]+n-1
,由于序列连续公差为1,nums[0]+n-1
就是nums[n-1]
。
而当缺失的数字是大于nums[n-2]
,小于nums[n-1]
的数字时,求大于缺失数字的最小值的最小下标,得到的结果(while循环跳出后left或right的值)也是n-1,find_missing函数返回的值也是nums[0]+n-1
,此时nums[0]+n-1
不为nums[n-1]
。
所以只有返回的nums[0]+n-1
的值与nums[n-1]
的值相等时,序列才是连续的。
(5)处选D。