leetcode c++(4)(哈希表unordered_map、散列表unordered_set、动态规划、substr()、有很多道双指针法)

1.验证回文串

在这里插入图片描述

①:运行很慢,用地址来循环,自己第一次写的(运行时间620ms左右)

ps:主要用到了:

isalnum()是判断是否是字母或数字的函数,
tolower()是如果是大写字母就转换为小写
erase()是删除这个地址,后面要搭配it–一起用,因为删除之后后面的就往前了,it++是往后推的意思,所以要用it–来抵消
reverse()反转

bool isPalindrome(string s) {
    
    
	for (auto it = s.begin(); it < s.end(); it++)
	{
    
    
		if (isalnum(*it) != 0)
		{
    
    
			*it=tolower(*it);
		}
		else
		{
    
    
			s.erase(it);
			it--;//和it++抵消
		}
	}
	string temp = s;
	reverse(s.begin(), s.end());
	return s == temp;
}

②:是①的改进版(运行时间26ms左右)

改进的地方:
不删地址了,只把符合字母数字的元素存到另一个空string里

bool isPalindrome(string s) {
    
    
	string temp1;
	for (auto it = s.begin(); it < s.end(); it++)
	{
    
    
		if (isalnum(*it) != 0)
		{
    
    
			*it=tolower(*it);
			temp1 += *it;
		}
	}
	string temp2 = temp1;
	reverse(temp1.begin(), temp1.end());
	return temp2 == temp1;
}

③:是②的改进版(运行时间22ms左右)

改进的地方:
不用reverse,而是逐个判断是否相等(比如如果第一位和最后一位就不一样了,这个方法就会快很多)

bool isPalindrome(string s) {
    
    
	string temp;
	for (auto it = s.begin(); it < s.end(); it++)
	{
    
    
		if (isalnum(*it) != 0)
		{
    
    
			*it=tolower(*it);
			temp += *it;
		}
	}
	for (auto it = temp.begin(),ie=temp.end(); it < temp.begin() + (temp.end() - temp.begin()) / 2; it++,ie--)
	{
    
    
		if (*it != *ie)
			return false;
	}
	return true;	
}

④:是③的改进版(运行时间18ms左右)

改进的地方:
不用地址,用数组来循环

bool isPalindrome(string s) {
    
    
	string temp;
	for (int i = 0; i < s.size(); i++)
	{
    
    
		if (isalnum(s[i]) != 0)
			temp += tolower(s[i]);
	}
	for (int i = 0; i < temp.size(); i++)
	{
    
    
		if (temp[i] != temp[temp.size() - 1 - i])
			return false;
	}
	return true;	
}

⑤:是④的改进版(运行时间12ms左右)

改进的地方:
只有一次for循环,快很多

bool isPalindrome(string s) {
    
    
	for (int left = 0,right=s.size()-1; left < right; left++,right--)
	{
    
    
		while (!isalnum(s[left]) && left < right)//从左开始找不符合的,跳过不符合的(isalnum为0时不是数字或字母)
			left++;
		while (!isalnum(s[right]) && left < right)从右开始找不符合的,跳过不符合的
			right--;
		if (tolower(s[left]) != tolower(s[right]))
			return false;
	}
	return true;	
}

ps:
判断数字:isdigit()

cout<<isalpha('a');//false
cout<<isalpha('2')//true

判断字母:isalpha()

cout<<isalpha('a');//true
cout<<isalpha('2')//false

判断字母或数字:isalnum()

cout<<isalpha('a');//true
cout<<isalpha('2')//true

判断小写字母:islower()

cout<<isalpha('a');//true
cout<<isalpha('A')//false

判断大写字母:isupper()

cout<<isalpha('a');//false
cout<<isalpha('A')//true

转成大写字母:toupper()

s[0]=toupper(s[0]);

转成小写字母:tolower()

s[0]=tolower(s[0]);

2.只出现一次的数字

在这里插入图片描述
思路:先排序,然后,从第二个数字开始逐两位遍历,如果遍历到的数和前一位不一致,那前一位就是只出现了一次。

int singleNumber(vector<int>& nums) {
    
    
	sort(nums.begin(), nums.end());
	for (int i=1; i < nums.size(); i+=2)
	{
    
    
		if (nums[i] != nums[i - 1])
			return nums[i - 1];
	}
	return nums[nums.size()-1];
}

int main()
{
    
    
	vector<int>nums = {
    
     1,2,1,2,4 };
	cout << singleNumber(nums);
	return 0;
}

3.两数相加

在这里插入图片描述
注意一下进位就好了,这题就是从个位往前加的。

方法1:

