Java实现LeetCode 题号:831 - 840

271 阅读9分钟

「这是我参与2022首次更文挑战的第29天,活动详情查看:2022首次更文挑战」。

LeetCode习题集 有些题可能直接略过了,整理一下之前刷leetcode

831. 隐藏个人信息

给你一条个人信息字符串 S,它可能是一个 邮箱地址 ,也可能是一串 电话号码 。

我们将隐藏它的隐私信息,通过如下规则:

  1. 电子邮箱

定义名称 name 是长度大于等于 2 (length ≥ 2),并且只包含小写字母 a-z 和大写字母 A-Z 的字符串。

电子邮箱地址由名称 name 开头,紧接着是符号 '@',后面接着一个名称 name,再接着一个点号 '.',然后是一个名称 name。

电子邮箱地址确定为有效的,并且格式是 "name1@name2.name3"。

为了隐藏电子邮箱,所有的名称 name 必须被转换成小写的,并且第一个名称 name 的第一个字母和最后一个字母的中间的所有字母由 5 个 '*' 代替。

  1. 电话号码

电话号码是一串包括数字 0-9,以及 {'+', '-', '(', ')', ' '} 这几个字符的字符串。你可以假设电话号码包含 10 到 13 个数字。

电话号码的最后 10 个数字组成本地号码,在这之前的数字组成国际号码。注意,国际号码是可选的。我们只暴露最后 4 个数字并隐藏所有其他数字。

本地号码是有格式的,并且如 "--1111" 这样显示,这里的 1 表示暴露的数字。

为了隐藏有国际号码的电话号码,像 "+111 111 111 1111",我们以 "+--*-1111" 的格式来显示。在本地号码前面的 '+' 号和第一个 '-' 号仅当电话号码中包含国际号码时存在。例如,一个 12 位的电话号码应当以 "+-" 开头进行显示。

注意:像 "(",")"," " 这样的不相干的字符以及不符合上述格式的额外的减号或者加号都应当被删除。

最后,将提供的信息正确隐藏后返回。

示例 1:

输入: "LeetCode@LeetCode.com"
输出: "l*****e@leetcode.com"
解释: 
所有的名称转换成小写, 第一个名称的第一个字符和最后一个字符中间由 5 个星号代替。
因此,"leetcode" -> "l*****e"。
示例 2:

输入: "AB@qq.com"
输出: "a*****b@qq.com"
解释: 
第一个名称"ab"的第一个字符和最后一个字符的中间必须有 5 个星号
因此,"ab" -> "a*****b"。
示例 3:

输入: "1(234)567-890"
输出: "***-***-7890"
解释: 
10 个数字的电话号码,那意味着所有的数字都是本地号码。
示例 4:

输入: "86-(10)12345678"
输出: "+**-***-***-5678"
解释: 
12 位数字,2 个数字是国际号码另外 10 个数字是本地号码 。
 

注意:

S.length <= 40。 邮箱的长度至少是 8。 电话号码的长度至少是 10。

PS:
    邮箱隐藏比较方便,就是只留下第一个字符和最后一个字符,全部转换成小写就可以了
    电话的比较麻烦,四位七位十位的时候都需要加上 - 
class Solution {
    public String maskPII(String S) {
		return (S.indexOf("@")!=-1)?maskEmail(S):maskPhone(S.toCharArray());
	}

	public String maskEmail(String s){
		StringBuilder sb = new StringBuilder();
		int index = s.indexOf("@");
		sb.append(s.charAt(0)).append("*****").append(s.charAt(index-1)).append(s.substring(index));
		return sb.toString().toLowerCase();
	}

	public String maskPhone(char[] chs){
		int cnt = 0;
		StringBuilder sb = new StringBuilder();

		for(int i=chs.length-1;i>=0;i--){
			char ch = chs[i];
			if(Character.isDigit(ch)){
				if(cnt == 4 || cnt == 7 || cnt == 10) 
				   sb.append("-");
				if (cnt < 4)
					sb.append(ch);
				else
					sb.append("*");
				cnt++; 
			} 
		}

		if(cnt>10){
			sb.append('+');
		}

		return sb.reverse().toString();
	}
}

832. 翻转图像

给定一个二进制矩阵 A,我们想先水平翻转图像,然后反转图像并返回结果。

水平翻转图片就是将图片的每一行都进行翻转,即逆序。例如,水平翻转 [1, 1, 0] 的结果是 [0, 1, 1]。

反转图片的意思是图片中的 0 全部被 1 替换, 1 全部被 0 替换。例如,反转 [0, 1, 1] 的结果是 [1, 0, 0]。

示例 1:

