LeetCode刷题总结:(1)数组相关问题

写在最前面:分析完成一道题,最重要的是完整的捋清楚解决问题的方法逻辑,而捋清一道题的小技巧我觉得是可以画出能够表达逻辑的图或表,清晰的呈现在自己的草稿纸上,之后实现代码的时候就会变得很轻松!!!

 

刷了近100道题后发现其实leetcode中类型最多的还是数组相关的问题。数组相关问题好多是可以通过维护相应的索引来解决的。总之在数组问题中对索引的意义,维护是很重要的。

3. 无重复字符的最长子串

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

示例:

给定 "abcabcbb" ,没有重复字符的最长子串是 "abc" ,那么长度就是3。

给定 "bbbbb" ,最长的子串就是 "b" ,长度是1。

给定 "pwwkew" ,最长子串是 "wke" ,长度是3。请注意答案必须是一个子串"pwke" 是 子序列  而不是子串。

class Solution {
public:
	int lengthOfLongestSubstring(string s) {

		// 双索引滑动查找思路:维护好[i,j]数据段内的数据不重复即可
		// 当j往前查询发现存在重复元素时,那么缩减i,直至数据段内没有重复元素
		int i = 0;
		int j = -1;
		int res = 0;
		int n = s.length();
		int contain[256] = { 0 };

		while (i < n) {
			// 往前探索的数据存在重复值
			if (contain[s[j + 1]]>0) {
				contain[s[i++]]--;
			}
			else {	// 往前探索的数据唯一
				if (j + 1 >= n)
					return res;
				contain[s[++j]]++;
				res = std::max(res, j - i + 1);
			}
		}

		return res;
	}
};

 

11. 盛最多水的容器

给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (iai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (iai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器,且 n 的值至少为 2。

示例:

输入: [1,8,6,2,5,4,8,3,7]
输出: 49
class Solution {
public:
    int maxArea(vector<int>& height) {
        
        // 对撞指针
		int l = 0;
		int r = height.size()-1;
		int max_val = 0;

		while (l < r) {
			max_val = std::max(max_val, std::min(height[l], height[r])*(r - l));
			if (height[l] < height[r])
				l++;
			else
				r--;
		}

		return max_val;
    }
};

 

26. 删除排序数组中的重复项

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

示例 1:

给定数组 nums = [1,1,2], 

函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 

你不需要考虑数组中超出新长度后面的元素。

示例 2:

给定 nums = [0,0,1,1,1,2,2,3,3,4],

函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。

你不需要考虑数组中超出新长度后面的元素。
class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        
        if (nums.empty())
			return 0;

		// 一次遍历,[0,k)为最终的不重复数组
		int n = nums.size();
		//int val = nums[0];
		int k = 1;
		for (int i = 1; i < n; i++) {
			if (nums[i] != nums[k-1]) {
				nums[k] = nums[i];
				k++;
			}
		}

		return k;
    }
};


// 给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。
// 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
// 上面的升级版:使得每个元素最多出现两次
class Solution1 {
public:
	int removeDuplicates(vector<int>& nums) {

		// 我们只需要在上面的逻辑加上对相同值计数的辅助变量
		if (nums.empty())
			return 0;

		// 一次遍历,[0,k)为最终的每个元素最多出现两次的数组
		int n = nums.size();
		int k = 1;
		int count = 1;
		for (int i = 1; i < nums.size(); i++) {
			if (nums[i] == nums[k - 1] && count == 1) {
				nums[k++] = nums[i];
				count++;
			}
			else if (nums[i] != nums[k - 1]) {
				nums[k++] = nums[i];
				count = 1;
			}
		
		}

		return k;

	}
};

 

27. 移除元素

给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 1:

给定 nums = [3,2,2,3], val = 3,

函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。

你不需要考虑数组中超出新长度后面的元素。

示例 2:

给定 nums = [0,1,2,2,3,0,4,2], val = 2,

函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。

注意这五个元素可为任意顺序。

