安排工作以达到最大收益
题目:有一些工作:difficulty[i] 表示第 i 个工作的难度,profit[i] 表示第 i 个工作的收益。现在我们有一些工人。worker[i] 是第 i 个工人的能力,即该工人只能完成难度小于等于 worker[i] 的工作。每一个工人都最多只能安排一个工作,但是一个工作可以完成多次。举个例子,如果 3 个工人都尝试完成一份报酬为 1 的同样工作,那么总收益为 0 。我们能得到的最大收益是多少?
示例:
输入: difficulty = [2,4,6,8,10], profit = [10,20,30,40,50], worker = [4,5,6,7]
输出: 100
解释: 工人被分配的工作难度是 [4,4,6,6] ,分别获得 [20,20,30,30] 的收益。
提示:
1 <= difficulty.length = profit.length <= 10000
1 <= worker.length <= 10000
difficulty[i], profit[i], worker[i] 的范围是 [1, 10^5]
思路:这个题我觉得简而言之就是取每一个工人能做的最赚钱的那个工作来做。看demo是排好序的,好像是越困难收益就越高。但是也不敢确定。能实现的方式也比较多,而三者的范围都是100000。暂时我有点打算用数组来存储值。就是下标是难度。值是收益。然后每一个工人能做的难度从当前往前遍历。取最大值。因为不一定难度对应着收益。所以这里好像可以把当前难度和当前能获取到的最大收益直接存储。我去试试代码。
贴第一版本代码:
class Solution {
public int maxProfitAssignment(int[] difficulty, int[] profit, int[] worker) {
int[] dp = new int[100001];
for(int i = 0;i<difficulty.length;i++) {
dp[difficulty[i]] = Math.max(profit[i], dp[difficulty[i]]);
}
int max = 0;
for(int i = 0;i<dp.length;i++) {
if(dp[i]>max) {//当前收益最大,则max替换为当前
max = dp[i];
}else {//max最大,则当前替换成max
dp[i] = max;
}
}
int ans = 0;
for(int i:worker) {//每个工人的难度。
ans += dp[i];
}
return ans;
}
}
这个题其实不难,但是我第一次wa 的原因是因为第一次for循环的时候是直接赋值的。然后在测试案例的提示下才发现一个时间可能有多个收益值。这里要取最大的而不能直接赋值。
至于第二个技巧应该是第二层for循环中,哪怕某个工时没有对应的收益,也要填充当前最大值。这样可以让最后一层循环直接获取值。
总而言之这个知识点应该还是一个数组表示多个值吧。下标代表难度,值代表最大收益。然后我上面代码性能挺好的,所以就不看别的代码了,直接下一题。
隐藏个人信息
题目:给你一条个人信息字符串 S,它可能是一个 邮箱地址 ,也可能是一串 电话号码 。我们将隐藏它的隐私信息,通过如下规则:
1. 电子邮箱
定义名称 name 是长度大于等于 2 (length ≥ 2),并且只包含小写字母 a-z 和大写字母 A-Z 的字符串。电子邮箱地址由名称 name 开头,紧接着是符号 '@',后面接着一个名称 name,再接着一个点号 '.',然后是一个名称 name。电子邮箱地址确定为有效的,并且格式是 "name1@name2.name3"。为了隐藏电子邮箱,所有的名称 name 必须被转换成小写的,并且第一个名称 name 的第一个字母和最后一个字母的中间的所有字母由 5 个 '*' 代替。
2. 电话号码
题目截图示例 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。
思路:因为这个题星星比较多,容易乱格式,所以这里用截图来看题目。思路的话我不知道是我没理解题目还是咋了,总而言之应该挺简单的吧:第一步判断字符串中有没有@,有的话说明是邮箱,没有说明是手机号。如果是手机号则用stringBuffer拼接所有的数字。判断是10个还是12个。再分别处理。如果是邮箱则统一小写。@之前的那个字符和第一个中间假如五个星星。反正思路就这样,我去实现下试试。
第一版本代码:
class Solution {
public String maskPII(String S) {
if(S.contains("@")) {//电子邮箱
String s = S.toLowerCase();
int idx = s.indexOf("@");
return s.charAt(0)+"*****"+s.substring(idx-1);
}else {//手机号
StringBuffer sb = new StringBuffer();
for(int i = 0;i<S.length();i++) {
if(Character.isDigit(S.charAt(i))) sb.append(S.charAt(i));
}
if(sb.length() == 10) {//本地号码
return "***-***-"+sb.toString().substring(6, 10);
}else {//国际号码
if(sb.length() == 12) {
return "+**-***-***-"+sb.toString().substring(8, 12);
}else if (sb.length() == 13) {
return "+***-***-***-"+sb.toString().substring(9, 13);
}else {
return "+*-***-***-"+sb.toString().substring(7, 11);
}
}
}
}
}
我不知道是我没get到这个题的考点,还是这个题本来就这么简单。反正就这么硬生生的解出来了。注意这个用词:硬生生。
没有什么技巧,就是一层一层判断。但是上面代码性能不怎么样。我去看看性能第一的代码:
class Solution {
public String maskPII(String S) {
int ai=S.indexOf('@'); //at index
StringBuilder sb=new StringBuilder();
if(ai>0){
S=S.toLowerCase();
sb.append(S.charAt(0)).append("*****").append(S.charAt(ai-1))
.append(S.substring(ai));
}else{
int n=0;
for(int i=S.length()-1;i>=0;i--){
char c=S.charAt(i);
if(c>='0'&&c<='9'){
++n;
if(n==5||n==8||n==11) sb.append('-');
sb.append(n>4?'*':c);
}
}
if(n>10) sb.append('+');
sb.reverse();
}
return sb.toString();
}
}
处理上可能是比我的要好一点,但是我也没觉得特别亮眼,感觉也就是字符串处理。下一题吧。
字符串中的查找与替换
题目:某个字符串 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 <= S.length <= 1000
S 仅由小写英文字母组成
0 <= indexes.length <= 100
0 <= indexes[i] < S.length
sources.length == indexes.length
targets.length == indexes.length
1 <= sources[i].length, targets[i].length <= 50
sources[i] 和 targets[i] 仅由小写英文字母组成
思路:这个题我刚刚试了下,索引是顺序没有强要求。也就是可以2.0这种。所以我目前的想法是treeMap排序。前提是索引对应的x是对的才会进入到Map。也就是treeMap中都是要修改的。然后因为自带排序,所以可以从头开始修改。然后有个系数代表当前下标和原字符串下标的差值。比如说字符串中用xxx替换了 aa.这样xxx后面的当前字符串的下标就比原字符串的+1了。当然如果是xx替换aaa。那么就是-1.反正差不多是这个意思,我去实现下看看。
第一版本代码:
class Solution {
public String findReplaceString(String S, int[] indexes, String[] sources, String[] targets) {
//key是起始下标 value是要替换成的字符串.前面的数字
TreeMap<Integer, String> map = new TreeMap<Integer, String>();
for(int i = 0;i<indexes.length;i++) {
int start = indexes[i];
int end = start+sources[i].length();
if(S.substring(start,end).equals(sources[i])) {
map.put(start, end+"-"+targets[i]);
}
}
int pre = 0;
StringBuffer sb = new StringBuffer();
for(Integer i : map.keySet()) {
//第一个是结束下标。第二个是要替换成的字符串
String[] arr = map.get(i).split("-");
if(i != pre) {//前面那段原样
sb.append(S.substring(pre, i));
}
sb.append(arr[1]);
pre = Integer.valueOf(arr[0]);
}
if(pre != S.length()) sb.append(S.substring(pre,S.length()));
return sb.toString();
}
}
虽然ac了,但是只能说性能惨不忍睹。又没啥好的别的思路,我去看看性能第一的代码:
class Solution {
public String findReplaceString(String S, int[] indexes, String[] sources, String[] targets) {
int N = S.length();
int[] match = new int[N];
Arrays.fill(match, -1);
for (int i = 0; i < indexes.length; ++i) {
int ix = indexes[i];
if (S.substring(ix, ix + sources[i].length()).equals(sources[i]))
match[ix] = i;
}
StringBuilder ans = new StringBuilder();
int ix = 0;
while (ix < N) {
if (match[ix] >= 0) {
ans.append(targets[match[ix]]);
ix += sources[match[ix]].length();
} else {
ans.append(S.charAt(ix++));
}
}
return ans.toString();
}
}
这个代码有一定的思路上的类似。然后没有用map而是用数组。并且数组的下标代表起始下标。数组的值指向对应的替换字符的下标。又是一个典型的一个数组表示多种状态。剩下的代码就没啥了。甚至我们好大一部分代码都是差不多的。。
起始一开始我确实想过这种方式。但是后来觉得数组长100000,所以我退缩了。我发现最近我这个直觉简直是反向的,我觉得性能不能好就容易好。我觉得性能好的惨不忍睹。。下一题了。
图像重叠
题目:给出两个图像 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
思路:这个题可以支持上下左右任何位置的滑动,所以说其实结果应该是由1的位置决定的。然后这两个数组的大小最大30乘30,也就是九百。这么少的数据范围不知道如果穷举会怎么样。就是比如A中0,0这个元素一共九百个位置都有可能。把这九百种可能都去和B去做比较。最后最大值就是这个题的答案。我去试试这种思路。
class Solution {
public int largestOverlap(int[][] img1, int[][] img2) {
int size = img1.length;
int max = -1;
for (int yi = -size; yi < size; yi++) {
for (int xi = -size,temp = 0; xi < size; xi++, temp = 0) {
for (int x = 0; x < size; x++) {
if (x-xi < 0) continue;
if (x-xi >= size) continue;
for (int y = 0; y < size; y++) {
if (y-yi < 0) continue;
if (y-yi >= size) continue;
if (img1[x-xi][y-yi] * img2[x][y] == 1) {
temp++;
}
}
}
if (temp > max) {
max = temp;
}
}
}
return max;
}
}
我之前去看了官方题解,两种方案,一个n的四次方时间复杂度,一个n的六次方时间复杂度。总而言之这个题感觉莫名其妙的,也没啥好的解法。都是暴力破解,上面代码的性能也不是很好,我去看看性能第一的代码:
class Solution {
public int largestOverlap(int[][] img1, int[][] img2) {
int m = img1.length;
int n = img1[0].length;
int[] a = new int[m];
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (1 == img1[i][j]) {
a[i] |= (1 << (n - 1 - j));
}
}
}
int[] b = new int[m];
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (1 == img2[i][j]) {
b[i] |= (1 << (n - 1 - j));
}
}
}
int ans = 0;
for (int rt = 0; rt < n; ++rt) {
for (int dwn = 0; dwn < m; ++dwn) {
int tmp = 0;
for (int i = 0; i < m - dwn; ++i) {
tmp += Integer.bitCount((a[i] >>> rt) & b[i + dwn]);
}
ans = Math.max(ans, tmp);
}
}
for (int rt = 0; rt < n; ++rt) {
for (int dwn = 0; dwn < m; ++dwn) {
int tmp = 0;
for (int i = 0; i < m - dwn; ++i) {
tmp += Integer.bitCount((a[i + dwn] >>> rt) & b[i]);
}
ans = Math.max(ans, tmp);
}
}
for (int rt = 0; rt < n; ++rt) {
for (int dwn = 0; dwn < m; ++dwn) {
int tmp = 0;
for (int i = 0; i < m - dwn; ++i) {
tmp += Integer.bitCount((b[i] >>> rt) & a[i + dwn]);
}
ans = Math.max(ans, tmp);
}
}
for (int rt = 0; rt < n; ++rt) {
for (int dwn = 0; dwn < m; ++dwn) {
int tmp = 0;
for (int i = 0; i < m - dwn; ++i) {
tmp += Integer.bitCount((b[i + dwn] >>> rt) & a[i]);
}
ans = Math.max(ans, tmp);
}
}
return ans;
}
}
这个位运算我完全看不懂,盲猜是什么偏移量计算的。但是我着实有点没懂。所以就不难为自己了,下一题。
推多米诺
题目:一行中有 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'; 以及 '.';
思路:我觉得这个题可以用染色来做。比如一个数组:1代表左,-1代表右。如果正好一个牌子受到了一左一右的力就抵消了。递归一秒一秒往下走。如果某一秒和上一秒一样说明走到头了,则返回。思路比较清楚,就是不知道这样会不会超时,我去试试。
第一版代码:
class Solution {
public String pushDominoes(String dominoes) {
if(dominoes.length()<=1) return dominoes;
if(dominoes.length()==2) {
if(dominoes.charAt(0) == 'R' && dominoes.charAt(1) == '.') return "RR";
if(dominoes.charAt(0) == '.' && dominoes.charAt(1) == 'L') return "LL";
return dominoes;
}
String ns = dfs(dominoes);
while (!ns.equals(dominoes)) {
dominoes = ns;
ns = dfs(dominoes);
}
return ns;
}
public String dfs(String dominoes) {
char[] c = dominoes.toCharArray();
int len = c.length;
//1代表L。 -1代表 R。
int[] d = new int[len];
if(c[0] == '.' && c[1] == 'L') d[0] = 1;
if(c[len-1] == '.' && c[len-2]=='R') d[len-1] = -1;
for(int i = 1;i<c.length-1;i++) {
//当前这个元素是立着的才会变
if(c[i] == '.') {
//前一个给了力往右倒。后面没有往左倒的力,所以当前倒向右
if(c[i-1] == 'R') d[i] += -1;
//后一个往左给力,前一个没有往右的力对冲,所以当前倒向左
if(c[i+1] == 'L') d[i] += 1;
}
}
for(int i = 0;i<len;i++) {
if(d[i] == 1) c[i] = 'L';
if(d[i] == -1) c[i] = 'R';
}
return new String(c);
}
}
要怎么说呢,虽然ac了但是性能贼可怜,单纯上面的代码其实可优化点还是不少的。我去试试优化一下吧。
第二版本代码:
class Solution {
public String pushDominoes(String dominoes) {
if(dominoes.length()<=1) return dominoes;
return dfs(dominoes.toCharArray());
}
public String dfs(char[] c) {
int len = c.length;
//1代表L。 -1代表 R。
int[] d = new int[len];
for(int i = 1;i<len;i++) {
//有往右的力
if(c[i] == '.' && c[i-1] == 'R') d[i] += -1;
}
for(int i = 0;i<len-1;i++) {
//有往左的力
if(c[i] == '.' && c[i+1] == 'L') d[i] += 1;
}
boolean flag = false;
for(int i = 0;i<len;i++) {
if(d[i] != 0) {
flag = true;
if(d[i] == 1) c[i] = 'L';
if(d[i] == -1) c[i] = 'R';
}
}
if(flag) return dfs(c);
return new String(c);
}
}
这么说吧,单单从性能上来说这两个版本其实变化挺大的。。但是从排名来说依然都是只超过百分之十。可能是思路的问题了。其实刚刚看了下这个题的标签居然有动态规划和双指针。。双指针能理解。但是dp有点想不到。难不成按照时间递推?emmm...我去看看题解吧。
看了题解才发现同样是ac,我是纯暴力,人家是有技巧的。简而言之这个题有个很好的思路:计算受力大小。比如说预计最开始是L/R是10000/-10000力。每秒减少1.本质上也就是传递一个减少1.比如 R..R..L 如果字符串是这样:那么这个受力分析可以是这样:原始都是0.同方向出了遇到新的同向会蓄满力(可以理解为L遇到L,R遇到R这样的),当然了遇到反向会消失力。否则就一直里递减下去。
先从左往右遍历:-10000 -9999 -9998 -10000 -9999 -9998 0
然后从右往左遍历(这个应该是从后往前展示比较好,但是为了容易理解所以知道数据是从后往前填充的就行。我还是从前往后写):
0,0,0,0,9998,9999,10000
然后我们把左右受到的力相加,变成如下:
-10000 -9999 -9998 -10000 -1 1 10000
而上面的数中正数说明向左的作用力大,所以往左倒,也就是L,负数说明是R。
其实这个例子不太好,因为没显示出受力为0.受力合计为0说明左右抵消了,也就是依然是“.”.这个思路就是一次遍历,而且是很有意思的解法。具体代码如下:
class Solution {
public String pushDominoes(String dominoes) {
int len = dominoes.length();
char[] c = dominoes.toCharArray();
int[] d = new int[len];
int temp = 0;
for(int i = 0;i<len;i++) {
if(c[i] == 'R') temp = -200000;
if(c[i] == 'L') temp = 0;
if(temp == 0) continue;//说明当前没受力
d[i] += temp;
temp++;
}
temp = 0;
for(int j = len-1;j>=0;j--) {
if(c[j] == 'L') temp = 200000;
if(c[j] == 'R') temp = 0;
if(temp == 0) continue;
d[j] += temp;
temp--;
}
StringBuffer sb = new StringBuffer();
for(int i : d) {
sb.append(i>0?'L':i<0?'R':'.');
}
return sb.toString();
}
}
这个和上两版代码从根本上就不一样。一个是n方的时间复杂度。一个是n的时间复杂度。不可同日而语。然后我写的这个性能不是那么好,估计是细节处理了,我去看看性能第一的代码:
class Solution {
public String pushDominoes(String dominoes) {
int guard = 0;
boolean guard_has_marked = false;
char[] dominoes_array = dominoes.toCharArray();
int len = dominoes_array.length;
for(int i = 0; i < len; i++) {
if (dominoes_array[i] == '.') {
if (!guard_has_marked) {
guard = i;
guard_has_marked = true;
}
if (i == len - 1 && guard > 0 && dominoes_array[guard - 1] == 'R') {
for (int j = guard; j <= i; j++) {
dominoes_array[j] = 'R';
}
}
} else if (dominoes_array[i] == 'L') {
if (guard_has_marked) {
if (guard > 0 && dominoes_array[guard - 1] == 'R') {
int half = (i - guard) / 2;
for (int j = guard; j < guard + half; j++) {
dominoes_array[j] = 'R';
}
for (int j = guard + half + (i - guard) % 2; j < i; j++) {
dominoes_array[j] = 'L';
}
} else {
for (int j = guard; j < i; j++) {
dominoes_array[j] = 'L';
}
}
guard_has_marked = false;
}
} else {
if (guard_has_marked) {
if (guard > 0 && dominoes_array[guard - 1] == 'R') {
for (int j = guard; j < i; j++) {
dominoes_array[j] = 'R';
}
}
guard_has_marked = false;
}
}
}
return String.valueOf(dominoes_array);
}
}
这个代码和我上面的思路又不一样。而是单纯的判断。毕竟其实多米诺的情况是可以猜出来的。R...L 。 L...R 。 R..R 。 L...L。还有一个是两端的情况。
中间我的点点是随机的。其实这个点点也就分单数双数。而且是在对冲的时候单双才有意义。这个题解性能可能不错,但是我真的觉得写起来太复杂了,还不如我之前的受力计算的思路。哈哈,反正这个题就这样了,不难,但是很有意义也很有意思。
钥匙和房间
题目:有 N 个房间,开始时你位于 0 号房间。每个房间有不同的号码:0,1,2,...,N-1,并且房间里可能有一些钥匙能使你进入下一个房间。在形式上,对于每个房间 i 都有一个钥匙列表 rooms[i],每个钥匙 rooms[i][j] 由 [0,1,...,N-1] 中的一个整数表示,其中 N = rooms.length。 钥匙 rooms[i][j] = v 可以打开编号为 v 的房间。最初,除 0 号房间外的其余所有房间都被锁住。你可以自由地在房间之间来回走动。如果能进入每个房间返回 true,否则返回 false。
示例 1:
输入: [[1],[2],[3],[]]
输出: true
解释:
我们从 0 号房间开始,拿到钥匙 1。
之后我们去 1 号房间,拿到钥匙 2。
然后我们去 2 号房间,拿到钥匙 3。
最后我们去了 3 号房间。
由于我们能够进入每个房间,我们返回 true。
示例 2:
输入:[[1,3],[3,0,1],[2],[0]]
输出:false
解释:我们不能进入 2 号房间。
提示:
1 <= rooms.length <= 1000
0 <= rooms[i].length <= 1000
所有房间中的钥匙数量总计不超过 3000。
思路:这个题看似就是一个眼熟到极致的题目。典型的深搜和记忆化。思路就是从起始位置开始构图。一直走到结尾。存在从头到尾都没走到的房子则false,否则true。然后如果走过一遍的房子就不用走了。我去代码实现下。
第一版本代码:
class Solution {
public boolean canVisitAllRooms(List<List<Integer>> rooms) {
boolean[] b = new boolean[rooms.size()];
b[0] = true;
Set<Integer> set = new HashSet<>(rooms.get(0));
while(set.size()>0){
Set<Integer> temp = new HashSet<>();
for(Integer i : set){
if(b[i] == true) continue;
temp.addAll(rooms.get(i));
b[i] = true;
}
set = temp;
}
for(boolean i : b) if(i == false) return false;
return true;
}
}
首先题目不难,ac还是很容易的。但是性能我觉得我可能是差在了数据上?反正不知道为啥我这个2ms,排名百分之五十多。
本来我之前想到了栈或者队列等数据结构。但是想想万一一批里有重复的房间呢,set自带的去重还是挺有用的,而且代码很简单,所以性能就没上去。
整体而言我觉得思路没啥问题。然后如果说优化的话我觉得优化的点还是数据结构上。我换种数据结构试试。
第二版本代码:
class Solution {
public boolean canVisitAllRooms(List<List<Integer>> rooms) {
boolean[] b = new boolean[rooms.size()];
b[0] = true;
Stack<Integer> stack = new Stack<>();
for(Integer i:rooms.get(0)) {
stack.push(i);
b[i] = true;
}
while(!stack.isEmpty()){
int size = stack.size();
for(int i = 0;i<size;i++){
for(int j :rooms.get(stack.pop())){
if(b[j] == true) continue;
stack.push(j);
b[j] = true;
}
}
}
for(boolean i : b) if(i == false) return false;
return true;
}
}
换了种数据结构,性能一点没变。。emmmm...我决定直接去看性能第一的代码了:
!!!!我踏马根据人家性能第一的代码又做了小改动,然后性能还是一点没动。。心好累。。直接附上第三版代码:
class Solution {
public boolean canVisitAllRooms(List<List<Integer>> rooms) {
boolean[] b = new boolean[rooms.size()];
b[0] = true;
Stack<Integer> stack = new Stack<>();
int n = 1;
stack.push(0);
while(!stack.isEmpty()){
int size = stack.size();
for(int i = 0;i<size;i++){
for(int j :rooms.get(stack.pop())){
if(b[j] == true) continue;
stack.push(j);
b[j] = true;
n++;
}
}
}
return n == rooms.size();
}
}
然后性能第一的代码用的递归,百分之九十思路都一样,完全get不到性能为什么差这么多,同样附上代码:
class Solution {
boolean[] vis;
int num;
public boolean canVisitAllRooms(List<List<Integer>> rooms) {
int n = rooms.size();
num = 0;
vis = new boolean[n];
dfs(rooms, 0);
return num == n;
}
public void dfs(List<List<Integer>> rooms, int x) {
vis[x] = true;
num++;
for (int it : rooms.get(x)) {
if (!vis[it]) {
dfs(rooms, it);
}
}
}
}
好了,这个题就这样了,其实比较简单。栈是显示递归而已,思路就是顺着一直往下走,然后记忆化不要重复就行。
本篇笔记就记到这里,如果稍微帮到你了记得点个喜欢点个关注。也祝大家工作顺顺利利,生活健康。今天看到一句很喜欢的话:愿我们在认清生活的真相后依然热爱生活~与君共勉!
网友评论