2022 05 字节题

leetcode 76 最小覆盖子串H

题目要求:
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
示例 1:

输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"

用下面这种方法讲解这个例子:
首先哈希表记录t中三个字母出现的次数 都为1
然后遍历s 对于在s中遍历到的每一个字母 其出现次数都会减一 ,则那些在t中出现过一次的其出现次数减为0 ;那些没在t中出现过 次数减为-1;
第一次len==t.length()的时候是右指针r指向第二个A时;
左指针l指向第二个B时,len不再等于s.length() ; 然后右指针r一直移动到s末尾


//从左向右遍历s 所有的元素出现的次数都会减一;
//对于那些在t中也出现过的元素 出现次数减一
class Solution 
{
    
    
public:
	string minWindow(string s, string t)
	{
    
    
		int count[256] = {
    
     0 };//数组作哈希
		for (auto c : t) ++count[c];        //初始化最小覆盖子串是s字符串的长度;
		int l = 0, r = 0, len = 0, min_len = s.size();//len是在s中包含t中所有元素的某个字符串的长度 min_len是最小覆盖字符串的长度;
		string res;//返回最小覆盖子串
		while (r < s.size())
		{
    
       //滑动窗口右边界右移的过程 在右移的过程中 当遇到t中出现的元素在s中也出现过了 len++
			if (--count[s[r++]] >= 0) ++len;//出现次数减一之后还是大于等于0说明之前出现的次数至少为1
			while (len == t.length())//在s中可以找到涵盖t的所有字符 // inner loop, shrink left until s[l..r) not contains t
			{
    
    
				if (r - l <= min_len)
				{
    
    
					min_len = r - l;//更新最短的可以覆盖的子串的长度
					res = s.substr(l, min_len);//截取子串
				}
				if (++count[s[l++]] > 0) --len;//缩小左边界 直到[l,r]内的元素不全包括t中的元素;跳出while循环  继续扩大右边界;
			}  //出现次数加一之后大于零 说明此时左边界的元素覆盖子串里的元素;因为此时左边界右移了 滑窗内的元素不能完全覆盖t中的所有的元素了 len要递减 然后右边界再扩大 继续寻找
		}
		return res;
	}
};


/===========================================================================
//最小覆盖子串
//滑动窗口解法
class Solution
{
    
    
public:
	string minWin(string s, string t)
	{
    
    
		int m = s.size(), n = t.size();
		int left = 0, right = 0;
		int needed[200] = {
    
     0 };//数组做哈希表
		int count = 0;//统计s中有几个和t中一样的元素
		int min_len = m;
		string ret = "";
		for (auto i : t)
		{
    
    
			needed[i]++;//统计t中的元素出现的次数
		}
		for (; right < m; right++)//遍历s串  左右指针遍历方式
		{
    
    
			char temp = s[right];
			if (--needed[temp] >= 0)// needed[temp]>=1-----t中的出现的元素在s中也出现过
			{
    
    
				count++;//重复的元素加一
			}
			while (count == n)//当s中重复的元素数目和t中元素一致
			{
    
    
				if (right - left + 1 <= min_len)
				{
    
    
					min_len = right - left + 1;
					ret = s.substr(left, min_len);
				}
				if (++needed[s[left]] > 0)
				{
    
    
					count--;
				}
				left++;
			}
		}
		return ret;
	}
};


leetcode 424 替换后的最长重复字符

# leetcode 424 替换后的最长重复字符
给你一个`字符串 s 和一个整数 k` 。你可以选择字符串中的任一字符,并将其更改为任何其他大写英文字符。该操作最多可执行 k 次。
在执行上述操作后,`返回包含相同字母的最长子字符串的长度`。

输入:`s = "AABABBA", k = 1`
输出:4
解释:
将中间的一个'A'替换为'B',字符串变为 "AABBBBA"。
子串 "BBBB" 有最长重复字母, 答案为 4**滑动窗口+双指针解法** 

