赞
踩
纪念本狗第三次AK!!!
给你一个四舍五入到两位小数的非负浮点数 celsius
来表示温度,以 摄氏度(Celsius)为单位。
你需要将摄氏度转换为 开氏度(Kelvin)和 华氏度(Fahrenheit),并以数组 ans = [kelvin, fahrenheit]
的形式返回结果。
返回数组 ans
。与实际答案误差不超过
1
0
−
5
10^{-5}
10−5 的会视为正确答案。
注意:
开氏度 = 摄氏度 + 273.15
华氏度 = 摄氏度 * 1.80 + 32.00
提示:
0 <= celsius <= 1000
示例
输入:celsius = 36.50
输出:[309.65000,97.70000]
解释:36.50 摄氏度:转换为开氏度是 309.65 ,转换为华氏度是 97.70 。
思路
简单模拟
// C++
class Solution {
public:
vector<double> convertTemperature(double celsius) {
vector<double> ans(2);
ans[0] = celsius + 273.15;
ans[1] = celsius * 1.80 + 32.00;
return ans;
}
};
给你一个整数数组 nums
和一个整数 k
,请你统计并返回 nums
的 子数组 中满足 元素最小公倍数为 k
的子数组数目。
子数组 是数组中一个连续非空的元素序列。
数组的最小公倍数 是可被所有数组元素整除的最小正整数。
提示:
1 <= nums.length <= 1000
1 <= nums[i], k <= 1000
示例
输入:nums = [3,6,2,7,1], k = 6
输出:4
解释:以 6 为最小公倍数的子数组是:
思路
暴力+gcd
。
由于数据范围较小,可以暴力枚举所有的子数组,然后计算子数组的最小公倍数,
// C++ class Solution { public: int gcd(int a, int b) { return b ? gcd(b, a % b) : a; } int subarrayLCM(vector<int>& nums, int k) { int ans = 0, n = nums.size(); for (int i = 0; i < n; i++) { int lcm = nums[i]; // 最小公倍数 for (int j = i; j < n; j++) { lcm = lcm * nums[j] / gcd(lcm, nums[j]); if (lcm == k) ans++; else if (lcm > k) break; } } return ans; } };
如果这道题的数据范围较大,则不能用暴力枚举。
可以考虑用双指针,但是从一个区间内去掉一个数,再更新这个区间的最小公倍数,并不是那么好计算,需要用线段树或树状数组进行优化。
一些在区间上的操作是能够合并,也能拆分的。比如一个区间[i, j]
的前缀和,从中去掉一个数,或往里添加一个数,都很容易计算出新的前缀和;但是有一些操作是只能合并,无法拆分的,比如一个区间[i, j]
的最大值,往里添加一个数,很容易计算出新的最大值,但从中去掉一个数,就无法计算新的最大值了。像这种无法进行拆分的操作,就需要用线段树来进行优化。
给你一个 值互不相同 的二叉树的根节点 root
。
在一步操作中,你可以选择 同一层 上任意两个节点,交换这两个节点的值。
返回每一层按 严格递增顺序 排序所需的最少操作数目。
节点的 层数 是该节点和根节点之间的路径的边数。
提示:
[1, 10^5]
。1 <= Node.val <= 10^5
示例
输入:root = [1,4,3,7,6,8,5,null,null,null,null,9,null,10]
输出:3
解释:
- 交换 4 和 3 。第 2 层变为 [3,4] 。
- 交换 7 和 5 。第 3 层变为 [5,6,8,7] 。
- 交换 8 和 7 。第 3 层变为 [5,6,7,8] 。
共计用了 3 步操作,所以返回 3 。
可以证明 3 是需要的最少操作数目。
思路
层序遍历+选择排序
要将同一层的节点排序,并且要采用交换,且有最小交换次数。我们可以使用选择排序进行模拟,每次将最小的节点交换到最左侧。用一个小根堆来维护未确认位置的点,并用一个指针标记当前需要确认的位置。
// C+++ typedef pair<int, int> PII; const int N = 1e5 + 10; class Solution { public: int idx_to_val[N]; int val_to_idx[N]; int minimumOperations(TreeNode* root) { int ans = 0; queue<TreeNode*> q; priority_queue<int, vector<int>, greater<int>> heap; q.push(root); while (q.size()) { memset(idx_to_val, -1, sizeof idx_to_val); memset(val_to_idx, -1, sizeof val_to_idx); int size = q.size(); for (int i = 0; i < size; i++) { auto x = q.front(); q.pop(); heap.push(x->val); idx_to_val[i] = x->val; val_to_idx[x->val] = i; if (x->left != nullptr) q.push(x->left); if (x->right != nullptr) q.push(x->right); } // 左侧边界 int cur = 0; while (heap.size()) { int x = heap.top(); heap.pop(); if (val_to_idx[x] != cur) { int val = x, o_val = idx_to_val[cur]; int idx = val_to_idx[x], o_idx = cur; idx_to_val[idx] = o_val; idx_to_val[o_idx] = val; val_to_idx[val] = o_idx; val_to_idx[o_val] = idx; ans++; // 需要交换 } cur++; } } return ans; } };
这道题的另一个经典做法是:置换环。
置换环算法能够求出数组排序需要的最小交换次数。其思想是;对每个元素,将其指向其排序后应该放到的位置,直到首尾相接形成一个环。
具体来说,若一个数,在排序前,其在数组中的位置是a
,排序后,其在数组中的位置是b
,那么新建一条边,从a
指向b
,a -> b
。那么其实,对于每个位置所代表的点x
,其都有一条出边和一条入边。
x
的出边指向的点,假设是y
,表示:排序前位置x
上的数,在排序后其最终的位置是y
;
x
的入边来自的点,假设是w
,表示:排序后最终位置是x
的数,其排序前的位置是w
。
容易得知,每个位置,该位置原本的数一定会去到一个最终位置(出边),也会有另一个位置的数的最终位置是该位置(入边)。所以,每个位置的出度和入度都不多不少恰好是1。
这样以来,将所有位置表示为点,将位置的关系用边进行连接,那么最终会产生一个或多个首尾相接的环。
当我们交换两个位置的数时,无非有两种情况
我们举一组样例数据:7 1 9 3 2 5 4
,
排序前: 7 1 9 3 2 5 4
排序后: 1 2 3 4 5 7 9
按照上述规则,画出的图如下
解释如下:
1
的数是7,排序后最终位置是6
,所以1->6
2
的数是1,排序后最终位置是1
,所以2->1
3
的数是9,排序后最终位置是7
,所以3->7
4
的数是3,排序后最终位置是3
,所以4->3
5
的数是2,排序后最终位置是2
,所以5->2
6
的数是5,排序后最终位置是5
,所以6->5
7
的数是4,排序后最终位置是4
,所以7->4
我们尝试交换位置2
和位置6
(交换同一个环内的2个位置),交换后,更新的图如下
如果我们交换位置3
和2
(交换不同环中的2给位置),交换后,更新的图如下
容易得到如下结论:
我们最终要得到的状态,是每个位置都是一个自环,即最终要得到n
个环。
那么,最少的交换次数,一定是每次交换,都将一个环拆成更小的环,而不是将2个环合并成1个更大的环。
假设一个环内的节点数量为k
,则这个环最终将被拆成k
个自环,共需要k - 1
次拆分,即需要k - 1
次交换。
一共有n
个环,每个环需要的拆分次数是环中节点的数量减1,则所有环需要的拆分次数就是所有节点的数量减去环的数量。
于是这样就能求出最小的交换次数。
由于这种环图的特殊性,每个环都是首尾相连,所以每个环都是一个连通块。故这道题也可以用并查集来做。我们只要按照上述规则,进行集合的合并,并维护一下连通块的数量,即可求得需要的最小交换次数。
// C++ class Solution { public: // 并查集模板 int find(int x, vector<int>& p) { if (x != p[x]) p[x] = find(p[x], p); return p[x]; } int calc(vector<int>& v) { int n = v.size(); // 记录元素和原位置的关系 unordered_map<int, int> idx; // 并查集 parent数组 vector<int> p(n); for (int i = 0; i < n; i++) { idx[v[i]] = i; p[i] = i; } sort(v.begin(), v.end()); // 开始做集合合并 int cnt = n; // 连通块个数 for (int i = 0; i < n; i++) { int a = i, b = idx[v[i]]; if (find(a, p) != find(b, p)) { p[find(a, p)] = find(b, p); cnt--; // 合并后连通块减少一个 } } return n - cnt; // 最少交换次数 } int minimumOperations(TreeNode* root) { queue<TreeNode*> q; q.push(root); int ans = 0; while (q.size()) { int sz = q.size(); vector<int> v(sz); for (int i = 0; i < sz; i++) { TreeNode* x = q.front(); q.pop(); v[i] = x->val; if (x->left != nullptr) q.push(x->left); if (x->right != nullptr) q.push(x->right); } ans += calc(v); // 计算这一层的交换次数 } return ans; } };
给你一个字符串 s
和一个 正 整数 k
。
从字符串 s
中选出一组满足下述条件且 不重叠 的子字符串:
k
。返回最优方案中能选择的子字符串的 最大 数目。
子字符串 是字符串中一个连续的字符序列。
提示:
1 <= k <= s.length <= 2000
s
仅由小写英文字母组成示例
输入:s = "abaccdbbd", k = 3
输出:2
解释:可以选择 s = "abaccdbbd" 中斜体加粗的子字符串。"aba" 和 "dbbd" 都是回文,且长度至少为 k = 3 。
可以证明,无法选出两个以上的有效子字符串。
思路
中心开花+贪心
先求出以每个点为中心的最短回文串,记录其左端点和右端点。然后将这些回文区间按照左端点排个序,随后挨个取出并计数。这是一种贪心的思想,基于的是要想选出的不重叠字符串数量最多,在尽可能要选择长度短的。
// c++ typedef pair<int, int> PII; class Solution { public: bool isPalin(string s, int i, int j) { while (i < j) { if (s[i] != s[j]) return false; i++; j--; } return true; } int maxPalindromes(string s, int k) { int n = s.size(); vector<PII> v; // 存储所有可能的回文 // 求出以每个点为中心的最短回文 for (int i = 0; i < n; i++) { // 求奇数长度 int l = i, r = i; bool yes = false; while (l >= 0 && r < n && s[l] == s[r]) { yes = true; if (r - l + 1 >= k) break; l--; r++; } if (yes && l >= 0 && r < n && s[l] == s[r] && r - l + 1 >= k) v.push_back({l, r}); // 求偶数长度 l = i; r = i + 1; yes = false; while (l >= 0 && r < n && s[l] == s[r]) { yes = true; if (r - l + 1 >= k) break; l--; r++; } if (yes && l >= 0 && r < n && s[l] == s[r] && r - l + 1 >= k) v.push_back({l, r}); } // 将全部的回文排序 sort(v.begin(), v.end()); int ans = 0; int r = -1; // 维护当前右端点 for (auto& p : v) { if (r == -1) { r = p.second; ans++; } else if (p.first > r) { r = p.second; ans++; } } return ans; } };
动态规划
也可以直接用动态规划来做,用g[i][j]
表示区间[i, j]
是否是回文;f[i]
表示从[0, i]
中能拆出的满足条件的回文串的最大数量。g
的状态一共有n^2
,计算需要
O
(
n
2
)
O(n^2)
O(n2),f
的状态也是 n^2
,所以总的时间复杂度就是
O
(
n
2
)
O(n^2)
O(n2),在这道题的数据范围下大概是
1
0
6
10^6
106,不会超时。
// C++ class Solution { public: int maxPalindromes(string s, int k) { int n = s.size(); // g[i][j] 表示[i, j]是否是回文串, 下标从1开始 vector<vector<bool>> g(n + 1, vector<bool>(n + 1, false)); // 状态转移, 区间DP, 需要从小到达枚举区间长度 for (int len = 1; len <= n; len++) { for (int i = 1; i + len - 1 <= n; i++) { int j = i + len - 1; if (len == 1) g[i][j] = true; else if (s[i - 1] == s[j - 1] && (len == 2 || g[i + 1][j - 1])) g[i][j] = true; } } // f[i]表示[1, i]区间选出不重叠的子字符串的最大数量,下标从1开始 vector<int> f(n + 1, 0); for (int i = 1; i <= n; i++) { f[i] = f[i - 1]; // 第i个位置不选 // 第i个位置选上, 则枚举可能的回文串的长度, 回文串长度至少是k // 注意这里要枚举到 j = 0 for (int j = i - k; j >= 0; j--) { if (g[j + 1][i]) { f[i] = max(f[i], f[j] + 1); } } } return f[n]; } };
第三次AK,还是有点小激动的!
T1是模拟;T2是数论;T3是层序遍历+选择排序/置换环;T4是回文串题目,其实是2个问题的组合,1是求回文串,2是求不重叠回文子串的最大数目;可以用中心开花求回文串,并用贪心求答案;也可以用更为朴素的动态规划。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。