你不需要考虑数组中超出新长度后面的元素。
// 一次遍历,使得遍历过程中[0,k)不为val值,
// [k,i]为val值,最终val值会被移动到最后
class Solution {
public:

	// 一次遍历,使得遍历过程中[0,k)不为val值,
	// [k,i]为val值,最终val值会被移动到最后
	int removeElement(vector<int>& nums, int val) {

		int k = 0;
		for (int i = 0; i < nums.size(); i++) {
			if (nums[i] != val)
				if (k != i)
					swap(nums[k++], nums[i]);
				else
					k++;
		}

		return k;
	}

	// 优化,使得最终可以不需要一次遍历,可以提前结束
	// [0,i]不为val值,(j,nums.size()-1]为val值
	// 注意:这个算法会改变数的顺序,但是题目是允许的
	// 这个算法步骤会少,但是判断条件鬼多,没有上面的简单明了
	int removeElement2(vector<int>& nums, int val) {

		int j = nums.size() - 1;
		for (int i = 0; i < nums.size(); i++) {
			
			if (i > j)
				return i;

			// 检测到等于val值,需要交换
			if (nums[i] == val) {
				// 倒数第一位不是val的数
				while (nums[j] == val)
					j--;

				// 实际上不应该出现这种情况,所以判断一下
				if (j < i)
					return i;

				// 数组中的值都为val
				if (j < 0)
					return 0;

				swap(nums[i], nums[j]);
				j--;
			}
		}

		return nums.size();
	}

};

 

75. 分类颜色

给定一个包含红色、白色和蓝色,一共 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

注意:
不能使用代码库中的排序函数来解决这道题。

示例:

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

进阶:

  • 一个直观的解决方案是使用计数排序的两趟扫描算法。
    首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。
  • 你能想出一个仅使用常数空间的一趟扫描算法吗?
class Solution {
public:
	void sortColors(vector<int>& nums) {

		// 计数排序:统计出1,2,3数字的个数,然后往nums赋值即可
		int count[3] = { 0 };
		int n = nums.size();
		for (int i = 0; i < n; i++) {
			count[nums[i]]++;
		}

		int index = 0;
		for (int i=0; i < count[0]; i++)
			nums[index++] = 0;
		for (int i = 0; i < count[1]; i++)
			nums[index++] = 1;
		for (int i = 0; i < count[2]; i++)
			nums[index++] = 2;
	}

	// 类似三路排序的优化方案:维护好三个指针
	// arr[0,zero_index]==0,arr[zero_index+1,i-1]==1,arr[two_index,n-1]==2
	// 终止条件:i>=two
	void sortColors2(vector<int>& nums) {

		int zero_index = -1;
		int n = nums.size();
		int two_index = n;
		for (int i = 0; i < n;) {
			if (i >= two_index)
				return;

			if (nums[i] == 1)
				i++;
			else if (nums[i] == 0) {
				swap(nums[++zero_index], nums[i]);
				i++;
			}
			else if (nums[i] == 2) {
				swap(nums[i], nums[--two_index]);
			}
		}
	}
};

 

88. 合并两个有序数组

给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 使得 num1 成为一个有序数组。

说明:

  • 初始化 nums1 和 nums2 的元素数量分别为 m 和 n
  • 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。

示例:

输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6],       n = 3

输出: [1,2,2,3,5,6]
class Solution {
public:
	void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {

		// 另外生成一个空间用于存储merge操作
		int *mergeSort = new int[m + n];

		int index1 = 0;
		int index2 = 0;

		for (int i = 0; i < m + n; i++) {
			// nums1放置完毕
			if (index1 >= m) {
				mergeSort[i] = nums2[index2++];
			}
			else if (index2 >= n) {		// nums2放置完毕
				mergeSort[i] = nums1[index1++];
			}
			else if (nums2[index2] < nums1[index1]) {
				mergeSort[i] = nums2[index2++];
			}
			else {
				mergeSort[i] = nums1[index1++];
			}
		}

		for (int i = 0; i < m + n; i++)
			nums1[i] = mergeSort[i];

		delete mergeSort;
	}


