【周赛总结】得到目标数的操作数、二维网格探测环、TreeSet、石子游戏IV

2020/08/23 第33场双周赛和第203场周赛

1558、得到目标数的操作数

在这里插入图片描述在这里插入图片描述

这道题在比赛时候把我关了。没有很敏锐的意识到涉及到2的次方的操作应该首先想到二进制的方法,这道题目很巧妙的计算出来了每个数字变为0所需要的次数。最终我们的答案应该是两部分的和,一个是对每一位数字进行修饰,修饰的表示就是保证每一位数字的二进制只有最高位是1,其余的都是0。第二部分就是计算数组中全部数组数字变为二进制之后最长的。

Java操作注意(num>>i & 1) == 1,这里的括号是不可省略的。

class Solution {
    public int minOperations(int[] nums) {
        int x = 0;
        int k = 0;
        int ans = 0;
        for (int num:nums){
            for (int i = 0; (num>>i) > 0; i++,x++ ){
                if (((num>>i) & 1) == 1) ans++; 
            }
            k = Math.max(k,x-1); // 因为是最后才++x
            x = 0;
        }
        return Math.max(k,0)+ans;
    }
}

1559、二维网格探测环

在这里插入图片描述
在这里插入图片描述

非常好的一个题目,可以使用并查集,可以使用bfs可以使用dfs。

首先使用比较熟练的bfs方法,需要注意一个细节是不能与之前的节点重复。因此需要另外维护一个队列保存父节点。

class Solution {
    public boolean containsCycle(char[][] grid) {
        int n = grid.length;
        int m = grid[0].length;
        int[][] vis = new int[n][m];
        Queue<int[]> queue = new LinkedList<>();
        Queue<int[]> pq = new LinkedList<>();
        int[][] dis = new int[][]{{0,1}, {1,0}, {-1,0}, {0,-1}};
        for (int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                if (vis[i][j] == 0){
                    char word = grid[i][j];
                    queue.offer(new int[] {i,j});
                    pq.offer(new int[] {-1,-1});
                    while (!queue.isEmpty()){
                        int[] cur = queue.poll();
                        int[] p = pq.poll();
                        for (int[] d:dis){
                            int cur_x = cur[0]+d[0];
                            int cur_y = cur[1]+d[1];
                            if (cur_y == p[1] && cur_x == p[0]) continue;
                            if (0<=cur_x && cur_x<n && 0<=cur_y && cur_y<m && grid[cur_x][cur_y] == word){
                                if (vis[cur_x][cur_y] == 1) return true;
                                else {
                                    queue.offer(new int[]{cur_x,cur_y});
                                    pq.offer(cur);
                                    vis[cur_x][cur_y] = 1;  
                                }
                                
                            }
                        }
                    }
                }
            }
        }
        return false;
    }
}

另外学习一下并查集的写法。

class UF {
    private int[] fa;
    private int[] sz;

    public UF(int N){
        fa = new int[N];
        for (int i = 0; i < N; i++) fa[i] = i;
        sz = new int[N];
        for (int i = 0; i<N; i++)sz[i] = 1;
    } 

    public int findfa(int p){
        if (fa[p] == p) return p;  // 如果是根节点就返回本身
        fa[p] = findfa(fa[p]);
        return fa[p];
          // 否则返回父节点的跟
    }

    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 (sz[afa]>=sz[bfa]){
            fa[bfa] = afa;
            sz[afa] += sz[bfa];
        }
        else{
            fa[afa] = bfa;
            sz[bfa] += sz[afa];
        }
    }

}

class Solution {
    // 并查集的方法
    public boolean containsCycle(char[][] grid) {
        int n = grid.length;
        int m = grid[0].length;
        UF uf = new UF(m*n);
        for (int i = 0; i<n; i++){
            for (int j = 0; j<m ;j++){
                if (i>0 && grid[i][j] == grid[i-1][j]){
                    if (uf.isUnion(i*m+j, (i-1)*m+j))return true;
                    else uf.union(i*m+j, (i-1)*m+j);
                }
                if (j>0 && grid[i][j] == grid[i][j-1]){
                    if (uf.isUnion(i*m+j, i*m+j-1))return true;
                    else uf.union(i*m+j, i*m+j-1);
                }
            }
        }
        return false;
    }
}



1564.查找区间M

在这里插入图片描述

这道题如果正向思考不是很好考虑,因为我们需要的是最后一个满足要求的解,因此我们这里倒着考虑,从最后一次(其实是倒数第二次开始考虑)。我们考虑了一个新的数据结构,Treeset这是一个有序的集合。我们可以每次找到集合中大于某个数字的最小数字或者小于某个数字的最大值

这样我们可以找到,当前位置为0,前一个0的位置,和后一个0的位置。如果出现了满足长度的解,就是我们所需要的。

  • TreeSet<Integer> set = new TreeSet<>();生成一个Treeset
  • int a = set.lower(index);寻找到小于index的最大值
  • int b = set.upper(index);寻找大于index的最小值
  • int a = set.floor(index);寻找到小于等于index的最大值
  • int b = set.ceiling(index);寻找大于等于index的最小值
//import java.util.TreeSet;

class Solution {
    public int findLatestStep(int[] arr, int m) {
        TreeSet<Integer> set=new TreeSet<>();
        set.add(0);
        set.add(arr.length+1);
        if(arr.length==m) return arr.length;
        int n=arr.length;
        for (int i = n-1; i >=0; i--) {
            int index=arr[i];
             分别找到左右两个最近的0的位置
            int a=set.lower(index); 
            int b=set.higher(index);
            if(index-a-1==m||b-index-1==m){
                return i;
            }
            set.add(index);
        }
        return -1;
    }
}

1563.石子游戏IV

在这里插入图片描述

思路还是区间dp, dp[i][j]表示区间【i, j】之间的最大数值。这次在代码实现的角度我们使用了先外层循环区间长度,中间层是起点,最后枚举中间切断点。每次实际的有效得分是,当前的可以得到的得分min(ls, rs)和对应的区间的潜力。

class Solution {
    public int stoneGameV(int[] stoneValue) {
        // 区间dp问题,决定采用三重循环,其中最外层是间隔,中间是起点,最后是分界点
        int n = stoneValue.length;
        int[][] dp = new int[n][n];
        int[] presum = new int[n];
        presum[0] = stoneValue[0];
        for (int i = 1; i < n ; i++){
            presum[i] = presum[i-1]+stoneValue[i];
        }
        for (int len = 2; len<=n; len++){ // 区间长度【2, n】
            for (int i = 0; i+len<=n; i++){
                int j = i+len-1; // 左端点j, 右端点k
                for (int m = i; m<j; m++){

                    int l = dp[i][m];
                    int r = dp[m+1][j];
                    int ls = presum[m]-(i>0?presum[i-1]:0);
                    int rs = presum[j]-presum[m];
                    if (rs == ls){
                        dp[i][j] = Math.max(dp[i][j], ls+Math.max(r,l));
                    }
                    else{
                        if (rs<ls){
                            dp[i][j] = Math.max(dp[i][j], rs+r);
                        }
                        else{
                            dp[i][j] = Math.max(dp[i][j], ls+l);
                        }
                    }
                }

            }
        }
        return dp[0][n-1];

    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值