中级算法之数组和字符串

总结:中级算法中的数组和字符串问题中依然沿用了初级算法问题中的双指针法,例如题目1;并且加入了一些数组中动态规划的问题,例如题目4,题目5,题目6。

题目1:三数之和

给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

思路:三数之和与两数之和其实是一个类型的题目,两数之和时a+b==0,而三数之和是a+b+c==0,转化一下就是a+b==-c,实际上还是两数之和问题。先对数组进行排序,之后遍历数组,依次将第i个元素当做c,之后在第i+1个元素和第len-1个元素各放一个指针分别当做b和c。不停将a,b,c相加,若大于目标则将右指针向前推一位(因为排过序所以等于缩小二者的和),反之则将左指针向后推一位(增大二者的和),直到两指针相遇。重复上述过程直到遍历完所有数组内元素。但要注意一点,无论是a,b,c,都要跳过相同数字。

vector<vector<int> > threeSum(vector<int>& nums) 
{
    int len = nums.size();
	vector<vector<int> > result;
	if(len<3)
		return result;
	sort(nums.begin(),nums.end());
	for(int i=0;i<len;i++)
	{
		//跳过相同的数字
		if(i>0&&nums[i]==nums[i-1])
			continue;
		int point1 = i+1;
		int point2 = len-1;
		while(point1<point2)
		{
			int sum = nums[i]+nums[point1]+nums[point2];
			if(sum==0)
			{
				vector<int> list{nums[i],nums[point1],nums[point2]};
				result.push_back(list);
				point1++;
				//跳过相同的数字 
				while((point1<point2)&&(nums[point1]==nums[point1-1]))
					point1++;
				point2--;
				//跳过相同的数字
				while((point1<point2)&&(nums[point2]==nums[point2+1]))
			    	point2--;
			}
			else if(sum<0)
			{
				point1++;
				//跳过相同的数字
				while((point1<point2)&&(nums[point1]==nums[point1-1]))
					point1++;
			}
			else if(sum>0)
			{
				point2--;
				//跳过相同的数字
				while((point1<point2)&&(nums[point2]==nums[point2+1]))
			    	point2--;
			}
		}
	}
	return result; 
}

题目2:矩阵置零

给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请使用原地算法。

示例 1:

输入: 
[
  [1,1,1],
  [1,0,1],
  [1,1,1]
]
输出: 
[
  [1,0,1],
  [0,0,0],
  [1,0,1]
]
示例 2:

输入: 
[
  [0,1,2,0],
  [3,4,5,2],
  [1,3,1,5]
]
输出: 
[
  [0,0,0,0],
  [0,4,5,0],
  [0,3,1,0]
]

思路:遍历二维数组,分别用两个一维数组记录二维数组需要变成0的行和列,之后分别遍历这两个数组将对应行和列化为0。

void setZeroes(vector<vector<int> >& matrix) 
{
	int m = matrix.size();
	if(m==0)
		return;
	int n = matrix[0].size();
	//记录有0的行 
	int row[m];
	memset(row,0,sizeof(int)*m);
    //记录有0的列
	int col[n];
	memset(col,0,sizeof(int)*n);
	for(int i=0;i<m;i++)
	{
		for(int j=0;j<n;j++)
		{
			if(matrix[i][j]==0)
			{
				if(row[i]==0)
					row[i] = 1;
				if(col[j]==0)
					col[j] = 1;
			}
		}
	}
	for(int i=0;i<m;i++)
	{
		if(row[i]==1)
		{
			for(int j=0;j<n;j++)
				matrix[i][j] = 0;	
		}
	}
	for(int i=0;i<n;i++)
	{
		if(col[i]==1)
		{
			for(int j=0;j<m;j++)
				matrix[j][i] = 0;
		}
	}    
}

题目3:字谜分组

给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。

示例:

输入: ["eat", "tea", "tan", "ate", "nat", "bat"],
输出:
[
  ["ate","eat","tea"],
  ["nat","tan"],
  ["bat"]
]

