LeetCode中级刷题

前言:由于开题好长时间不刷题了,中级的算法题好难啊!一定要再次坚持下去。

数组与字符串

1、三数之和

1)题目:
在这里插入图片描述
2)解析:排序+双指针(参考官方解法)
注意关键词:不重复。要用三重循环来解决此题
如果我们固定了前两重循环枚举到的元素 a 和 b,那么只有唯一的 c 满足 a+b+c=0。当第二重循环往后枚举一个元素 b’时,由于 b’ > b,那么满足 a+b’+c’=0的 c’ 一定有 c’ < c,即 c’在数组中一定出现在 c的左侧。也就是说,我们可以从小到大枚举 b同时从大到小枚举 c,即第二重循环和第三重循环实际上是并列的关系。
有了这样的发现,我们就可以保持第二重循环不变,而将第三重循环变成一个从数组最右端开始向左移动的指针,
这个方法就是我们常说的「双指针」,当我们需要枚举数组中的两个元素时,如果我们发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法,将枚举的时间复杂度从 O(N^2) 减少至 O(N)。
总结:
a、把数组排序
b、三重循环,第一重从0 到length-2 遍历数组;第二重从i+1开始遍历;第三重从length-1开始遍历
c、比较nums[left] + nums[right] 与 nums[i]的关系;若相等,推入数组;若小于,left指针右移;若大于,right指针左移。
d、注意两次“去重”,第一次是三重循环,第二重从小到大,第三重从大到小;第二次,若比较相等left右移找到不等的值,right左移。

vector<vector<int>> threeSum(vector<int>& nums) {
		sort(nums.begin(), nums.end());
		int length = nums.size();
		vector<vector<int>> vec;
		for (int i = 0; i < length-2; i++)
		{
			//过滤重复
			if (i > 0 && nums[i] == nums[i - 1])
				continue;
			//第二重循环从小到大枚举
			int left = i + 1; 
			//第三重循环从大到小枚举
			int right = length - 1;
			while (left < right)
			{
				if (nums[left] + nums[right] + nums[i] == 0) {
					vec.push_back(vector<int>());
					vec.back().push_back(nums[left]);
					vec.back().push_back(nums[right]);
					vec.back().push_back(nums[i]);
					//过滤重复
					//注意left与他的下一个left+1比较,不是前一个left-1哦,刚开始想的left-1一直不对,
					while (left < right && nums[left] == nums[left + 1])
					{
						left++;
					}
					//过滤重复
					//注意right与他的上一个right-1比较,不是前一个right+1哦,刚开始想的right+1一直不对,
					while (left < right && nums[right] == nums[right - 1])
					{
						right--;
					}
					left++;
					right--;
				}
				//因为已经排序过了,(-nums[i])作为目标元素,如果nums[left] + nums[right]小于目标值,让left向右移动就会变大
				else if (nums[left] + nums[right] < (-nums[i])) {
					left++;
				}
				else
				{
					//right向左移动相加值就会变小
					right--;
				}
				
			}	
		}
		return vec;

	}

2、矩阵置零

1)题目: 给定一个n*n的矩阵,如果一个元素为0,则将其所在行和列的元素都设为0,请使用“原地”算法。
在这里插入图片描述
2)思路一:
第一次循环把是0的位置的行数和列数存在队列里,(pair可以把两个元素合并成一个元素);第二次循环把对应元素置0。

//空间复杂度:O(m + n)
	void setZeroes2(vector<vector<int>>& matrix) {
		//第一次遍历把0元素的行和列存在队列中
		//pair 将两个数据组合成一个数据
		//当一个函数需要两个数据时,也可以用pair                    
		queue<pair<int, int>> rot;
		//行数
		int line = matrix.size();
		//列数
		int row = matrix[0].size();
		for (int i = 0; i < line; i++)
		{
			for (int  j = 0; j < row; j++)
			{
				if (matrix[i][j] == 0)
					rot.emplace(i, j);
			}
		}
		//第二次遍历把对应元素改为0
		while (!rot.empty())
		{
			//行
			int h = rot.front().first;
			//列
			int l = rot.front().second;
			rot.pop();
			for (int i = 0; i < line; i++)
			{
				matrix[i][l] = 0;
			}
			for (int j = 0; j < row; j++)
			{
				matrix[h][j] = 0;
			}
		}
	}

