链接:LeetCode
[Leetcode]1486. 数组异或操作
给你两个整数,n 和 start 。
数组 nums 定义为:(nums[i] = start + 2*i)(下标从 0 开始)且 n == nums.length 。
请返回 nums 中所有元素按位异或(XOR)后得到的结果。
暴力遍历自然可以。另外的话,我们可以利用异或的性质:
首先,题目的要求是:
我们可以对题目进行一次处理,也就是对所有异或元素右移一位,得到(start/2 oplus start/2 + 1 oplus start/2 + 2 ... oplus start/2 + n-1)
相当于我们将最终结果向右移动了一位,那么最后一位是什么呢?
我们会发现最后一位是由 start 和 数组长度 n 决定的。这是由于 2,4,... 这些转换成二进制后最后一位都是 0。
- 因此如果 start 是偶数,那么最后一位肯定是 0.
- 如果 start 是奇数,但是 n 是偶数,得到最后一位也是 0.
- 其他情况下,最后一位是 1.
所以:
- 如果(start/2)是偶数,我们只需要看 n 是否是偶数即可:
- 如果 n 是偶数,该公式结果就是 n/2个 1 进行异或。也就是 ((n/2) & 1)
- 如果 n 是奇数,该公式结果就是$ ((n/2) & 1) oplus (start+n-1)$
- 如果(start/2)是奇数,那么我们可以在前面补充((start/2-1) oplus (start/2-1)),就回到了情况 1.
class Solution:
def xorOperation(self, n: int, start: int) -> int:
ans = 2*self.xor(n,start//2)
if n&start&1:
ans += 1
return ans
def xor(self,n,start):
if start &1:
return (start-1) ^ self.helper(n+1,start-1)
return self.helper(n,start)
def helper(self,n,start):
if n%2==0:return (n//2)&1
else:
return (n//2)&1 ^ (start+n-1)
[Leetcode]1487. 保证文件名唯一
给你一个长度为 n 的字符串数组 names 。你将会在文件系统中创建 n 个文件夹:在第 i 分钟,新建名为 (names[i]) 的文件夹。
由于两个文件 不能 共享相同的文件名,因此如果新建文件夹使用的文件名已经被占用,系统会以 (k) 的形式为新文件夹的文件名添加后缀,其中 k 是能保证文件名唯一的 最小正整数 。
返回长度为 n 的字符串数组,其中 (ans[i]) 是创建第 i 个文件夹时系统分配给该文件夹的实际名称。
关键在于每一次分配的时候保存改文件名下最小正整数k。这里存储结构使用哈希表即可。
class Solution:
def getFolderNames(self, names: List[str]) -> List[str]:
d, res = {}, []
for name in names:
if name in d:
k = d[name]
while name + "(" + str(k) + ")" in d:
k += 1
d[name] = k + 1
name = name + "(" + str(k) + ")"
res.append(name)
d[name] = 1
return res
[Leetcode]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 天选择抽干的湖泊。
- 如果有多种可行解,请返回它们中的 任意一个 。如果没办法阻止洪水,请返回一个 空的数组 。
请注意,如果你选择抽干一个装满水的湖泊,它会变成一个空的湖泊。但如果你选择抽干一个空的湖泊,那么将无事发生。
贪心加二分。可以通过哈希表存储所有已经满的湖,并且记录该湖下雨的天数,假设最先重复下雨的为第 (i) 天和第 (j) 天。那么我们期望在 ((i, j)) 之间找到最小的可以抽干的时间。
class Solution:
def avoidFlood(self, rains: List[int]) -> List[int]:
used_num = collections.defaultdict(int)
used_ind = collections.defaultdict(int)
empty_ind = []
ans = [1 for i in range(len(rains))]
for i,rain in enumerate(rains):
if rain > 0:
ans[i] = -1
if used_num[rain] > 0:
if empty_ind and empty_ind[-1] > used_ind[rain]:
ind = self.getInd(empty_ind,used_ind[rain])
empty_ind.remove(ind)
ans[ind] = rain
else:
return []
else:
used_num[rain] += 1
used_ind[rain] = i
else:
empty_ind.append(i)
return ans
def getInd(self,nums,target):
lo,hi = 0,len(nums)-1
while lo<=hi:
mid = lo+(hi-lo)//2
if nums[mid] < target:
lo = mid+1
else:
hi = mid-1
return nums[lo]
[Leetcode]1489. 找到最小生成树里的关键边和伪关键边
给你一个 n 个点的带权无向连通图,节点编号为 0 到 n-1 ,同时还有一个数组 edges ,其中 (edges[i] = [fromi, toi, weighti]) 表示在 fromi 和 toi 节点之间有一条带权无向边。最小生成树 (MST) 是给定图中边的一个子集,它连接了所有节点且没有环,而且这些边的权值和最小。
请你找到给定图中最小生成树的所有关键边和伪关键边。如果最小生成树中删去某条边,会导致最小生成树的权值和增加,那么我们就说它是一条关键边。伪关键边则是可能会出现在某些最小生成树中但不会出现在所有最小生成树中的边。
请注意,你可以分别以任意顺序返回关键边的下标和伪关键边的下标。
首先,最小生成树有 2 种常规的算法,prim 和 kruskal。prim以点作为考察对象,每次向已有的生成树中增加新的最小距离的点;kruskal以边作为考察对象,每次选择最小权值的边将两棵树合并。本题需要我们分析关键边,显然应该选择 kruskal 算法。
首先使用最小生成树算法得到最小生成树的路径和min_cost。
如何判断一条边是不是关键边:将这条边从路径中去除,然后利用最小生成树算法求路径和,如果路径和大于min_cost或者不连通,
那么这条边就是关键边。
如何判断一条边为伪关键边:首先调用上面判断其是不是关键边,如果去除之后路径和不变,则说明其可以没有。那么怎么判断它可能会出现在
某些最小生成树呢?只需要一开始将就这条边加入到最小生成树中,然后使用算法求路径和。如果路径和等于min_cost,则其就是伪关键边,否则就不是。
class Solution {
public:
int p[110];
int find(int a) {
if (a != p[a]) p[a] = find(p[a]);
return p[a];
}
int work1(int n, vector<vector<int>>& edges, int k) { // 不选第k条边的最小生成树的权重
for (int i = 0; i < n; i ++ ) p[i] = i;
int cost = 0, cnt = 0;
for (auto& e:edges) {
if (e[3] == k) continue; // 如果是第k条边,则跳过
int f1 = find(e[1]), f2 = find(e[2]);
if (f1 != f2) {
cost += e[0];
cnt ++;
if (cnt == n - 1) break;
p[f1] = f2;
}
}
if (cnt == n - 1) return cost;
else return INT_MAX;
}
int work2(int n, vector<vector<int>>& edges, int k) { // 一定选第k条边的最小生成树的权重
for (int i = 0; i < n; i ++ ) p[i] = i;
int cost = 0, cnt = 0;
for (auto& e : edges) { // 先向第k条边加入到集合中
if (e[3] == k) {
cost += e[0];
cnt ++;
p[e[1]] = e[2];
break;
}
}
for (auto& e: edges) {
int f1 = find(e[1]), f2 = find(e[2]);
if (f1 != f2) {
cost += e[0];
cnt ++;
if (cnt == n - 1) break;
p[f1] = f2;
}
}
if (cnt == n - 1) return cost;
else return INT_MAX;
}
vector<vector<int>> findCriticalAndPseudoCriticalEdges(int n, vector<vector<int>>& edges) {
int m = edges.size();
for (int i = 0; i < m; i ++ ) {
auto& e = edges[i];
swap(e[0], e[2]);
e.push_back(i);
}
sort(edges.begin(), edges.end());
int min_cost = work1(n, edges, -1); // 求出最小生成树权重
// cout << min_cost << endl;
vector<vector<int>> ans(2);
for (int i = 0; i < m; i ++ ) {
if (work1(n, edges, i) > min_cost) ans[0].push_back(i); // 判断是否为关键边
else if (work2(n, edges, i) == min_cost) ans[1].push_back(i); // 判断是否为伪关键边
}
return ans;
}
};
参考:
Leetcode-xor-operation-in-an-array
Leetcode-find-critical-and-pseudo-critical-edges-in-minimum-spanning-tree