输入: [[1,1,0],[1,0,1],[0,0,0]] 输出: [[1,0,0],[0,1,0],[1,1,1]] 解释: 首先翻转每一行: [[0,1,1],[1,0,1],[0,0,0]]; 然后反转图片: [[1,0,0],[0,1,0],[1,1,1]] 示例 2:

输入: [[1,1,0,0],[1,0,0,1],[0,1,1,1],[1,0,1,0]] 输出: [[1,1,0,0],[0,1,1,0],[0,0,0,1],[1,0,1,0]] 解释: 首先翻转每一行: [[0,0,1,1],[1,0,0,1],[1,1,1,0],[0,1,0,1]]; 然后反转图片: [[1,1,0,0],[0,1,1,0],[0,0,0,1],[1,0,1,0]] 说明:

1 <= A.length = A[0].length <= 20 0 <= A[i][j] <= 1

PS:
    因为矩阵里除了 0 就是 1
    异或1 就是取反   只针对于0  和  1  来看
    换位的时候,异或上一个 1 就做到了即换位又替换
class Solution {
     public int[][] flipAndInvertImage(int[][] A) {
        int C = A[0].length;
        for (int[] row: A)
            for (int i = 0; i < (C + 1) / 2; ++i) {
                int tmp = row[i] ^ 1;
                row[i] = row[C - 1 - i] ^ 1;
                row[C - 1 - i] = tmp;
            }

        return A;
    }
 
}

833. 字符串中的查找与替换

对于某些字符串 S,我们将执行一些替换操作,用新的字母组替换原有的字母组(不一定大小相同)。

每个替换操作具有 3 个参数:起始索引 i,源字 x 和目标字 y。规则是如果 x 从原始字符串 S 中的位置 i 开始,那么我们将用 y 替换出现的 x。如果没有,我们什么都不做。

举个例子,如果我们有 S = “abcd” 并且我们有一些替换操作 i = 2,x = “cd”,y = “ffff”,那么因为 “cd” 从原始字符串 S 中的位置 2 开始,我们将用 “ffff” 替换它。

再来看 S = “abcd” 上的另一个例子,如果我们有替换操作 i = 0,x = “ab”,y = “eee”,以及另一个替换操作 i = 2,x = “ec”,y = “ffff”,那么第二个操作将不执行任何操作,因为原始字符串中 S[2] = 'c',与 x[0] = 'e' 不匹配。

所有这些操作同时发生。保证在替换时不会有任何重叠: S = "abc", indexes = [0, 1], sources = ["ab","bc"] 不是有效的测试用例。

示例 1:

输入:S = "abcd", indexes = [0,2], sources = ["a","cd"], targets = ["eee","ffff"] 输出:"eeebffff" 解释: "a" 从 S 中的索引 0 开始,所以它被替换为 "eee"。 "cd" 从 S 中的索引 2 开始,所以它被替换为 "ffff"。 示例 2:

输入:S = "abcd", indexes = [0,2], sources = ["ab","ec"], targets = ["eee","ffff"] 输出:"eeecd" 解释: "ab" 从 S 中的索引 0 开始,所以它被替换为 "eee"。 "ec" 没有从原始的 S 中的索引 2 开始,所以它没有被替换。

提示:

0 <= indexes.length = sources.length = targets.length <= 100 0 < indexes[i] < S.length <= 1000 给定输入中的所有字符都是小写字母。

PS;
    先循环原字符串,然后如果在指定位置是指定的字符串,则记录一下要替换的字符串长度,和被替换的下标
    一直循环完原字符串
    然后在拼接原字符串,每次拼接都是原字符串和被替换的字符串,然后索引指向到下一个原字符串(也就是被替换字符串结束)
class Solution {
     public String findReplaceString(String S, int[] indexes, String[] sources, String[] targets) {
        int[] array = new int[S.length()];
        int[] temp = new int[S.length()];
        for (int i = 0; i < indexes.length; i++) {
            if (S.startsWith(sources[i], indexes[i])) {
                array[indexes[i]] = sources[i].length();
                temp[indexes[i]] = i;
            }
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0, j = 0; j <= array.length; j++) {
            if (j == array.length) {
                sb.append(S.substring(i));
                break;
            }
            if (array[j] > 0) {
                sb.append(S.substring(i, j)).append(targets[temp[j]]);
                i = j + array[j];
            }
        }
        return sb.toString();
    }
}

834. 树中距离之和

给定一个无向、连通的树。树中有 N 个标记为 0...N-1 的节点以及 N-1 条边 。

第 i 条边连接节点 edges[i][0] 和 edges[i][1] 。

返回一个表示节点 i 与其他所有节点距离之和的列表 ans。

示例 1:

输入: N = 6, edges = [[0,1],[0,2],[2,3],[2,4],[2,5]] 输出: [8,12,6,10,10,10] 解释: 如下为给定的树的示意图: 0 /
1 2 /|
3 4 5