	// 再思考一下有没有可能进行原地操作呢???
	// 应该是可以的,将nums2先插入到nums1的后面,然后进行插入排序操作,但是这个时间复杂度会比上面的算法高
	// 优点是空间复杂度为1
	void merge1(vector<int>& nums1, int m, vector<int>& nums2, int n) {

		/*int index = m;
		for (int i = 0; i < n; i++)
			nums1[index++] = nums2[i];*/

		int j = 0;
		for (int i = m; i < (m + n); ++i)
		{
			nums1[i] = nums2[j++];
		}

		// 开始插入排序
		for (int i = 1; i < m + n; i++) {

			if (nums1[i] < nums1[i - 1]) {
				int j = i-1;
				// 寻找的同时维护数据顺序
				while (nums1[j] > nums1[i]) {
					nums1[j + 1] = nums1[j];
					j--;
					// 注意这里的j有可能越界,所以要判断一下
					if (j < 0)
						break;
				}
			
				// 找到了该插入的位置
				nums1[j + 1] = nums1[i];
			}	
			
		}
	}


	// 还有一个抖机灵的办法是在算法一的思维上换成从后往前放置数据
	// 那么你可以原地排序了,空间复杂度也为最小
	void merge2(vector<int>& nums1, int m, vector<int>& nums2, int n) {

		int index1 = m - 1;
		int index2 = n - 1;
		for (int i = m + n - 1; i >= 0; i--) {
			
				// nums1放置完毕
				if (index1 < 0) {
					nums1[i] = nums2[index2--];
				}
				else if (index2 < 0) {		// nums2放置完毕
					nums1[i] = nums1[index1--];
				}
				else if (nums2[index2] >= nums1[index1]) {
					nums1[i] = nums2[index2--];
				}
				else {
					nums1[i] = nums1[index1--];
				}	
		}
	}

};

 

125. 验证回文串

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。

说明:本题中,我们将空字符串定义为有效的回文串。

示例 1:

输入: "A man, a plan, a canal: Panama"
输出: true

示例 2:

输入: "race a car"
输出: false
class Solution {
public:
	bool isPalindrome(string s) {

		int l = 0;
		int r = s.length() - 1;
		while (l < r) {
			// 首先判断一下是否是字母或者数字,同时找寻数字或者字母
			while (!isNumOrAlphabet(s[l]))
			{
				l++;
				// 找着找着超过r了说明l和r之间都是可以忽略的,那么返回true
				if (l > r)
					return true;
			}
			while (!isNumOrAlphabet(s[r]))
			{
				r--;
				// 同理
				if (r < l)
					return true;
			}

			int t = s[l];
			// 数字之间的回文比较
			if (t <= 57) {
				if (s[l] != s[r])
					return false;
			}
			// 字母之间的回文比较
			else {
				// 已知一个是小写字母的比较
				if (t >= 97) {
					if (s[l] != s[r] && s[l] != (s[r] + 32))
						return false;
				}
				// 已知一个是大写字母的比较
				else {
					if (s[l] != s[r] && s[l] != (s[r] - 32))
						return false;
				}
			}

			// 进行下一组回文比较
			l++;
			r--;			
		}

		return true;
	}

	string reverseString(string s) {

		int l = 0;
		int r = s.length() - 1;
		while (l<r) {
			swap(s[l], s[r]);
			l++;
			r--;
		}

		return s;
	}

private:

	bool isNumOrAlphabet(char t) {
		if ((t >= 48 && t <= 57) || (t >= 65 && t <= 90) || (t >= 97 && t <= 122))
			return true;
		else
			return false;
	}
};

 

167. 两数之和 II - 输入有序数组

给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。

函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2

说明:

  • 返回的下标值(index1 和 index2)不是从零开始的。
  • 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。

示例:

