2496. 数组中字符串的最大值
一个由字母和数字组成的字符串的 值 定义如下:
- 如果字符串 只 包含数字,那么值为该字符串在
10 进制下的所表示的数字。 - 否则,值为字符串的 长度 。
给你一个字符串数组 strs ,每个字符串都只由字母和数字组成,请你返回 strs 中字符串的 最大值 。
提示:
1 <= strs.length <= 100 1 <= strs[i].length <= 9 strs[i] 只包含小写英文字母和数字。
示例
输入:strs = ["alic3","bob","3","4","00000"]
输出:5
解释:
- "alic3" 包含字母和数字,所以值为长度 5 。
- "bob" 只包含字母,所以值为长度 3 。
- "3" 只包含数字,所以值为 3 。
- "4" 只包含数字,所以值为 4 。
- "00000" 只包含数字,所以值为 0 。
所以最大的值为 5 ,是字符串 "alic3" 的值。
思路
简单模拟即可
class Solution {
public:
int parse(string& s) {
bool isNum = true;
int num = 0;
for (int i = 0; i < s.size(); i++) {
if (s[i] >= '0' && s[i] <= '9') {
num = num * 10 + s[i] - '0';
} else {
isNum = false;
break;
}
}
return isNum ? num : s.size();
}
int maximumValue(vector<string>& strs) {
int ans = 0;
for (auto& s : strs) ans = max(ans, parse(s));
return ans;
}
};
2497. 图中最大星和
给你一个 n 个点的无向图,节点从 0 到 n - 1 编号。给你一个长度为 n 下标从 0 开始的整数数组 vals ,其中 vals[i] 表示第 i 个节点的值。
同时给你一个二维整数数组 edges ,其中 edges[i] = [ai, bi] 表示节点 ai 和 bi 之间有一条双向边。
星图 是给定图中的一个子图,它包含一个中心节点和 0 个或更多个邻居。换言之,星图是给定图中一个边的子集,且这些边都有一个公共节点。
下图分别展示了有 3 个和 4 个邻居的星图,蓝色节点为中心节点。
星和 定义为星图中所有节点值的和。
给你一个整数 k ,请你返回 至多 包含 k 条边的星图中的 最大星和 。
提示:
n == vals.length 1 <= n <= 10^5 -10^4 <= vals[i] <= 10^4 0 <= edges.length <= min(n * (n - 1) / 2``, 10^5) edges[i].length == 2 0 <= ai, bi <= n - 1 ai != bi 0 <= k <= n - 1
示例
输入:vals = [1,2,3,4,10,-10,-20], edges = [[0,1],[1,2],[1,3],[3,4],[3,5],[3,6]], k = 2
输出:16
解释:上图展示了输入示例。
最大星和对应的星图在上图中用蓝色标出。中心节点是 3 ,星图中还包含邻居 1 和 4 。
无法得到一个和大于 16 且边数不超过 2 的星图。
思路
乍的一看以为是一道图论的题目。仔细审题后发现根本都不用建图。
首先,一个星图,需要包含一个中心点,以及0或多个邻接点。要求解的是最多包含k 条边的最大星和。
也就是最多有k 个邻接点。
那么只需要枚举一下以每个点作为中心点的星图即可。至于最多包含k 个邻接点,选哪k 个点呢?当然是vals 最大的前k 个点啦!注意,如果 vals 为负数,纳入到星图里会使得星和变小。
所以,我们只需要维护一下每个点的所有邻接点,然后把邻接点按照vals 值从大到小排序,然后以每个点作为中心点,取前k 个vals 值大于0的邻接点纳入到星图中即可。
时间复杂度
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
class Solution {
public:
int maxStarSum(vector<int>& vals, vector<vector<int>>& edges, int k) {
int n = vals.size();
vector<vector<int>> f(n);
for (auto& e : edges) {
int a = e[0], b = e[1];
f[a].push_back(b);
f[b].push_back(a);
}
int ans = -1e4;
for (int i = 0; i < n; i++) {
int sum = vals[i];
vector<int>& v = f[i];
sort(v.begin(), v.end(), [&](int a, int b) {
return vals[a] > vals[b];
});
for (int j = 0; j < v.size() && j < k && vals[v[j]] > 0; j++) sum += vals[v[j]];
ans = max(ans, sum);
}
return ans;
}
};
看到其他大佬的代码,发现其实可以写的更为简短
class Solution {
public:
int maxStarSum(vector<int>& vals, vector<vector<int>>& edges, int k) {
vector<vector<int>> g(vals.size());
for (auto& e : edges) {
g[e[0]].push_back(vals[e[1]]);
g[e[1]].push_back(vals[e[0]]);
}
int n = vals.size(), res = INT_MIN;
for (int i = 0; i < n; ++i) {
sort(g[i].rbegin(), g[i].rend());
int temp = vals[i], t = k;
for (int v : g[i]) {
if (v > 0 && t--) temp += v;
else break;
}
res = max(res, temp);
}
return res;
}
};
2498. 青蛙过河II
给你一个下标从 0 开始的整数数组 stones ,数组中的元素 严格递增 ,表示一条河中石头的位置。
一只青蛙一开始在第一块石头上,它想到达最后一块石头,然后回到第一块石头。同时每块石头 至多 到达 一次。
一次跳跃的 长度 是青蛙跳跃前和跳跃后所在两块石头之间的距离。
- 更正式的,如果青蛙从
stones[i] 跳到 stones[j] ,跳跃的长度为 |stones[i] - stones[j]| 。
一条路径的 代价 是这条路径里的 最大跳跃长度 。
请你返回这只青蛙的 最小代价 。
提示:
2 <= stones.length <= 10^5 0 <= stones[i] <= 10^9 stones[0] == 0 stones 中的元素严格递增。
示例
输入:stones = [0,2,5,6,7]
输出:5
解释:上图展示了一条最优路径。
这条路径的代价是 5 ,是这条路径中的最大跳跃长度。
无法得到一条代价小于 5 的路径,我们返回 5 。
思路
由于中间的每块石头最多只能到达一次,所以我们需要将全部石头分成两部分,一部分是去程中到达过的石头,另一部分是回程中到达过的石头。这里稍微停顿一下,有没有可能某个石头没有被到达过呢?
一定不会!假设某个石头x 没有被到达过,则在去程中,一定需要从x 左侧跳到x 右侧,如果引入x 这块石头,一定能使得这段跳跃的距离被拆成更小的2段。引入x 一定不会使得答案变得更差。所以我们可以认为每块石头都被恰好到达过1次。
由于一条路径的代价是这条路径中的最大跳跃长度,我们要使得代价尽可能小,则每次跳跃尽可能要选择更近的石头。
可以先这样来考虑,假设只有去程,没有回程,那么很明显的,挨个石子地跳,就能使得最大跳跃长度最短。
现在需要加入去程,那么在去程的石子尽可能相邻的情况下,要挑出一些石子来留给回程。
接下来这样考虑,从起点的石头(下标为0 )开始,去程的第一跳,我们要选择尽可能近的石头,不妨选择下标为1 的石头;然后,镜像的考虑,回程的最后一跳,需要从某块石头跳到第0 号石头,由于最近的1 号石头已经被跳过了,那么回程的最后一跳最近的就只能选择2 号石头。
容易想到,只有交替分配石头,即1 号石头作为去程,2 号作为回程,3 号作为去程,4 号作为回程,只有这样才能使得整个路径的最大跳跃长度最短。
class Solution {
public:
int maxJump(vector<int>& stones) {
int n = stones.size();
if (n == 2) return stones[1];
int ans = stones[1];
for (int i = 3; i < n; i += 2) ans = max(ans, stones[i] - stones[i - 2]);
ans = max(ans, stones[2]);
for (int i = 4; i < n; i += 2) ans = max(ans, stones[i] - stones[i - 2]);
return ans;
}
};
代码还可以简化
class Solution {
public:
int maxJump(vector<int>& stones) {
int ans = stones[1];
for (int i = 2; i < stones.size(); i++) ans = max(ans, stones[i] - stones[i - 2]);
return ans;
}
};
如果想不到这种贪心策略,也可以二分答案。因为最远距离是10^9 ,我们每次进行二分出一个值x ,并判断是否能以不超过x 作为最大跳跃距离,完成去程和回程,即可。二分总次数最多大概30次,每次判断能否完成往返程,需要遍历一次,总的时间复杂度最多是 30 × 10^5 ,大概在10^6 级别。
class Solution {
public:
vector<bool> st;
bool check(vector<int>& stones, int limit) {
int n = stones.size(), i = 0;
for (int j = 0; j < n; j++) st[j] = false;
while (i < n - 1) {
int l = i + 1, r = n - 1;
while (l < r) {
int mid = l + r + 1 >> 1;
if (stones[mid] - stones[i] <= limit) l = mid;
else r = mid - 1;
}
if (stones[l] - stones[i] > limit) return false;
st[l] = true;
i = l;
}
i = n - 1;
while (i > 0) {
int j = i - 1;
while (j > 0 && st[j]) j--;
if (stones[i] - stones[j] > limit) return false;
i = j;
}
return true;
}
int maxJump(vector<int>& stones) {
int n = stones.size();
st.resize(n);
int l = 0, r = stones[n - 1];
while (l < r) {
int mid = l + r >> 1;
if (check(stones, mid)) r = mid;
else l = mid + 1;
}
return l;
}
};
注意到,实际上,上面的二分过程中也包含了贪心的操作,去程中每次跳跃取了不超过limit 的最大值,二分中套了二分。
2499. 让数组不相等的最小总代价
给你两个下标从 0 开始的整数数组 nums1 和 nums2 ,两者长度都为 n 。
每次操作中,你可以选择交换 nums1 中任意两个下标处的值。操作的 开销 为两个下标的 和 。
你的目标是对于所有的 0 <= i <= n - 1 ,都满足 nums1[i] != nums2[i] ,你可以进行 任意次 操作,请你返回达到这个目标的 最小 总代价。
请你返回让 nums1 和 nums2 满足上述条件的 最小总代价 ,如果无法达成目标,返回 -1 。
提示:
n == nums1.length == nums2.length 1 <= n <= 10^5 1 <= nums1[i], nums2[i] <= n
示例
输入:nums1 = [1,2,3,4,5], nums2 = [1,2,3,4,5]
输出:10
解释:
实现目标的其中一种方法为:
- 交换下标为 0 和 3 的两个值,代价为 0 + 3 = 3 。现在 nums1 = [4,2,3,1,5] 。
- 交换下标为 1 和 2 的两个值,代价为 1 + 2 = 3 。现在 nums1 = [4,3,2,1,5] 。
- 交换下标为 0 和 4 的两个值,代价为 0 + 4 = 4 。现在 nums1 = [5,3,2,1,4] 。
最后,对于每个下标 i ,都有 nums1[i] != nums2[i] 。总代价为 10 。
还有别的交换值的方法,但是无法得到代价和小于 10 的方案。
思路
这是一道思维题,需要分类讨论。
首先,对于满足nums1[i] = nums2[i] 的所有位置i 。这些位置是一定要被交换的,这是确定的。而每个i 是和另一个位置做交换,不确定的是这另一个位置。
我周赛当天只想到这里,后面就卡住,然后一通乱想,没有再进行下去了。
对于那些需要交换的位置i ,我们可以按照元素大小分别进行统计计数。
比如
nums1: 1 1 2 2 2 3 5 4
nums2: 1 1 2 2 2 5 3 4
一共有5个位置的数需要交换,其中1 有2个,2 有3个。
关键的一步在于,要推出:
出现次数最多的数,如果其次数没有过半,则这些需要交换的位置,可以内部互相两两交换
现在我们只考虑,需要交换的那些位置。
可以这样想:如果某个数x 出现了n 次,那么只要其余的数至少也为n 个,则其余的数一定是与x 不同的,则全部的x 一定可以被换掉(换一次也同时换掉了同等数量的其他的数)。同理,我们依次看每一个数,看这个数的出现次数,以及剩余的数的次数,只要剩余的数的次数之和大于等于这个数的次数,就一定能把这个数给全部换掉。
所以,只要出现次数最多的那个数,不超过总次数的一半,就一定能在内部通过两两互换进行解决。
这里还有另一个问题,由于每次互换是交换2个数。
-
如果总的次数是偶数,那么很明显,两两配对即可。总的代价就是全部需要交换的位置的下标之和。 -
如果总的次数是奇数,那么会多出一个可怜的单身汉,没人和他配对。此时这个单身汉需要和另外一个数额外做一次交换。 那么和哪个数做交换呢?肯定要和另外一个不等于这个数的数做交换才行。 实际我们可以证明,这个单身汉总是可以和下标为0 的数进行交换,从而使得额外的开销为0 ,总代价也是全部需要交换的位置的下标之和 下面证明一下这个结论,设最后剩下的单身汉为x (其实最后剩下的那个数我们可以自己任选):
-
若下标为0 的数,在需要交换的位置当中,那么有nums1[0] = nums2[0] ,我们只需要让最后剩下的那单身汉不等于nums1[0] 就好了 -
若下标为0 的数,不在需要交换的位置当中,那么有nums1[0] != nums2[0] ,那么最后剩下的单身汉不能为nums1[0] ,也不能为nums2[0] 。这就要求数字的种类至少为3,能保证吗?答案是可以的。因为当总次数为奇数时,一定有至少3种不同的数字,恰好比2种多至少1种。(可以尝试下,总次数为奇数,无非就是1,3,5,…,但次数为1时,不满足出现次数最多的数不过半;当次数为3时,只能是3种数字各出现1次;次数为5同理;所以能得出,出现次数为奇数时,数字的种类数至少为3)
于是,通过上面的分类讨论,我们推导出了:当出现次数最多的数,不过半时,答案就是所有需要交换的数的下标之和。
那么当出现次数最多的数,过半了,会怎么样呢?
那就是,这个数字只有一部分能够被内部消化(与同样需要交换的那些位置进行交换)。多出来一部分,需要与那些不需要交换的位置进行交换,比如
nums1: 2 3 1 1 1 1 2 2 4
nums2: 3 2 1 1 1 1 2 2 5
需要交换的位置的总次数为6,其中1 出现了4次,2 出现了2次。
1 的出现次数(4)过了半(3)。那么有2个1 ,可以和2 进行交换;还剩2个1 ,需要和其他的位置进行交换。
我们只需要按照位置从小到大遍历一次数组,找到那些nums1[i] != nums2[i] ,并且nums1[i] != 1 && nums2[i] != 1 的位置,来消耗掉多出的1 的次数。
如果多出的1 能够被消耗完,说明能够完成目标。否则不能。
class Solution {
public:
long long minimumTotalCost(vector<int>& nums1, vector<int>& nums2) {
int sameCnt = 0, majorCnt = 0, majorNum = 0, n = nums1.size();
long long ans = 0;
unordered_map<int, int> freq;
for (int i = 0; i < n; i++) {
if (nums1[i] == nums2[i]) {
ans += i;
sameCnt++;
if (++freq[nums1[i]] > majorCnt) {
majorNum = nums1[i];
majorCnt = freq[nums1[i]];
}
}
}
if (majorCnt * 2 <= sameCnt) return ans;
int remainCnt = 2 * majorCnt - sameCnt;
for (int i = 0; i < n && remainCnt > 0; i++) {
if (nums1[i] != nums2[i] && nums1[i] != majorNum && nums2[i] != majorNum) {
ans += i;
remainCnt--;
}
}
return remainCnt == 0 ? ans : -1;
}
};
总结
T1是模拟;T2是遍历+排序;T3是贪心(也可以用二分);T4是思维题,需要分类讨论。
|