我们可以计算出 dist(0,1) + dist(0,2) + dist(0,3) + dist(0,4) + dist(0,5) 也就是 1 + 1 + 2 + 2 + 2 = 8。 因此,answer[0] = 8,以此类推。 说明: 1 <= N <= 10000

PS:
	这个题看了半个小时没找到思路,只能去看题解,原来我的菜
class Solution {
    //这个题我看了半天,怎么看怎么超时,原来是中间有规律,该说不说,这个规律真的难找
  int[] ans, count;
    List<Set<Integer>> graph;
    int N;
    public int[] sumOfDistancesInTree(int N, int[][] edges) {
        this.N = N;
        graph = new ArrayList<Set<Integer>>();
        ans = new int[N];
        count = new int[N];
        Arrays.fill(count, 1);

        for (int i = 0; i < N; ++i)
            graph.add(new HashSet<Integer>());
        for (int[] edge: edges) {
            graph.get(edge[0]).add(edge[1]);
            graph.get(edge[1]).add(edge[0]);
        }
        dfs(0, -1);
        dfs2(0, -1);
        return ans;
    }

    public void dfs(int node, int parent) {
        for (int child: graph.get(node))
            if (child != parent) {
                dfs(child, node);
                //count[node]是以node为根的节点个数
                count[node] += count[child];
                //ans[node]是所有结点道node的距离
                //就是ans[child]+child与node的距离
                ans[node] += ans[child] + count[child];
            }
    }

    public void dfs2(int node, int parent) {
        for (int child: graph.get(node))
            if (child != parent) {
                // ans[node] += ans[child] + count[child];
                //这是ans[child]的前半部分,
                //后半部分是,不是以child为根的节点个数
                //后半部分是,因为,我们可以发现,每一个count都是多加了自己本身的,
                //也就是我们前面的child是多加了自己本身的,所以不是child的根节点都没加上
                ans[child] = ans[node] - count[child] + N - count[child];
                dfs2(child, node);
            }
    }
 
}

835. 图像重叠

给出两个图像 A 和 B ,A 和 B 为大小相同的二维正方形矩阵。(并且为二进制矩阵,只包含0和1)。

我们转换其中一个图像,向左,右,上,或下滑动任何数量的单位,并把它放在另一个图像的上面。之后,该转换的重叠是指两个图像都具有 1 的位置的数目。

(请注意,转换不包括向任何方向旋转。)

最大可能的重叠是什么?

示例 1:

输入:A = [[1,1,0], [0,1,0], [0,1,0]] B = [[0,0,0], [0,1,1], [0,0,1]] 输出:3 解释: 将 A 向右移动一个单位,然后向下移动一个单位。 注意:

1 <= A.length = A[0].length = B.length = B[0].length <= 30 0 <= A[i][j], B[i][j] <= 1

PS:
    是整个图像的转换,不要弄混
    因为是转换,所以这里我们做两个,a转换成b,或者b转换成a
    
    比较i,j右面,下面的矩阵和原矩阵相等的,两个位置 & 如果相等都是1就算做一个,如果有一个是0就不是1
    
    
class Solution {
       public int largestOverlap(int[][] A, int[][] B) {
        int max = 0;
        int length = A.length;
        
        for(int i = 0; i < length; i++) {
            for(int j = 0; j < length; j++) {
                max = Math.max(max, countOverlap(A, B, i, j));
                max = Math.max(max, countOverlap(B, A, i, j));
            }
        }
        
        return max;
    }
    
    private int countOverlap(int[][] A, int[][] B, int m, int n) {
        int length = A.length;
        int count = 0;
        
        for(int i = m; i < length; i++) {
            for(int j = n; j < length; j++) {
                count += A[i][j] & B[i-m][j-n];
            }
        }
        
        return count;
    }

}

836. 矩形重叠

矩形以列表 [x1, y1, x2, y2] 的形式表示,其中 (x1, y1) 为左下角的坐标,(x2, y2) 是右上角的坐标。

如果相交的面积为正,则称两矩形重叠。需要明确的是,只在角或边接触的两个矩形不构成重叠。

给出两个矩形,判断它们是否重叠并返回结果。

示例 1:

输入:rec1 = [0,0,2,2], rec2 = [1,1,3,3] 输出:true 示例 2:

输入:rec1 = [0,0,1,1], rec2 = [1,0,2,1] 输出:false

提示:

两个矩形 rec1 和 rec2 都以含有四个整数的列表的形式给出。 矩形中的所有坐标都处于 -10^9 和 10^9 之间。 x 轴默认指向右,y 轴默认指向上。 你可以仅考虑矩形是正放的情况。

PS:
	矩形如果不重叠,从x轴和y轴上看两个矩形就变成了两条线段,这两条线段肯定是不相交的,也就是说左边的矩形的最右边小于右边矩形的最左边,