输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
// 有一点要注意的是程序内部索引从零开始,外部从1开始
class Solution {
public:
	vector<int> twoSum(vector<int>& numbers, int target) {

		// 一次遍历,对当前i之后的数据段进行二分查找,使得最终numbers[i]+numbers[j]==target
		int r = numbers.size()-1;
		for (int i = 0; i < numbers.size(); i++) {

			int res = binarySearch(numbers, i + 1, r, target - numbers[i]);
			if (res != -1) {
				int vec[2] = { i + 1,res + 1 };
				return vector<int>(vec, vec + 2);
			}
		}

		throw invalid_argument("The input has no solution.");
	}


	// 上述算法为O(nlogn)
	// 对撞指针O(n)
	vector<int> twoSum2(vector<int>& numbers, int target) {

		// 初始化需要维护的两个对撞指针
		int i = 0;
		int j = numbers.size() - 1;
		// 题目说明索引相同的两个值加在一起不算
		while (i < j) {
			if (numbers[i] + numbers[j] == target) {
				int res[2] = { i + 1,j + 1 };
				return vector<int>(res, res + 2);
			}
			else if (numbers[i] + numbers[j] > target) {
				j--;
			}
			else {
				i++;
			}
		}

		throw invalid_argument("The input has no solution.");
	}


private:

	// 二分查找,返回相应值的索引
	// 关键点在[l,r]之内进行二分查找target
	int binarySearch(vector<int>& numbers, int l, int r, int target) {

		while (l <= r) {
			int mid = l + (r - l) / 2;
			if (numbers[mid] == target)
				return mid;
			else if (target < numbers[mid])
				r = mid - 1;
			else
				l = mid + 1;
		}

		// 没找到匹配的
		return -1;
	}
};

 

209. 长度最小的子数组

给定一个含有 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组如果不存在符合条件的连续子数组,返回 0。

示例: 

输入: s = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。

进阶:

如果你已经完成了O(n) 时间复杂度的解法, 请尝试 O(n log n) 时间复杂度的解法。

// 基本思路:题目中说的是正整数数组,考虑用双索引技术维护好滑动窗口
class Solution {
public:
	int minSubArrayLen(int s, vector<int>& nums) {

		// 根据当前nums[i,j]的值与s的大小关系决定i,j索引的更新
		int i = 0;
		int j = -1;
		int minLen = nums.size() + 1;
		int sum = 0;
		int n = nums.size();
		while (i < n) {
			// 当前的滑动窗口sum小于s,那么就要滑动j,使得sun增加
			if (sum < s) {
				sum += nums[++j];
				// 如果滑窗的j滑出了数组右边界,说明i之后的数据都不会符合题意了,可以提前退出
				if (j >= n)
					break;
			}
			else {
				minLen = std::min(minLen, j - i + 1);
				sum -= nums[i++];
			}
		}

		return minLen == n + 1 ? 0 : minLen;
	}
};

 

215. 数组中的第K个最大元素

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

输入: [3,2,1,5,6,4] 和 k = 2
输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4

说明:

你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

class Solution {
public:
	int findKthLargest(vector<int>& nums, int k) {

		// 可以用快排思维查找第k个元素的值
		int r = nums.size()-1;
		// 外部第k大和内部数组索引之间需要自己维护
		int ret = quickSearch(nums, 0, r, k-1);

		srand(time(NULL));

		return nums[ret];

	}

private:

	// 子函数:用于寻找[l,r]之间起始数值在区间内的正确排序索引
	int _quickSearch(vector<int>& nums, int l, int r, int k) {

		// 随机取[l,r]之间的随机数
		int t = rand() % (r - l + 1) + l;
		swap(nums[l], nums[t]);

		// 最终[l,j-1]内的数值都>v
		int v = nums[l];
		int j = l;
		for (int i = l + 1; i <= r; i++) {
			if (nums[i] > v) {
				swap(nums[++j], nums[i]);
			}
		}
		swap(nums[l], nums[j]);

		return j;
	}