#include <iostream>
using namespace std;
#include <bits/stdc++.h>
struct ListNode {
    
    
	int val;
	ListNode *next;
	ListNode(int x) : val(x), next(NULL) {
    
    }	
};


ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
    
    
	ListNode* ans = new ListNode(-1);
	ListNode* cur = ans;//移动到ans的位置
	int count = 0;
    int sum=0;
	while (l1 != nullptr || l2 != nullptr)
	{
    
    
		sum = count;//同位和的初值
		if (l1 != nullptr)
		{
    
    
			sum += l1->val;
			l1 = l1->next;
		}
		if (l2 != nullptr)
		{
    
    
			sum += l2->val;
			l2 = l2->next;
		}
		if (sum >= 10)
		{
    
    
			count = 1;
			sum -= 10;
		}
		else
			count = 0;
		cur->next=new ListNode(sum);//cur->val不能赋值了,因为cur->next是空
		cur = cur->next;
	}
    if(count==1)
        cur->next=new ListNode(1);
	return ans->next;
}

方法2:

ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
    
    
	ListNode* ans = new ListNode(-1);
	ListNode* cur = ans;//移动到ans的位置
	int count = 0;
	while (l1 != nullptr || l2 != nullptr)
	{
    
    
		int sum = count;//同位和的初值
		if (l1 != nullptr)
		{
    
    
			sum += l1->val;
			l1 = l1->next;
		}
		if (l2 != nullptr)
		{
    
    
			sum += l2->val;
			l2 = l2->next;
		}
		if (sum >= 10)
		{
    
    
			count = 1;
			sum -= 10;
		}
		else
			count = 0;
		cur->val = sum;
        if(l1 != nullptr || l2 != nullptr)//时间会久一点,但空间ListNode少一位
        {
    
    
		    cur->next=new ListNode(-1);
		    cur = cur->next;
        }
	}
	if (count == 1)
		cur->next=new ListNode(1);
	return ans;
}

4.无重复字符的最长子串(哈希表、散列表)

在这里插入图片描述


哈希表unordered_map使用
代码:

int main()
{
    
    
	unordered_map<string, int >map = {
    
     {
    
    "one",1}, {
    
    "two",2 } };//创建
	
	map["ten"]=10;//哈希表这样写是可以的!!
	
	if (map.find("four") != map.end())//判断:如果找得到"four"(或者写成if(map.count("four") > 0)也行)
		cout << map["four"]<<endl;
	else//找不到
		cout << "four not found"<<endl;

	if (map.find("one") != map.end())
		cout << map["one"]<<endl;
	else
		cout << "one not found"<<endl;
	if (map.find("ten") != map.end())//找得到
		cout << map["ten"] << endl;
	return 0;
}


输出:
four not found
1
10


方法一和方法二是哈希表,方法三是散列表

都是用的滑动窗口方法

哈希表的做法是检测到相同的就返回到在前面的相同的那一位的后一位再建新窗口(所以用while)

散列表的做法是逐位建新窗口(所以用for),直到检测到相同的这个窗口才停(所以里面用while)。



方法一:慢(因为clear()慢)

在这里插入图片描述

#include <iostream>
using namespace std;
#include <bits/stdc++.h>
#include <unordered_map>
int lengthOfLongestSubstring(string s) {
    
    
	unordered_map<char, int>hash;
	int cur = 0;
	int window = 0;
	int ans=0;
	int old = 0;
	while (cur < s.size())
	{
    
    
		if (hash.count(s[cur]) > 0)//有了
		{
    
    
			ans = max(ans, window);
			cur = hash[s[cur]] + 1;
			window = 0;
			hash.clear();
		}
		else//没有
		{
    
    
			hash[s[cur]] = cur;
			window++;
			cur++;
		}
	}
	ans = max(window, ans);
	return ans;
}
int main()
{
    
    
	cout<<lengthOfLongestSubstring("pwwkew");
	return 0;
}

方法二:
在这里插入图片描述

int lengthOfLongestSubstring(string s) {
    
    
	unordered_map<char, int>hash;
	int cur = 0;
	int window = 0;
	int ans = 0;
	int tempcur=0;
	int left = 0;
	while (cur < s.size())
	{
    
    
		if (hash.count(s[cur]) > 0 && cur != hash[s[cur]] && hash[s[cur]] >= left)//有了
		{
    
    
			ans = max(ans, window);
			tempcur = cur;
			cur = hash[s[cur]] + 1;
			left = cur;
			hash[s[tempcur]] = tempcur;
			window = 0;				
		}
		else//没有
		{
    
    
			hash[s[cur]] = cur;
			window++;
			cur++;
		}
	}
	ans = max(window, ans);
	return ans;
}

方法三:
在这里插入图片描述

