周赛链接:https://leetcode-cn.com/contest/weekly-contest-205/
T1.替换所有的问号
给你一个仅包含小写英文字母和 ‘?’ 字符的字符串 s ,请你将所有的 ‘?’ 转换为若干小写字母,使最终的字符串不包含任何 连续重复 的字符。
注意:你 不能 修改非 ‘?’ 字符。
题目测试用例保证 除 ‘?’ 字符 之外,不存在连续重复的字符。
在完成所有转换(可能无需转换)后返回最终的字符串。如果有多个解决方案,请返回其中任何一个。可以证明,在给定的约束条件下,答案总是存在的。
示例 1:
输入:s = “?zs”
输出:“azs”
解释:该示例共有 25 种解决方案,从 “azs” 到 “yzs” 都是符合题目要求的。只有 “z” 是无效的修改,因为字符串 “zzs” 中有连续重复的两个 ‘z’ 。
提示:
1 <= s.length <= 100
s 仅包含小写英文字母和 ‘?’ 字符
分析:相邻的字符不同,相邻字符包括左右两边,那么当前字符选择的限制,就来自左边两边的字符,那就简单了,我们只需要选择一个 既不和左边又不和右边相等的小写字母。
思路:如何进行选择呢?
1.首先我们处理特殊情况,
(1)如果前一个字符/或者后一个字符,不存在(越界),我们设为‘#’(只需要设置为小写字母之外的字符即可)
(2)如果后一个字符为‘?’,我们同样设置为‘#’
2.处理好特殊情况后,我们很容易得到 当前字符的左右相邻的字符
3.从a~z中选择一个 既不等于左边字符 又不等于右边字符 的小写字母即可
4.当然 直接在首尾预先插入‘#’,就不同特判越界的情况(本代码未添加,比赛直接写了,没想这么多)
代码:
class Solution
{
public:
char fd(char p, char s)//选择一个合适的小写字母
{
for (char c = 'a'; c <= 'z'; c++)
{
if (c != p && c != s)
{
return c;
}
}
return '#';
}
string modifyString(string s)
{
string t = "";
for (int i = 0; i < s.length(); i++)
{
if (s[i] != '?')
{
t += s[i];
}
else
{
char x, y;
if (i - 1 < 0)
{
x = '#';
}
else
{
x = t[i - 1];
}
if (i + 1 > s.length() - 1 || s[i + 1] == '?')
{
y = '#';
}
else
{
y = s[i + 1];
}
char tp = fd(x, y);
t += tp;
}
}
return t;
}
};
T2. 数的平方等于两数乘积的方法数
给你两个整数数组 nums1 和 nums2 ,请你返回根据以下规则形成的三元组的数目(类型 1 和类型 2 ):
类型 1:三元组 (i, j, k) ,如果 nums1[i]2 == nums2[j] * nums2[k] 其中 0 <= i < nums1.length 且 0 <= j < k < nums2.length
类型 2:三元组 (i, j, k) ,如果 nums2[i]2 == nums1[j] * nums1[k] 其中 0 <= i < nums2.length 且 0 <= j < k < nums1.length
分析:
就是一个数组里的值,等于另外一个数组里两个元素的积
思路:
很直接,
1.预处理两个数组里所有不同元素的积
2.遍历每个数组,查询即可
注意!
数值范围爆int,要用long long
代码:
class Solution
{
public:
typedef long ll;
map<ll, ll> m1, m2;
int numTriplets(vector<int> &nums1, vector<int> &nums2)
{
for (int i = 0; i < nums1.size(); i++)
{
for (int j = i + 1; j < nums1.size(); j++)
{
m1[(ll)nums1[i] * (ll)nums1[j]]++;
}
}
for (int i = 0; i < nums2.size(); i++)
{
for (int j = i + 1; j < nums2.size(); j++)
{
m2[(ll)nums2[i] * (ll)nums2[j]]++;
}
}
int sum = 0;
for (int i = 0; i < nums1.size(); i++)
{
sum += m2[(ll)nums1[i] * (ll)nums1[i]];
}
for (int i = 0; i < nums2.size(); i++)
{
sum += m1[(ll)nums2[i] * (ll)nums2[i]];
}
return sum;
}
};
T3.避免重复字母的最小删除成本
给你一个字符串 s 和一个整数数组 cost ,其中 cost[i] 是从 s 中删除字符 i 的代价。
返回使字符串任意相邻两个字母不相同的最小删除成本。
请注意,删除一个字符后,删除其他字符的成本不会改变。
示例 1:
输入:s = “abaac”, cost = [1,2,3,4,5]
输出:3
提示:
s.length == cost.length
1 <= s.length, cost.length <= 10^5
1 <= cost[i] <= 10^4
s 中只含有小写英文字母
分析:
看完题目,应该比较好分析
1.要使得相邻字母不同,那么我们要 对连续且字母相同的一段区间 进行处理了
2.怎么处理,我们需要将它删除得只剩下一个字符,如何删除使得成本最小
3.当然是保留这一段中cost最大的,剩余的全删除了
4.统计删除掉的cost总和,即答案
思路:
1.用栈保存字符相等且连续的一段区间。我们用sum记录区间cost和,用mx记录区间cost最大值
2.进栈时,
(1)如果栈顶元素与当前元素相等,则进栈,更新sum和mx
(2)若不同,清空栈,计算前一段连续区间的答案,然后在进栈,更新sum和mx
没啥坑,就是注意区间长度大于1才要计算答案
代码:
class Solution
{
public:
int minCost(string s, vector<int> &cost)
{
stack<char> st;
int sum, mx;
int res = 0;
sum = 0;
mx = 0;
for (int i = 0; i < s.length(); i++)
{
if (st.empty())
{
st.push(s[i]);
sum = cost[i];
mx = cost[i];
}
else
{
if (st.top() == s[i])
{
st.push(s[i]);
sum += cost[i];
mx = max(mx, cost[i]);
}
else
{
if (st.size() > 1)
{
res += (sum - mx);
//cout<
// cout<
}
while (!st.empty())
{
st.pop();
}
st.push(s[i]);
sum = mx = cost[i];
}
}
}
if (!st.empty() && st.size() > 1)
{
res += (sum - mx);
}
return res;
}
};
T4.保证图可完全遍历
Alice 和 Bob 共有一个无向图,其中包含 n 个节点和 3 种类型的边:
类型 1:只能由 Alice 遍历。
类型 2:只能由 Bob 遍历。
类型 3:Alice 和 Bob 都可以遍历。
给你一个数组 edges ,其中 edges[i] = [typei, ui, vi] 表示节点 ui 和 vi 之间存在类型为 typei 的双向边。请你在保证图仍能够被 Alice和 Bob 完全遍历的前提下,找出可以删除的最大边数。如果从任何节点开始,Alice 和 Bob 都可以到达所有其他节点,则认为图是可以完全遍历的。
返回可以删除的最大边数,如果 Alice 和 Bob 无法完全遍历图,则返回 -1 。
链接:https://leetcode-cn.com/problems/remove-max-number-of-edges-to-keep-graph-fully-traversable
分析:
这个3个图叠加在一起的问题,我们分开考虑/分析
1.首先问题是 使图联通的情况下,可以删除的最大边数,好像不太熟悉,那转换一下,->使图联通的情况下,可以添加的最小边数!!!介不是MST(最小生成树)吗!
2.如果有公共边,我们肯定用公共边对不对?因为它一条边顶两条边用啊,保证添加最少边的性质
公共边组成的子图里怎么可以最简(边最少)呢?那就是最小生成树啊!!!
3.那么对于 Alice和 Bob 的图怎么处理,使得添加的边最少呢??
因为公共边已经处理(MST,最小生成树)好了,在公共边的基础上,如何添加边,让 Alice和 Bob 都能遍历
分别对 Alice的边和Bob 的边做MST,在公共边的基础上,分别对ALice和Bob的边集合,用kurskal添加边即可。
思路:
1.对 type3的边 单独做最小生树
2.1中最小生成树的基础上,分别type1和type==2的边做最小生成树
3.经过1和 2,我们可以得到 添加最小边数使得 图“可完全遍历”的边数 ,用总边数减去即是答案
注意:用 并查集 判断 不能 保证图可完全遍历的情况
代码:
class Solution
{
public:
int fa[100005];
int fd(int x)
{
if (fa[x] != x)
{
fa[x] = fd(fa[x]);
}
else
{
return fa[x];
}
return fa[x];
}
void un(int x, int y)
{
int a, b;
a = fd(x);
b = fd(y);
if (a != b)
{
fa[a] = b;
}
}
void init()
{
for (int i = 1; i < 100005; i++)
fa[i] = i;
}
bool ck(int n)
{
map<int, int> mp;
//cout<
for (int i = 1; i <= n; i++)
{
//cout<
mp[fd(i)]++;
}
// cout<
if (mp.size() > 1)
return 0;
return 1;
}
int maxNumEdgesToRemove(int n, vector<vector<int>> &edges)
{
int e3 = 0;
int e2, e1;
e2 = e1 = 0;
int res = 0;
init();
for (auto v : edges)
{
if (v[0] == 3)
{
if (fd(v[1]) != fd(v[2]))
{
un(v[1], v[2]);
e3++;
res++;
}
}
}
for (auto v : edges)
{
if (v[0] == 1)
{
if (fd(v[1]) != fd(v[2]))
{
un(v[1], v[2]);
e1++;
res++;
}
}
}
if (!ck(n))
return -1;
init();
for (auto v : edges)
{
if (v[0] == 3)
{
if (fd(v[1]) != fd(v[2]))
{
un(v[1], v[2]);
e3++;
//res++;
}
}
}
for (auto v : edges)
{
if (v[0] == 2)
{
if (fd(v[1]) != fd(v[2]))
{
un(v[1], v[2]);
e1++;
res++;
}
}
}
if (!ck(n))
return -1;
return edges.size() - res;
}
};
总结:总体难度偏小,需要一点分析能力