LeetCode-27
[Medium] 29. 两数相除
分析
- 只能用加减法,最朴素的方法是循环相减/加,直到小于0/大于0,计算加/减的次数
- 这样算法是o(n),考虑到
i+=i
或者i<<=1
相当于i*=2
,i>>=1
相当于i/=2
- 只考虑divisor, divident都大于0的情况,先找到整数p,使得 $divisor2^p <= divident$,$divident-=divisor2^p, ratio+=2^p$若divident为0,则商为ratio,否则重复上面的过程,直到divident为0。
- 考虑divisor, divident到可能正,可能负,而负数的范围大于正数,直接将所有整数变成负数,并记录符号
- 注意取相反数的时候要用位运算
~x+1
代码
1 | class Solution { |
275. H 指数 II
代码
1 | class Solution { |
优化
1 | class Solution { |
274. H 指数
代码
1 | class Solution { |
2558. 从数量最多的堆取走礼物
1 | class Solution { |
1465. 切割后面积最大的蛋糕
1 | class Solution { |
2520. 统计能整除数字的位数
1 | class Solution { |
2698. 求一个整数的惩罚数
思路
- 首先要计算出所有满足sum(split(i*i)) == i的元素
- 发现很少,直接放到数组里去查表
- 对于一个数i,希望它拆分后和等于target
- 先分成a,b两份,判断是否等于,等于return true,不等于就把a分开(递归)
代码
1 | class Solution { |
1155. 掷骰子等于目标和的方法数
思路
- 先暴力递归,然后发现可以总结出dp公式,然后把递归改成dp尝试,成功了
代码
1 | class Solution { |
优化
观察代码可知,dp[i][t]就是dp[i-1][t-1]+…dp[i-1][t-k],可以用前缀和优化
1 | class Solution { |
1402. 做菜顺序
- 一眼dp,鉴定为,纯纯的简单题
- 一遍就做对啦哈哈哈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class Solution {
public:
int maxSatisfaction(vector<int>& satisfaction) {
int n = satisfaction.size();
vector<int> dp(n+1, 0);
vector<int> sums(n+1, 0);
sums[0] = accumulate(satisfaction.begin(), satisfaction.end(), 0);
sort(satisfaction.begin(), satisfaction.end(), less<int>());
for(int i = 0; i < n; i++) {
dp[0] += (i+1) * satisfaction[i];
}
for(int i = 1; i <= n; i++) {
if(dp[i-1] < dp[i-1] - sums[i-1]) {
dp[i] = dp[i-1] - sums[i-1];
sums[i] = sums[i-1] - satisfaction[i-1];
} else {
dp[i] = dp[i-1];
sums[i] = sums[i-1];
}
}
return dp[n];
}
};
优化
dp数组和sum数组可以优化掉
1 | class Solution { |
2678. 老人的数目
1 | class Solution { |
2316. 统计无向图中无法互相到达点对数
1 | class Solution { |
并查集
- 这里一直写不出来,发现并查集无法把所有相连的点归到一个集合中,因为在遍历的时候没有用union操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24class Solution {
public:
vector<int> s;
int find(int x) {
return x == s[x] ? x : (s[x] = find(s[x]));
}
long long countPairs(int n, vector<vector<int>>& edges) {
s = move(vector<int>(n, 0));
vector<int> cnt = move(vector<int>(n, 0));
iota(s.begin(), s.end(), 0);
size_t elen = edges.size();
for(int j = 0; j < elen; j++) {
s[find(edges[j][1])] = find(edges[j][0]);
}
for(int i = 0; i < n; i++) {
cnt[find(i)]++;
}
long long res = 0;
for(int i = 0; i < n; i++) {
res += (n - (long long)cnt[i])*cnt[i];
}
return res >> 1;
}
};
继续优化
- 换个公式减少一次循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class Solution {
public:
vector<int> s;
int find(int x) {
return x == s[x] ? x : (s[x] = find(s[x]));
}
long long countPairs(int n, vector<vector<int>>& edges) {
s = move(vector<int>(n, 0));
vector<int> cnt = move(vector<int>(n, 0));
iota(s.begin(), s.end(), 0);
size_t elen = edges.size();
for(int j = 0; j < elen; j++) {
s[find(edges[j][1])] = find(edges[j][0]);
}
long long res = 0;
for(int i = 0; i < n; i++) {
res += cnt[find(i)]++;
}
return (long long)n*(n-1)/2 - res;
}
};
2525. 根据规则将箱子分类
1 | class Solution { |
117. 填充每个节点的下一个右侧节点指针 II
思路
- 首先想到的就是层次遍历,非常简单
- 对于进阶使用
O(1)
空间的算法,一直没想到利用已有的next指针- 树每层最左侧节点找parent,把同层都连起来就好
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51class Solution {
public:
Node* levelConnect(Node* root) {
queue<Node *> q;
if(root) q.push(root);
while(!q.empty()) {
Node *node = nullptr;
int n = q.size();
while(n--) {
if(node) node->next = q.front();
node = q.front();
if(node->left) q.push(node->left);
if(node->right) q.push(node->right);
q.pop();
}
}
return root;
}
Node* connect(Node* root) {
// return levelConnect(root);
if(root) recursiveConnect(root, new Node(0, root, nullptr, nullptr), true);
return root;
}
void recursiveConnect(Node* root, Node *parent, bool isLeft) {
Node *move = root;
while(move && !move->next) {
Node *to = nullptr;
if(isLeft && parent->right) {
to = parent->right;
isLeft = false;
} else {
Node *pMove = parent->next;
while(pMove && !pMove->left && !pMove->right) pMove = pMove->next;
if(pMove) {
if(pMove->left) {
isLeft = true;
to = pMove->left;
} else if(pMove->right) {
isLeft = false;
to = pMove->right;
}
parent = pMove;
}
}
move->next = to;
move = to;
}
if(root->right) recursiveConnect(root->right, root, false);
if(root->left) recursiveConnect(root->left, root, true);
}
};
- 树每层最左侧节点找parent,把同层都连起来就好
整理下代码
1 | class Solution { |
2127. 参加会议的最多员工数
并查集
- 一个重要结论,图中一定有环(n个节点,大于n-1条边)
- 如果环的大小大于2,则只能将环上的节点上桌,支链上的点无法上桌
- 如果环的大小不大于2,则环上节点的最长支链也可以跟着上桌
- 多个环长为2的子图(不论是否有支链),都可以全部上桌
故,统计所有环长大于2的最大环长,为maxRing
统计所有环长不大于2的环长之和,以及环上各节点的最大之链长度之和,为maxNumber
返回max(maxRing, maxNumber)
步骤
并查集将节点分类
degree统计每个节点的入度
dSetSize统计每个集合的大小
按照是否存在入度为0的点,将子图划分两类
- 存在,则图含有支链,计算环的长度(findMaxRing)
- 大于2,统计最大环长(maxRing)
- 不大于2,从任意度为0的节点开始遍历,将环上节点的visited变成非0,并标记该子图为
is2
,累加maxNumber(环长为2,都可以上桌)
- 不存在,则图不含有支链,计算环的长度(findMaxRing)
- 大于2,统计最大环长(maxRing)
- 不大于2,累加如maxNumber(环长为2,都可以上桌)
- 存在,则图含有支链,计算环的长度(findMaxRing)
对所有入度为0,且所在子图是
is2
的节点,计算该点到环上点的距离为支链长度,记录最大支链长度所有最大支链长度累加maxNumber
返回max(maxNumber, maxRing)
时间 1848 ms 击败 5.2%
内存 98.4 MB 击败 35.8%1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97class Solution {
public:
int maxRing = 0;
vector<int> visited;
vector<int> addtional_len;
vector<int> dSet;
vector<bool> dSetVisited;
vector<int> dSetSize;
vector<bool> isRing;
vector<bool> is2;
vector<int> degree;
int find(int i) {
return i == dSet[i] ? i : (dSet[i] = find(dSet[i]));
}
int maximumInvitations(vector<int>& favorite) {
int n = favorite.size();
visited = move(vector<int>(n, 0));
dSetVisited = move(vector<bool>(n, false));
isRing = move(vector<bool>(n, true));
is2 = move(vector<bool>(n, false));
addtional_len = move(vector<int>(n, 0));
degree = move(vector<int>(n, 0));
dSet = move(vector<int>(n, 0));
iota(dSet.begin(), dSet.end(), 0);
dSetSize = move(vector<int>(n, 0));
for(int i = 0; i < n; i++) {
degree[favorite[i]]++;
dSet[find(i)] = find(favorite[i]);
}
for(int i = 0; i < n; i++) {
dSetSize[find(i)]++;
if(degree[i] == 0) isRing[dSet[i]] = false;
}
int maxNumber = 0;
for(int i = 0; i < n; i++) {
int len = 1;
int ringNode = -1;
if(isRing[dSet[i]]) {
if(!dSetVisited[dSet[i]]) {
dSetVisited[dSet[i]] = true;
int ring = dSetSize[dSet[i]]++;
if(ring > 2) {
maxRing = max(maxRing, ring);
} else {
maxNumber += ring;
}
}
} else {
if(!dSetVisited[dSet[i]] && degree[i] == 0) {
dSetVisited[dSet[i]] = true;
int ring = findMaxRing(favorite, i, len, ringNode);
if(ring > 2) {
maxRing = max(maxRing, ring);
} else {
// addtional_len[ringNode] = max(len - ring, addtional_len[ringNode]);
int move = i;
while(move != ringNode) {
visited[move] = false;
move = favorite[move];
}
is2[dSet[i]] = true;
maxNumber += ring;
}
}
}
}
for(int i = 0; i < n; i++) {
if(is2[dSet[i]] && degree[i] == 0) {
int move = i;
int len = 0;
while(!visited[move]) {
len++;
move = favorite[move];
}
addtional_len[move] = max(addtional_len[move], len);
}
}
for(int i = 0; i < n; i++) {
// cout << i << ", " << addtional_len[i] << endl;
if(addtional_len[i] > 0) {
maxNumber += addtional_len[i];
}
}
return max(maxNumber, maxRing);
}
int findMaxRing(const vector<int>& favorite, int node, int &len, int &ringNode) {
visited[node] = len;
int child = favorite[node];
if(visited[child]) {
ringNode = child;
return len - visited[child] + 1;
}
len++;
return findMaxRing(favorite, child, len, ringNode);
}
};优化
上面的代码在遇到有公共部分的支链,(会分叉的支链时),会导致重叠部分反复被访问
使用arcFav统计被喜欢图,根据被喜欢图从环上开始寻找最大支链,可以避免,速度也得到了极大的优化
时间 284ms 击败 12.78% 使用 C++ 的用户
内存 166.84MB 击败 25.57% 使用 C++ 的用户
1 | class Solution { |
并查集+拓扑排序
1 | class Solution { |
更慢了,原因是不能在拓扑排序的同时计算最大支链长度
优化掉并查集
- 在拓扑排序中寻找最长支链,为每个节点定义f(x)
- $ f(x) = max_{i=0}^j(f(aFav(x)_i) + 1) $
- 每个节点初始为0,拓扑排序时,每次i将j的degree被减小,其值为max(f(i)+1, f(j))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50class Solution {
public:
int maxRing = 0;
vector<bool> visited;
vector<int> degree;
vector<int> f;
int maximumInvitations(vector<int>& favorite) {
int n = favorite.size();
visited = move(vector<bool>(n, false));
degree = move(vector<int>(n, 0));
f = move(vector<int>(n, 0));
vector<int> ringNodes;
unordered_map<int, int> ringSize;
for(int i = 0; i < n; i++) {
degree[favorite[i]]++;
}
queue<int> topologyQueue;
for(int i = 0; i < n; i++) {
if(degree[i] == 0) {
topologyQueue.push(i);
}
}
while(!topologyQueue.empty()) {
int front = topologyQueue.front();
topologyQueue.pop();
f[favorite[front]] = max(f[favorite[front]], f[front] + 1);
visited[front] = true;
if((--degree[favorite[front]]) == 0) {
topologyQueue.push(favorite[front]);
}
}
int maxNumber = 0;
for(int i = 0; i < n; i++) {
if(!visited[i])
if(favorite[favorite[i]] == i) {
maxNumber += f[i] + 1;
} else {
int ring = 1;
int move = favorite[i];
while(!visited[move] && move != i) {
visited[move] = true;
move = favorite[move];
ring++;
}
maxRing = max(maxRing, ring);
}
}
return max(maxNumber, maxRing);
}
};