思路:遍历数组,用一个flag数组记录数组中元素是否已经被分组。当遍历到i元素,先将i元素按字母分解,将每个字母出现的次数存储到一个长度为26的数组arr中(代表26个字母),之后遍历其后未被分组的元素,按相同方法进行拆解并保存到另一个长度为26的数组temp中,之后对两个数组进行比较,若各字母出现次数均相同则为其分组,并标记已分组。重复上述过程直到所有元素均被分组。

vector<vector<string> > groupAnagrams(vector<string>& strs) 
{
	int len = strs.size();
	vector<vector<string> > result;
	if(len==0)
		return result;
	int flag[len];
	memset(flag,0,len*sizeof(int));
	int arr[26];
	int temp[26];
	for(int i=0;i<len;i++)
	{
		if(flag[i]==0)
		{
			vector<string> list;
			list.push_back(strs[i]); 
			memset(arr,0,26*sizeof(int));
			int s1_len = strs[i].size();
			//拆解原始字符串的字母 
			for(int j=0;j<s1_len;j++)
			{
				int index = strs[i][j]-'a';
				arr[index] += 1;
			}
			//与原始字符串进行比较 
			for(int j=i+1;j<len;j++)
			{
				if(flag[j]==0)
				{
					int s2_len = strs[j].size();
					if(s2_len!=s1_len)
						continue;
					memset(temp,0,26*sizeof(int));
					int k = 0;
					//分解被比较字符串 
					for(k=0;k<s2_len;k++)
					{
						int index = strs[j][k]-'a';
						temp[index] += 1; 
					}
					//比较 
					for(k=0;k<26;k++)
						if(temp[k]!=arr[k])
							break;
					if(k==26)
					{
						list.push_back(strs[j]);
						flag[j] = 1;
					}
				}  
			}
			result.push_back(list);
		}
	} 
	return result;   
}

题目4:无重复字符的最长子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:

输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:

输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
         请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

思路:使用动态规划,用dp[i]记录以i元素结尾的最长子串长度。遍历字串,当遍历到元素s[i],首先计算s[i]与其上次出现时相隔的字母数count,一.如果count等于0,那么说明该元素之前从没出现过,那么此时以i元素结尾的最长子串长度就相当于在i-1元素结尾的最长子串长度上加1,此时的状态转移方程是dp[i]=dp[i-1]+1;二.如果count不等于0,那么此时就要判断一下,如果count>dp[i-1],那么说明s[i]上次出现的位置不在以i-1元素结尾的最长字串内,那么此时以i元素结尾的最长子串就是以i-1元素结尾的最长字串+s[i],状态转移方程同样为dp[i]=dp[i-1]+1;如果count<dp[i-1],那么说明s[i]上次出现的位置在以i-1元素结尾的最长字串内,那么此时以i元素结尾的最长子串就是s[i]与其上次出现时相隔的这段子串,此时状态转移方程为dp[i]=count。

int lengthOfLongestSubstring(string s) 
{
	int len = s.size();
	if(len<=1)
		return len==0?0:1;
	//以i元素结尾的最长子串长度 
	int dp[len];
	memset(dp,0,len*sizeof(int));
	//记录每个字母上次出现的位置  
	dp[0] = 1;
	int max = 1;
	//字符距其上次出现位置的距离
	int count = 0;
	for(int i=1;i<len;i++)
	{
		count = 0; 
		//这段处理可以用map做记录来简化算法 
		for(int j=i-1;j>=0;j--)
		{
			if(s[i]==s[j])
			{
				count = i-j;
				break;
			}
		} 
		//count等于0意味着这个字符没有出现过 
		if(count!=0)
		{
			if(count>dp[i-1])
				dp[i] = dp[i-1]+1;
			else
				dp[i] = count;
		}
		else
			dp[i] = dp[i-1]+1;
		max = max>dp[i]?max:dp[i];
	} 
	return max;
}

题目5:最长回文子串

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:

输入: "cbbd"
输出: "bb"

