第194场周赛
20200621
1486. 数组异或操作
题目描述1
给你两个整数,n 和 start 。
数组 nums 定义为:nums[i] = start + 2*i(下标从 0 开始)且 n == nums.length 。
请返回 nums 中所有元素按位异或(XOR)后得到的结果。
示例 1:
输入:n = 5, start = 0
输出:8
解释:数组 nums 为 [0, 2, 4, 6, 8],其中 (0 ^ 2 ^ 4 ^ 6 ^ 8) = 8 。
"^" 为按位异或 XOR 运算符。
示例 2:
输入:n = 4, start = 3
输出:8
解释:数组 nums 为 [3, 5, 7, 9],其中 (3 ^ 5 ^ 7 ^ 9) = 8.
示例 3:
输入:n = 1, start = 7
输出:7
示例 4:
输入:n = 10, start = 5
输出:2
提示:
- 1 <= n <= 1000
- 0 <= start <= 1000
- n == nums.length
Solution1
class Solution {
public int xorOperation(int n, int start) {
int ans = start;
for(int i = 1; i < n; i++){
ans ^= (start + 2 * i);
}
return ans;
}
}
1487. 保证文件名唯一
题目描述2
给你一个长度为 n 的字符串数组 names 。你将会在文件系统中创建 n 个文件夹:在第 i 分钟,新建名为 names[i] 的文件夹。
由于两个文件 不能 共享相同的文件名,因此如果新建文件夹使用的文件名已经被占用,系统会以 (k) 的形式为新文件夹的文件名添加后缀,其中 k 是能保证文件名唯一的 最小正整数 。
返回长度为 n 的字符串数组,其中 ans[i] 是创建第 i 个文件夹时系统分配给该文件夹的实际名称。
示例 1:
输入:names = ["pes","fifa","gta","pes(2019)"]
输出:["pes","fifa","gta","pes(2019)"]
解释:文件系统将会这样创建文件名:
"pes" --> 之前未分配,仍为 "pes"
"fifa" --> 之前未分配,仍为 "fifa"
"gta" --> 之前未分配,仍为 "gta"
"pes(2019)" --> 之前未分配,仍为 "pes(2019)"
示例 2:
输入:names = ["gta","gta(1)","gta","avalon"]
输出:["gta","gta(1)","gta(2)","avalon"]
解释:文件系统将会这样创建文件名:
"gta" --> 之前未分配,仍为 "gta"
"gta(1)" --> 之前未分配,仍为 "gta(1)"
"gta" --> 文件名被占用,系统为该名称添加后缀 (k),由于 "gta(1)" 也被占用,所以 k = 2 。实际创建的文件名为 "gta(2)" 。
"avalon" --> 之前未分配,仍为 "avalon"
示例 3:
输入:names = ["onepiece","onepiece(1)","onepiece(2)","onepiece(3)","onepiece"]
输出:["onepiece","onepiece(1)","onepiece(2)","onepiece(3)","onepiece(4)"]
解释:当创建最后一个文件夹时,最小的正有效 k 为 4 ,文件名变为 "onepiece(4)"。
示例 4:
输入:names = ["wano","wano","wano","wano"]
输出:["wano","wano(1)","wano(2)","wano(3)"]
解释:每次创建文件夹 "wano" 时,只需增加后缀中 k 的值即可。
示例 5:
输入:names = ["kaido","kaido(1)","kaido","kaido(1)"]
输出:["kaido","kaido(1)","kaido(2)","kaido(1)(1)"]
解释:注意,如果含后缀文件名被占用,那么系统也会按规则在名称后添加新的后缀 (k) 。
提示:
- 1 <= names.length <= 5 * 10^4
- 1 <= names[i].length <= 20
- names[i] 由小写英文字母、数字和/或圆括号组成。
Solution2
首先想到的是用hashset来判断出没出现过,但是会超时
要改成hashmap,用map里的value用来保存key出现的次数,这样就避免了超时
//这样写会超时
class Solution {
public String[] getFolderNames(String[] names) {
Set<String> set = new HashSet<>();
String[] ans = new String[names.length];
int i = 0;
for(String name : names){
if(!set.contains(name)){
set.add(name);
ans[i++] = name;
}else{
//存在 加后缀
int num = 1;
String name1 = name;
while(set.contains(name)){
String k = String.valueOf(num);
name = name1 + "(" + k + ")";
num++;
}
set.add(name);
ans[i++] = name;
}
}
return ans;
}
}
class Solution {
public String[] getFolderNames(String[] names) {
// 用map里的value用来保存key出现的次数,这样就避免了超时
Map<String,Integer> map = new HashMap<>();
String[] ans = new String[names.length];
int i = 0;
for(String name : names){
if(!map.containsKey(name)){
//map中不存在,直接写入答案对应位置
map.put(name, 1);
ans[i++] = name;
}else{
//map中存在
//取出之前出现的次数
int count = map.get(name);
// 后面有没有出现过
while(map.containsKey(name + "(" + count + ")")){
count++;
}
// 更新map
map.put(name + "(" + count + ")", 1);
map.put(name, map.get(name) + 1);
ans[i++] = name + "(" + count + ")";
}
}
return ans;
}
}
1488. 避免洪水泛滥
题目描述3
你的国家有无数个湖泊,所有湖泊一开始都是空的。当第 n 个湖泊下雨的时候,如果第 n 个湖泊是空的,那么它就会装满水,否则这个湖泊会发生洪水。你的目标是避免任意一个湖泊发生洪水。
给你一个整数数组 rains ,其中:
rains[i] > 0 表示第 i 天时,第 rains[i] 个湖泊会下雨。
rains[i] == 0 表示第 i 天没有湖泊会下雨,你可以选择 一个 湖泊并 抽干 这个湖泊的水。
请返回一个数组 ans ,满足:
ans.length == rains.length
如果 rains[i] > 0 ,那么ans[i] == -1 。
如果 rains[i] == 0 ,ans[i] 是你第 i 天选择抽干的湖泊。
如果有多种可行解,请返回它们中的 任意一个 。如果没办法阻止洪水,请返回一个 空的数组 。
请注意,如果你选择抽干一个装满水的湖泊,它会变成一个空的湖泊。但如果你选择抽干一个空的湖泊,那么将无事发生(详情请看示例 4)。
示例 1:
输入:rains = [1,2,3,4]
输出:[-1,-1,-1,-1]
解释:第一天后,装满水的湖泊包括 [1]
第二天后,装满水的湖泊包括 [1,2]
第三天后,装满水的湖泊包括 [1,2,3]
第四天后,装满水的湖泊包括 [1,2,3,4]
没有哪一天你可以抽干任何湖泊的水,也没有湖泊会发生洪水。
示例 2:
输入:rains = [1,2,0,0,2,1]
输出:[-1,-1,2,1,-1,-1]
解释:第一天后,装满水的湖泊包括 [1]
第二天后,装满水的湖泊包括 [1,2]
第三天后,我们抽干湖泊 2 。所以剩下装满水的湖泊包括 [1]
第四天后,我们抽干湖泊 1 。所以暂时没有装满水的湖泊了。
第五天后,装满水的湖泊包括 [2]。
第六天后,装满水的湖泊包括 [1,2]。
可以看出,这个方案下不会有洪水发生。同时, [-1,-1,1,2,-1,-1] 也是另一个可行的没有洪水的方案。
示例 3:
输入:rains = [1,2,0,1,2]
输出:[]
解释:第二天后,装满水的湖泊包括 [1,2]。我们可以在第三天抽干一个湖泊的水。
但第三天后,湖泊 1 和 2 都会再次下雨,所以不管我们第三天抽干哪个湖泊的水,另一个湖泊都会发生洪水。
示例 4:
输入:rains = [69,0,0,0,69]
输出:[-1,69,1,1,-1]
解释:任何形如 [-1,69,x,y,-1], [-1,x,69,y,-1] 或者 [-1,x,y,69,-1] 都是可行的解,其中 1 <= x,y <= 10^9
示例 5:
输入:rains = [10,20,20]
输出:[]
解释:由于湖泊 20 会连续下 2 天的雨,所以没有没有办法阻止洪水。
提示:
- 1 <= rains.length <= 10^5
- 0 <= rains[i] <= 10^9
Solution3
优先队列+贪心,每次抽干最近要下雨的湖
class Solution {
public int[] avoidFlood(int[] rains) {
int n = rains.length;
int[] ans = new int[n];
int[] next = new int[n];//记录每个湖下一次的下雨时间
Arrays.fill(next, n+1);
Map<Integer, Integer> tm = new HashMap<>();//每个湖最早下雨的时间 湖-天数
for(int i = n-1; i >= 0; i--){
int r = rains[i];
if(r > 0){
if(tm.containsKey(r)){
next[i] = tm.get(r);
}
tm.put(r, i); //更新第r个湖下雨的时间
}
}
Map<Integer, Boolean> st = new HashMap<>();//湖泊的状态
PriorityQueue<Pair<Integer, Integer>> pq = new PriorityQueue<>((o1, o2) -> o1.getKey()-o2.getKey());//小根堆,value-湖泊编号,key-这个湖泊下一次最早哪天下雨
for(int i = 0; i < n; i++){
int r = rains[i];
if(r > 0){
// 如果这天下雨了
if(st.getOrDefault(r, false) == true){
//如果这个湖泊之前已经有水了,则洪水发生
return new int[]{};
}
st.put(r, true);
pq.offer(new Pair(next[i], r));
ans[i] = -1;
}else{
// 如果这天没有下雨,要抽水
if(pq.isEmpty()){
ans[i] = 1;//如果堆是空的,则抽第一个湖
}else{
//取出最近要下雨的,这个湖抽水的需求最迫切。也就是堆的key最小的
Pair<Integer, Integer> t = pq.poll();
st.put(t.getValue(), false);
ans[i] = t.getValue();
}
}
}
return ans;
}
}
参考:
- https://www.bilibili.com/video/BV1ja4y1Y7nk
- https://leetcode-cn.com/problems/avoid-flood-in-the-city/solution/you-xian-ji-dui-lie-tan-xin-mei-ci-xian-chou-gan-z/
1489. 找到最小生成树里的关键边和伪关键边
题目描述4
给你一个 n 个点的带权无向连通图,节点编号为 0 到 n-1 ,同时还有一个数组 edges ,其中 edges[i] = [fromi, toi, weighti] 表示在 fromi 和 toi 节点之间有一条带权无向边。最小生成树 (MST) 是给定图中边的一个子集,它连接了所有节点且没有环,而且这些边的权值和最小。
请你找到给定图中最小生成树的所有关键边和伪关键边。如果从图中删去某条边,会导致最小生成树的权值和增加,那么我们就说它是一条关键边。伪关键边则是可能会出现在某些最小生成树中但不会出现在所有最小生成树中的边。
请注意,你可以分别以任意顺序返回关键边的下标和伪关键边的下标。
提示:
- 2 <= n <= 100
- 1 <= edges.length <= min(200, n * (n - 1) / 2)
- edges[i].length == 3
- 0 <= fromi < toi < n
- 1 <= weighti <= 1000
- 所有
(fromi, toi)
数对都是互不相同的。
Solution4
最小生成树:构造最小生成树的算法主要有:克鲁斯卡尔(Kruskal)算法和普利姆(Prim)算法
class Solution {
/*
* 思路详解:
* 1. 整体思路:
* a. 通过Kruska算法(加边法)可以计算出最小生成树MST的最小权值minCost
* b. 遍历所有的边:
* i. 如果去掉这个边,MST的最小权值 > minCost,说明这个边是一个关键边
* ii. 否则,说明如果去掉这个边,MST的最小权值 == minCost,这个又分为两种情况
* 1) 如果包含这个边的MST的最小权值 == minCost, 说明这个边是一个伪关键边
* 2) 否则,说明包含这个边的MST的最小权值 > minCost,说明这个边一定不是关键边
* 2. getMSTExclude / getMSTInclude 算法:
* a. 初始化ends数组,值为-1,表示每个节点都是树的终点
* b. newEdges是按照权值从小到大排列的有序数组,遍历数组,对于每条边的两个节点,如果他们不连通,则连接这两个节点对应的两颗树
* c. 最终的结果有两种可能,通过n-1条边把树连通为一颗树(返回边权值),或者最终不足以连通为一颗树(返回最大整数)
*/
public List<List<Integer>> findCriticalAndPseudoCriticalEdges(int n, int[][] edges) {
int height = edges.length;
int[][] newEdges = new int[height][4];
for(int i = 0; i < height; i++){
newEdges[i][0] = edges[i][0];
newEdges[i][1] = edges[i][1];
newEdges[i][2] = edges[i][2];
newEdges[i][3] = i;
}
Arrays.sort(newEdges, new Comparator<int[]>(){
@Override
public int compare(int[] o1, int[] o2){
return o1[2] - o2[2];
}
});
List<Integer> realCri = new ArrayList<>();
List<Integer> fakeCri = new ArrayList<>();
int minCost = getMSTExclude(n, newEdges, -1);
for(int i = 0; i < height; i++){
if(getMSTExclude(n, newEdges, i) > minCost){
realCri.add(i);
}else if(getMSTInclude(n, newEdges, i) == minCost){
fakeCri.add(i);
}
}
List<List<Integer>> ans = new ArrayList<>();
ans.add(realCri);
ans.add(fakeCri);
return ans;
}
//不包含边edge的MST
private int getMSTExclude(int n, int[][] edges, int edge){
int height = edges.length;
int[] ends = new int[n];//记录每个节点指向的终点
Arrays.fill(ends, -1);//-1代表指向自己
int cost = 0;
int counter = 0;
for(int[] e : edges){
if(e[3] == edge){//排除edge边
continue;
}
int end1 = getEnd(ends, e[0]);
int end2 = getEnd(ends, e[1]);
if(end1 != end2){
//连接两条边
ends[end1] = end2;
cost += e[2];
counter++;
}
}
return counter == n - 1 ? cost : Integer.MAX_VALUE;
}
//包含边edge的MST
private int getMSTInclude(int n, int[][] edges, int edge){
int height = edges.length;
int[] ends = new int[n];//记录每个节点指向的终点
Arrays.fill(ends, -1);//-1代表指向自己
int cost = 0;
int counter = 0;
//连接edge边
for(int[] e : edges){
if(e[3] == edge){
ends[e[0]] = e[1];
cost += e[2];
counter++;
}
}
for(int[] e : edges){
int end1 = getEnd(ends, e[0]);
int end2 = getEnd(ends, e[1]);
if(end1 != end2){
//连接两条边
ends[end1] = end2;
cost += e[2];
counter++;
}
}
return counter == n - 1 ? cost : Integer.MAX_VALUE;
}
//值为-1的才是树的终点
private int getEnd(int[] ends, int node){
if(ends[node] == -1){
return node;
}else{
ends[node] = getEnd(ends, ends[node]);
return ends[node];
}
}
}
我的公众号:GitKid
暂时每日分享LeetCode,我在不断学习的过程中,公众号也在不断充实,欢迎大家扫码关注。
TODO:《剑指offer》系列