第218场LeetCode周赛
No 1. 设计 Goal 解析器
请你设计一个可以解释字符串 command 的 Goal 解析器 。command 由 “G”、"()" 和/或 “(al)” 按某种顺序组成。Goal 解析器会将 “G” 解释为字符串 “G”、"()" 解释为字符串 “o” ,"(al)" 解释为字符串 “al” 。然后,按原顺序将经解释得到的字符串连接成一个字符串。
给你字符串 command ,返回 Goal 解析器 对 command 的解释结果。
示例 1:
输入:command = “G()(al)”
输出:“Goal”
解释:Goal 解析器解释命令的步骤如下所示:
G -> G
() -> o
(al) -> al
最后连接得到的结果是 “Goal”
提示:
1 <= command.length <= 100
command 由 “G”、"()" 和/或 “(al)” 按某种顺序组成
解析
本题例行签到题,字符串一定是合法的,也即要么是单独的G,要么是空括号对,要么是(al),所以只要顺序遍历字符串command,遇到G保持,遇到左括号(,判断一下他右边是右括号还是字母a,转换成不同的字符。
C++代码如下:
//No 1
string interpret(string command) {
string ans;
int n = command.size(),i=0;
while (i < n) {
if (command[i] == 'G') {
ans += 'G';
++i;
}
else {
if (command[i] == '(' && command[i + 1] == ')') {
ans += 'o';
i += 2;
}
else {
ans += "al";
i += 4;
}
}
}
return ans;
}
No 2. K 和数对的最大数目
给你一个整数数组 nums 和一个整数 k 。
每一步操作中,你需要从数组中选出和为 k 的两个整数,并将它们移出数组。
返回你可以对数组执行的最大操作数。
示例 1:
输入:nums = [1,2,3,4], k = 5
输出:2
解释:开始时 nums = [1,2,3,4]:
- 移出 1 和 4 ,之后 nums = [2,3]
- 移出 2 和 3 ,之后 nums = []
不再有和为 5 的数对,因此最多执行 2 次操作。
示例 2:
输入:nums = [3,1,3,4,3], k = 6
输出:1
解释:开始时 nums = [3,1,3,4,3]:
- 移出前两个 3 ,之后nums = [1,4,3]
不再有和为 6 的数对,因此最多执行 1 次操作。
提示:
1
<
=
n
u
m
s
.
l
e
n
g
t
h
<
=
1
0
5
1 <= nums.length <= 10^5
1<=nums.length<=105
1
<
=
n
u
m
s
[
i
]
<
=
1
0
9
1 <= nums[i] <= 10^9
1<=nums[i]<=109
1
<
=
k
<
=
1
0
9
1 <= k <= 10^9
1<=k<=109
解析
本题也比较简单,其实就是类似two sum这题,只不过这里每个元素只能使用一次(不是数值而是元素),所以其实只要排序+双指针就可以了。数组长度 1 0 5 10^5 105也不会超时。
//No 2
int maxOperations(vector<int>& nums, int k) {
sort(nums.begin(), nums.end());
int n = nums.size(), i = 0, j = n - 1,ans=0;
while (i < j) {
if (nums[i] + nums[j] == k) {
++ans;
++i;
--j;
}
else if (nums[i] + nums[j] < k) {
++i;
}
else --j;
}
return ans;
}
No 3. 连接连续二进制数字
给你一个整数 n ,请你将 1 到 n 的二进制表示连接起来,并返回连接结果对应的 十进制 数字对 10^9 + 7 取余的结果。
示例 1:
输入:n = 1
输出:1
解释:二进制的 “1” 对应着十进制的 1 。
示例 2:
输入:n = 3
输出:27
解释:二进制下,1,2 和 3 分别对应 “1” ,“10” 和 “11” 。
将它们依次连接,我们得到 “11011” ,对应着十进制的 27 。
提示:
1 <= n <= 10^5
解析
本题要求我们将1至n的数字的二进制连接起来,再转化为十进制,并输出这个数对1e9+7的余数。实际上本题并不是把每个数字(32位)全部二进制表示都拼接,看样例可以知道,先导0是不考虑的,也即数字3只拼接11,5只考虑101这样的。
二进制的拼接,实际上等价于移位再相加。例如将数字a和b的二进制数字拼接起来,实际上相当于a左移t位,再加b,这个t就是数字b去掉先导0,也就是最左边1所在的位。而左移,显然又对应了十进制下乘 2 t 2^t 2t运算。
总结来说,如果我们要知道1到i进行二进制拼接后十进制数
f
[
i
]
f[i]
f[i]是多少,首先我们将这一问题分解为在已知1到i-1结果
f
[
i
−
1
]
f[i-1]
f[i−1],然后在此基础上:
求解数字i有几位(从最左边1开始):
t
=
l
o
g
2
i
+
1
=
l
o
g
10
i
/
l
o
g
10
2
+
1
t=log_{2}i+1=log_{10}i/log_{10}2+1
t=log2i+1=log10i/log102+1
然后计算:
f
[
i
]
=
f
[
i
−
1
]
∗
2
t
+
i
f[i]=f[i-1]*2^t+i
f[i]=f[i−1]∗2t+i
显然这是一个动态规划的过程。接下来就是一般动规的步骤了。
这里要注意,因为C++没有2为底的对数函数,就采用10为底的对数加换底公式计算;动规计算过程要每一步都取余防止溢出。
C++代码如下。
//No 3
int concatenatedBinary(int n) {
if (n == 1) return 1;
int M = 1e9 + 7;
vector<long long>dp(n + 1, 0);
dp[1] = 1;
for (int i = 2; i <= n; ++i) {
int t = (int)(log10((double)i) / log10(2.0)) + 1;
dp[i] = ((dp[i - 1] * (long long)pow(2,t)) % M + i) % M;
}
return dp[n];
}
No 4. 最小不兼容性
给你一个整数数组 nums 和一个整数 k 。你需要将这个数组划分到 k 个相同大小的子集中,使得同一个子集里面没有两个相同的元素。
一个子集的 不兼容性 是该子集里面最大值和最小值的差。
请你返回将数组分成 k 个子集后,各子集 不兼容性 的 和 的 最小值 ,如果无法分成分成 k 个子集,返回 -1 。
子集的定义是数组中一些数字的集合,对数字顺序没有要求。
示例 1:
输入:nums = [1,2,1,4], k = 2
输出:4
解释:最优的分配是 [1,2] 和 [1,4] 。
不兼容性和为 (2-1) + (4-1) = 4 。
注意到 [1,1] 和 [2,4] 可以得到更小的和,但是第一个集合有 2 个相同的元素,所以不可行。
示例 2:
输入:nums = [6,3,8,1,3,1,2,2], k = 4
输出:6
解释:最优的子集分配为 [1,2],[2,3],[6,8] 和 [1,3] 。
不兼容性和为 (2-1) + (3-2) + (8-6) + (3-1) = 6 。
示例 3:
输入:nums = [5,3,3,6,3,3], k = 3
输出:-1
解释:没办法将这些数字分配到 3 个子集且满足每个子集里没有相同数字。
提示:
1 <= k <= nums.length <= 16
nums.length 能被 k 整除。
1 <= nums[i] <= nums.length
解析
本题给定一个数组和数字k,要求我们将数组分为k组,每组元素数量相同,且不含相同元素(k个子集),同时定义每个子集中最大最小元素的差叫做不兼容性。如果能够划分,就返回能做到的最小的不兼容性和,否则返回-1。
本题我一开始没有太好的思路,想了一个贪心+搜索的憨憨方法,我甚至不能证明这个贪心是对的,然而就过了,我服了。简单说一下吧,真要做题还得采取正统状压DP的路子。
这个憨憨方法是这样的。
首先统计数组元素的值与其个数,放入一个数组,数组存储{value, num}数对,然后按照value大小排序。
下面把分割子集的过程想象成如下的流程,首先分为k组,我们假设每组有ng个不同元素。接下来先划分第一组,我们考虑使不兼容性最小的方法,那就是要么选最小的几个不同数,要么选最大的几个。我处于以下的原因这么考虑:
- 最小值无论分到哪个子集都是最小的,要想使不兼容性更小就要在这个子集中尽可能放比较小的数。最大值也是如此。
- 如果我们从靠中间部分取数字,就会导致后续的子集能选的数字的数值差异进一步扩大。
出于这个直觉,每次都分两种情况,要么从小到大选不同的元素构成一个子集,要么从大到小选。然后递归去求解剩下的元素构造k-1个子集得到的最小不兼容性和。搜索过程中每一步判断有没有足够的不同元素,如果没有就说明这条路分下去会导致某个子集元素重复,所以直接返回-1即可。由于元素数量最大16,每划分一次将产生2条路,最后得到最多 2 16 2^{16} 216种情况,不会超时。
//No 4
int dfs(int k, int ng, vector<pair<int, int>>vm) {
if (k == 1) {
for (auto p : vm) {
if (p.second > 1) return -1;
}
int ans = vm.back().first - vm[0].first;
return ans;
}
else {
if (vm.size() < ng) return -1;
//front
int ans1 = vm[ng-1].first-vm[0].first;
vector<pair<int, int>>tfront = vm;
for (int i = 0; i < ng; ++i) {
tfront[i].second--;
}
for (auto it = tfront.begin(); it != tfront.end();) {
if (it->second == 0) {
it = tfront.erase(it);
}
else ++it;
}
int t1 = dfs(k - 1, ng, tfront);
if (t1 == -1) ans1 = -1;
else ans1 += t1;
//back
int ans2 = vm[vm.size()-1].first - vm[vm.size()-ng].first;
vector<pair<int, int>>tfback = vm;
for (int i = tfback.size()-1; i > tfback.size()-1-ng; --i) {
tfback[i].second--;
}
for (auto it = tfback.begin(); it != tfback.end();) {
if (it->second == 0) {
it = tfback.erase(it);
}
else ++it;
}
int t2 = dfs(k - 1, ng, tfback);
if (t2 == -1) ans2 = -1;
else ans2 += t2;
if (ans1 == -1 && ans2 == -1) return -1;
else if (ans1 == -1) return ans2;
else if (ans2 == -1) return ans1;
else return min(ans1, ans2);
}
}
int minimumIncompatibility(vector<int>& nums, int k) {
map<int, int>m;
int len = nums.size(),ng=len/k;
vector<int>v(len+1, 0);
vector<pair<int, int>>vm;
for (auto n : nums) {
m[n]++;
v[n]++;
}
int maxN = 0;
for (auto it = m.begin(); it != m.end(); ++it) {
maxN = max(maxN, it->second);
vm.push_back(*it);
}
if (maxN > k) return -1;
sort(vm.begin(), vm.end(), [](pair<int, int>& p1, pair<int, int>& p2) {return p1.first < p2.first; });
int ans = dfs(k, ng, vm);
return ans;
}
上述这个憨憨做法只是通过了本题的实例,但我不能保证理论上是对的,也不知道能不能找到反例,大家看个乐就好。
下面介绍一下正经做本题的思路吧。状态压缩动态规划。
这个可以参考大佬的题解
这种方法使用二进制方式表示数组中的数字是否选过,以及表示待选择的子集。当我们选择mask数据组成若干子集的最小不兼容性和就等于,除掉当前选入的某个子集剩下的最小和,加上这个子集的不兼容性。也就是对于当前要选择的元素,遍历所有可能的候选子集,选取一种不兼容性和最小的作为达到当前元素选择的最小取值。
这里标记候选、子集等都采用二进制位操作进行。具体可查看上边大佬的题解。虽然靠直觉蒙了一种解法,而且感觉这个递归能改动规,不过还是太菜了,先去消化一下。

本文介绍了四道LeetCode周赛题目,包括设计字符串Goal解析器、求解最大数对操作次数、连接连续二进制数字以及最小不兼容性问题。涉及字符串处理、排序、动态规划等算法,详细解析了每道题目的解题思路和C++实现。
509

被折叠的 条评论
为什么被折叠?