3)思路二:
用两个标记数组代替思路一的队列,一个标记行,一个标记列。如果元素为0,就把元素对应的行数和列数标记为true。第二次遍历,再把行数或列数为true的元素置0。

	//空间复杂度:O(m + n)
	//用两个标记数组代替下面的队列
	void setZeroes3(vector<vector<int>>& matrix) {
		int m = matrix.size();
		int n = matrix[0].size();
		vector<int> row(m), col(n);
		for (int i = 0; i < m; i++) {
			for (int j = 0; j < n; j++) {
				if (!matrix[i][j]) {
					row[i] = col[j] = true;
				}
			}
		}
		for (int i = 0; i < m; i++) {
			for (int j = 0; j < n; j++) {
				if (row[i] || col[j]) {
					matrix[i][j] = 0;
				}
			}
		}
	}

4)思路三:
把矩阵的第一行和第一列作为标记数组。
第一次遍历所有元素,如果matrix[i][j]是0就把第一行第一列对应的行和列(matrix[i][0] matrix[0][j])置0,如果第一行或第一列原本就有0,用flag_row或flag_col标记一下。
第二次遍历除第一行第一列以外的其他元素,如果他所在对应的matrix[i][0] matrix[0][j]为0,则把该元素置为0。
如果第一行或第一列原本就有0(flag_row或flag_col为true),就把第一行或第一列置0。

//我们可以用矩阵的第一行和第一列代替方法一中的两个标记数组,以达到 O(1) 的额外空间。
	void setZeroes(vector<vector<int>>& matrix) {
		int m = matrix.size();
		int n = matrix[0].size();
		//标记第一行是否有数字0
		int flag_row = false;
		//标记第一列是否有数字0
		int flag_col = false; 
		for (int i = 0; i < m; i++)
		{
			//如果matrix[i][j]是0就把第一行第一列对应的行和列(matrix[i][0] matrix[0][j])置0
			for (int j = 0; j < n; j++) {
				if (matrix[i][j] == 0) {
					//如果第一行或第一列原本就有0,标记一下
					if (i == 0)
						flag_row = true;
					if (j == 0)
						flag_col = true;
					matrix[i][0] = matrix[0][j] = 0;
				}
			}
		}
		//遍历除第一行第一列以外的其他元素,如果他所在对应的matrix[i][0] matrix[0][j]为0,则把该元素置为0
		for (int i = 1; i < m; i++)
		{
			for (int j = 1; j < n; j++) {
				if (!matrix[i][0] || !matrix[0][j]) {
					matrix[i][j] = 0;
				}
			}
		}
		//如果第一行原本就有0,就把第一行置0
		if (flag_row)
		{
			for (int i = 0; i < n; i++)
			{
				matrix[0][i] = 0;
			}
		}
		//如果第一列原本就有0,就把第一列置0
		if (flag_col)
		{
			for (int j = 0; j < m; j++)
			{
				matrix[j][0] = 0;
			}
		}

	}

3、字母异位词

1)题目:
在这里插入图片描述
2)思路:由于互为字母异位词的两个字符串包含的字母相同,因此对两个字符串分别进行排序之后得到的字符串一定是相同的,故可以将排序之后的字符串作为哈希表的键。

	vector<vector<string>> groupAnagrams(vector<string>& strs) {
		//对字符串数组中的每一个字符串排序,排序结果相同的就是字母异位词
		//键值是源字符串,键是排序后的字符串。妙妙妙!!!
		//unordered_map 哈希映射 无序  map是有序容器
		unordered_map<string, vector<string>> stp;
		vector<vector<string>> res;
		int length = strs.size();
		//注意是 string& 不是 string
		for (string& str : strs) {
			string key = str;
			sort(key.begin(), key.end());
			stp[key].emplace_back(str);//stp[key]表示key这组异位词的集合
			
		}
		//unordered_map 前向迭代期
		for (auto it = stp.begin(); it != stp.end(); ++it) {
			res.emplace_back(it->second);//遍历哈希表,将字母异位词的集合存入结果集中
		}
		return res;
	}

4、无重复字符的最长子串

1)题目:
在这里插入图片描述
2)思路:
定义一个bool型的标记数组str[255],字母s[i]出现以后,对应的str[s[i]]=true;当字母再次出现时,追溯到第一次出现的位置j,无重复字符的长度就是 i-j+1;