int lengthOfLongestSubstring(string s) {
    
    
	unordered_set<char>uos;
	int cur = 0;
	int n = s.size();
	int ans = 0;
	for (int i = 0; i < n; i++)
	{
    
    
		if (i > 0)
			uos.erase(s[i - 1]);
		while (cur < n && !uos.count(s[cur]))//没有
		{
    
    
			uos.insert(s[cur]);
			cur++;
		}
		ans = max(ans, cur - i);
	}
	return ans;
}

5. 最长回文子串(动态规划)

在这里插入图片描述

在这里插入图片描述

https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zhong-xin-kuo-san-dong-tai-gui-hua-by-liweiwei1419/

填表:(用空间换时间)
在这里插入图片描述
思路:和左下角判断&&判断本位,可得填表顺序即先把列填满(相对左的格)

解法:r等于l时true(对角线),l-r==1时判断l和r上的字符等不等(对角线的右边一位),其它位置就和左下角的那个判断&&l和r上的字符等不等

主要用到了动态规划和substr函数。substr(起始位,长度)

string longestPalindrome(string s) {
    
    
	bool dp[1002][1002];
	string ans;
	for (int l = 0; l < s.size(); l++)//列
	{
    
    
		for (int r = 0; r <= l; r++)//行
		{
    
    
			if (r == l)
				dp[r][l] = true;//r相当于子串最左端,l相当于子串最右端,所以如果要往里的话是r+1、l-1
			else if (l - r == 1)
				dp[r][l] = s[r] == s[l];
			else
			{
    
    
				dp[r][l] = (s[r] == s[l] && dp[r + 1][l - 1]);
			}
			if (dp[r][l] && l - r + 1 > ans.size())
				ans = s.substr(r, l-r+1);//起始位,长度
		}
	}
	return ans;
}
int main()
{
    
    
	cout << longestPalindrome("cbbd");
	return 0;
}

6.Z字形变换

在这里插入图片描述

思路:触底就往上,触顶就往下

string convert(string s, int numRows) {
    
    
	vector<string>vs(numRows);
	string ans;
	if (numRows == 1)//注意特殊情况!
		return s;
	int flag = 0;//0往下
	int num = 0;//顶层
	for (int i = 0; i < s.size(); i++)
	{
    
    
		vs[num] += s[i];
		if (flag == 0)
			num++;
		else
			num--;
		if (num == numRows - 1)//底层
			flag = 1;//往上
		else if (num == 0)
			flag = 0;
	}
	for (int i = 0; i < numRows; i++)
	{
    
    
		ans += vs[i];
	}
	return ans;
}
int main()
{
    
    
	cout << convert("AB", 1);
	return 0;
}

7.字符串转换整数

在这里插入图片描述
在这里插入图片描述

注意这里,容易出错:

在这里插入图片描述
正确代码:

int myAtoi(string str) {
    
    
	long ans = 0;
	int flag=1;
	int i = 0;
	while (str[i] == ' ')
		i++;
	if (str[i] == '-')
		flag = -1;
	if (str[i] == '-'||str[i] == '+')//实现:只能有一个符号
		i++;
	for (; i<str.size() && isdigit(str[i]); i++)
	{
    
    
		ans = 10 * ans + int(str[i] - '0');
		if (ans >= INT_MAX && flag == 1)
			return INT_MAX;
		if (ans > INT_MAX&&flag == -1)
			return INT_MIN;
	}
	return flag * ans;
}

中间那里也可以写成:

	if (str[i] == '-')
	{
    
    
		flag = -1;
		i++;
	}
	else if (str[i] == '-'||str[i] == '+')//实现:只能有一个符号
		i++;

这题也可以用自动机来做:https://leetcode-cn.com/problems/string-to-integer-atoi/solution/san-chong-fang-fa-zheng-chang-bian-li-you-xian-zhu/

8.盛水最多的容器(经典的双指针法)

在这里插入图片描述
思路:left和right较短的那条边往内移。

#include <iostream>
using namespace std;
#include <bits/stdc++.h>
int maxArea(vector<int>& height) {
    
    
	int left = 0, right = height.size() - 1,ans=0;
	while (left < right)
	{
    
    
		ans = max(ans, min(height[right],height[left])*(right - left));
		if (height[right] > height[left])
			left++;
		else
			right--;
	}
	return ans;
}
int main()
{
    
    
	vector<int>height = {
    
     1,8,6,2,5,4,8,3,7 };
	cout<<maxArea(height);
	return 0;
}

9.整数转罗马数字 (贪心法)

在这里插入图片描述
在这里插入图片描述
思路:把每位带1、4、5、9的情况列出来,再将num从高位向低位判断
这里用到了两个数组来存数据比较巧妙(遍历方便)

