目录
一,3309. 连接二进制表示可形成的最大数值
二,3310. 移除可疑的方法
三,3311. 构造符合图结构的二维矩阵
四,3312. 查询排序后的最大公约数
一,3309. 连接二进制表示可形成的最大数值
本题数据范围较小,可以使用递归枚举每一种排列方式,计算出最大值,代码如下:
class Solution {
int ans = 0;
public int maxGoodNumber(int[] nums) {
dfs(0, "", nums);
return ans;
}
void dfs(int i, String res, int[] nums){
if(i == (1<<3)-1){
ans = Math.max(ans, Integer.parseInt(res, 2));
return;
}
for(int j=0; j<3; j++){
if((i>>j&1)==1) continue;
dfs(i|1<<j, res + Integer.toBinaryString(nums[j]), nums);
}
return ;
}
}
但是该方式只适用于数据范围较小的题,下面介绍一种 O(nlogn) 的做法,我们先用十进制的方式来思考一下,给你一个数组 [9,10] ,如何使得这个数组用十进制并接得到的值更大?我们当然是比较一下:是9在前大,还是10在前大,然后决定这两个拼接的顺序。那么拓展到大小为 n 的数组如何决定它们的顺序?和上述做法一样,相邻的数依次比较,类似于冒泡排序。那么对于本题使用二进制拼接也是同理,代码如下:
class Solution {
public int maxGoodNumber(int[] t) {
Integer[] nums = new Integer[t.length];
for(int i=0; i<t.length; i++)
nums[i] = t[i];
//注意要想按照自己的想法排序,必须使用Integer数组
Arrays.sort(nums, (x, y)->{
int len_x = Integer.toBinaryString(x).length();
int len_y = Integer.toBinaryString(y).length();
int xy = x << len_y | y;
int yx = y << len_x | x;
return yx - xy;
});
int ans = 0;
for(int x : nums){
int len = Integer.toBinaryString(x).length();
ans = ans << len | x;
}
return ans;
}
}
二,3310. 移除可疑的方法
本题题意给你一个可疑方法 k,如果被 k 直接调用/间接调用,那么这些方法也被视为可疑方法(注意:调用可疑方法的方法不是可疑方法),如果没有其他方法调用可疑方法,返回非可疑方法;否则,返回所用方法。
我们可以建立一个图,使用 dfs/bfs 找到所有的可疑方法,然后枚举 invocations 数组,比如每个元素为 [x,y],如果 x 不是可疑方法 && y 是可疑方法,说明有非可疑方法调用可疑方法,返回所有方法;否则只返回非可疑方法。
代码如下:
class Solution {
public List<Integer> remainingMethods(int n, int k, int[][] invocations) {
List<Integer> ans = new ArrayList<>();
List<Integer>[] g = new ArrayList[n];
Arrays.setAll(g, e->new ArrayList<>());
for(int[] e : invocations){
g[e[0]].add(e[1]);
}
Queue<Integer> que = new LinkedList<>();
que.add(k);
Set<Integer> set = new HashSet<>();
set.add(k);
while(!que.isEmpty()){
int x = que.poll();
for(int y : g[x]){
if(!set.contains(y)){
que.add(y);
set.add(y);
}
}
}
for(int[] e : invocations){
if(!set.contains(e[0]) && set.contains(e[1])){
for(int i=0; i<n; i++)
ans.add(i);
return ans;
}
}
for(int i=0; i<n; i++){
if(!set.contains(i))
ans.add(i);
}
return ans;
}
}
三,3311. 构造符合图结构的二维矩阵
本题可以想象成拼拼图,我们可以先找到四个角落的点(如果行列>=3,那么它只有两个相邻的点),然后不断的往里拼,但是本题不知道四个角的对应位置,所以我们可以先找到一个角洛的点,然后往一边拼,注意一个边上点(除了角落的点),它们相邻的点有三个,不断的填充,直到找到一个这条边的另一个角落(即只有两个相邻点的点),这样我们就拼出一个边了,然后通过这个边不断的往下拼就行了。
上述是行列>=3的情况,如果它只有一行/列,那么它所有点中最少的相邻点必须为1,所以只需要判断最少的相邻是否为1,就直到是否是一行/列。
如果它只有两行/列,那么只需要判断所有点中最多的相邻点是否为4,如果不为4且不是一行/列,说明它只用两行/列。
代码如下:
class Solution {
public int[][] constructGridLayout(int n, int[][] edges) {
List<Integer>[] g = new ArrayList[n];
Arrays.setAll(g, e->new ArrayList<>());
for(int[] e : edges){
g[e[0]].add(e[1]);
g[e[1]].add(e[0]);
}
int[] node = new int[5];
Arrays.fill(node, -1);
for(int x=0; x<n; x++){
node[g[x].size()] = x;
}
List<Integer> row = new ArrayList<>();
if(node[1] != -1){
row.add(node[1]);
}else if(node[4]==-1){
int x = node[2];
for(int y : g[x]){
if(g[y].size() == 2){
row.add(x);
row.add(y);
break;
}
}
}else{
int x = node[2];
row.add(x);
int pre = x;
x = g[x].get(0);
while(g[x].size() == 3){
row.add(x);
for(int y : g[x]){
if(y != pre && g[y].size() < 4){
pre = x;
x = y;
break;
}
}
}
row.add(x);
}
int k = row.size();
int[][] ans = new int[n/k][k];
boolean[] vis = new boolean[n];
for(int j=0; j<k; j++){//把第一行放进去
int x = row.get(j);
ans[0][j] = x;
vis[x] = true;
}
for(int i=1; i<ans.length; i++){
for(int j=0; j<k; j++){
for(int y : g[ans[i-1][j]]){
if(!vis[y]){
vis[y] = true;
ans[i][j] = y;
break;
}
}
}
}
return ans;
}
}
四,3312. 查询排序后的最大公约数
本题最重要的部分就是计算出每个 gcd 出现的次数,对于查询,我们可以使用二分前缀和的方式算出答案。
如何计算每个 gcd 出现的次数?假设要计算 gcd 等于 2 出现的次数,比如说有 n 个 2 的倍数,那么我们可以得到 Cn2 个数对(即 (n-1)*n/2 ),但是这么多数对,不一定每一个 gcd 的值都为 2,比如 4 和 8 的 gcd 就为 4,6 和 12 的 gcd 就为 6,所以我们需要从 (n-1)*n/2 个数对中减去数对的 gcd 为 4,6,8,....。这样就能得到 gcd 为 2 的数对个数了。
定义 cntGcd[i] 表示 gcd 为 i 的数对的个数,cntGcd[i] = (n-1)*n/2 - cntGcd[2*i] - cntGcd[3*i] - ...,从上述公式可以看出,要想计算出 cntGcd[i],必须先计算出 cntGcd[2*i]、cntGcd[3*i],所以我们需要从后往前遍历。
代码如下:
class Solution {
//cnt[2] = C(n,2) - cnt[4] - cnt[6] - ...
public int[] gcdValues(int[] nums, long[] queries) {
int[] cntX = new int[50001];
int mx = 0;
for(int x : nums){
cntX[x]++;
mx = Math.max(mx, x);
}
long[] cnt = new long[mx+1];
for(int i=mx; i>=1; i--){
int s = 0;
for(int j=i; j<mx+1; j+=i){
s += cntX[j];
cnt[i] -= cnt[j]; //去除2i,3i,4i...(虽然j从i开始,但是cnt[i]初始值为0,所以实际上没有减去i)
//为什么不是从2*i开始?因为要计算i的倍数的和s,如果从2i开始会少计算i出现的个数!!
}
cnt[i] += (long)(s - 1) * s / 2;
}
for(int i=1; i<mx+1; i++){
cnt[i] += cnt[i-1];
}
int[] ans = new int[queries.length];
for(int i=0; i<queries.length; i++){
int l = 1, r = mx;
while(l <= r){
int mid = (l + r) / 2;
if(cnt[mid] <= queries[i]){
//为什么加=?因为query[i]可以取到0(当然也可以写成cnt[mid]-1 < queries[i])
l = mid + 1;
}else{
r = mid - 1;
}
}
ans[i] = l;
}
return ans;
}
}