int lengthOfLongestSubstring(string s) {
		//双指针,i指针是遍历字符串,j指针回溯
		int length = s.size();
		bool nums[255] = { false };
		int j = 0,maxnum=0;
		for (int i = 0; i < length; i++)
		{
			//2、访问到重复字符,将其推移,直到找到第一个重复字符
			while (nums[s[i]])
			{
				nums[s[j]] = false;
				++j;
			}
			//1、每访问一个新的字符,就将其进行标记
			nums[s[i]] = true;
 			maxnum = max(maxnum, i - j + 1);
		}
		return maxnum;
	}

5、最长回文子串

1)题目:
在这里插入图片描述
2)思路:中心扩散法
回文子串中心互为镜像,因此,回文可以从他的中心展开,奇数有一个中心点,偶数有两个中心点。

//最长回文串(毫无头绪啊!!!!!!)
	//中心扩展法
	string longestPalindrome(string s) {
		int length = s.size();
		if (length < 2)
			return s;
		int start = 0;//最长回文串起始位置
		int end = 0;//终止位置
		int maxlength = 0;
		for (int i = 0; i < length; i++)
		{
			int odd = strlength(s, i, i);//奇数中心
			int even = strlength(s, i, i + 1);//偶数中心
			maxlength = max(max(odd, even), maxlength);
			if (maxlength > (end - start + 1)) {
				start = i - (maxlength - 1) / 2;
				end = i + maxlength / 2;
			}
		}
		return s.substr(start, maxlength); //该函数的意思是获取从start开始长度为mlen长  度的字符串
	}
	//以left和right为中心的回文串长度
	int strlength(string s, int left, int right) {
	//注意left可以取到0
		while ( left>=0 &&  right<s.size() && s[left] == s[right])
		{
			left--;
			right++;
		}
		return right - left - 1;//返回回文子串的长度
	}

6、递增的三元子序列

1)题目:
在这里插入图片描述
2)思路:注意一点三元子序列只要是递增的就可以,可以连续也可以不连续。
定义两个变量,一个用来存储最小值,一个用来存储中间值,注意他们的初始值是 INT_MAX。扫描数组:
如果当前数字小于等于最小值,就更新最小值;
如果当前数字大于最小值但小于等于中间值,就更新中间值;
如果当前值既不小于等于最小值也不小于等于中间值,则说明找到了三个递增子序列,直接返回true。

//子序列可以不连续
	bool increasingTriplet(vector<int>& nums) {
		int length = nums.size();
		if (length < 3)
			return false;
		int minnum = INT_MAX, mid = INT_MAX;
		for (int i = 0; i < length; i++)
		{
			if (nums[i] <= minnum)
				minnum = nums[i];
			else if (nums[i] <= mid )
			{
				mid = nums[i];
			}
			else
			{
				return true;
			}
		}
		return false;
	}

链表

1、两数相加

1)题目:
在这里插入图片描述

2)错误思路:
刚开始想的是先把两个链表转化成对应的数字,然后两个数字相加计算出和,最后把和转化成链表输出。由于长时间不做链表的题导致都不会常见链表了,废了好多时间。而且这种方法还没有通过测试,因为两数之和超出了INT的范围。
在这里插入图片描述

3)正确思路:
由于输入的两个链表都是 逆序 存储数字的位数的,因此两个链表中同一位置的数字可以直接相加。
我们同时遍历这两个链表,逐位计算他们的和,并与当前位置的进位值相加。
此处有两点注意:a、最终结果也是逆序存储 b、进位值的处理

ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
		//创建list链表存出结果
		ListNode* list = new ListNode;
		//ptr是临时链表,存储每一位数字,再把他插入list
		ListNode* ptr = list;
		int number = 0;
		while (number || l1!=nullptr || l2 != nullptr)
		{
			if (l1 != nullptr) {
				number += l1->val;
				l1 = l1->next;
			}
			if (l2 != nullptr) {
				number += l2->val;
				l2 = l2->next;
			}
			//number % 10只取末位数字存入链表
			//在list末尾插入数据,保证最终结果也是逆序存储(最低位是个位)
			ptr->next = new ListNode(number % 10, nullptr);
			ptr = ptr->next;
			//进位值
			number = number / 10;
		}
		return list->next;
	}

2、奇偶链表