string intToRoman(int num) {
    
    
	vector<int>vnum = {
    
     1000,900,500,400,100,90,50,40,10,9,5,4,1 };
	vector<string>vstr = {
    
     "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I" };
	string ans;
	for (int i = 0; i < vstr.size(); i++)
	{
    
    
		while (num >= vnum[i])
		{
    
    
			num -= vnum[i];
			ans += vstr[i];
		}
	}
	return ans;
}
int main()
{
    
    
	cout << intToRoman(999);
	return 0;
}

10.三数之和(难度大一点点的双指针法)

在这里插入图片描述
思路:先排序,再对排序后数组进行遍历,运用双指针法,逐位遍历时的那一位是本次判断必带数,另外两个数靠left和right来计算,如果和大于0就right–,和小于0就left++。left初始是i+1,即遍历数的后面那个,right初始是数组的末位数。
在判断等于的时候,双指针往内缩,并且,除重。

vector<vector<int>> threeSum(vector<int>& nums) {
    
    
	vector<vector<int>>ans;
	sort(nums.begin(), nums.end());//先排序
	for (int i = 0; i < nums.size(); i++)
	{
    
    
		int left = i + 1;
		int right = nums.size() - 1;
		if (i > 0 && nums[i] == nums[i - 1])//除重,因为在前一次已经判断完这个数的情况了
			continue;
		while (left < right)
		{
    
    
			if (nums[i] + nums[left] + nums[right] > 0)
				right--;
			else if (nums[i] + nums[left] + nums[right] < 0)
				left++;	
            else
			{
    
    
				ans.push_back(vector<int>{
    
    nums[i], nums[left], nums[right]});
				while (right > left&&nums[right] == nums[right - 1])//除重,下次如果是同一数字,在上一次已经判断完这个数的情况
					right--;
				while (right > left&&nums[left] == nums[left + 1])//除重
					left++;
				left++;
				right--;
			}			
		}
	}
	return ans;
}

11.最接近的三数之和(双指针法,比10简单一点)

在这里插入图片描述
思路和10 差不多,不过找到答案的时候不用除重。

int threeSumClosest(vector<int>& nums, int target) {
    
    
	sort(nums.begin(), nums.end());
	int dif = 10000;//差值,有正负,target-cur=dif,找dif绝对值最小,return target-dif
	for (int i = 0; i < nums.size(); i++)
	{
    
    
		if (i > 0 && nums[i] == nums[i - 1])
			continue;
		int left = i + 1;
		int right = nums.size() - 1;
		while (left < right)
		{
    
    
			if (abs(target - (nums[i] + nums[left] + nums[right])) < abs(dif))//绝对值比较小
				dif = target - (nums[i] + nums[left] + nums[right]);
			if (nums[i] + nums[left] + nums[right] > target)
				right--;
			else if (nums[i] + nums[left] + nums[right] < target)
				left++;
			else
				return target;
		}
	}
	return target - dif;
}

12.四数之和(双指针法)(比三数之和再难一点点)

在这里插入图片描述

ps:括号里标注了与三数之和的区别
五数之和、六数之和也是同理可得

vector<vector<int>> fourSum(vector<int>& nums, int target) {
    
    
	vector<vector<int>>ans;
	if (nums.size() < 4)
		return ans;
	sort(nums.begin(), nums.end());//先排序
	for (int m = 0; m < nums.size()-3; m++)//(最外层加了一层循环)
	{
    
    
		if (m > 0 && nums[m] == nums[m - 1])//(初始除重)
			continue;
		for (int i = m+1; i < nums.size()-2; i++)//(i=m+1与三数不同)
		{
    
    
			int left = i + 1;
			int right = nums.size() - 1;
			if (i > m+1 && nums[i] == nums[i - 1])//除重,因为在前一次已经判断完这个数的情况了(i>m+1与三数不同)
				continue;
			while (left < right)
			{
    
    
				if (nums[m]+nums[i] + nums[left] + nums[right] > target)//(求和与三数不同)
					right--;
				else if (nums[m]+nums[i] + nums[left] + nums[right] < target)
					left++;
				else
				{
    
    
					ans.push_back(vector<int>{
    
    nums[m],nums[i], nums[left], nums[right]});//(输出与三数不同)
					while (right > left&&nums[right] == nums[right - 1])//除重,下次如果是同一数字,在上一次已经判断完这个数的情况
						right--;
					while (right > left&&nums[left] == nums[left + 1])//除重
						left++;
					left++;
					right--;
				}
			}
		}
	}	
	return ans;
}

猜你喜欢

转载自blog.csdn.net/weixin_44575911/article/details/107997704