思路:使用动态规划,用一个二维数组dp[i][j]记录从i到j的子串是否回文。以为单个字母肯定回文,所以先将单个字母dp[i][i]全部记录为回文子串,之后判断字符串各个子串是否为回文子串。当s[i]==s[j]时,若其长度小于3,那么它一定是回文子串,若其长度大于3,若s[i+1]到s[j-1]形成的子串为回文子串,则s[i]到s[j]形成的子串也为回文子串。

string longestPalindrome(string s) 
{
	int len = s.size();
	if(len<=1)
		return s;
	//最长子串的起始下标和长度 
	int start = 0;
	int length = 1;
	//用于表示从i到j的子串是否回文 
	int dp[len][len];
	memset(dp,0,sizeof(dp));
	for(int i=0;i<len;i++)
		dp[i][i] = 1;
	//在判断dp[i][j]之前必须保证dp[i+1][j-1]被判断过了
	//所以要采取i降序j升序的遍历法 
	for(int i=len-2;i>=0;i--)
	{
		for(int j=i+1;j<len;j++)
		{
			if(s[i]==s[j]&&(j-i<3||dp[i+1][j-1]==1))
			{
				dp[i][j] = 1; 
				if(j-i+1>length)
				{
					start = i;
					length = j-i+1;
				}
			}
		}
	}
	return s.substr(start,length); 
}

题目6:递增的三元子序列

给定一个未排序的数组,判断这个数组中是否存在长度为 3 的递增子序列。

数学表达式如下:

如果存在这样的 i, j, k,  且满足 0 ≤ i < j < k ≤ n-1,
使得 arr[i] < arr[j] < arr[k] ,返回 true ; 否则返回 false 。
说明: 要求算法的时间复杂度为 O(n),空间复杂度为 O(1) 。

示例 1:

输入: [1,2,3,4,5]
输出: true
示例 2:

输入: [5,4,3,2,1]
输出: false

思路:遍历数组,用一个数组dp记录i元素之前比其小元素的个数。当遍历到第i个元素时,向前依次遍历i之前元素j,若i元素大于j元素,并且dp[j]大于1,这说明j元素之前比其小的元素个数大于1,而i又大于j,此时形成了一个递增三元序列。 

bool increasingTriplet(vector<int>& nums) 
{
	int len = nums.size();
	if(len<3)
		return false;
	//记录i元素之前比其小的数的个数 
	int dp[len];
	memset(dp,0,len*sizeof(int));
	for(int i=1;i<len;i++)
	{
		for(int j=i-1;j>=0;j--)
		{
			if(nums[j]<nums[i])
			{
				if(dp[j]>=1)
					return true;
				else
					dp[i] += 1;	
			}
		}
	}
	return false;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
大数据和算法、数据分析的应用场景非常广泛,可以涵盖各个行业和领域。以下是一些常见的大数据和算法、数据分析应用场景: 电子商务:通过收集用户消费习惯、季节和产品生命周期的数据,建立算法模型来确定下一个月、几个月甚至一年的消费者需求。这样可以提高订单转化率。在营销方面,可以给买家贴标签,建立人群画像,针对不同人群精准投放广告和优惠券。 医疗保健:医生根据患者的症状和检查结果,结合自身经验得出结论,最终提供相应的治疗方案。不同地区的医疗水平各不相同,尤其是高水平医生短缺,好医院分布不均。根据患者的症状检测报告,通过病理分析模型确定病因,并提供具体的治疗方案。即使在医疗保健不发达的地区,也只需要输入患者的症状和医疗记录,就可以体验高级医生的服务。 金融风险管理:金融机构利用大数据技术来分析交易数据、市场趋势和经济指标,识别潜在的风险和欺诈行为。大数据技术还可以用于建立预测模型,帮助金融机构预测市场变化,制定有效的风险管理策略。 物流和供应链管理:大数据技术可以优化物流运输路线、库存管理和供应链协调。通过分析大量的实时物流数据和市场需求数据,企业可以提高配送效率,减少成本,并提供更好的客户服务。 智能城市和交通管理:大数据技术可以帮助城市管理者监测和分析交通流量、能源消耗和环境污染等数据,为城市规划和交通管理提供决策支持。通过智能化的交通系统和城市基础设施,可以提高交通效率

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值