1486. 數組異或操作
給你兩個整數,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
代碼
class Solution {
public int xorOperation(int n, int start) {
int ans = start, i = 0;
for (i=1; i<n; ++i) {
ans ^= (start + 2 * i);
}
return ans;
}
}
1487. 保證文件名唯一
題目難度Medium
給你一個長度爲 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] 由小寫英文字母、數字和/或圓括號組成。
思路
哈希表。注意兩個序列可能會發生衝突,注意區分,例如測試用例["kaido","kaido(1)","kaido","kaido(1)"]
代碼
class Solution {
public String[] getFolderNames(String[] names) {
int n = names.length, i = 0;
HashMap<String, Integer> nameMap = new HashMap<>();
String[] ans = new String[n];
for (i=0; i<n; ++i) {
String name = names[i];
if (nameMap.containsKey(name)) {
int id = nameMap.get(name) + 1;
String nameid = name + "(" + String.valueOf(id) + ")";
while (nameMap.containsKey(nameid)) {
++id;
nameid = name + "(" + String.valueOf(id) + ")";
}
nameMap.put(name, id);
nameMap.put(nameid, 0);
ans[i] = nameid;
} else {
ans[i] = name;
nameMap.put(name, 0);
}
}
return ans;
}
}
1488. 避免洪水氾濫
你的國家有無數個湖泊,所有湖泊一開始都是空的。當第 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’
思路
貪心。從前往後遍歷每一天,在不下雨的時候,選擇目前有水且之後最早下雨的湖泊把其中的水抽乾。難點在於如何動態維護當前有水和之後最早下雨兩個集合:當前有水使用HashSet,之後最早下雨使用最小堆。此題是一個比較綜合的數據結構題,貪心的思想需要憑藉良好的數據結構設計才能在較低的時間複雜度內實現。該實現的時間複雜度爲O(nlogn),其中n = rains.length
。
代碼
class Solution {
class Lake implements Comparable<Lake> {
public int num, occur;
public Lake(int num, int occur) {
this.num = num;
this.occur = occur;
}
@Override
public int compareTo(Lake other) {
return this.occur - other.occur;
}
}
public int[] avoidFlood(int[] rains) {
int n = rains.length, i = 0, j = 0;
HashSet<Integer> fulls = new HashSet<>();
PriorityQueue<Lake> pq = new PriorityQueue<>();
HashMap<Integer, ArrayList<Integer>> occurMap = new HashMap<>();
HashMap<Integer, Integer> indexMap = new HashMap<>();
for (i=0; i<n; ++i) {
if (!occurMap.containsKey(rains[i])) {
occurMap.put(rains[i], new ArrayList<Integer>());
}
occurMap.get(rains[i]).add(i);
indexMap.put(rains[i], 0);
}
boolean success = true;
int[] ans = new int[n];
for (i=0; i<n; ++i) {
if (rains[i] > 0) {
if (fulls.contains(rains[i])) {
success = false;
break;
}
ans[i] = -1;
fulls.add(rains[i]);
indexMap.put(rains[i], indexMap.get(rains[i]) + 1);
if (indexMap.get(rains[i]) < occurMap.get(rains[i]).size()) {
pq.add(new Lake(rains[i], occurMap.get(rains[i]).get(indexMap.get(rains[i]))));
}
} else {
if (!pq.isEmpty()) {
Lake head = pq.poll();
ans[i] = head.num;
fulls.remove(head.num);
} else {
ans[i] = 1;
}
}
}
if (!success) {
ans = new int[0];
}
return ans;
}
}
1489. 找到最小生成樹裏的關鍵邊和僞關鍵邊
給你一個 n 個點的帶權無向連通圖,節點編號爲 0 到 n-1 ,同時還有一個數組 edges ,其中 edges[i] = [fromi, toi, weighti] 表示在 fromi 和 toi 節點之間有一條帶權無向邊。最小生成樹 (MST) 是給定圖中邊的一個子集,它連接了所有節點且沒有環,而且這些邊的權值和最小。
請你找到給定圖中最小生成樹的所有關鍵邊和僞關鍵邊。如果最小生成樹中刪去某條邊,會導致最小生成樹的權值和增加,那麼我們就說它是一條關鍵邊。僞關鍵邊則是可能會出現在某些最小生成樹中但不會出現在所有最小生成樹中的邊。
請注意,你可以分別以任意順序返回關鍵邊的下標和僞關鍵邊的下標。
示例 1:
輸入:n = 5, edges = [[0,1,1],[1,2,1],[2,3,2],[0,3,2],[0,4,3],[3,4,3],[1,4,6]]
輸出:[[0,1],[2,3,4,5]]
解釋:上圖描述了給定圖。
下圖是所有的最小生成樹。
注意到第 0 條邊和第 1 條邊出現在了所有最小生成樹中,所以它們是關鍵邊,我們將這兩個下標作爲輸出的第一個列表。
邊 2,3,4 和 5 是所有 MST 的剩餘邊,所以它們是僞關鍵邊。我們將它們作爲輸出的第二個列表。
示例 2 :
輸入:n = 4, edges = [[0,1,1],[1,2,1],[2,3,1],[0,3,1]]
輸出:[[],[0,1,2,3]]
解釋:可以觀察到 4 條邊都有相同的權值,任選它們中的 3 條可以形成一棵 MST 。所以 4 條邊都是僞關鍵邊。
提示:
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) 數對都是互不相同的。
思路
LeetCode上的第一道最小生成樹 (MST) 題. 本題需要多次求解最小生成樹,所以直接對邊關於weight做排序,沒有使用最小堆優化。由於題目與邊有關,因此在求解Prim和Kruskal算法中選擇Kruskal算法。Kruskal算法使用並查集優化,並查集使用路徑壓縮優化。
回到題目本身,『關鍵邊』等價於去掉該邊,則圖不連通或最小生成樹的邊權和增大,『僞關鍵邊』等價於去掉該邊,則最小生成樹的邊權和不變(說明該邊不是關鍵邊),且以該邊爲起點用Kruskal算法求解最小生成樹的邊權和不變(說明至少有一個最小生成樹包含該邊)
代碼
class Solution {
class Edge implements Comparable<Edge> {
public int from, to, weight, index;
public Edge(int from, int to, int weight, int index) {
this.from = from;
this.to = to;
this.weight = weight;
this.index = index;
}
@Override
public int compareTo(Edge other) {
return this.weight - other.weight;
}
}
class DisjointSets {
int[] dsets;
int[] ranks;
public DisjointSets(int n) {
dsets = new int[n];
ranks = new int[n];
for (int i=0; i<n; ++i) {
dsets[i] = i;
}
}
public int find(int x) {
if (dsets[x] != x) {
dsets[x] = find(dsets[x]);
}
return dsets[x];
}
public void union(int x, int y) {
x = find(x);
y = find(y);
if (x != y) {
if (ranks[x] > ranks[y]) {
dsets[y] = x;
} else {
dsets[x] = y;
if (ranks[x] == ranks[y]) {
++ranks[y];
}
}
}
}
}
/**
* if k == -1, use all edges
* if return -1, graph not connected
*/
private int KruskalWithoutEdgeK(ArrayList<Edge> edgeArr, int n, int k) {
int cnt = 0, i = 0, cost = 0, m = edgeArr.size();
DisjointSets djs = new DisjointSets(n);
while (cnt < n - 1) {
if (i == m) {
return -1;
}
Edge curEdge = edgeArr.get(i++);
if (k >= 0 && k == curEdge.index) {
continue;
}
int r1 = djs.find(curEdge.from);
int r2 = djs.find(curEdge.to);
if (r1 != r2) {
cost += curEdge.weight;
djs.union(r1, r2);
++cnt;
}
}
return cost;
}
private int KruskalStartsWithEdgeK(ArrayList<Edge> edgeArr, int n, Edge startEdge) {
DisjointSets djs = new DisjointSets(n);
int cnt = 1, i = 0, cost = startEdge.weight, m = edgeArr.size(), r1 = djs.find(startEdge.from), r2 = djs.find(startEdge.to);
djs.union(r1, r2);
while (cnt < n - 1) {
if (i == m) {
return -1;
}
Edge curEdge = edgeArr.get(i++);
if (startEdge.index == curEdge.index) {
continue;
}
r1 = djs.find(curEdge.from);
r2 = djs.find(curEdge.to);
if (r1 != r2) {
cost += curEdge.weight;
djs.union(r1, r2);
++cnt;
}
}
return cost;
}
public List<List<Integer>> findCriticalAndPseudoCriticalEdges(int n, int[][] edges) {
ArrayList<List<Integer>> ans = new ArrayList<>();
ans.add(new ArrayList<Integer>());
ans.add(new ArrayList<Integer>());
ArrayList<Edge> edgeArr = new ArrayList<>();
int i = 0;
for (int[] e: edges) {
edgeArr.add(new Edge(e[0], e[1], e[2], i++));
}
Collections.sort(edgeArr);
int mstCost = KruskalWithoutEdgeK(edgeArr, n, -1);
for (Edge edge: edgeArr) {
int curCost = KruskalWithoutEdgeK(edgeArr, n, edge.index);
if (curCost == -1 || curCost > mstCost) {
ans.get(0).add(edge.index);
} else if (curCost == mstCost && KruskalStartsWithEdgeK(edgeArr, n, edge) == mstCost) {
ans.get(1).add(edge.index);
}
}
return ans;
}
}