LeetCode -- 352场周赛
思路:动态规划
首先计算原数组的条件数组,及所有的元素都%2
f[i]表示从零到i中选,且以第i项为结尾的最长奇偶子数组。
class Solution { public: int longestAlternatingSubarray(vector<int>& nums, int threshold) { int n = nums.size(); vector<int> d(n + 5, 0); vector<int> f(n + 5, 0); for(int i = 0; i < n; i ++ ) d[i] = nums[i] % 2; for(int i = 0; i< n; i ++ ) { if(nums[i] > threshold) continue; if(d[i] == 0) f[i] = 1; if(i && nums[i - 1] <= threshold && d[i] == d[i - 1] ^ 1 ) { f[i] = max(f[i], f[i - 1] + 1); } } int res = 0; for(int i = 0; i < n; i ++ ) res = max(res, f[i]); return res; } };
不间断子数组即为:某子数组中,最大值与最小值的差 <= 2
利用滑动窗口 + 平衡树可进行操作
在 c ++ 中set底层利用了红黑平衡树,里面的元素是有序的,默认情况下 *s.begin()为最小元素,*s.rbegin()为最大元素。
class Solution { public: long long continuousSubarrays(vector<int>& nums) { long long res = 0; multiset<int> s; int left = 0, n = nums.size(); for(int right = 0; right < n; right ++ ) { s.insert(nums[right]); while(*s.rbegin() - *s.begin() > 2) { s.erase(s.find(nums[left ++ ])); } res += right - left + 1; } return res; } };
本题可以利用线性筛和哈希表进行处理。
先用线性筛筛出0 - n中所有的质数,再用哈希表快速查找质数与其相对于n的补数是否合法。
注:在lc平台函数尽量写成匿名函数形式,不然总是出点意料之外的错误。
class Solution { public: vector<vector<int>> findPrimePairs(int n) { int priems[1000005] = {0}, cnt = 0; bool st[1000005] = {0}; auto get_priems = [&]() { st[0] = st[1] = true; for(int i = 2; i <= n; i ++ ) { if(!st[i]) priems[cnt ++ ] = i; for(int j = 0; priems[j] * i <= n; j ++ ) { st[priems[j] * i] = true; if(i % priems[j] == 0) break; } } }; get_priems(); vector<vector<int>> res; for(int i = 0; i < cnt; i ++ ) { if(!st[priems[i]]) { if(!st[n - priems[i]]) { res.push_back({priems[i], n - priems[i]}); st[priems[i]] = st[n - priems[i]] = true; } } } return res; } };
方法一:枚举
O(n**2)的枚举方法,固定左端点,枚举右端点。利用哈希表快速查找子数组中是否含有nums[j - 1] 和 nums[j + 1]在对答案进行加和。
class Solution { public: int sumImbalanceNumbers(vector<int>& nums) { const int n = nums.size(); bool st[n + 2]; int res = 0; for(int i = 0; i < n; i ++ ) { memset(st, 0, sizeof st); st[nums[i]] = true; int cnt = 0; for(int j = i + 1; j < n; j ++ ) { int k = nums[j]; if(!st[k]) { cnt += 1 - st[k - 1] - st[k + 1]; st[k] = true; } res += cnt; } } return res; } };
方法二:贡献法(基本就是预处理 + 枚举)
优化枚举,对于每个nums[i],它产生贡献的子数组是nums[i + 1]没有出现的范围。另外,当有相同的数字出现时,我们规定只有第一个数字有贡献,避免重复问题。
因此,对每个数字v我们找到其之前最近的v + 1出现的下标p;找到其之后最近的v或v + 1的下标s。可产生贡献的子数组个数为 (i - p) * (s - i)
由于我们找的是v 或 v + 1的下标,这个是不会进行贡献的,所以要减去子数组个数。
class Solution { public: int sumImbalanceNumbers(vector<int>& nums) { int n = nums.size(); vector<int> prefix(n, -1), suffix(n, n); { unordered_map<int, int> mp; for(int i = 0; i < n; i ++ ) { int v = nums[i]; mp[v] = i; auto itor = mp.find(v + 1); if(itor != mp.end()) { prefix[i] = itor -> second; } } } { unordered_map<int, int> mp; for(int i = n - 1; i >= 0; i -- ) { int v = nums[i]; auto itor = mp.find(v + 1); if(itor != mp.end()) { suffix[i] = itor -> second; } itor = mp.find(v); if(itor != mp.end()) { suffix[i] = min(suffix[i], itor -> second); } mp[v] = i; } } int res = 0; for(int i = 0; i < n; i ++ ) { res += (i - prefix[i]) * (suffix[i] - i); } return res - n * (n + 1) / 2; } };
方法三:还是贡献法
方法二中,我们使用了unordered_map进行贡献数组的初始化,实在是慢,下面参考灵神的代码,利用手开数组哈希的方法进行快速初始化。
class Solution { public: int sumImbalanceNumbers(vector<int>& nums) { const int n = nums.size(); int right[n], idx[n + 1]; fill(idx, idx + n + 1, n); for(int i = n - 1; i >= 0; i -- ) { int x = nums[i]; right[i] = min(idx[x], idx[x - 1]); idx[x] = i; } int res = 0; memset(idx, -1, sizeof(idx)); for(int i = 0; i < n; i ++ ) { int x = nums[i]; res += (i - idx[x - 1]) * (right[i] - i); idx[x] = i; } return res - n * (n + 1) / 2; } };
最后一题总结:边遍历边插入,这种遍历方式感觉就像权值线段树求逆序对,从前往后或者从后往前遍历,遍历到某个点,就将某个点的贡献加入答案。只不过,权值线段树求逆序对的预处理过程可以用O(logn)的时间复杂度直接算得,而本题则需要预处理。