	// 返回每一次快排的起始随机标志的索引
	int quickSearch(vector<int>& nums, int l, int r, int k) {

		int pivot = _quickSearch(nums, l, r, k);
		if (pivot == k)
			return pivot;
		else if (k < pivot)
			return quickSearch(nums, 0, pivot - 1, k);
		else
			return quickSearch(nums, pivot + 1, r, k);
	}

};

 

242. 有效的字母异位词

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的一个字母异位词。

示例 1:

输入: s = "anagram", t = "nagaram"
输出: true

示例 2:

输入: s = "rat", t = "car"
输出: false

说明:
你可以假设字符串只包含小写字母。

进阶:
如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?

class Solution {
public:
	bool isAnagram(string s, string t) {

		// 字符串长度不一致直接返回false
		if (s.length() != t.length())
			return false;
		// 判断一下是否空串的情况
		if (s.length() == 0)
			return true;

		unordered_map<char, int> record_s;
		unordered_map<char, int> record_t;
		for (int i = 0; i < s.length(); i++) {
			record_s[s[i]]++;
			record_t[t[i]]++;
		}
		for (int i = 0; i<t.length(); i++) {
			if (record_s[t[i]] != record_t[t[i]])
				return false;
		}
		
		return true;
	}
};

 

283. 移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]

说明:

  1. 必须在原数组上操作,不能拷贝额外的数组。
  2. 尽量减少操作次数。
class Solution {
public:

	// 时间复杂度O(n)
	// 空间复杂度O(n)
	void moveZeroes(vector<int>& nums) {

		// 策略:一次循环,将取出非零元素,放置进开辟的非零向量中,
		//      最后取出非零向量中的元素放置进原向量,后面补0
		vector<int> nonZeroElements;
		for (int i = 0; i < nums.size(); i++) {
			if (nums[i] != 0)
				nonZeroElements.push_back(nums[i]);
		}

		for (int i = 0; i < nonZeroElements.size(); i++) {
			nums[i] = nonZeroElements[i];
		}
		for (int i = nonZeroElements.size(); i < nums.size(); i++) {
			nums[i] = 0;
		}
	}

	// 优化成空间复杂度为常数的算法
	// 策略: 一次循环,维护好循环索引和非零元素个数索引值,即[0,k)的区间内都是原序的非零索引,
	//       一次循环完后,后面补零
	void moveZeros2(vector<int>& nums) {

		// 初始化两个需要维护的索引的值
		int k = 0;
		for (int i = 0; i < nums.size(); i++) {
			if (nums[i] != 0) {
				nums[k] = nums[i];
				k++;
			}
		}

		for (int i = k; i < nums.size(); i++)
			nums[i] = 0;
	}

	// 再次优化,使得只有一次循环,不需要额外的补零操作
	// 遍历到第i个元素的时候,保证[0,i]中的非零元素
	// 都按照顺序放置在[0,k)中
	// 零元素都放置在[k,i]中
	void moveZeros3(vector<int>& nums) {

		int k = 0;
		for (int i = 0; i < nums.size(); i++) {

			if (nums[i] != 0) {
				if (k != i) {
					swap(nums[k], nums[i]);
					k++;
				}
				else
					k++;
			}
		}
	}

};

 

290. 单词模式

给定一种 pattern(模式) 和一个字符串 str ,判断 str 是否遵循相同的模式。

这里的遵循指完全匹配,例如, pattern 里的每个字母和字符串 str 中的每个非空单词之间存在着双向连接的对应模式。

示例1:

输入: pattern = "abba", str = "dog cat cat dog"
输出: true

示例 2:

输入:pattern = "abba", str = "dog cat cat fish"
输出: false

示例 3:

输入: pattern = "aaaa", str = "dog cat cat dog"
输出: false

示例 4:

输入: pattern = "abba", str = "dog dog dog dog"
输出: false

说明:
你可以假设 pattern 只包含小写字母, str 包含了由单个空格分隔的小写字母。    

