牛牛今天和大家玩了一个新游戏,除了牛牛以外还有 n 个人参加游戏,现在这 n 个人中的每个人从 [1, n] 中选择一个数字,保证选出的数字均不重复。牛牛作为第 n + 1 个人,充当卧底的角色,要求卧底从 1 到 n 中选择一个数字,现在将 n + 1 个数字重新打乱顺序,请找出卧底选择的数字是多少。
备注:
其中1 <= n <= 100000。
要求时间复杂度为 O(n),额外空间复杂度为 O(1)。
输入
4,[1,2,1,4,3]
输出
1
这种找重复数字的题目还挺常见的。最直接的方法是用哈希表存储数字,遇到新数字判断它是否在哈希表里,若在就说明重复。
不过题目要求空间复杂度为 O ( 1 ) O(1) O(1)。那么就不能用内置哈希表了。
注意到数字范围是 [1, n],并且数组长度为 n + 1,因此我们可以把原数组当成哈希表,通过交换,将数字放到对应下标的位置即可。
例如:遇到了数字10,那么我们就去看看下标为 10 的数字是不是 10,是的话说明数字重复了,否则交换这两个数字。
public int search (int n, int[] a) {
while(a[0] != a[a[0]]){
int temp = a[0];
a[0] = a[a[0]];
a[temp] = temp;
}
return a[0];
}
记卧底选的数字是 b,那么 ∑ i ∈ [ 0 , n ] a [ i ] = ∑ x ∈ [ 1 , n ] x + b \sum_{i \in [0,n]} a[i] = \sum_{x \in [1,n]} x + b ∑i∈[0,n]a[i]=∑x∈[1,n]x+b。显然求出两个求和的结果,相减即可得到答案。
public int search (int n, int[] a) {
int sum = 0;
for(int i = 0; i <= n; i++) sum += a[i];
for(int i = 1; i <= n; i++) sum -= i;
return sum;
}
剑指 offer 里有一道很经典的题:给一个数组,只有一个数字仅出现一次,其他数字都出现了两次,问如何找到这个数字。
那题是利用了异或运算得到答案的。并且我们知道异或的结果与奇偶次有关,并不局限于一次两次的限定。
这题是只有一个数字出现了两次,其他数字都仅出现一次。如何与那题关联起来呢?
很简单,就让 1 ~ n 再出现一次,这样只有一个数字出现了三次,其他数字都出现了两次。这样就可以用异或来解决啦。
public int search (int n, int[] a) {
int x = 0;
for(int i = 1; i <= n; i++) x ^= i;
for(int i = 0; i <= n; i++) x ^= a[i];
return x;
}
在一颗有 n 个结点且以 1 为根节点树上,起初每个结点的初始权值为 0 。
现在有 q 次操作,每次操作选择将以 r i r_i ri 为根节点的子树上的所有结点权值增加 x i x_i xi 。
求 q 次操作后从 1 到 n 每个结点的权值。
输入
第一个参数为 n,1 ≤ n ≤ 100,000
第二个参数为边 ( u i , v i ) (u_i, v_i) (ui,vi) 的集合,其中 ( u i , v i ) (u_i, v_i) (ui,vi) 表示结点 u i u_i ui 与结点 v i v_i vi 之间有一条边, 1 ≤ u i , v i ≤ n 1 ≤ u_i, v_i ≤ n 1≤ui,vi≤n
第三个参数为 q,1 ≤ q ≤ 100,000
第四个参数为 q 次询问的 ( r i , v i ) (r_i,v_i) (ri,vi) 的集合, 1 ≤ r i ≤ n , 0 ≤ ∣ v i ∣ ≤ 1000 , 000 1≤r_i≤n,0≤∣v_i∣≤1000,000 1≤ri≤n,0≤∣vi∣≤1000,000
返回
从 1 到 n 每个结点的权值。
输入
5,[(2,5),(5,3),(5,4),(5,1)],2,[(1, 3), (2, -1)]
输出
[3,2,3,3,3]
说明
第一次操作,将以 1 为根节点的子树上的所有结点权值增加 3,此时结点的权值分别为 [3, 3, 3, 3, 3] ;
第二次操作,将以 2 为根节点的子树上的所有结点权值增加 -1,此时结点的权值分别为 [3, 2, 3, 3, 3] ;
本题难点在于:
对于第一点,由于是树结构,遍历的时候肯定是从根节点往子节点遍历,因此可以用布尔数组 visited
记录访问过的节点,用列表数组 next
记录节点 i
的相邻节点。若 next[i]
中的某节点 node
已访问,说明 node
是 i
的父节点。
对于第二点,用数组 ans
保存每个节点的权值:
用 Java 的小伙伴们注意,树的遍历通常用 BFS 或 DFS。但是这里的节点是十万个,如果最坏情况下树退化成了单链表,使用 DFS 意味着需要十万个栈空间,显然会栈溢出。因此我们使用 BFS 来进行遍历。
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)
public long[] solve (int n, Point[] Edge, int q, Point[] Query) {
long[] ans = new long[n];
List<Integer>[] next = new List[n];
boolean[] visited = new boolean[n];
for(int i = 0; i < n; i++) next[i] = new LinkedList<Integer>();
for(Point p: Edge){
next[p.x - 1].add(p.y - 1);
next[p.y - 1].add(p.x - 1);
}
for(Point p: Query) ans[p.x - 1] += p.y;
Queue<Integer> queue = new LinkedList();
queue.offer(0);
while(!queue.isEmpty()){
int root = queue.poll();
visited[root] = true;
for(Integer node: next[root]){
if(visited[node]) continue;
ans[node] += ans[root];
queue.offer(node);
}
}
return ans;
}
牛牛有一个长为 n 的排列 p ,与 m 对 ( x i , y i ) (x_i,y_i) (xi,yi).
每对 ( x i , y i ) (x_i,y_i) (xi,yi) 表示可以将 p x i p_{x_i} pxi 的值与 p y i p_{y_i} pyi 的值互换。
m 对 ( x i , y i ) (x_i,y_i) (xi,yi) 的使用顺序与次数不限。
牛牛想知道,任意次操作之后他能得到的字典序最小的排列是什么
字典序定义:对于数字 1、2、3…n 的排列,不同排列的先后关系是从左到右逐个比较对应的数字的先后来决定的。例如对于5个数字的排列 12354 和 12345,排列 12345 在前,排列 12354 在后。按照这样的规定,5 个数字的所有的排列中最前面的是 12345,最后面的是 54321。(来自百度百科)
输入
第一个参数为 n,1 ≤ n ≤ 100,000
第二个参数为 m,1 ≤ m ≤ 100,000
第三个参数为初始排列 p, 1 ≤ p i ≤ n 1 ≤p_i≤n 1≤pi≤n
第四个参数为 m 对 ( x i , y i ) (x_i,y_i) (xi,yi), 1 ≤ x i , y i ≤ n , x i ≠ y i 1≤x_i,y_i≤n,x_i≠y_i 1≤xi,yi≤n,xi=yi
返回
字典序最小的排列
输入
5,3,[5,2,3,4,1],[(2,4),(1,4),(3,4)]
输出
[2,3,4,5,1]
说明
1. 交换 (3, 4), 交换后的序列为: 5, 2, 4, 3, 1
2. 交换 (2, 4), 交换后的序列为: 5, 3, 4, 2, 1
3. 交换 (1, 4), 交换后的序列为: 2, 3, 4, 5, 1
把下标看作图中的节点, ( x i , y i ) (x_i,y_i) (xi,yi) 表示节点 p x i p_{x_i} pxi 和 p y i p_{y_i} pyi 直接相连。由于 m 对 ( x i , y i ) (x_i,y_i) (xi,yi) 的使用顺序与次数不限,因此同一连通分量中的节点可以任意排列。
例如:对原排列 a, b, c
, 给定 (a, b), (b, c) ,我可以排列成 b, a, c
(仅用到 (a, b)),也可以排列成 a, c, b
(仅用到 (b, c))。
显然使用并查集可以方便的得到所有的连通分量。之后对每一个连通分量内部进行排序,小的值放在小的下标上。
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn).
空间复杂度: O ( n ) O(n) O(n).
class Solution {
public int[] solve (int n, int m, int[] perm, Point[] Pair) {
int[] ans = new int[n];
Union un = new Union(n);
for(Point p: Pair) un.union(p.x - 1, p.y - 1);
Map<Integer, Queue<Integer>> valueMap = new HashMap();
for(int i = 0; i < n; i++){
int fa = un.find(i);
if(!valueMap.containsKey(fa)) valueMap.put(fa, new PriorityQueue());
valueMap.get(fa).add(perm[i]);
}
for(int i = 0; i < n; i++){
Queue<Integer> valueSet = valueMap.get(un.find(i));
ans[i] = valueSet.poll();
}
return ans;
}
}
class Union{
private int[] parent;
public Union(int size){
parent = new int[size];
for(int i = 0; i < size; i++) parent[i] = i;
}
public int find(int x){
if(x != parent[x]) parent[x] = find(parent[x]);
return parent[x];
}
public void union(int x, int y){
parent[find(x)] = find(y);
}
}
大家好,我是往西汪,一位坚持原创的新人博主。
如果本文对你有帮助,请动动小手指点个赞。你的支持是我创作路上的最大动力。谢谢大家!
也欢迎来公众号【往西汪】找我玩耍~