代码随想录算法训练营第一天 | 二分查找&双指针

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 左闭右开写法[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.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左边界往左一个位置】
}

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

/*找到右边界的往右一个位置*/
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右边界往右一个位置】
}

当获取到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;
}

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第二十二天的算法训练营主要涵盖了Leetcode题目中的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串中找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组中的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组中找到长度最小的子数组,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值