// 思路1 : 构建两个map,<char,int>和<string,int>,一次遍历,查看对应模式下int数值是否一致
class Solution {
public:
	bool wordPattern(string pattern, string str) {

		istringstream in(str);
		vector<string> words;
		unordered_map<char, int> pat_map;
		unordered_map<string, int> str_map;

		int i = 0;
		for (string word; in >> word; i++) {

			// 如果当前的s[i]和word都不在map中,说明是一对新的对应关系
			if (pat_map.find(pattern[i]) == pat_map.end() && str_map.find(word) == str_map.end()) {
				pat_map[pattern[i]] = str_map[word] = i + 1;			// 这里选择i+1,而不是简单的++是因为能保证value值唯一,不会重复
			}
			else {		// 某一个key值已存在
				if (pat_map[pattern[i]] != str_map[word])	// 对应关系不符合
					return false;
			}
		}

		return i == pattern.length();	// 到这里如果i==字符串长度,说明分割出来的word数和patter字符长度一致,不一致那么久对应不了
	}
};
// 思路2 : 既然是找对应关系,那么可以直接将map对应为key值为patter中的字母,value为string中的word
//		   一直向后查找,当key对应的value不同时那么久可以false退出了,哎,程序都差不多,改几处地方就行了,不想写了

 

349. 两个数组的交集

给定两个数组,写一个函数来计算它们的交集。

例子:

 给定 num1[1, 2, 2, 1]nums2 = [2, 2], 返回 [2].

提示:

  • 每个在结果中的元素必定是唯一的。
  • 我们可以不考虑输出结果的顺序。
// 利用set
// set:只存key值,且不会重复,重复插入同一数值没有用
class Solution {
public:
	vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {

		set<int> record(nums1.begin(),nums1.end());
		
		set<int> resultSet;
		for (int i = 0; i < nums2.size(); i++)
			if (record.find(nums2[i]) != record.end())		// 找到共同元素
				resultSet.insert(nums2[i]);					// 插入,重复插入没有关系,只是维护一个key值

		return vector<int>(resultSet.begin(), resultSet.end());

	}
};

 

350. 两个数组的交集 II

给定两个数组,写一个方法来计算它们的交集。

例如:
给定 nums1 = [1, 2, 2, 1]nums2 = [2, 2], 返回 [2, 2].

注意:

  •    输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。
  •    我们可以不考虑输出结果的顺序。

跟进:

  • 如果给定的数组已经排好序呢?你将如何优化你的算法?
  • 如果 nums1 的大小比 nums2 小很多,哪种方法更优?
  • 如果nums2的元素存储在磁盘上,内存是有限的,你不能一次加载所有的元素到内存中,你该怎么办?
class Solution2 {
public:
	vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {

		//map<int, int> record(nums1.begin(), nums1.end());
		map<int, int> record;
		//for (int i = 0; i < nums1.size(); i++) {
		//	record[nums1[i]]++;
		//}

		for (int i = 0; i < nums1.size(); i++) {
			if (record.find(nums1[i]) == record.end())
				record.insert(make_pair(nums1[i], 1));
			else
				record[nums1[i]]++;
		}

		vector<int> resVector;
		/*for (int i = 0; i < nums2.size(); i++) {
			if (record[nums2[i]] > 0) {
				resVector.push_back(nums2[i]);
				record[nums2[i]]--;
			}
		}*/
		for (int i = 0; i < nums2.size(); i++) {
			if (record.find(nums2[i]) != record.end()) {
				resVector.push_back(nums2[i]);
				record[nums2[i]]--;
				if (record[nums2[i]] == 0)
					record.erase(nums2[i]);
			}
		}

		return resVector;
	}
};

 

438. 找到字符串中所有字母异位词

给定一个字符串 和一个非空字符串 p,找到 中所有是 的字母异位词的子串,返回这些子串的起始索引。

字符串只包含小写英文字母,并且字符串 和 的长度都不超过 20100。

说明:

  • 字母异位词指字母相同,但排列不同的字符串。
  • 不考虑答案输出的顺序。

