使用二分查找前提条件:
二分查找易错点是条件的判断,条件判断分为两种
其实这取决于我们在用二分法时,是想用左闭右闭,还是左闭右开的区间来进行二分
当我们想用左闭右闭的区间时,那我们初始值的设定时应该让right = nums.length - 1,因为我们要比较所有元素,包括数组最右边的值
当我们选择左闭右闭这样的区间时,就意味着left 是可以等于 right 的。
可能有小伙伴会疑惑道相等不就是一个数吗,一个数也可以是区间?其实此区间非彼区间,我们这里的区间,其本质是数的集合,只需要让这个集合囊括到所有数的判断即可,而单个数也可以作为左闭右闭区间,是因为左闭右闭区间我们认为是两边都可以取到,中间部分可以有值也可以没有值,而单个数即中间没有值的情况,
因为我们是左闭右闭,所以当middle的值进行判断后,则不需要重复判断,所以right 是 middle - 1
当我们想用左闭右开的区间时,那我们初始值的设定时应该让right = nums.length,这样就能包括所有数
当我们选择左闭右开这样的区间时,就意味着left 是不可以等于 right 的,否则不符合我们对于区间的定义
因为我们是左闭右开,所以当middle的值进行判断后,也不需要重复判断,但是我们不能让right等于middle - 1 因为我们是左闭右开,取到middle - 1,就意味着该区间最右边的数是middle - 2,那没有囊括middle - 1这个数的判断,则会出错
代码:
class Solution {
public int search(int[] nums, int target) {
// 左闭右闭
if(target < nums[0] || target > nums[nums.length -1]){
return -1;
}
int right = nums.length - 1;
int left = 0;
while(left <= right){
int middle = left + (right - left) / 2;
if(nums[middle] > target){
right = middle - 1;
}
else if(nums[middle] < target){
left = middle + 1;
}else{
return middle;
}
}
return -1;
}
}
class Solution {
public int search(int[] nums, int target) {
// 左闭右开
if(target < nums[0] || target > nums[nums.length -1]){
return -1;
}
int right = nums.length;
int left = 0;
while(left < right){
int middle = left + (right - left) / 2;
if(nums[middle] > target){
right = middle;
}
else if(nums[middle] < target){
left = middle + 1;
}else{
return middle;
}
}
return -1;
}
}
不做过多解释,主要是需要一个参数新数组的长度,然后两层循环即可,时间复杂度O(n2)
class Solution {
public int removeElement(int[] nums, int val) {
int total = nums.length;
int i = 0;
while (i < total) {
// 判断如果为nums就左移,左移的右边界为total
if (nums[i] == val) {
for (int j = i; j < total - 1; j++) {
nums[j] = nums[j + 1];
}
total--;
continue;
}
i++;
}
return total;
}
class Solution {
public int removeElement(int[] nums, int val) {
int fast = 0;
int slow = 0;
while (fast < nums.length){
if (nums[fast] != val){
nums[slow++] = nums[fast];
}
fast++;
}
return slow;
}
首先明确我们的目的:把数组中不等于val的值放在一起,返回长度
快指针:指向数组中不等于val的值,即指向我们需要但还没有放在一起的目标元素的下标
慢指针:指向我们下一个需要放新元素的下标(还没放),慢指针帮助我们创造了一个“新数组”
我们可以理解为快指针在搜索原数组中不等于val的目标值,找到后就想放到新数组里,即放到慢指针的位置上,这样可以发现慢指针的值始终等于新数组的长度
时间复杂度:O(n)
class Solution {
public int removeElement(int[] nums, int val) {
int right = nums.length - 1;
int left = 0;
while (left <= right){
while (left <= right && nums[left] != val){
left++;
}
while (left <= right && nums[right] == val){
right--;
}
if (left < right){
nums[left++] = nums[right--];
}
}
return left;
}
其实和快慢指针类似
左指针:即“新数组”指针,目标是从左开始遍历,如果遇到数值等于val的时候,将右指针指向的值赋给该位置
右指针:从右开始,指向不等于val的值,指向的值是还未被“新数组收纳的值”,右指针指向右边的数组值是已经被“新数组”收纳,或者等于val不需要被收纳的值
终结条件:当左指针比右指针高一位,相当于把右指针指向的值进行收纳
时间复杂度:O(n)
相较于快慢指针的优势:移动元素位置更少