Templete1
while(left<=right)
为什么要有等号?如果数组长度为1:[3],target=3,不加等号不能进入循环体
public int findPosition(int[] nums, int target) {
if(nums == null || nums.length == 0){
return -1;
}
int left = 0;
int right = nums.length-1;
int mid = 0;
while(left<=right){
mid = left+(right-left)/2;
if(nums[mid]==target){
return mid;
}else if (nums[mid]<target){
left = mid+1;
} else{
right = mid-1;
}
}
return -1;
}
Templete2
这里right的定义为大于target的边界,所以若数组长度为1,可进入循环
right=mid 保证每次right 大于target
public int findPosition(int[] nums, int target) {
if(nums == null || nums.length == 0){
return -1;
}
int left = 0;
//important!!!
int right = nums.length;
int mid = 0;
while(left<right){
mid = left+(right-left)/2;
if(nums[mid]==target){
return mid;
}else if (nums[mid]<target){
left = mid+1;
} else{
//important!!!
right = mid;
}
}
return -1;
}
Templete3
left+1
可能mid==target,也可能是left或者right中的一个
public int findPosition(int[] nums, int target) {
if(nums == null || nums.length == 0){
return -1;
}
int left = 0;
int right = nums.length-1;
int mid = 0;
//important!!!
while(left+1<right){
mid = left+(right-left)/2;
if(nums[mid]==target){
return mid;
}else if (nums[mid]<target){
left = mid+1;
} else{
right = mid-1;
}
}
//important!!!
if(nums[left]==target){
return left;
}
if(nums[right] == target){
return right;
}
return -1;
}
查找第一次出现target的位置
public static int findFirst(int[] nums, int target) {
if (nums == null || nums.length == 0) {
return -1;
}
int left = 0;
int right = nums.length - 1;
while (left + 1 < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid;
} else {
right = mid;
}
}
if (nums[left] == target) {
return left;
}
if (nums[right] == target) {
return right;
}
return -1;
}
查找最后一次出现target的位置
public static int findLast(int[] nums, int target) {
if (nums == null || nums.length == 0) {
return -1;
}
int left = 0;
int right = nums.length - 1;
int mid = 0;
while (left + 1 < right) {
mid = left + (right - left) / 2;
if (nums[mid] <= target) {
left = mid;
} else {
right = mid;
}
}
if (nums[right] == target) {
return right;
}
if (nums[left] == target) {
return left;
}
return -1;
}
查找与target最接近的数的位置
public int findClosest(int[] arr, int target) {
if (arr == null || arr.length == 0) {
return -1;
}
int left = 0;
int right = arr.length - 1;
int mid = 0;
while (left + 1 < right) {
mid = left + (right - left) / 2;
//通过这个判断,如果数组中不包含target,剩余的两个值必然是离target最近的两个数
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid;
} else {
right = mid;
}
}
return Math.abs(arr[left] - target) < Math.abs(arr[right] - target) ? left : right;
}
第一个小于target的数
public int findLargestSmallerOfTarget(int[] nums, int target) {
if (nums == null || nums.length == 0) {
return -1;
}
int left = 0;
int right = nums.length - 1;
int mid = 0;
while (left + 1 < target) {
mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid;
} else {
right = mid;
}
}
if (nums[left] == target && left != 0) {
return left - 1;
} else if (nums[left] == target && left == 0) {
return -1;
} else {
return left;
}
}
第一个大于target 的数
public int findSmallesrLargetOfTargest(int[] nums, int target) {
if (nums == null || nums.length == 0) {
return -1;
}
int left = 0;
int right = nums.length - 1;
int mid = 0;
while (left + 1 < target) {
mid = left + (right - left) / 2;
if (nums[mid] <= target) {
left = mid;
} else {
right = mid;
}
}
if (nums[right] == target && right != nums.length - 1) {
return right + 1;
} else if (nums[right] == target && right == nums.length - 1) {
return -1;
} else {
return right;
}
}
题目描述
你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n
个版本 [1, 2, ..., n]
,你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion(version)
接口来判断版本号 version
是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
代码
public class Solution extends VersionControl {
public int firstBadVersion(int n) {
int left = 1;
int right = n;
int mid = 0;
//这里没有等于号:left=right时=判断终止
while(left<right){
mid = left+(right-left)/2;
if(isBadVersion(mid)){
//如果mid = true,则mid可能是最后输出值,所以必须保证mid进入到下次循环
right = mid;
}else{
//mid = false,不可能是最后结果,所以从mid+1开始下次循环
left = mid+1;
}
}
//return left 也可
return right;
}
}
题目描述
我们正在玩一个猜数字游戏。 游戏规则如下:
我从 1 到 n 选择一个数字。 你需要猜我选择了哪个数字。
每次你猜错了,我会告诉你这个数字是大了还是小了。
你调用一个预先定义好的接口 guess(int num)
,它会返回 3 个可能的结果(-1
,1
或 0
):
-1 : 我的数字比较小
1 : 我的数字比较大
0 : 恭喜!你猜对了!
代码
public class Solution extends GuessGame {
public int guessNumber(int n) {
int left = 1;
int right = n;
int mid = 0;
//left<=right,return-1; 或left
while(left<=right){
mid = left+(right-left)/2;
if(guess(mid)== 1){
left = mid+1;
}else if(guess(mid) == -1){
right = mid-1;
}else{
return mid;
}
}
return -1;
}
}
给定一个按照升序排列的整数数组
nums
,和一个目标值target
。找出给定目标值在数组中的开始位置和结束位置。你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回
[-1, -1]
。
class Solution {
public int[] searchRange(int[] nums, int target) {
if(nums == null || nums.length==0){
return new int[]{-1,-1};
}
//二分查找
int left = 0;
int right = nums.length-1;
int mid = 0;
//left+1
//精确查找最后剩[left,mid,right]
//范围查找最后剩[left,right]
//如果left=target,一定是最左
//如果right!=target ,一定没有target
while(left+1<right){
mid = left + (right - left)/2;
if(nums[mid] < target){
left = mid;
}else{
right = mid;
}
}
int leftIndex = -1;
if(nums[left] == target){
leftIndex = left;
}else if(nums[right] == target){
leftIndex = right;
}else{
return new int[]{-1,-1};
}
int rightIndex = leftIndex;
for(int i = leftIndex+1;i<nums.length;i++){
if(nums[i] == target){
rightIndex++;
}else{
break;
}
}
return new int[]{leftIndex,rightIndex};
}
}
峰值元素是指其值大于左右相邻值的元素。
给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。
数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。
你可以假设 nums[-1] = nums[n] = -∞。
class Solution {
public int findPeakElement(int[] nums) {
int l = 0;
int r = nums.length-1;
int mid = 0;
while(l<r){
mid = l+(r-l)/2;
if(nums[mid]>nums[mid+1]){
r = mid;
}else{
l= mid+1;
}
}
return l;
}
}
给一个按照升序排序的非负整数数组。这个数组很大以至于你只能通过固定的接口
ArrayReader.get(k)
来访问第k个数(或者C++里是ArrayReader->get(k)),并且你也没有办法得知这个数组有多大。找到给出的整数target第一次出现的位置。你的算法需要在O(logk)的时间复杂度内完成,k为target第一次出现的位置的下标。
如果找不到target,返回-1。
public int searchBigSortedArray(ArrayReader reader, int target) {
// write your code here
int r = 1;
int l = 0;
while(reader.get(r)!=2147483647 && reader.get(r)<target){
l = r;
r = r*2;
}
//在l——r 上查找
int mid = 0;
while(l+1<r){
mid = l + (r - l) / 2 ;
if (reader.get(mid) == 2147483647 || reader.get(mid)>=target) {
r = mid;
}else {
l = mid;
}
}
if(reader.get(l) == target ){
return l;
}
if(reader.get(r) == target ){
return r;
}
return -1;
}
编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:
- 每行中的整数从左到右按升序排列。
- 每行的第一个整数大于前一行的最后一个整数。
输入:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 3
输出: true
public class Main {
public boolean searchMatrix(int[][] matrix, int target) {
//第一个数字小于target 直接返回false
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return false;
}
//获取第一列的值
int[] col = new int[matrix.length];
for (int i = 0; i < matrix.length; i++) {
col[i] = matrix[i][0];
}
int row = getRowNum(col, target);
int result = getResult(matrix[row], target);
if (result == -1) {
return false;
} else {
return true;
}
}
//二分查找第几行,查找小于或等于target的最大值
public int getRowNum(int[] arr, int target) {
int l = 0;
int r = arr.length - 1;
int mid = 0;
while (l + 1 < r) {
mid = l + (r - l) / 2;
if (arr[mid] >= target) {
r = mid;
} else {
l = mid;
}
}
//反思:能不能直接返回r,好像是可以的
if (arr[r] <= target) {
return r;
} else {
return l;
}
}
//二分查找第几列,没有查找到返回false
public int getResult(int[] arr, int target) {
int l = 0;
int r = arr.length - 1;
int mid = 0;
while (l + 1 < r) {
mid = l + (r - l) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
l = mid + 1;
} else {
r = mid - 1;
}
}
if (arr[l] == target) {
return l;
} else if (arr[r] == target) {
return r;
} else {
return -1;
}
}
}
##旋转数组-LeetCode153 旋转排序数组最小值
mid和r比
class Solution {
public int findMin(int[] nums) {
//预处理
if(nums == null || nums.length == 0){
return Integer.MIN_VALUE;
}
//定义变量
int l = 0;
int r = nums.length-1;
int mid;
while(l < r ){
mid = l + (r - l)/2;
if(nums[mid]>nums[r]){
l = mid+1 ;
}else{
r = mid ;
}
}
return(nums[l]);
}
}
mid和l比会出问题
class Solution {
public int findMin(int[] nums) {
//预处理
if(nums == null || nums.length == 0){
return Integer.MIN_VALUE;
}
//定义变量
int l = 0;
int r = nums.length-1;
int mid;
while(l< r ){
mid = l + (r - l)/2;
if(nums[mid]<nums[l]){
r = mid ;
}else{
//这里必须 l=mid,如果l = mid+1, [1,2] 会返回2 如果只改这里会出现死循环
l = mid ;
}
}
return(nums[l]);
}
}
##旋转数组-LeetCode 154 旋转排序数组最小值(有重复元素)
这道题是 寻找旋转排序数组中的最小值 的延伸题目。
允许重复会影响算法的时间复杂度吗?会如何影响,为什么?
class Solution {
public int findMin(int[] nums) {
if(nums == null || nums.length == 0){
return Integer.MIN_VALUE;
}
int left = 0;
int right = nums.length-1;
int mid = 0;
while(left<right){
mid = left + (right - left)/2;
if(nums[mid] < nums[left]){
right = mid;
}else if (nums[mid] > nums[right]){
left = mid+1;
}else{
//不能直接left++
//最后如果剩下[1,2],left++,会返回2
if(nums[left]>nums[right]){
left++;
}else{
right--;
}
}
}
return nums[left];
}
}
##旋转数组-LeetCode 33 搜索旋转排序数组(无重复元素)
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组
[0,1,2,4,5,6,7]
可能变为[4,5,6,7,0,1,2]
)。搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回
-1
。你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
solution
public int search(int[] nums, int target) {
//预处理
if(nums == null || nums.length == 0){
return -1;
}
int left = 0;
int right = nums.length-1;
int mid = 0;
// <= [1,2] 搜索2, 如果小于 搜不到2
while(left<=right){
mid = left + (right - left) / 2;
if(nums[mid] == target){
return mid;
}
//假设左半边有序 注意这里有等于号
//如果最后剩两个元素,比如【3,1】 target = 1;这里左半部分和有半部分都可看做有序
//但是mid的值 在左半边比较才有意义 所以加了等号
if(nums[left]<=nums[mid]){
if(nums[left]<=target && target< nums[mid]){
right = mid - 1 ;
}else{
left = mid + 1;
}
}else{
//右半边有序
if(nums[mid]<target && target <= nums[right]){
left = mid + 1;
} else{
right = mid - 1;
}
}
}
return -1;
}
##旋转数组-LeetCode81 搜索旋转排序数组(有重复元素)
- 这是 上题的延伸题目,本题中的
nums
可能包含重复元素。- 这会影响到程序的时间复杂度吗?会有怎样的影响,为什么?
solution
class Solution {
public boolean search(int[] nums, int target) {
//预处理
if(nums == null || nums.length == 0){
return false;
}
int left = 0;
int right = nums.length-1;
int mid = 0;
while(left<=right){
mid = left + (right - left) / 2;
if(nums[mid] == target){
return true;
}
//假设左半边有序 注意这里有等于号
//如果最后剩两个元素,比如【3,1】 target = 1;这里左半部分和有半部分都可看做有序
//但是mid的值 在左半边比较才有意义 所以加了等号
if(nums[left]<nums[mid]){
if(nums[left]<=target && target< nums[mid]){
right = mid - 1 ;
}else{
left = mid + 1;
}
}else if (nums[mid] < nums[right]){
//右半边有序
if(nums[mid]<target && target <= nums[right]){
left = mid + 1;
} else{
right = mid - 1;
}
}else{
if(nums[left] == target){
return true;
}
left++;
}
}
return false;
}
}
另一个版本
class Solution {
public boolean search(int[] nums, int target) {
int l = 0, r = nums.length - 1;
while (l <= r) {
int m = l + (r - l) / 2;
if (nums[m] == target) {
return true;
}
if (nums[l] < nums[m]) {
if (nums[l] <= target && target < nums[m]) {
r = m - 1;
} else {
l = m + 1;
}
}
//这里是重要的不同点
else if (nums[m] < nums[l]) {
if (nums[m] < target && target <= nums[r]) {
l = m + 1;
} else {
r = m - 1;
}
} else {
// A[m] == A[l]
// for example [1,3,1,1,1] target is 3
//这里不用做判断 因为这里 num[left]==num[mid] 不可能等于target
l++;
}
}
return false;
}
}