示例 1:

输入:
s: "cbaebabacd" p: "abc"

输出:
[0, 6]

解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的字母异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的字母异位词。

 示例 2:

输入:
s: "abab" p: "ab"

输出:
[0, 1, 2]

解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的字母异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的字母异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的字母异位词。
class Solution {
public:
	vector<int> findAnagrams(string s, string p) {

		if (p.length() > s.length())
			return vector<int>();

		//  滑动技术:保持每一次滑动的窗口与p的长度相等,检查窗口内的字母是否与p相同
		int contain_s[26] = { 0 };
		int contain_p[26] = { 0 };
		int n = s.length();
		int len = p.length();
		vector<int> res;
		// 初始化
		for (int i = 0; i < len; i++) {
			contain_s[s[i]-'a']++;
			contain_p[p[i]-'a']++;
		}

		// 遍历
		for (int i = 0; i < n + 1 - len; i++) {

			// 检查以i索引为起点的固定滑窗内的数据的一致性
			int j = 0;
			for (; j < 26; j++) {
				if (contain_s[j] != contain_p[j])
					break;
			}
			if (j == 26)
				res.push_back(i);

			// 维护下一次检查的contain_s
			if (i + 1 >= n + 1 - len)
				break;
			contain_s[s[i]-'a']--;
			contain_s[s[i + len]-'a']++;
		}
		
		return res;
	}
};

 

451. 根据字符出现频率排序

给定一个字符串,请将字符串里的字符按照出现的频率降序排列。

示例 1:

输入:
"tree"

输出:
"eert"

解释:
'e'出现两次,'r'和't'都只出现一次。
因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。

示例 2:

输入:
"cccaaa"

输出:
"cccaaa"

解释:
'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。
注意"cacaca"是不正确的,因为相同的字母必须放在一起。

示例 3:

输入:
"Aabb"

输出:
"bbAa"

解释:
此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。
注意'A'和'a'被认为是两种不同的字符。
// 思路:
//题目要求我们按照字符出现频率从高到低进行排序,大小写区分。看到字符出现频率,很自然地就想到用unordered_map哈希表来存储每个字符出现的次数。
//然后通过对整个哈希表进行排序,最后将其进行输出。但是这里有一个问题,就是stl的sort算法只能对线性序列容器进行排序(即vector, list, deque)。
//所以我们这里就要设法将存储在哈希表里面的内容转移到vector这些容器中了。这里由于哈希表存储的是key - value对,
//所以vector应该声明成pair某种模板类型,这样在vector中才能通过value找到相对应的key值。
//在将键值对全部插入到vector之后,就要对value值进行排序了。stl的sort函数有三个参数,其中第三个参数是可选的,
//是一个返回值类型可转化为bool的函数,表示sort的方式。如果我们不传入第三个参数,那么sort完成后输出的结果就是按ascii值从小到大进行排序的结果。
//因此,在这里有必要传入第三个参数。

class Solution {
public:

	// 这里面要声明成静态的是因为sort函数是外部的一个类,不能直接调用类内的函数,声明成静态那就说明此函数不是该类独有的了
	static bool comparePair(const pair<char, int> &x, const pair<char, int> &y) {
		return x.second > y.second;
	}

	string frequencySort(string s) {

		string res_str = "";

		// 将s存储成哈希表
		unordered_map<char, int> sMap;
		for (int i = 0; i < s.length(); i++)
			sMap[s[i]]++;

		// 将哈希表中的pair对放置进vector
		vector<pair<char, int>> vecPair;
		for (auto it = sMap.begin(); it != sMap.end(); it++) {
			vecPair.push_back(make_pair(it->first, it->second));
		}

		// 排序
		sort(vecPair.begin(), vecPair.end(), comparePair);

		// 整理成输出
		for (auto it = vecPair.begin(); it != vecPair.end(); it++) {
			for (int i = 0; i < it->second; i++) {
				res_str += it->first;
			}
		}

		return res_str;
	}

};

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值