文章目录
第220,221场周赛和第42场双周赛
1696. 跳跃游戏 VI (单调栈)
利用单调栈的思路维护。单调栈的特点在于,栈内存储的是坐标而比较的内容则是映射的数据的数值大小。
对于这道题目,我们在进行转移的时候,首先检查栈头的元素是不是距离太远。再加入当前的元素,然后维护单调栈。
class Solution {
public int maxResult(int[] nums, int k) {
// 单调栈的思路,单调栈中存储的index
// dp[i]表示当右端点为ii时候的最大值
// 首先需要判断index和当前的index的距离,删除不合法的
// 然后计算出加上当前的nums[index]的最大值
// 单调栈的维护保证新入栈的位置是递减的。也即是说,要是栈尾元素小于当前元素就弹出
// 添加当前的元素
int n = nums.length;
Deque<Integer> queue = new LinkedList<>();
int[] dp = new int[n];
dp[0] = nums[0];
queue.offer(0);
for(int i = 1; i<n;i++){
while(!queue.isEmpty() && i-queue.peekFirst()>k){
queue.pollFirst();
}
dp[i] += dp[queue.peekFirst()]+nums[i];
while(!queue.isEmpty() && dp[queue.peekLast()]<=dp[i]){
queue.pollLast();
}
queue.offerLast(i);
}
return dp[n-1];
1697. 检查边长度限制的路径是否存在 (离线查询)
离线查询的思路特点在于,限制了查询的范围。也就是在小于某个数值的范围内进行搜索。特点是对需要查询的数值进行排序,一边进行插入,一边进行排序。对于这道题目,我们排序之后。先插入小于限制的数字,然后检查连通性。这样得到的结果就是符合限制的。
特别需要注意,这种题目与,需要额外在排序时候加上index,这样每次查询之后需要对应到合适的位置去
class Solution {
public boolean[] distanceLimitedPathsExist(int n, int[][] edgeList, int[][] queries) {
UF uf = new UF(n);
int m = queries.length;
int l = edgeList.length;
int[][] q = new int[m][4];
for(int i = 0;i<m;i++) {
q[i][3] = i;
q[i][0] = queries[i][0];
q[i][1] = queries[i][1];
q[i][2] = queries[i][2];
}
Arrays.sort(edgeList, (a,b)->a[2]-b[2]);
Arrays.sort(q, (a,b)->a[2]-b[2]);
int index = 0;
boolean[] ans = new boolean[m];
// ---------------典型特点:外层循环查询,内层while循环,不断插入满足要求的内容。-----
for(int i = 0;i<m;i++){
int limit = q[i][2];
while(index<l){
if(edgeList[index][2]>=limit) break;
uf.union(edgeList[index][0], edgeList[index][1]);
index++;
}
ans[q[i][3]] = uf.isUnion(q[i][0],q[i][1]);
}
return ans;
}
class UF{
private int[] fa;
private int[] sz;
public UF(int N){
fa = new int[N];
sz = new int[N];
for(int i = 0 ;i<N;i++) fa[i] = i;
Arrays.fill(sz,1);
}
public int findFa(int n){
if(fa[n] == n) return n;
fa[n] = findFa(fa[n]);
return fa[n];
}
public boolean isUnion(int a, int b){
return findFa(a) == findFa(b);
}
public void union(int a, int b){
int afa = findFa(a);
int bfa = findFa(b);
if(afa == bfa) return;
int asz = sz[a];
int bsz = sz[b];
if(asz>bsz){
fa[bfa] = afa;
sz[a] += sz[b];
}else{
fa[afa] = bfa;
sz[b] += sz[a];
}
}
}
}
1703. 得到连续 K 个 1 的最少相邻交换次数 (绝对值不等式)
首先有一个模板问题,对于一系列数字,选取一个值作为特殊点,使得其余点到特殊点的距离和最小。请问特殊点是哪一个。这个问题的答案特殊点就是中点。
但是对于这个题目这里需要稍微变化,因为我们是要求连续的,因此实际是 ∣ a − x ∣ + ∣ a − x − 1 ∣ + ∣ a − x − 2 ∣ |a-x|+|a-x-1|+|a-x-2| ∣a−x∣+∣a−x−1∣+∣a−x−2∣,因为会少一个位置。
对于这个问题,我们可以稍微修改,a’ = a-a.size();这样就回到了我们最熟悉的问题。最后的两边的移动和,其实就是,
- 左侧 ( a ′ [ m i d ] − a ′ [ l ] ) + ( a ′ [ m i d ] − a ′ [ l + 1 ] ) . . . + ( a ′ [ m i d ] − a ′ [ m i d − 1 ] ) = ( m i d − l ) ∗ a ′ [ m i d ] − ( s u m [ m i d − 1 ] − s u m [ l ] ) (a'[mid]-a'[l])+(a'[mid]-a'[l+1])...+(a'[mid]-a'[mid-1]) = (mid-l)*a'[mid] - (sum[mid-1]-sum[l]) (a′[mid]−a′[l])+(a′[mid]−a′[l+1])...+(a′[mid]−a′[mid−1])=(mid−l)∗a′[mid]−(sum[mid−1]−sum[l]),
- 右侧 ( a ′ [ m i d + 1 ] − a ′ [ m i d ] ) + ( a ′ [ m i d + 2 ] − a ′ [ m i d ] ) . . . + ( a ′ [ m i d + r ] − a ′ [ m i d ] ) = ( s u m [ r ] − s u m [ m i d ] ) − ( r − m i d ) ∗ a ′ [ m i d ] (a'[mid+1]-a'[mid])+(a'[mid+2]-a'[mid])...+(a'[mid+r]-a'[mid]) = (sum[r]-sum[mid])- (r-mid)*a'[mid] (a′[mid+1]−a′[mid])+(a′[mid+2]−a′[mid])...+(a′[mid+r]−a′[mid])=(sum[r]−sum[mid])−(r−mid)∗a′[mid]
// 对于随机index访问的get和set方法,一般ArrayList的速度要优于LinkedList。因为ArrayList直接通过数组下标直接找到元素;LinkedList要移动指针遍历每个元素直到找到为止。
// 新增和删除元素,一般LinkedList的速度要优于ArrayList。因为ArrayList在新增和删除元素时,可能扩容和复制数组;LinkedList实例化对象需要时间外,只需要修改指针即可。
class Solution {
public int minMoves(int[] nums, int k) {
// 绝对值不等式的问题,最小值是在中点位置
// |a-x|+|a-x-1|+|a-x-2|
List<Integer> dis = new ArrayList<>(); // 移动到位置0时候需要的次数
//List<Integer> dis = new LinkedList<>();
int n = nums.length;
for (int i = 0; i<n;i++){
if (nums[i] == 1){
dis.add(i-dis.size());
}
}
int m = dis.size();
int[] sum = new int[m+1];
for(int i = 1; i<=m; i++){
sum[i] = sum[i-1]+dis.get(i-1);
}
int ans = Integer.MAX_VALUE;
for(int l = 0;l<=m-k;l++){
int r = l+k-1;
int mid = (l+r)>>1;
int am = dis.get(mid);
int left = (mid-l)*am-(sum[mid]-sum[l]);
int right = (sum[r+1]-sum[mid+1])-(r-mid)*am;
ans = Math.min(ans, left+right);
}
return ans;
}
}
1707. 与数组中元素的最大异或值(Trie树)
典型字典树的题目!!!模板题目
核心函数就是两个,insert和query
class Solution {
int[][] son; // 用数组实现字典树
int idx = 0;
public int[] maximizeXor(int[] nums, int[][] queries) {
Arrays.sort(nums);
int n = nums.length;
son = new int[n * 31][2]; // 最大就是2^31-1,最小是-2^31 并且可能每个数字的路径都完全不重复。
Pair[] pair = new Pair[queries.length];// 添加index
for(int i = 0; i < queries.length; i++) {
pair[i] = new Pair(queries[i][0], queries[i][1], i);
}
Arrays.sort(pair, (o1, o2) ->(o1.m - o2.m));//把queries按照mi从小到大排序
int[] ans = new int[queries.length];
int pos = 0;
for(Pair q : pair) {
//所有<=m的数字,可以加入当前字典树中
while(pos < n && nums[pos] <= q.m) {
insert(nums[pos]);
pos++;
}
//写答案
if(idx == 0) { // 树是空的
ans[q.index] = -1;
}else {
int t = query(q.x); //返回此时trie树中的数字里,与a异或最大的数字
ans[q.index] = q.x ^ t;
}
}
return ans;
}
private int query(int a){
int p = 0, res = 0;
for(int i = 30; i >= 0; i--){
int u = (a >> i) & 1;//最高位,次高位,...
if(son[p][u ^ 1] != 0){// 为1表示当前这位异或存在 //如果可以走,u是1,就往0走。u是0,就往1走
res = res * 2 + u ^ 1;
p = son[p][u ^ 1];
}else{// 为0表示当前这位异或不存在,那就只能勉强的选择与当前位一样的。也就是异或为0的
res = res *2 + u;
p = son[p][u];
}
}
return res;
}
//在trie树中插入数字a
private void insert(int a){
int p = 0;
for(int i = 30; i >= 0; i--){
int u = (a >> i) & 1;
if(son[p][u] == 0){ // son[p][u]表示当前状态还没有发生过,我们需要标记下,然后改变idx从而添加新的状态
idx++;
son[p][u] = idx;
}
p = son[p][u];
}
}
}
class Pair{
int x;
int m;
int index;
public Pair(int x, int m, int index) {
this.x = x;
this.m = m;
this.index = index;
}
}