赞
踩
数组是存放在连续内存空间上的相同类型数据的集合。
数组可以方便的通过下标索引的方式获取到下标下对应的数据。
注意
因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。
题目:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
提示:
你可以假设 nums 中的所有元素是不重复的。
n 将在 [1, 10000]之间。
nums 的每个元素都将在 [-9999, 9999]之间。
【文字太长不看系列:↓直接跳到末尾查看打印出循环过程的代码便于理解】
区间定义后,每次改变区间后,区间性质不能变:保持 左闭右闭[a, b] / 左闭右开[a, b),处理规则要按照区间定义来写,使得每次操作的范围划分满足区间的定义。
分析:如何判断问号处该使用小于(<)号还是小于等于(<=)号?
例如:当下标区间为[x,x]时,此时区间长度为1,区间内只有一个元素。while判断条件若使用"<=“,则 left=nums[x],right=nums[x],此时条件 left<=right 成立,能使得进入循环的区间满足循环不变;若使用”<",left=nums[x],right=nums[x],此时条件 left<right 不成立,无法使进入循环的区间满足循环不变。
简单来看,闭区间,左边界==右边界是有意义的,因此判断条件可以写成 left <= right。
分析:右边界更新为 mid 还是 mid - 1?
若目标值小于二分后的中值,即 target < nums[mid],则 nums[mid] 一定不在要查找的范围内,不可能为要查找的目标值,即 target!=nums[mid]。此时更新右边界值,right 的正确更新方式为 right = mid - 1,而非 right = mid,否则就将不属于查找范围中的值 nums[mid] 包含了进来。若错误采用了 right < mid 写法,紧接下来的循环可能不会立即导致错误,但当程序运行到二分区间只剩一个元素时,很可能时本该被查到的目标值因为不满足 left < right 条件而退出循环,导致最终找不到目标值。
分析:左边界更新为 mid 还是 mid - 1?
由右边界更新方式同理可知,nums[mid] 不在区间中,左边界更新只需 left = mid + 1 。
分析:如何判断问号处该使用小于(<)号还是小于等于(<=)号?
例如:当下标区间为[x,x)时,此时区间长度为1,区间内只有一个元素。while判断条件若使用"<=“,则表示可以有 left=nums[x],right=nums[x],此时条件 left<=right ,不能使得进入循环的区间满足循环不变;若使用”<",left=nums[x],right=nums[x+1],此时条件 left<right 成立,使进入循环的区间满足循环不变。
简单来看,闭区间,**左边界==右边界 **是有意义的,因此判断条件可以写成 left <= right。
分析:右边界更新为 mid 还是 mid - 1?
若目标值小于二分后的中值,即 target < nums[mid],则 nums[mid] 一定不在要查找的范围内,不可能为要查找的目标值,即 target!=nums[mid]。此时更新右边界值,right 可更新为 right = mid ,表示取不到 mid ,而非 right = mid - 1,否则就将应该属于查找范围中的值 nums[mid - 1] 剔除了。
分析:左边界更新为 mid 还是 mid - 1?
由1中闭区间更新方式,同理可知,nums[mid] 不在区间中,左边界更新仍为 left = mid + 1 。
将数组区间和数学数列中的区间联系起来也很好理解。
左闭右闭方式,将一个完整区间分为3部分 (<t, =t, >t),最终左右边界会相等,指向最后一次查找的位置;
左闭右开方式,将一个完整区间分为2部分 (<t, >=t),该方式左右边界不会有相等的情况,只会相邻。
int search(int* nums, int numsSize, int target) { int left = 0, right = numsSize - 1; while (left <= right) { int mid = left + (right - left >> 1); if (target < nums[mid]) { right = mid - 1; } else if (target > nums[mid]) { left = mid + 1; } else { return mid; } } return -1; }
#include <iostream> class Solution { public: int search(vector<int>& nums, int target) { int left = 0; int right = nums.size(); while (left < right) { int middle = left + ((right - left) / 2); if (target < nums[middle]) { left = middle + 1; } else if (target > nums[middle]) { right = middle; } else { return middle; } } return -1; } };
/************************************************************************ > File Name: 0704_binarySearch.c > Author: leon-ais > Mail: leon_ais@qq.com > Created Time: Wed 21 Sep 2022 05:43:48 PM CST > Description: ************************************************************************/ #include <stdio.h> // 辅助函数用于打印数组 void show(int *a, int n) { printf("arr=["); for (int i = 0; i < n; ++i) { printf("%d", a[i]); if (i != n - 1) { printf(" "); } } printf("]\n"); } // 辅助函数用于打印二分过程 void msg(int* a, int n, int l, int m, int r) { show(a, n); if (l == m || m == r) { printf("loop:"); printf("%*sl\n", l*2, ""); printf("loop:"); printf("%*sm\n", m*2, ""); printf("loop:"); printf("%*sr\n", r*2, ""); } else { printf("loop:"); printf("%*sl", l*2, ""); printf("%*sm", m*2-l*2-1, ""); printf("%*sr\n", r*2-(m*2)-1, ""); } printf("l=%d m=%d r=%d\n", l, m, r); } int search(int* a, int n, int x) { int l = 0, r = n - 1; while (l <= r) { int m = l + (r - l >> 1); msg(a, n, l, m, r); if (x < a[m]) { r = m - 1; } else if (x > a[m]) { l = m + 1; } else { return m; } } return -1; } int main(int argc,char* argv[]) { int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; int n = 10; int x = 3; // show(a, n); int i = search(a, n, x); printf("\nThe value \"%d\" is at a[%d].\n", x, i); return 0; }
// 程序运行结果 Program returned: 0 Program stdout arr=[0 1 2 3 4 5 6 7 8 9] loop:l m r l=0 m=4 r=9 arr=[0 1 2 3 4 5 6 7 8 9] loop:l m r l=0 m=1 r=3 arr=[0 1 2 3 4 5 6 7 8 9] loop: l loop: m loop: r l=2 m=2 r=3 arr=[0 1 2 3 4 5 6 7 8 9] loop: l loop: m loop: r l=3 m=3 r=3 The value "3" is at a[3].
问题背景:
二分法每次更新mid需要除以2
有可能溢出的写法:
mid = (left+right) / 2;
原因分析:
当给定的数组长度很长,待查找元素的位置靠近数组尾部时,当循环快结束时,left和right可能都会很大,还没等除以2求得平均值,left+right求和的值就已经超出了int最大范围,导致溢出错误。
问题背景:
二分法每次更新mid需要除以2
错误写法:
mid = left+(right-left) >> 1;
原因分析:
右移运算符>>的优先级不如加减法+-高,先计算加减法,再进行移位,错误写法等价于(left+(right-left))/2;
则在不确定符号优先级时,尽量使用圆括号()维护运算顺序。
正确写法:
mid = left+(right-left >> 1);或mid = left+((right-left) >> 1);
OJ术语:AC、WA、RE、CE、TLE、MLE、PE、OLE、分别什么意思 AC Accepted 答案正确/通过 WA Wrong Answer 答案错误 RE Runtime Error 运行时错误 CE Complie Error 编译错误 TLE Time Limit Exceed 超出时间限制/超时 MLE Memory Limit Exceed 超出内存限制 PE Presentation Error 格式错误 OLE Output Limit Exceed 输出超出限制/输出超限
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。