一、二分查找
二分查找触发条件:1)有序数组;2)无重复元素(否则可能没有唯一解)
1.704二分查找
1)左闭右闭
while内判断为left<=right
缩小区间时right调整为mid-1,因为mid-1可以取到,而mid已经确认不是target
class Solution {
//左闭右闭
public int search(int[] nums, int target) {
if (target < nums[0] || nums[nums.length - 1] < target) {
return -1;
}
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == target) {
return mid;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] < target) {
left = mid + 1;
}
}
return -1;
}
}
2)左闭右开
while内判断为left<right
缩小区间时right调整为mid,因为mid取不到,而mid已经确认不是target
本方法用例通过但提交没过
提交没过解决:right初始化为nums.length不用-1,因为是右开区间取不到
class Solution {
//左闭右开
public int search(int[] nums, int target) {
if (target < nums[0] || nums[nums.length - 1] < target) {
return -1;
}
int left = 0;
//right = nums.length不-1
int right = nums.length;
while (left < right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == target) {
return mid;
} else if (nums[mid] > target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
}
}
return -1;
}
}
2.35搜索插入位置
1)暴力解法
class Solution {
// 暴力解法
public int searchInsert(int[] nums, int target) {
int n = nums.length;
for (int i = 0; i < n; i++) {
// 三种情况
// 1.target小于数组中所有元素
// 2.target存在于数组中,等于数组某元素
// 3.target不存在于数组中,大于数组某元素
if (nums[i] >= target) {
//=时为情况二,target直接插入即可
// >时为情况三,target插入数组的位置就是刚刚大于它的元素的位置
// 相当于顶替这个元素原本的位置,这个元素的位置变为i+1
return i;
}
}
return n;
}
}
2)二分法(左闭右闭)
return right+1;
class Solution {
// 二分法
public int searchInsert(int[] nums, int target) {
int left=0;
int n = nums.length;
int right=n-1;
while(left<=right){
int mid=left+((right-left)>>1);//防止溢出
if(nums[mid]==target){//target存在于数组中
return mid;
}else if(nums[mid]<target){//target大于mid,在右区间
left=mid+1;
}else if(nums[mid]>target){//target小于mid,在左区间
right=mid-1;
}
}
// 分别处理如下四种情况
// 目标值在数组所有元素之前 [0, -1]
// 目标值等于数组中某一个元素 return middle;
// 目标值插入数组中的位置 [left, right],return right+1
// 目标值在数组所有元素之后的情况 [left, right],因为是右闭区间,所以return right+1
return right+1;
}
}
3)二分法(左闭右开)
return right;
class Solution {
// 二分法
public int searchInsert(int[] nums, int target) {
int left=0;
int n = nums.length;
int right=n;//右开区间,right=n不-1,与左闭右闭作区分
while(left<right){
int mid=left+((right-left)>>1);//防止溢出
if(nums[mid]==target){//target存在于数组中
return mid;
}else if(nums[mid]<target){//target大于mid,在右区间
left=mid+1;
}else if(nums[mid]>target){//target小于mid,在左区间
right=mid;
}
}
// 分别处理如下四种情况
// 目标值在数组所有元素之前 [0, 0)
// 目标值等于数组中某一个元素 return middle;
// 目标值插入数组中的位置 [left, right),return right+1
// 目标值在数组所有元素之后的情况 [left, right),因为是右开区间,所以return right+1
return right;
}
}
3.34在排序数组中查找元素的第一个和最后一个位置
寻找target在数组里的左右边界,有如下三种情况:
- 情况一:target 在数组范围的右边或者左边,例如数组{3, 4, 5},target为2或者数组{3, 4, 5},target为6,此时应该返回{-1, -1}
- 情况二:target 在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1, -1}
- 情况三:target 在数组范围中,且数组中存在target,例如数组{3,6,7},target为6,此时应该返回{1, 1}
1)解法一,只调一次二分查找函数
// 解法2
// 1、首先,在 nums 数组中二分查找 target;
// 2、如果二分查找失败,则 binarySearch 返回 -1,表明 nums 中没有 target。此时,searchRange 直接返回 {-1, -1};
// 3、如果二分查找成功,则 binarySearch 返回 nums 中值为 target 的一个下标。然后,通过左右滑动指针,来找到符合题意的区间
自己写的,没ac
class Solution {
public int[] searchRange(int[] nums, int target) {
int index = binarySearch(nums, target);
//nums中存在target
if (index == -1) {
return new int[] { -1, -1 };// 匿名数组
}
//nums中不存在target
int left = index;
int right = index;
//防止数组越界,防止逻辑短路,两个条件顺序不能改变
if (left - 1 >= 0 && nums[left - 1] == nums[index]) {
left--;
}
if (right + 1 < nums.length && nums[right + 1] == nums[index]) {
right++;
}
return new int[] { left, right };
}
public int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;// 左闭右闭,循环不变量
while (left <= right) {// 左闭右闭,循环不变量
int mid = left + ((right - left) >> 2);
if (nums[mid] == target) {
return mid;
} else if (nums[mid] > target) {
right = mid - 1;// 左闭右闭,循环不变量
} else {
left = mid + 1;
}
}
return -1;
}
}
教程解答,ac了...
class Solution {
public int[] searchRange(int[] nums, int target) {
int index = binarySearch(nums, target); // 二分查找
if (index == -1) { // nums 中不存在 target,直接返回 {-1, -1}
return new int[] {-1, -1}; // 匿名数组
}
// nums 中存在 targe,则左右滑动指针,来找到符合题意的区间
int left = index;
int right = index;
// 向左滑动,找左边界
while (left - 1 >= 0 && nums[left - 1] == nums[index]) { // 防止数组越界。逻辑短路,两个条件顺序不能换
left--;
}
// 向右滑动,找右边界
while (right + 1 < nums.length && nums[right + 1] == nums[index]) { // 防止数组越界。
right++;
}
return new int[] {left, right};
}
/**
* 二分查找
* @param nums
* @param target
*/
public int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1; // 不变量:左闭右闭区间
while (left <= right) { // 不变量:左闭右闭区间
int 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; // 不存在
}
}
2)解法二,分别找左边界右边界
个人认为没有解法一好理解,这个方法还没在力扣上自己敲
class Solution {
int[] searchRange(int[] nums, int target) {
int leftBorder = getLeftBorder(nums, target);
int rightBorder = getRightBorder(nums, target);
// 情况一
if (leftBorder == -2 || rightBorder == -2) return new int[]{-1, -1};
// 情况三
if (rightBorder - leftBorder > 1) return new int[]{leftBorder + 1, rightBorder - 1};
// 情况二
return new int[]{-1, -1};
}
int getRightBorder(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int rightBorder = -2; // 记录一下rightBorder没有被赋值的情况
while (left <= right) {
int middle = left + ((right - left) / 2);
if (nums[middle] > target) {
right = middle - 1;
} else { // 寻找右边界,nums[middle] == target的时候更新left
left = middle + 1;
rightBorder = left;
}
}
return rightBorder;
}
int getLeftBorder(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int leftBorder = -2; // 记录一下leftBorder没有被赋值的情况
while (left <= right) {
int middle = left + ((right - left) / 2);
if (nums[middle] >= target) { // 寻找左边界,nums[middle] == target的时候更新right
right = middle - 1;
leftBorder = right;
} else {
left = middle + 1;
}
}
return leftBorder;
}
}
二、移除元素
1.27
注:题干允许数组元素顺序发生变化,故只需要用!=val的元素覆盖掉==val的元素,在原数组上直接操作就行,因为要求O(1)的空间复杂度
1)暴力解法
class Solution {
//暴力解法
//时间复杂度O(n^2)
//空间复杂度O(1)
public int removeElement(int[] nums, int val) {
int size=nums.length;
for(int i=0; i<size;i++){
if(nums[i]==val){
for(int j=i+1;j<size;j++){
//数组元素集体前移一位操作
nums[j-1]=nums[j];
}
i--;//下表i后的元素都向前移动了一位,故i也减少1
size--;
}
}
return size;
}
}
2)双指针(快慢指针)法
精髓方法,快指针指向当前遍历到的元素,慢指针指向当前要填充元素的位置
class Solution {
//双指针(快慢指针)法
//时间复杂度O(n)
//空间复杂度O(1)
//本题要求返回数组下标的同时,对数组本身进行修改,将==val的元素覆盖掉
//注:返回的数组中元素可以改变
public int removeElement(int[] nums, int val) {
int slowIndex=0;
//终止条件为fastIndex<nums.length而不是fastIndex<nums.length-1
//因为是<,刚好取到nums.length-1的元素
for(int fastIndex=0;fastIndex<nums.length;fastIndex++){
if(nums[fastIndex]!=val){
nums[slowIndex]=nums[fastIndex];
slowIndex++;
}
}
return slowIndex;
}
}
3)相向指针法(一)
修正right判断条件:right>0修正为right>=0
注:用right--操作来移除right位置元素
class Solution {
// 相向双指针法(一)
// 时间复杂度O(n)
// 空间复杂度O(1)
// 本题要求返回数组下标的同时,对数组本身进行修改,将==val的元素覆盖掉
// 注:返回的数组中元素可以改变
public int removeElement(int[] nums, int val) {
int left = 0;
int right = nums.length - 1;
// 将right易到第一个!=val的元素位置
while (right >= 0 && nums[right] == val)
right--;
while (left <= right) {
if (nums[left] == val) {// left位置元素可移除
// 用right位置元素覆盖left位置元素
nums[left] = nums[right];
right--;// right位置移除
}
left++;
while (right >= 0 && nums[right] == val)
right--;
}
return left;
}
}
4)相向指针法(二)
class Solution {
// 相向双指针法(二)
// 时间复杂度O(n)
// 空间复杂度O(1)
// 本题要求返回数组下标的同时,对数组本身进行修改,将==val的元素覆盖掉
// 注:返回的数组中元素可以改变
public int removeElement(int[] nums, int val) {
int left = 0;
int right = nums.length - 1;
// 将right易到第一个!=val的元素位置
while (right >= 0 && nums[right] == val)
right--;
while (left <= right) {
if (nums[left] == val) {// left位置元素可移除
// 用right位置元素覆盖left位置元素
nums[left] = nums[right];
right--;// right位置移除
}else{//兼容法(一)的两种情况
left++;
}
}
return left;
}
}