1)题目:
在这里插入图片描述
2)思路: 双指针(链表经常应用双指针)——参考“数据结构和算法”的解题思路
一个指针把奇数位置串联起来,另一个指针把偶数位置连起来,再把偶数位置链表头部接到奇数位置指针尾部。
在这里插入图片描述
所以需要定义几个变量:
记录奇数位置的头部指针:oddhead
奇数位置扫描指针:oddcur
记录偶数位置的头部指针:evenhead
偶数位置扫描指针:evencur

ListNode* oddEvenList(ListNode* head) {
		if (head == nullptr || head->next == nullptr) {
			return head;
		}
		//分别记录奇数链表的头节点和尾节点,偶数链表的头节点和尾节点
		//奇数链表的头结点
		ListNode* odd = head;
		//奇数链表的当前节点
		ListNode* oddcur = odd;
		//偶数链表的头结点
		ListNode* even = head->next;
		//偶数链表的当前节点
		ListNode* evencur = even;
		while (evencur !=nullptr && evencur->next!=nullptr)//注意这是偶数当前节点,不能是奇数;因为偶数在奇数后面 
		{
			//奇数节点串在一起
			oddcur->next = oddcur->next->next;
			//偶数节点串在一起
			evencur->next = evencur->next->next;
			//奇偶指针往后移动
			oddcur = oddcur->next;
			evencur = evencur->next;
		}
		//最后把偶数链表的头部串在奇数链表的尾部
		oddcur->next = even;
		//返回奇数指针的头部
		return odd;
	}

3、相交链表

1)题目:
在这里插入图片描述
在这里插入图片描述
2)思路:这道题看起来说的很多,很复杂是的,其实就是找两个链表的交点。我首先想到的是先遍历一个链表,并把它的值存入哈希表中,再遍历另一个链表,如果哈希表中存在此链表中的当前值,当前值就是两个链表的焦点。但是哈希表存两个值,此处只需要保存一个值就够了,用哈希表浪费空间。参考评论区,选用的是集合不是哈希表。

ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
		//把headA链表遍历一遍,把值存在集合中;再遍历headB链表,若果在集合中存在headB中的值就返回该值
		set<ListNode*> mac;
		while (headA !=nullptr)
		{
			mac.insert(headA);
			headA = headA->next;
		}
		while (headB !=nullptr)
		{
			if (mac.count(headB)) {
				return headB;
			}
			headB = headB->next;
		}
		return nullptr;
		
	}

3)思路2:先计算两个链表的长度,如果两个链表的长度不相等就让长的先走,直到两个链表一样长了再同时走。同时比较当前节点是否相同,如果不同就同时继续往后移动。

//计算链表长度
	int length(ListNode* head) {
		int n = 0;
		while (head!=nullptr)
		{
			head = head->next;
			n++;
		}
		return n;
	}
	ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
		int length1 = length(headA);
		int length2 = length(headB);
		//如果两个链表长度不相等,长度长的先走
		while (length1!=length2)
		{
			if (length1 > length2) {
				headA = headA->next;
				length1--;
			}
			else
			{
				headB = headB->next;
				length2--;
			}
		}
		//两个长度相等了再开始比较
		while (headA!=headB)
		{
			headA = headA->next;
			headB = headB->next;
		}
		//走到最后,最终会有两种可能,一种是headA为空,
		//也就是说他们俩不相交。还有一种可能就是headA
		//不为空,也就是说headA就是他们的交点
		return headA;


	}

4)思路:双指针——参考“数据结构与算法”解法
假设A有5个节点,B有8个节点
因为A和B的总节点数是不变的13,A指针把A链表走完以后就开始从B链表头部开始走,同时B指针把B链表走完了就开始从A链表头部开始走。如果A链表和B链表相交,A、B指针肯定会在相交点相遇,因为到相交点出A、B指针走过的路一样长,都刚好吧整个链表 完整的走了 一遍
下面这条评论简洁又有趣的概括了这种思路:
在这里插入图片描述

	ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
		//双指针:A指针如果把A链表走完了,然后就从B链表开始走;B指针如果把B链表走完了就从A开始走;
		//如此循环,只要A和B链表相交,总会相遇
		ListNode* listA = headA;
		ListNode* listB = headB;
		while (listA!=listB)
		{
			//如果指针listA不为空,listA就往后移一步。
			//如果指针listA为空,就让指针listA指向headB(注意这里是headB不是listB)
			listA = listA == nullptr ? headB : listA->next;
			listB = listB == nullptr ? headA : listB->next;
		}
		//listA要么是空,要么是两链表的交点
		return listA;

	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值