【算法与数据结构】34、 LeetCode在排序数组中查找元素的第一个和最后一个位置

所有的LeetCode题解索引,可以看这篇文章——【算法和数据结构】LeetCode题解

题目

在这里插入图片描述

一、暴力穷解法

  思路分析:看到查找数组这类题目,暴力穷解法一般都可以解决,那么我们首先调用库函数find()找到左边界,然后从左边界出发找右边界(left=right),这里要注意一个特殊的情况,就是左边界等于数组末端索引。这时候右边界等于左边界,可以直接返回。剩下的情况就是while循环找到第一个不等于target的值的索引,这个就是右边界(不包含target),返回时右边界要减去1。如果找不到,那么right本身就是右边界。

	// 1、暴力穷解法
	vector<int> searchRange2(vector<int>& nums, int target) {
		auto it = find(nums.begin(), nums.end(), target);   // 返回索引
		if (it == nums.end()) {
			return { -1, -1 };
		}
		else {	
			int left = it - nums.begin();			// 左边界
			int right = left;
			if (left != nums.size() - 1) {
				while (right < (nums.size() - 1) && nums[++right] == target);		// 右边界
			}
			return {left, nums[right] != target ? right - 1 : right};
		}
	}

  和其他人的算法比较:速度稍微慢了点,而且内存占用多。
复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),最坏的情况下要遍历整个数组。
  • 空间复杂度: O ( 1 ) O(1) O(1),运行时占用的内存是一个常数。
    在这里插入图片描述

二、二分法

  思路分析:阅读题目我们可以得出三种情况:

  • 1.target不在数组内,返回{-1, -1}。
  • 2.target在数组内, 正常返回{ left, right}。
  • 3.target在数组内,一些特殊情况。

  第一种情况使用普通的二分法就可以搞定,第二种先找到target的索引middle,然后向两边延伸找到边界值,那么如何延伸呢?

  我们同样使用while循环找到边界值,首先要限定左右边界索引的范围,比如左边界必须大于0,右边界必须小于数组容量。索引不断变换,直到数组的值不等于target,我们就找到边界值,正常的情况是能够找到不等于target的边界值,那么返回{left +1, right - 1 }就可以。第三种就是特殊情况,当找不到不等于target的边界值时,返回边界值索引本身。

	// 2、二分法
	vector<int> searchRange(vector<int>& nums, int target) {
		int left = 0, right = nums.size() - 1, middle = 0;
		while (left <= right) {
			middle = (left + right) / 2;
			if (nums[middle] < target) {
				left = middle + 1;
			}
			else if (nums[middle] > target) {
				right = middle - 1;
			}
			else {
				left = middle;
				right = middle;
				while (right < (nums.size() - 1) && nums[++right] == target );
				while (left > 0 && nums[--left] == target);
				return { nums[left] != target ? left + 1 : left, nums[right] != target ? right - 1 : right };
			}
		}
		return { -1, -1 };
	}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),虽然用了二分法,但是实际时间复杂度没有达到题目要求,本质上是靠while循环找到边界值,最坏的情况就是遍历整个数组,后续有待改进。
  • 空间复杂度: O ( 1 ) O(1) O(1),固定内存。

  算法对比:较之暴力穷解法,速度和内存方面都有很大提升。
在这里插入图片描述

完整代码

// 34. 在排序数组中查找元素的第一个和最后一个位置
# include <iostream>
# include <vector>
# include <algorithm>
using namespace std;

class Solution {
public:
	// 1、暴力穷解法
	vector<int> searchRange2(vector<int>& nums, int target) {
		auto it = find(nums.begin(), nums.end(), target);   // 返回索引
		if (it == nums.end()) {
			return { -1, -1 };
		}
		else {	
			int left = it - nums.begin();			// 左边界
			int right = left;
			if (left != nums.size() - 1) {
				while (right < (nums.size() - 1) && nums[++right] == target);		// 右边界
			}
			return {left, nums[right] != target ? right - 1 : right};
		}
	}

	// 2、二分法
	vector<int> searchRange(vector<int>& nums, int target) {
		int left = 0, right = nums.size() - 1, middle = 0;
		while (left <= right) {
			middle = (left + right) / 2;
			if (nums[middle] < target) {
				left = middle + 1;
			}
			else if (nums[middle] > target) {
				right = middle - 1;
			}
			else {
				left = middle;
				right = middle;
				while (right < (nums.size() - 1) && nums[++right] == target );
				while (left > 0 && nums[--left] == target);
				return { nums[left] != target ? left + 1 : left, nums[right] != target ? right - 1 : right };
			}
		}
		return { -1, -1 };
	}
};

void my_print(vector<int>& v, string msg)
{
	cout << msg << endl;
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
		cout << *it << ' ';
	}
	cout << endl;
}

int main()
{
	int target = 8;
	int arr[] = { 5, 7, 7, 8, 8, 10 };
	vector<int> nums;
	Solution s1;
	for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
	{
		nums.push_back(arr[i]);
	}
	my_print(nums, "目标数组:");
	vector<int> index = s1.searchRange(nums, target);
	my_print(index, "查找结果:");
	system("pause");
	return 0;
}

