文章目录
d第223,224场周赛和43场双周赛的题目解析。中等题目不难,主要是H题目都还是很有特色的。
1713. 得到子序列的最少操作次数 H 最长递增子序列问题的nlogn解法
本质上在考察最长连续上升子序列问题,我们需要在坐标对应层次上寻找到一个最长上升子序列。
核心是二分的思路,我们维护一个数组,数组中的数字索引是单调递增的(如果要求严格单调,也会是不重复的)。我们每次获取新的备选数字时会考虑能否更新数组,我们会寻找大于等于target的最小值,更新为target(对于严格单调的,可以不考虑等于)。
class Solution {
public int minOperations(int[] target, int[] arr) {
// 可以转化为最长递增子序列问题
HashMap<Integer, Integer> map = new HashMap<>();
int n = target.length;
int m = arr.length;
for(int i = 0;i<n;i++){
map.put(target[i], i);
}
int[] res = new int[m];
for(int i = 0;i<m;i++){
if(map.containsKey(arr[i])){
res[i] = map.get(arr[i]);
}else{
res[i] = -1;
}
}
// 用List慢的令人发指 2000 ms
// List<Integer> queue = new LinkedList<>();
int[] queue = new int[m];
int j = 0;
for (int i = 0;i<m;i++){
if(res[i] == -1) continue;
if(j == 0 || res[i]>queue[j-1]){
queue[j] = res[i];
j++;
}else{
int index = binarySearch(0, j-1, res[i], queue);
queue[index] = res[i];
}
}
return n-j;
}
最小递增子序列的dp, 大于(等于)target的最小值
private int binarySearch(int left, int right, int target, int[] queue){
while (left<=right){
int mid = (left+right)/2;
int cur = queue[mid];
if(target>cur){
left = mid+1;
}else if (target<cur){
right = mid-1;
}else{
//right = mid-1;
return mid; // 等于的情况也不能更新,大于(等于)target的最小值。因此可以直接返回
}
}
return left;
}
}
1719. 重构一棵树的方案数 H
这个当且仅当条件一定要读清楚。因此我们完全可以按照pairs数组的东西先尝试构造一棵树,然后再检验是不是合理。并且我们可以发现,越是根的树,祖孙关系数量应该越多。换言之,祖孙关系最多的就是根节点。我们可以根据这个性质构造树。
如何检验呢?我们每次构造之后维护fa[]数组,是每个节点的亲父节点。如果两个节点有祖孙关系,但是亲父节点不同。则存在矛盾。因为这说明某个节点的父节点更新时候,竟然没有更新到这“逆子节点”,可见两者不存在关系。
因此我们需要统计,每个节点的祖孙关心,用于寻找根节点。然后维护祖孙邻接表。每次更新。
优雅的函数式编程
如何实现给一个list数组添加东西呢?常规方法分三步,找到对应的数组,添加元素进数组,把数组存回字典。
函数编程一个函数就可以computeIfAbsent()
这个函数可以实现如果没有先构建一个数组,然后返回数组让我们更新。
比较Intger的数值
在java中,如果是基本数据类型,==判断的是值。如果是对象类型,==判断的是对象的地址。
对于-128~127的Integer数字,还是可以正常判断的。但是大了就不可以了。因此我们推荐使用Objects.equals(a,b)
。来判断两个Integer类型的大小。
graph.computeIfAbsent(p[0], t->new ArrayList<Integer>()).add(p[1]);
class Solution {
public int checkWays(int[][] pairs) {
int ans = 1;
HashMap<Integer,List<Integer>> graph = new HashMap<Integer, List<Integer>>();
HashMap<Integer, Integer> map = new HashMap<>();
// 构建邻接矩阵
for (int[] p:pairs){
map.put(p[0], map.getOrDefault(p[0],0)+1);
map.put(p[1], map.getOrDefault(p[1],0)+1);
// 优雅的函数式编程
graph.computeIfAbsent(p[0], t->new ArrayList<Integer>()).add(p[1]);
graph.computeIfAbsent(p[1], t->new ArrayList<Integer>()).add(p[0]);
}
int n = map.size();
Integer[] nums = new Integer[n];
int y = 0;
for (Integer x:map.keySet()){
nums[y++] = x;
}
Arrays.sort(nums, new Comparator<Integer>(){
//@override
public int compare(Integer a, Integer b){
return map.get(b)-map.get(a);
}
});
if(map.get(nums[0]) != n-1)return 0;
for(int[] p:pairs){
//如果是基本数据类型,==判断的是值
//如果是对象类型,==判断的是对象的地址
if (Objects.equals(map.get(p[1]),map.get(p[0]))){ // 某一对具有祖孙关系的节点的 祖孙复杂关系也一样,说明可以交换
ans = 2;
break;
}
}
HashSet<Integer> vis = new HashSet<>();
HashMap<Integer, Integer> fa = new HashMap<>();
for(int i:nums){
fa.put(i,nums[0]); // 初始下,全部节点都链接在根节点下
}
vis.add(nums[0]);
for(int i = 1;i<n;i++){;
for (int j: graph.get(nums[i])){ // 找到nums[i]这个节点的邻接
if(!vis.contains(j)){ // vis不包含说明是当前nums[i]节点的孙节点
if(fa.get(j)!=fa.get(nums[i])){
return 0;
}
fa.put(j, nums[i]);
}
}
vis.add(nums[i]);
}
return ans;
}
}
1723. 完成所有工作的最短时间 H DFS/状态压缩dp
三种主要的思路,一个是暴力的dfs加上一定的剪枝和贪心。具体就是首先我们对整个队列进行排序按照从大到小排序。然后每次枚举添加的任务到哪个人的队列里面。
第二个思路是状态压缩dp,dp[i][j]表示前 j 名员工,完成 i 所代表的任务需要的最短时间。每次在更新的时候dp[i][j] = Math.min(dp[i][j], Math.max(dp[i-p][j-1], tot[p]));
得到更新的结果。其中tot
表示一名员工完成s任务需要的时间。 时间复杂度 主要是枚举子集为
3
n
3^n
3n,总复杂度是
k
3
n
k3^n
k3n
第三种思路是状态压缩DP+二分。我们采用dp[i]表示 i 个员工完成全部任务的时间。我们发现具有单调性,也就是说5个人能在T时间内完成的话,6个人一定也可以。因此我们用二分的方法寻找k个人完成任务的下界时间。时间复杂度 log ( s u m ( j o b s ) ) 3 N \log(sum(jobs)) 3^N log(sum(jobs))3N.。如果k大于sum(jobs)会更好。
class Solution {
int ans = 100000000;
int[] jobs;
int k;
public int minimumTimeRequired(int[] jobs, int k) {
// dfs搜索的方法,
this.jobs = jobs;
this.k = k;
Arrays.sort(jobs);
int n = jobs.length;
dfs(0, n-1, new ArrayList<Integer>());
return ans;
}
private void dfs(int min, int index, List<Integer> queue){
if (min>=ans) return;
if(index == -1){
ans = Math.min(min, ans);
return;
}
int n = queue.size();
for(int i = 0; i<n;i++){
queue.set(i, queue.get(i)+jobs[index]);
dfs(Math.max(min, queue.get(i)), index-1,new ArrayList<>(queue));
queue.set(i, queue.get(i)-jobs[index]);
}
if (n<k){
queue.add(jobs[index]);
dfs(Math.max(min, jobs[index]), index-1,new ArrayList<>(queue));
}
}
}
============================状态压缩=======================
class Solution {
public int minimumTimeRequired(int[] jobs, int k) {
int n = jobs.length;
int N = 1<<n;
int[] tot = new int[N];
for(int i = 1;i<N;i++){
for(int j = 0;j<n;j++){
if ((i&(1<<j)) != 0){
tot[i] += jobs[j];
}
}
}
int[][] dp = new int[N][k+1];
for(int i = 1;i<N;i++) dp[i][1] = tot[i];
for(int i = 1;i<N;i++){
for(int j = 2;j<=k;j++){
dp[i][j] = Integer.MAX_VALUE;
for (int p = i;p>0;p = (p-1)&i){
dp[i][j] = Math.min(dp[i][j], Math.max(dp[i-p][j-1], tot[p]));
}
}
}
return dp[N-1][k];
}
}
=============================状态压缩+二分===========================================
class Solution {
public int minimumTimeRequired(int[] jobs, int k) {
int n = jobs.length;
int N = 1<<n;
int[] tot = new int[N];
for(int i = 1;i<N;i++){
for(int j = 0;j<n;j++){
if ((i&(1<<j)) != 0){
tot[i] += jobs[j];
}
}
}
int l = 0;
int r = 0;
for (int i = 0;i<n;i++){
l = Math.max(l,jobs[i]);
r += jobs[i];
}
while(l<=r){
int mid = (l+r)/2;
int[] dp = new int[N];
for (int i = 1;i<N;i++){
dp[i] = Integer.MAX_VALUE/2;
for(int p = i;p>0;p=(p-1)&i){
if(tot[p]<=mid){
dp[i] = Math.min(dp[i], dp[i-p]+1);
}
}
}
if (dp[N-1]<k){
r = mid-1;
}else if (dp[N-1]>k){
l = mid+1;
}else{
r = mid-1;
}
}
return l;
}
}