赞
踩
给定一个 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
二分时的条件到底是 while(left < right)
还是 while(left <= right)
二分完后根据target的位置不同,指针到底应该指向哪,是right = middle
,还是要right = middle - 1
target
是在一个在左闭右闭的区间里,即[left, right]
此时left == right
是有意义的,如果(nums[middle] > target)
, right
要赋值为 middle - 1,因为当前这个nums[middle]
一定不是target
,那么接下来要查找的左区间结束下标位置就是 middle - 1
public class BinarySearch { public int search1 (int[] nums, int target){ // whlie循环中为左闭右闭区间 // 定义左右指针及中间位置 int left = 0; int right = nums.length - 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 if (target == nums[mid]){ return mid; } } return -1; } }
target
是在一个在左闭右开的区间里,即[left, right)
因为left == right
在区间[left, right)
是没有意义的,如果(nums[middle] > target)
,right
更新为 middle,因为当前nums[middle]
不等于target
,去左区间继续寻找,而寻找区间是左闭右开区间,right
即使更新为middle也是在开区间上,不会参与比较
public int search2 (int[] nums, int target){ // whlie循环中为左闭右开区间 // 定义左右指针及中间位置 int left = 0; int right = nums.length; // 左闭右开时右指针要指出数组右边界后一位,开区间取不到 while (left < right){ int mid = left + ((right - left) >> 1); if (target < nums[mid]){ right = mid; }else if (target > nums[mid]){ left = mid + 1; }else if (target == nums[mid]){ return mid; } } return -1; }
时间复杂度:O(logn)
给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1)
额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案
示例 2:
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
定义快慢指针
当快指针指向的值不等于目标值,那么快指针此时指向的值便可以赋值给慢指针所指向的新数组
public class RemoveElement {
public int removeElement(int[] nums, int val){
int slowIndex = 0; // 寻找新数组的元素 ,新数组就是不含有目标元素的数组
int fastIndex = 0; // 新新数组下标
for (fastIndex = 0; fastIndex < nums.length; fastIndex++){
if (nums[fastIndex] != val){
nums[slowIndex] = nums[fastIndex];
slowIndex++;
}
}
return slowIndex;
}
}
O(n)
核心就是把不等于val
的全部放在数组左边,等于val的全部放在数组右边
// 双向指针的方法 int leftIndex = 0; // 左指针 int rightIndex = nums.length - 1; // 右指针 while(leftIndex <= rightIndex){ // 找到左边等于val的元素 while (leftIndex <= rightIndex && nums[leftIndex] != val){ leftIndex++; } //找到右边不等于val的元素 while (leftIndex <= rightIndex && nums[rightIndex] == val){ rightIndex--; } //找到左边等于val的就与右边不等于val的元素交换 if(leftIndex < rightIndex){ nums[leftIndex++] = nums[rightIndex--]; } } return leftIndex;
难点是关于while中<
与<=
的使用
while (leftIndex <= rightIndex && nums[leftIndex] != val){
leftIndex++;
}
当leftIndex=rightIndex
两个指针重合,说明左边已经没有val
要移动到右边,左指针指向两指针重合的下一位,当leftIndex>rightIndex
循环停止
while (leftIndex <= rightIndex && nums[rightIndex] == val) {
-- rightIndex;
}
此时leftIndex
已经大于rightIndex
,循环停止。
if (leftIndex < rightIndex)
leftIndex=rightIndex
时当二指针重合时不需要交换
完整代码:
public int removeElement2(int[] nums, int val){ // 双向指针的方法 int leftIndex = 0; // 左指针 int rightIndex = nums.length - 1; // 右指针 while(leftIndex <= rightIndex){ // 找到左边等于val的元素 while (leftIndex <= rightIndex && nums[leftIndex] != val){ leftIndex++; } //找到右边不等于val的元素 while (leftIndex <= rightIndex && nums[rightIndex] == val){ rightIndex--; } //找到左边等于val的就与右边不等于val的元素交换 if(leftIndex < rightIndex){ nums[leftIndex++] = nums[rightIndex--]; } } return leftIndex; }
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n)
的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 7
输出: 4
只要看到面试题里给出的数组是有序数组,都可以想一想是否可以使用二分法。
同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的。
如果用二分法遍历完毕之后还没有找到插入位置,则插入位置在数组的最后
完整代码:
public int searchInsert(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) { right = mid - 1; }else if (nums[mid] < target) { left = mid + 1; }else { return mid; } } return right + 1; }
给你一个按照非递减顺序排列的整数数组 nums
,和一个目标值 target
。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target
,返回 [-1, -1]
。
你必须设计并实现时间复杂度为 O(log n)
的算法解决此问题。
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:
输入:nums = [], target = 0
输出:[-1,-1]
这道题与35题的区别就是这道题是存在重复元素的
寻找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}接下来,在去寻找左边界,和右边界了。
采用二分法来去寻找左右边界,为了让代码清晰,我分别写两个二分来寻找左边界和右边界。
寻找右边界实际上是寻找数组里第一个大于等于target
的数
确定好:计算出来的右边界是不包含target的右边界,左边界同理。
如果rightBorder
为没有被赋值(即target
在数组范围的左边
当nums[middle] <= target
时,说明此时mid右边可能还有大于等于target
的数,首先更新left
为mid+1,然后就要更新rightBorder
为此时的left
// 二分查找,寻找target的右边界(不包括target) int getRightBorder(int[] nums, int target) { // 定义target在左闭右闭的区间里,[left, right] int left = 0; int right = nums.length - 1; int rightBorder = -2; // 右边界初始化 while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] > target) { right = mid - 1; } else { left = mid + 1; rightBorder = left; } } return rightBorder; }
// 二分查找,寻找target的左边界leftBorder(不包括target) int getLeftBorder(int[] nums, int target) { // 定义target在左闭右闭的区间里,[left, right] int left = 0; int right = nums.length - 1; int leftBorder = -2; // 右边界初始化 while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] < target) { left = mid + 1; } else { right = mid - 1; leftBorder = right; } } return leftBorder; }
左右边界计算完之后,看一下主体代码,这里把上面讨论的三种情况,都覆盖了
public 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};
}
完整代码
public 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}; } // 二分查找,寻找target的右边界(不包括target) int getRightBorder(int[] nums, int target) { // 定义target在左闭右闭的区间里,[left, right] int left = 0; int right = nums.length - 1; int rightBorder = -2; // 右边界初始化 while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] > target) { right = mid - 1; } else { left = mid + 1; rightBorder = left; } } return rightBorder; } int getLeftBorder(int[] nums, int target) { // 定义target在左闭右闭的区间里,[left, right] int left = 0; int right = nums.length - 1; int leftBorder = -2; // 右边界初始化 while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] < target) { left = mid + 1; } else { right = mid - 1; leftBorder = right; } } return leftBorder; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。