end

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
数据结构复习试题——线性表 一、 选择题 1.下列哪一条是顺序存储结构的优点 【 】 A. 插入运算方便 B. 可方便地用于各种逻辑结构的存储表示 C. 存储密度大 D. 删除运算方便 2. 下面关于线性表的叙述,错误的是哪一个 【 】 A. 线性表采用顺序存储,必须占用一片连续的存储单元。 B. 线性表采用顺序存储,便于进行插入和删除操作。 C. 线性表采用链接存储, 不必占用一片连续的存储单元 D. 线性表采用链接存储,便于进行插入和删除操作。 3. 线性表是具有n个【 】的有序序列(n>0) A. 表元素 B. 字符 C. 数据元素 D.数据项 4. 若某线性表最常用的操作是存取任一指定序号的元素和在最后进行插入和删除运算,则利用【 】存储方式最节约时间。 A. 顺序表 B. 双链表 C. 带头结点的双循环链表 D.单循环链表 5. 若某线性表最常用的操作是在最后一个元素之后插入一个元素和删除第一个元素,则采用【 】存储方式最节省运算时间。 A. 单链表 B. 仅有头指针的单循环链表 C. 双链表 D. 仅有尾指针的单循环链表 6. 若某线性表最常用的操作是在末尾插入结点和删除尾结点,则选用【 】最节省运算时间。 A. 带头结点的双循环链表 B. 单循环链表 C. 带尾指针的单循环链表 D. 单链表 7. 若某线性表最常用的操作是存取第I个元素及其前驱和后继元素的值,则采用【 】存储方式最节省运算时间。 A. 单链表 B. 双向链表 C. 单循环链表 D. 顺序表 8. 对于一个头指针为head的带头结点的单链表,判定该表为空的条件是【 】 A. head==NULL B. head->next==NULL C. head->next==head D. head!=NULL 9. 链表不具有的特点是 【 】 A. 插入、删除不需要移动元素 B. 可随机访问任一元素 C. 不必事先估计存储空间 D. 所需空间与线性长度成正比 10. 单链表,增加一个头结点的目的是为了 【 】 A. 使单链表至少有一个结点 B. 标示表结点首结点的位置 C. 方便运算的实现 D. 说明单链表是线性表的链式存储 11. 在一个以h为头的单循环链,p指针指向链尾的条件是 【 】 A. p->next==h B. p->next==NULL C. p->next->next==h D. p->data==-1 12. 对于一个线性表既要求能够进行快速的插入和删除,又要求存储结构能反映数据之间的逻辑关系,则应该使用 【 】 A. 顺序存储方式 B.链式存储方式 C. 散列存储方式 D.以上均可以 13. 一个算法应该具有“确定性”等5个特性,下面对另外4个特性的描述错误的是【 】 A. 有零个或多个输入 B. 有零个或多个输出 C. 有穷性 D. 可行性 14、一个线性表第一个元素的存储地址是100,每个元素的长度为2,则第5个元素的地址是【 】 (A)110 (B)108 (C)100 (D)120 15、向一个有127个元素的顺序表插入一个新元素并保持原来顺序不变,平均要移动【 】个元素。 (A)64 (B)63 (C)63.5  (D)7 16、线性表采用链式存储结构时,其地址【 】。 (A) 必须是连续的 (B) 部分地址必须是连续的 (C) 一定是不连续的 (D) 连续与否均可以 17、. 在一个单链表,若p所指结点不是最后结点,在p之后插入s所指结点,则执行【 】 (A)s->next=p;p->next=s; (B) s->next=p->next;p->next=s; (C)s->next=p->next;p=s; (D)p->next=s;s->next=p; 18、在一个单链表,若删除p所指结点的后续结点,则执行【 】 (A)p->next=p->next->next; (B)p=p->next; p->next=p->next->next; (C)p->next=p->next; (D)p =p->next->next; 19、线性表是具有n个【 】的有限序列(n≠0) (A)表元素 (B)字符 (C)数据元素  (D)数据项 20、下列有关线性表的叙述,正确的是(  ) (A)线性表元素之间隔是线性关系 (B)线性表至少有一个元素 (C)线性表任何一个元素有且仅有一个直接前趋 (D)线性表任何一个元素有且仅有一个直接后继 二、 填空题 1、已知P为单链表的非首尾结点,在P结点后插入S结点的语句为:_______________________ 。 2、顺序表逻辑上相邻的元素物理位置 相邻, 单链表逻辑上相邻的元素物理位置_________相邻。 3.、线性表L=(a1,a2,...,an)采用顺序存储,假定在不同的n+1个位置上插入的概率相同,则插入一个新元素平均需要移动的元素个数是________________________ 4、在非空双向循环链表,在结点q的前面插入结点p的过程如下: p->prior=q->prior; q->prior->next=p; p->next=q; ______________________; 5、已知L是无表头结点的单链表,是从下列提供的答案选择合适的语句序列,分别实现: (1)表尾插入s结点的语句序列是_______________________________ (2) 表尾插入 s结点的语句序列是_______________________________ 1) p->next=s; 2) p=L; 3) L=s; 4) p->next=s->next; 5) s->next=p->next; 6) s->next=L; 7) s->next=null; 8) while(p->next!= Q) p=p-next; 9) while(p->next!=null) p=p->next; 6、 向一个长度为n的向量的第i个元素(1≤i≤n+1)之前插入一个元素时,需向后移动( )个元素。 7、向一个长度为n的向量删除第i个元素(1≤i≤n)时,需向前移动 个元素。 8、在顺序表插入或删除一个元素,需要平均移动 元素,具体移动的元素个数与 有关。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晚安66

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值