```cpp

//双指针
//我们可以枚举字符串中的每一个位置作为右端点,然后找到其最远的左端点的位置
//满足该区间内除了出现次数最多的那一类字符之外,剩余的字符数量不超过k个
//这样我们可以想到使用双指针维护这些区间,每次右指针右移
//如果区间仍然满足条件,那么左指针不移动,否则左指针至多右移一格,保证区间长度不减小。


class Solution
{
    
    
public:
	int characterReplacement(string s, int k)
	{
    
    
		vector<int>map(26);//数组作哈希
		int n = s.length();
		int maxn = 0;//维护一个重复字符出现次数的历史最大值
		int left = 0, right = 0;
		while (right < n)
		{
    
    
			map[s[right] - 'A']++;// - 'A'是转换成大写字母
			maxn = max(maxn, map[s[right] - 'A']);//更新右移位置的字符出现的次数,尝试用它更新重复字符出现次数的历史最大值
			if (right - left + 1 - maxn > k)//可以被替换的字符数目超过k个
			{
    
    
				map[s[left] - 'A']--;//缩小左边界
				left++; //左指针右移直到:非最长重复字符 = k 个
			}
			right++;//一直向右遍历 走到这里的时候 right会多走一步
		}
		return right - left;//因为right多走一步 所以 结果为 (right-1)-left+1==right-left
	}
};


判断一个字符串s1是不是包含另一个字符串s2

给你两个字符串s1 s2 写一个函数来判断s2是否包含s1的排列 如果是 返回true 如果不是 返回false
例如:
输入:s1 = "ab" s2 = "eidbaooo"
输出:true
解释:s2 包含 s1 的排列之一 ("ba").
在这里插入图片描述

//利用哈希表加双指针的方法
//设置一个双指针 右指针先移动一个单位 当遍历到哈希表中没有出现过的元素时 左指针也开始移动一个单位 和右指针重合;
//当遇到哈希表中出现过的元素时,右指针移动  左指针不移动 最后左右指针的下标之差符合我们要找的字符串的长度 说明我们在s2中找到了s1的一个排列;
class Solution
{
    
    
public:
	bool checkInclusion(string s1, string s2)
	{
    
    
		unordered_map<char, int>map;//集合中的值默认初始化为0 哈希表只存储s1中出现的元素
		for (auto& c : s1)map[c]++;
		int l = 0, r = 0;
		while (r < s2.size())
		{
    
    
			char c = s2[r++];
			map[c]--;//当遇到在s2中出现 在s1中没出现过的元素时,值会减为负数
			while (l < r && map[c] < 0)
			{
    
    
				map[s2[l++]]++;//将在s2中出现过但是没在s1中出现过的元素的出现次数加一, 跳出当前的while循环 进入上一个循环,从而进行下一步判断
			}
			if (r - l == s1.size())return true;
		}
		return false;
	}
};

将x减少到0的最少操作次数

给你一个整数数组 nums 和一个整数 x 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。
如果可以将 x 恰好 减到 0 ,返回 最小操作数 ;否则,返回 -1

在这里插入图片描述

//反向寻找数组中减去x之后的元素和target
class Solution
{
    
    
public:
	int minOperations(vector<int>& nums, int x)
	{
    
    
		int n = nums.size();
		int ans = INT_MAX;
		int target = accumulate(nums.begin(), nums.end(),0)-x;//accumulate的参数:累加初始地址 累加结束地址 累加初值
		if (target == 0)return n;//数组内所有的元素恰好相加之和就是给定的x 所有的元素都会被减一遍
		int left = 0, right = 0;//双指针
		int sum = 0;
		while (right < n)
		{
    
    
			sum += nums[right];//右指针先移动 进行累加
			while (sum > target && left < right)
			{
    
    
				sum -= nums[left];//左指针进行递减
				++left;
			}
			if (sum == target)ans = min(ans, n-1 + left - right);//n-1-(左右指针之间的距离)=操作次数;左右指针之间的距离=right-left
			++right;
		}
		if (ans == INT_MAX)return -1;
		return ans;
	}
};

int main()
{
    
    
	vector<int>nums = {
    
    3,2,20,1,1,3};
	int ans = 0;
	Solution so;
	ans = so.minOperations(nums, 10);
	cout << ans << endl;
	system("pause");
	return 0;
}

leetcode 202 快乐数

用一个例子说明什么是快乐数

输入:n = 19
输出:true
解释:

12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

class Solution2
{
    
    
public:
	bool isHappy(int n)
	{
    
    
		unordered_set<int>set;//无序 不含重复元素
		while (1)
		{
    
    
			int sum = getSum(n);
			if (sum == 1)return true;
			if (set.find(sum) != set.end())return false;
			else set.insert(sum);
			n = sum;//将一次计算的结果重新赋值给n 进行下一次的计算
		}
	}
	int getSum(int n)//对一个数字进行每个位的数字求平方和
	{
    
    
		int sum = 0;
		while (n)
		{
    
    
			sum += (n % 10) * (n % 10);//先从最低位的平方和开始求
			n /= 10;//再求高位的平方和
		}
		return sum;//返回一个数字一次求各位平方和的结果
	}
};

leetcode 88 合并两个有序数组

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
**注意:**最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

例如:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。


//合并两个已排序数组
class Solution
{
    
    
public:
	void merge(vector<int>& nums1, int m, vector<int>& nums2, int n)
	{
    
    
		int i = nums1.size() - 1;//num1初始长度为m+n;
		m--; n--;
		while (n >= 0)
		{
    
              //从后向前比较
			if(m >= 0 && nums1[m] > nums2[n])//数组nums1的最后一个元素大于nums2的最后一个元素值;
			{
    
    
				nums1[i] = nums1[m];//将两数组最后一个元素中较大的那个放入到nums1的末尾
				m--;//继续比较nums1的倒数下一个元素
				i--;
			}
			else
			{
    
    
				nums1[i] = nums2[n];
				n--;//继续比较nums2的倒数下一个元素
				i--;
			}
		}
	}
};


leetcode 4 寻找两个正序数组的中位数 H

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。



///中位数:奇数的话就是中间的数 偶数的话就是中间两个数字的平均值
//通过了 但是没考虑时间复杂度的要求
class Solution
{
    
    
public:
	double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
	{
    
    
		vector<int>vec;//用来存放合并之后的数组 合并后的数组是有序的从小到大
		int i = 0, j = 0;//双指针用来遍历两个数组
		while (i < nums1.size() && j < nums2.size()) //实现两个数组的合并 
		{
    
    
			if (nums1[i] <= nums2[j])
			{
    
    
				vec.push_back(nums1[i++]);
			}
			else
			{
    
    
				vec.push_back(nums2[j++]);
			}
		}
		while (i < nums1.size())vec.push_back(nums1[i++]);//nums2全部合并到新的数组中 但是nums1还有未合并的部分的情况
		while (j < nums2.size())vec.push_back(nums2[j++]);//nums1全部合并到新数组中去 但是nums2还有未合并的部分的情况
		return vec.size() % 2 ? vec[vec.size() / 2] : (vec[vec.size() / 2 - 1] + vec[vec.size() / 2]) / 2.0;//判断合并之后的数组的奇偶性 并返回中位数
	}
};


leetcode 41 缺失的第一个整数 H

给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。
在这里插入图片描述


//小顶堆实现排序函数
vector<int>sortArray(vector<int>& nums)
{
    
    
	vector<int>res;
	priority_queue<int, vector<int>, greater<int>>q;//小顶堆实现
	for (auto num : nums)
	{
    
    
		q.push(num);
	}
	while (!q.empty())
	{
    
    
		res.push_back(q.top());
		q.pop();
	}
	return res;
}

class Solution
{
    
    
public:
	int firstMissingPositive(vector<int>& nums)
	{
    
    
		int res=1;
		vector<int>ret;
		ret=sortArray(nums);//自定义函数实现数组的排序
		for (auto&num : ret)
		{
    
    
			if (num > res)return res;//遍历到的第一个数字是大于res的 直接返回res
			else if (num == res)res++;
			else if (num < 0)continue;//所求的正整数 负数直接跳过 0不做处理
		}
		return res;
	}
};


leetcode 42 接雨水H

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
在这里插入图片描述

思路:对于每个位置可以存储的雨水 都会计算一遍;


class Solution
{
    
    
public:
	int trap(vector<int>& height)
	{
    
    
		int n = height.size();
		vector<int>left(n), right(n);//left[i]表示i左边的最大高度;right[i]表示i右侧的最大高度;
		for (int i = 1; i < n; i++) //因为第一个位置左侧是空的 所以从正数第二个位置开始遍历
		{
    
    
			left[i] = max(left[i - 1], height[i - 1]);
		}
		for (int i = n - 2; i >= 0; i--)//从倒数第二个位置开始遍历
		{
    
    
			right[i] = max(right[i + 1], height[i + 1]);
		}
		int water = 0;
		for (int i = 0; i < n; i++)
		{
    
    
			int high = min(left[i], right[i]);//对于每一个位置都会求出其左侧最大高度和右侧最大高度
			water += max(0, high - height[i]);//累加每个位置的面积即可;每个位置可以接水的多少可视为宽度为1 高度为high的矩形的面积;
		}
		return water;
	}
};





leetcode 5 最长回文子串

给定一个字符串s 返回s中最长的回文子串
例如:
在这里插入图片描述


//最长回文子串--代码随想录--动态规划解法
//在确定递推公式时,就要分析如下几种情况。

//整体上是两种,就是s[i]与s[j]相等,s[i]与s[j]不相等这两种。
///
//当s[i]与s[j]不相等,那没啥好说的了,dp[i][j]一定是false。
//
//当s[i]与s[j]相等时,这就复杂一些了,有如下三种情况
//
//情况一:下标i 与 j相同,同一个字符例如a,当然是回文子串
//情况二:下标i 与 j相差为1,例如aa,也是文子串
//情况三:下标:i 与 j相差大于1的时候,
//例如cabac,此时s[i]=c 与s[j]=c 已经相同了,我们看i到j区间是不是回文子串就看aba是不是回文就可以了
//那么aba的区间就是 i + 1 与 j - 1区间,这个区间是不是回文就看dp[i + 1][j - 1]是否为true。

class Solution
{
    
    
public:
	string longestPalindrome(string s)
	{
    
    
		int n = s.size();
		vector<vector<int>>dp(n, vector<int>(n, false));//dp[i][j]表示区间[i,j]范围内的子串是否是回文子串 如果是回文子串dp[i][j]为true
		int maxlen = 0;
		int left = 0;
		int right = 0;
		for (int i = n - 1; i >= 0; i--)//倒序遍历i
		{
    
    
			for (int j = i; j < n; j++)//正序遍历j
			{
    
    
				if (s[i] == s[j])
				{
    
    
					if (j - i <= 1)dp[i][j] = true;//情况一和情况二
					else if (dp[i + 1][j - 1])dp[i][j] = true;//情况三
				}
				if (dp[i][j] && j - i + 1 > maxlen)
				{
    
    
					maxlen = j - i + 1;//更新最长回文子串的长度
					left = i;//保存最长子串左边界
					right = j;//保存最长子串右边界
				}
			}
		}
		return s.substr(left, right - left + 1);//提取最长回文子串即可
	}
};


int main()
{
    
    
	string s = "babad";
	string ans;
	Solution so;
	ans=so.longestPalindrome(s);
	cout << ans << endl;
	system("pause");
	return 0;
}



leetcode 440 字典序的第k小数字H

在这里插入图片描述

解题思路:构建字典树的方法
在这里插入图片描述

观察易知:前序遍历该字典树即可得到字典序从小到大的数字序列,遍历到第k个节点即为第k小的数字。
字典树的根节点数值是1
在这里插入图片描述
代码实现:


class Solution
{
    
    
public:
	int findKthNumber(int n, int k)
	{
    
    
		int cur = 1;//1是字典序最小的
		k--;//当k=1时 直接返回1 不进入while循环
		while (k > 0)
		{
    
    
			int nodes = getNodes(cur, n);//steps表示当前节点cur下有多少个比n小的子节点(包括n本身
			if (nodes <= k)//当前节点下的子节点不够 需要去邻近节点寻找
			{
    
    
				k -= nodes;//意味着前面的steps个数包含在cur节点下 接下来进入兄弟节点找第k-steps小的数
				cur++;//到达临近兄弟节点
			}
			else
			{
    
    
				cur = cur * 10;//从当前层最左侧开始寻找
				k--;//减去当前节点
			}
		}
		return cur;
	}
	int getNodes(int cur, long n)//计算节点cur下有多少个比n小的子节点
	{
    
    
		int nodes = 0;
		long first = cur;
		long last = cur;
		while (first <= n)//当前层有符合要求的节点
		{
    
    
			nodes += min(last, n) - first + 1;
			first = first * 10;//进入到下一层 first表示每一层的第一个节点;
			last = last * 10 + 9;//进入到下一层 last表示每一层的最后一个节点;
		}
		return nodes;
	}
};

猜你喜欢

转载自blog.csdn.net/weixin_48433164/article/details/124560200