Leetcode 第174场周赛 题解(完结)
Leetcode 第174场周赛 题解
方阵中战斗力最弱的 K 行
签到题,统计一下每行的军人数量,然后设置一下排序规则即可。
时间复杂度 \(O(nlogn)\)
typedef long long ll;
typedef double db;
#define _for(i,a,b) for(int i = (a);i < b;i ++)
#define _rep(i,a,b) for(int i = (a);i > b;i --)
#define INF 0x3f3f3f3f3f3f3f3f
#define ZHUO 11100000007
#define SHENZHUO 1000000007
#define MIKUNUM 39
#define pb push_back
#define debug() printf("Miku Check OK!\n")
#define maxn 50003
#define X first
#define Y second
class Solution
{
public:
struct p
{
int i;
int val;
bool operator < (p b)
{
if(val != b.val)
return val < b.val;
return i < b.i;
}
};
vector<int> kWeakestRows(vector<vector<int>>& mat, int k)
{
vector<int> rnt;
vector<p> tmp;
_for(i,0,mat.size())
{
int cnt = 0;
_for(j,0,mat[i].size())
if(mat[i][j]==1)
cnt ++;
tmp.pb({i,cnt});
}
sort(tmp.begin(),tmp.end());
_for(i,0,k)
rnt.pb(tmp[i].i);
return rnt;
}
};
数组大小减半
因为每次删除是选一个数,然后把这个数在数组中的全部都给删掉,每删一次数组长度减一,但是我们的消耗不管选谁,都是每次选一个数,算是每次代价恒定,要求删的越多越好,那选什么数?自然是选在数组中出现次数较多的数!也就是贪心算法啦。
统计一下每个数出现的次数,然后排个序,然后不断贪心去删,直到删到满足题目条件即可。
时间复杂度 \(O(nlogn)\)
typedef long long ll;
typedef double db;
#define _for(i,a,b) for(int i = (a);i < b;i ++)
#define _rep(i,a,b) for(int i = (a);i > b;i --)
#define INF 0x3f3f3f3f3f3f3f3f
#define ZHUO 11100000007
#define SHENZHUO 1000000007
#define MIKUNUM 39
#define pb push_back
#define debug() printf("Miku Check OK!\n")
#define maxn 50003
#define X first
#define Y second
class Solution
{
public:
struct p
{
int num;
int times;
bool operator < (p b)
{
return times > b.times;
}
};
int minSetSize(vector<int>& arr)
{
int n = arr.size();
map<int,int> mp;
_for(i,0,arr.size())
mp[arr[i]] ++;
vector<p> tmp;
for(auto p:mp)
tmp.pb({p.first,p.second});
sort(tmp.begin(),tmp.end());
int rnt = 0;
int now = 0;
_for(i,0,tmp.size())
{
now += tmp[i].times;
rnt ++;
if(now>=n/2)
break;
}
return rnt;
}
};
分裂二叉树的最大乘积
其实也没啥好说的,比较显而易见,如果只是删除一条边的话,只要枚举删除的这条边即可,主要就是分开以后的值的计算。
先预处理出一个变量 \(tol\) ,代表了这棵树的价值总和(所有节点值相加),然后再遍历一遍,假设当前节点为 \(x\) ,则可以尝试删除 \(x\) 连接左儿子的边和连接右儿子的边。左子树的价值总和我们可以通过递归回溯得到,其余一棵由 \(x\) 和他的右儿子以及树的其余部分的价值总和就是 \(tol-tol(left)\) ,也就是原树的价值总和减去左子树的价值总和,最后记得取余即可。
时间复杂度 \(O(n)\)
typedef long long ll;
typedef double db;
#define _for(i,a,b) for(int i = (a);i < b;i ++)
#define _rep(i,a,b) for(int i = (a);i > b;i --)
#define INF 0x3f3f3f3f3f3f3f3f
#define ZHUO 11100000007
#define MOD 1000000007
#define MIKUNUM 39
#define pb push_back
#define debug() printf("Miku Check OK!\n")
#define maxn 50003
#define X first
#define Y second
class Solution
{
public:
ll tol;
ll rnt;
void pre(TreeNode* root)
{
if(root->left)
{
pre(root->left);
tol += root->left->val;
}
if(root->right)
{
pre(root->right);
tol += root->right->val;
}
}
ll dfs(TreeNode* root)
{
ll leftcnt = 0,rightcnt = 0;
if(root->left)
leftcnt = dfs(root->left);
if(root->right)
rightcnt = dfs(root->right);
rnt = max(rnt,(rightcnt*(tol-rightcnt)));
rnt = max(rnt,(leftcnt*(tol-leftcnt)));
return leftcnt + rightcnt + root->val;
}
int maxProduct(TreeNode* root)
{
rnt = 0;tol = root->val;
pre(root);
dfs(root);
rnt %= MOD;
return rnt;
}
};
跳跃游戏 V
跳跃游戏系列都是 \(dp\) 就不用我多说了吧(不是)
要求跳的次数最多,你想一下,你在乎他怎么跳吗?你并不在乎。如果从一个地方可以跳到另一个地方,我们只在乎他起跳的位置和从这里可以跳多少次。
所以有 \(dp[i][j]\) 表示当前在下标 \(i\) 处起跳,继续跳 \(j\) 次,值为 \(1\) 代表可行,\(0\) 代表不可行。这样如果我们能在 \(k \in [max(0,i-d),min(n-1,i+d)]\) 中间找到符合条件(不被挡住)的一个比他更高的地方 \(k\),跳到 \(i\) 处,那是不是 \(dp[k][j+1]\) 就可以从 \(dp[i][j]\) 转移过来?
这样我们先搞定初始状态,就是能不能跳一次,搞定以后我们去迭代更新 \(dp\) 数组就行了。
如果 \(dp[?][k] == 1\) ,则说明跳 \(k\) 次可行。
时间复杂度看起来是 \(O(n^3)\) ,但其实是 \(O(n^2)\),最里面一层不管对于什么情况都能快速 \(break\) 出来,所以其实是 \(O(maxn)=O(1)\),再加上一些优化 ( \(flag\) 可以在从哪儿都无法继续跳以后迅速 \(break\) ),\(1000\) 的数据量还是问题不大的。
typedef long long ll;
typedef double db;
#define _for(i,a,b) for(int i = (a);i < b;i ++)
#define _rep(i,a,b) for(int i = (a);i > b;i --)
#define INF 0x3f3f3f3f3f3f3f3f
#define ZHUO 11100000007
#define SHENZHUO 1000000007
#define MIKUNUM 39
#define pb push_back
#define debug() printf("Miku Check OK!\n")
#define maxn 2003
#define X first
#define Y second
class Solution
{
public:
int dp[maxn][maxn];
int maxJumps(vector<int>& arr, int d)
{
int n = arr.size();
memset(dp,0,sizeof(dp));
int rnt = 0;
//能否跳一次
_for(i,0,n)
{
int le = max(0,i-d);
int ri = min(n-1,i+d);
for(int j = i+1; j <= ri; j ++)
{
if(arr[j] >= arr[i])
break;
if(arr[j] < arr[i])
dp[i][1] = 1,rnt = 1;
}
for(int j = i-1; j >= le; j --)
{
if(arr[j] >= arr[i])
break;
if(arr[j] < arr[i])
dp[i][1] = 1,rnt = 1;
}
}
//跳k次
_for(k,2,n)
{
int flag = 0;
//枚举起跳位置i
_for(i,0,n)
{
int le = max(0,i-d);
int ri = min(n-1,i+d);
for(int j = i+1; j <= ri; j ++)
{
if(arr[j] >= arr[i])
break;
if(dp[j][k-1])
{
dp[i][k] = 1;
flag = 1;
break;
}
}
for(int j = i-1; j >= le; j --)
{
if(arr[j] >= arr[i])
break;
if(dp[j][k-1])
{
dp[i][k] = 1;
flag = 1;
break;
}
}
}
if(!flag)
break;
else
rnt ++;
}
return rnt+1;
}
};