文章目录
- 题目
- 思路
- 如何建立左右区间?
- 如何查找最高点?
- 那我们怎么判断 `num` 到底处于什么样的位置呢?
- 如何确定插入位置?
- 插入元素
- 代码
题目
给一个只循环递增一次的数组 res,res 满足首元素大于等于尾元素,形如:
4,5,6,7,2,4
再给出一个整型数字 num,将其插入到数组中应在的位置。
示例:
输入:
res = 4,5,6,7,2,4
num = 3
输出:
4,5,6,7,2,3,4
思路
用二分查找确定应该插入的位置,难点在于左右区间的建立。
如何建立左右区间?
首先明确两点,对于整个数组而言:
- 首元素一定
大于等于
尾元素 - 以数组的最大值为界限,最大值左边的元素一定
大于等于
右边的元素
用图像表示数组是这样的(黑色表下标,紫色表值):
我们可以看到,要插入的元素无非在最高点的左边或右边(自下文始,最高点用high替代,首元素位置用begin表示,尾元素位置用last表示):
- 当
num
处于最高点左边时,二分查找的范围应该是[begin, high]
- 当
num
处于最高点右边时,二分查找的范围应该是[high+1,last]
也就是说,划定二分查找范围(左右区间的建立)的重中之重在于最高点的确定。
如何查找最高点?
个人想到两种方法:
- 遍历查找,时间复杂度O(n)
算法思想没有什么多说的,中规中矩的遍历。
int high = 0;
for (int i = 1; i < res.size(); i++) {if (res[high] > res[i]) {break;}high = i;
}
- 二分查找,时间复杂度O(log2n)
算法思想是:
- 先以数组首元素、尾元素作为二分查找的左右边界,中间元素暂定为high
- 以
左边界小于右边界
作为while的循环条件 - 首先判断此时的high是否大于首元素
- 大于首元素证明此时的high处于
真正最高点的左边
或就是真正最高点
,此时需要判定high+1中元素和high中元素之间的关系:
- 如果high+1中元素大于high中元素,表明
真正最高点应该在high的左边
,因此更新左边界——left = high + 1
- 如果high+1中元素小于high中元素,表明
high即为真正最高点
,因此break出while循环即可
- 小于首元素证明此时的high处于
真正最高点的右边
,此时需要判定high和high-1所指元素之间的关系:
- 如果
res[high - 1] < res[high]
(如举例中high=6时,4>3),表明最高点仍在 high-1的左边,也就是high-1也处于真正最高点的右边,因此要更新右边界——right = high - 2
- 如果
res[high - 1] > res[high]
, 此时表明high-1即为最高点,因此将high–并break出while循环即可
- 每次循环更新完左右边界之后需要更新high值——
high = left + (right - left) / 2;
- 跳出while循环得到的high即为最高点的位置
int size = res.size() - 1;
int left = 0;
int right = size;
int high = left + (right - left) / 2;
int flag = res[0];
while (left < right) {if (res[high] >= flag) {if (res[high + 1] > res[high]) {left = high + 1;}else {break;}}else {if (res[high - 1] < res[high]) {right = high - 2;}else {high--;break;}}high = left + (right - left) / 2;
}
那我们怎么判断 num
到底处于什么样的位置呢?
这时应该结合之前我们说到的一句话:
首元素一定 大于等于
尾元素
那我们做个归纳,如果:
num > res[0]
,num 处于 high 左边res[end] < num == res[0]
,num 处于 high 右边,插入到尾元素的位置res[end] == num == res[0]
,不可能出现这种情况,因为这种情况下num没有位置可以插入。res[end] == num < res[0]
,num 处于 high 左边,插入到首元素的位置res[end] > num
,num 处于 high 右边
第二点和第四点是什么意思呢?
关于第二点,我们举这样一个例子:
输入:
res = 5,6,7,2,4
num = 5
输出:
res = 5,6,7,2,4,5
关于第四点,我们举这样一个例子:
输入:
res = 6,7,2,4,5
num = 5
输出:
res = 5,6,7,2,4,5
因此根据上面的归纳我们可以得到代码:
注意:因为第三种情况不可能出现,因此我们在描述第2、4种情况时可以省略大小比较,因为当2、4种描述的等于关系成立时,大小关系必然成立。
if (res[size] > num || num == res[0]) { // num处于high右边left = high + 1;right = size;
}
if(num > res[0] || num == res[size]) { // num处于high左边left = 0;right = high;
}
如何确定插入位置?
建立好左右边界后就可以根据二分查找来确定插入位置了。
- 当左边界小于右边界时执行二分查找。
- 中间点(mid)对应的元素小于
num
时,左边界更改为mid+1
。 mid
对应的元素大于num
时,右边界更改为mid-1
。
int mid = left + (right - left) / 2;
while (left <= right)
{if (res[mid] < num) {left = mid + 1; }else {right = mid - 1;}mid = left + (right - left) / 2;
}
插入元素
最终使用insert函数进行插入:
res.insert(res.begin() + mid, num);
insert会将给定值插入到给定位置之前。
代码
class Solution {
public:vector<int> fun(vector<int> res, int num) {int size = res.size() - 1;int left = 0;int right = size;/*int high = 0;for (int i = 1; i < res.size(); i++) {if (res[high] > res[i]) {break;}high = i;}*/// 与上面注释部分一样都是查最高点int high = left + (right - left) / 2;int flag = res[0];while (left < right) {if (res[high] > flag) {if (res[high + 1] > res[high]) {left = high + 1;}else {break;}}else {if (res[high - 1] < res[high]) {right = high - 2;}else {high--;break;}}high = left + (right - left) / 2;}// 确定左右边界if (res[size] > num || num == res[0]) { // num处于high右边left = high + 1;right = size;}if(num > res[0] || num == res[size]) { // num处于high左边left = 0;right = high;} // 确定插入位置int mid = left + (right - left) / 2;while (left <= right){if (res[mid] < num) {left = mid + 1; }else {right = mid - 1;}mid = left + (right - left) / 2;} res.insert(res.begin() + mid, num);return res;}
};
用例测试:
int main() {Solution s;vector<int> iv = { 4,5,6,7,1,2,4 };int num = 3;iv = s.fun(iv, num);for (auto i = iv.begin(); i != iv.end(); i++) {cout << *i << " ";}cout << endl;
}