当前位置:   article > 正文

代码随想录算法训练营第一天 | 二分查找&双指针_代码随想录二分查找在idea怎么运行

代码随想录二分查找在idea怎么运行

1、二分法

!!!二分法最重要的就是循环不变量,即区间的定义始终不变。这个一定一定要记住!!

讲解视频:手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode:704. 二分查找_哔哩哔哩_bilibili

文章讲解:代码随想录 (programmercarl.com)

以下两种写法都可以完成LeetCode704。

拓展题目:35、34、69、367

二分法的写法分两种:左闭右闭写法[left,right]和左闭右开写法[left,right)。

根据写法的不同,来确定:

while(left<right)还是while(left<=right)

right=mid还是right=mid-1

核心:区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。

1.1 左闭右闭写法[left,right]

时间复杂度O(log n),空间复杂度O(1)

int bSearch1(int nums[], int len, int target) {
	int left = 0, right = len - 1;

	while (left <= right) {	//注意点1:这里用<=。原因:这是左闭右闭写法,假设left=right=1时,[left,right]=[1,1]成立,故这里取等于。
		int mid = (left + right) / 2;
		if (nums[mid] > target) {
			right = mid - 1;//注意点2:这里为mid-1。原因:由于nums[mid]!=target,此时nums[mid]已经不在待选区间内,这是左闭右闭写法,若令right=mid,则会将已经不在待选择区间内的元素nums[mid]引入到下一轮待选区间中。
		}
		else if (nums[mid] < target) {
			left = mid + 1;//这里与注意点2同理。
		}
		else {
			return mid;
		}
	}
	return -1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

1.2 左闭右开写法[left,right)

时间复杂度O(log n),空间复杂度O(1)

int bSearch2(int nums[], int len, int target) {
	int left = 0, right = len;	//注意点0:这里right=len。因为是左闭右开,右边不包含。

	while (left < right) {	//注意点1:这里用<。原因:这是左闭右开写法,假设left=right=1时,[left,right)=[1,1)不成立,故这里不取等于。
		int mid = (left + right) / 2;
		if (nums[mid] > target) {
			right = mid;//注意点2:这里为mid。原因:这是左闭右开写法[left,right),也就是不包含nums[right]。此时nums[mid]>target,说明下一个搜索区间是不包含nums[mid]。故令mid=right即可。
		}
		else if (nums[mid] < target) {
			left = mid + 1;//注意点3:这里为mid+1。原因:这是左闭右开写法[left,right),即包含nums[left]。此时nums[mid]<target,说明下一个搜索区间是不包含nums[mid],故令mid=left+1。
		}
		else {
			return mid;
		}
	}
	return -1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

1.3 拓展题

LeetCode34, 中等题,值得收藏。

文章讲解:代码随想录 (programmercarl.com)

思路:代码和文章讲解中几乎相同,改了点地方,让自己更好理解。

!寻找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,6,7},target为6,此时应该返回{1, 2}

接下来,就是找target的左右边界,左右边界分别用两个函数来寻找,避免混乱。

①寻找左边界往左一个位置(!!注意:不是左边界)

/*找到左边界的往左一个位置*/
int getLeft(int nums[], int len, int target) {
	int left = 0, right = len - 1;
	while (left <= right) {
		int mid = (left + right) / 2;
		if (nums[mid] > target) {
			right = mid - 1;
		}
		else if (nums[mid] < target) {
			left = mid + 1;
		}
		else {//!若找到target,此时nums[mid]==target,【target左边界往左一个位置】在mid左边,故将候选框仍然向左移动
			right = mid - 1;
		}
	}
	return right;//!最终,候选框的right就是【target左边界往左一个位置】
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

②寻找右边界往右一个位置(!!注意:不是右边界)

/*找到右边界的往右一个位置*/
int getRight(int nums[], int len, int target) {
	int left = 0, right = len - 1;
	while (left <= right) {
		int mid = (left + right) / 2;
		if (nums[mid] > target) {
			right = mid - 1;
		}
		else if (nums[mid] < target) {
			left = mid + 1;
		}
		else {//!若找到target,此时nums[mid]==target,【target右边界往右一个位置】在mid右边,故将候选框仍然向右移动
			left = mid + 1;
		}
	}
	return left;//!最终,候选框的left就是【target右边界往右一个位置】
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

当获取到target的左边界往左一个位置右边界往右一个位置后,在main()中对以上三种情况分别处理:

int main()
{
	int nums[6] = { 5,7,7,8,8,10 }, target = 8, len = 6;//题目
    
	//!这里之所以初始化为-2而不是-1的原因:getLeft()找的是最左端再往左一个位置,值有可能是-1;rigth_pos只要是负数即可
	int left_pos = -2, right_pos = -2;	//left_pos:左边界往左一个位置,right_pos:右边界往右一个位置

	left_pos = getLeft(nums, len, target);
	right_pos = getRight(nums, len, target);

    //情况一:target 在数组范围的【右边+1位置】或者【左边-1位置】
	if (left_pos == -2 || right_pos == -2) {
		cout << "[-1,-1]";
		return 0;
	}
    //!情况三:若数组中存在target,即使只有一个,right_pos-left_pos也等于2,故必大于1。
	if (right_pos - left_pos > 1) {
		cout << left_pos + 1 << "," << right_pos - 1;
		return 0;
	}
    //情况二
	cout << "[-1,-1]";
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

2、双指针(快慢指针)

讲解视频:数组中移除元素并不容易! | LeetCode:27. 移除元素_哔哩哔哩_bilibili.

拓展题目:26、283、844、977

文章讲解:代码随想录 (programmercarl.com)

题目大概:从数组nums=[0,1,2,2,3,0,4,2]中删掉数字2,返回删除后的数组和数组长度。

思路:

用一层for循环实现。 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

将快指针和慢指针放在同一个for循环里,从而实现两个指针都可以向右移动。不过慢指针需要嵌套在if条件下,所以不是每一次for遍历都要移动慢指针;而快指针在每一次for遍历都要移动。

如下图所示,定义快指针fast和慢指针slow:快指针是寻找新数组需要的元素(比如例子中值不为2的元素),然后将该元素赋给新数组当前下标slow所在的位置。慢指针是获取新数组中需要更新的位置。本质上都是在一个数组上操作的。

在这里插入图片描述

代码实现

时间复杂度O(n),空间复杂度O(1)

int main()
{
	int nums[4] = { 3,2,2,3 };
	int val = 3;
	int len = sizeof(nums) / sizeof(nums[0]);

	int slow = 0;	//慢指针
	for (int fast = 0; fast < len; ++fast) {//快指针的移动
		if (nums[fast] != val) {//快指针找到新数组所需的元素,更新新数组
			nums[slow] = nums[fast];//将该元素赋给新数组当前下标slow所在的位置
			slow++;
		}
	}
	cout << slow << endl;
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/561267
推荐阅读
相关标签
  

闽ICP备14008679号