class Solution {
     public boolean isRectangleOverlap(int[] rec1, int[] rec2) {

          if(rec2[1] >= rec1[3] || rec1[1] >= rec2[3]){
	         return false;
          }
	  if(rec1[0] >= rec2[2] || rec1[2] <= rec2[0]){
	         return false;
          }
	  return true;
    }
}

837. 新21点

爱丽丝参与一个大致基于纸牌游戏 “21点” 规则的游戏,描述如下:

爱丽丝以 0 分开始,并在她的得分少于 K 分时抽取数字。 抽取时,她从 [1, W] 的范围中随机获得一个整数作为分数进行累计,其中 W 是整数。 每次抽取都是独立的,其结果具有相同的概率。

当爱丽丝获得不少于 K 分时,她就停止抽取数字。 爱丽丝的分数不超过 N 的概率是多少?

示例 1:

输入:N = 10, K = 1, W = 10 输出:1.00000 说明:爱丽丝得到一张卡,然后停止。 示例 2:

输入:N = 6, K = 1, W = 10 输出:0.60000 说明:爱丽丝得到一张卡,然后停止。 在 W = 10 的 6 种可能下,她的得分不超过 N = 6 分。 示例 3:

输入:N = 21, K = 17, W = 10 输出:0.73278 提示:

0 <= K <= N <= 10000 1 <= W <= 10000 如果答案与正确答案的误差不超过 10^-5,则该答案将被视为正确答案通过。 此问题的判断限制时间已经减少。

PS:
    分数不超过N,我们就设定N
    在N以内进行抽奖
    然后把N以内的所有都加起来
class Solution {
      public double new21Game(int N, int K, int W) {
        double res = 0;

        int bound = (N<K+W)?N:K+W;
        double dp[] = new double[bound+1];
        double p = (double) 1 / W;
        dp[0] = 1;
        
        
        int lower = 0;
        double acc = 0;
        for(int i = 1; i <= bound; i++){            
            if(lower < i-W){
                acc -= dp[lower];
                lower++;                
            }
            if(i<=K){               
                acc += dp[i-1];
            }
            dp[i] = acc * p;
        }
        for(int q = K; q <= bound; q++){
            res += dp[q];
        }
        return res;
    }
 
}

838. 推多米诺

一行中有 N 张多米诺骨牌,我们将每张多米诺骨牌垂直竖立。

在开始时,我们同时把一些多米诺骨牌向左或向右推。

在这里插入图片描述

每过一秒,倒向左边的多米诺骨牌会推动其左侧相邻的多米诺骨牌。

同样地,倒向右边的多米诺骨牌也会推动竖立在其右侧的相邻多米诺骨牌。

如果同时有多米诺骨牌落在一张垂直竖立的多米诺骨牌的两边,由于受力平衡, 该骨牌仍然保持不变。

就这个问题而言,我们会认为正在下降的多米诺骨牌不会对其它正在下降或已经下降的多米诺骨牌施加额外的力。

给定表示初始状态的字符串 "S" 。如果第 i 张多米诺骨牌被推向左边,则 S[i] = 'L';如果第 i 张多米诺骨牌被推向右边,则 S[i] = 'R';如果第 i 张多米诺骨牌没有被推动,则 S[i] = '.'。

返回表示最终状态的字符串。

示例 1:

输入:".L.R...LR..L.." 输出:"LL.RR.LLRRLL.." 示例 2:

输入:"RR.L" 输出:"RR.L" 说明:第一张多米诺骨牌没有给第二张施加额外的力。 提示:

0 <= N <= 10^5 表示多米诺骨牌状态的字符串只含有 'L','R'; 以及 '.';

PS:
    我们分范围讨论
    如果导向了一个方向,直接全导向一个方向就好
    
    如果不是一个方向,左面的向右导,右面的向左导,那就一步一步的像中间倒
    直到中间只剩下一个,或者不剩下的时候才会停止倒
    l 和 i 都是已经有方向的,当两个差的等于2的时候,证明中间差一个,否则就是他俩相等就是都倒
class Solution {
      public String pushDominoes(String dominoes) {
        char[] d = dominoes.toCharArray();
        int l = -1;
        for (int r = 0; r <= d.length; r++) {
            if (r == d.length || d[r] != '.') {
                char charL = l == -1 ? 'L' : d[l];
                char charR = r == d.length ? 'R' : d[r];
                if (charL == charR) {
                    while (l + 1 < r)
                        d[++l] = charL;
                } else if (charR == 'L') {
                    int i = r;
                    while (l + 2 < i) {
                        d[++l] = charL;
                        d[--i] = charR;
                    }
                }
                l = r;
            }
        }
        return new String(d);
    }
 
}