lc825_适龄的朋友 如何瓦解价值数十亿美金的美国监狱行业?【TED演讲】 samba的配置文件/etc/samba/smb.conf CentOS 7.9 编译的程序在 RedHat 7.9 上运行失败分析
Algorithm
lc825_适龄的朋友
硬刚partition_point解题
思路:
看数据范围可以知道,这题肯定要O(nlogn)
,所以可以排序后用二分查找满足数据范围的下标数。
其中需要注意不能向自己发好友请求。然后相同的年龄可以一起计算,不用反复计算。
遍历数组x, 满足这三个条件算出y的下标范围,然后计算下标得出好友邀请数。
y > 0.5x + 7, y <= x, y <= 100 || x >= 100
条件二包含了条件三,所以条件三可以省略。
感觉 cursor claude 最近变菜了…一开始 j=i+1 写成了 j=i,导致结果一直不对。
原来是升级cursor之后,默认关掉了claude-3.5-sonnet 20241022。
rust没有lower_bound和upper_bound,所以只能用partition_point来找到下标。但是不熟悉,导致有些地方有问题。导致排查了较久。
最终修改后,还是过不了用例二:
输入 ages = [16,17,18]
输出 3
预期结果 2
反思今天这个中等题做了太久的原因如下:
- 好久没练习,变菜了
- rust这个partition_point的用法不熟悉,边界问题排查了较久。
- 还有奇偶数处理有点麻烦的。
- 这个题目真不适合用rust的partition_point, 导致边界联动处理下标 -1 的部分很麻烦。
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
|
struct Solution {
}
impl Solution {
pub fn num_friend_requests(ages: Vec<i32>) -> i32 {
let mut ages = ages;
ages.sort_unstable();
let n = ages.len();
let mut ans = 0;
let mut i = 0;
while i < n {
// 1. 注意一开始j=i+1
let mut j = i + 1;
while j < n && ages[j] == ages[i] {
j += 1;
}
let age = ages[i];
// 2. 判断奇偶数去处理 y > 0.5x + 7 的情况,偶数要精确大于,所以加0.1再ceil来大于等于,
// 奇数ceil之后可以直接大于等于
// 这里其实不需要判断奇偶数,因为题目要求是严格大于0.5x+7,所以直接ceil就可以了
// 比如 7.5 取到 8, 然后用 partition_point 找到小于8的下标, 然后后面都是大于7.5的
let min_age = age as f64 * 0.5 + 7.0;
// 计算可以发送和接受好友请求的年龄范围
// 3. 使用partition_point来找到下标
// 这里 upper_bound 获取大于 min_age 的前一个下标值
let lower = ages.partition_point(|&x| (x as f64) <= min_age);
// upper_bound - 1 得到等于的最后一个下标值
let upper = ages.partition_point(|&x| x < age + 1) - 1;
// println!("当前年龄: {}, min_age: {}", age, min_age);
// println!("lower: {}, upper: {}", lower, upper);
// println!("ages[lower..upper]: {:?}", &ages[lower..upper]);
// 上面的写法过不了rust编译器,很难受...
// if upper + 1 > lower {
if upper + 1 > lower {
let count = j - i;
let mut valid = upper - lower + 1;
// 如果当前年龄在范围内,需要减去自己
// 导致错误用例二: if i >= lower && i < upper {
if age >= ages[lower] && age <= ages[upper] {
valid -= 1;
}
// println!("count: {}, valid: {}, 增加请求数: {}", count, valid, count * valid);
ans += count * valid;
}
i = j;
}
ans as i32
}
}
fn main() {
let ages = vec![16, 17, 18];
let result = Solution::num_friend_requests(ages);
println!("\n最终结果:");
println!("输出 {}", result);
println!("预期结果 2");
}
|
花了比较久,终于用rust partition_point 写出了正确答案 AC 了题目…
partition_point 真有点反直觉,配合rust的严格编译器,真的有点麻烦,感觉cpp还是舒服的。
partition_point详细学习
让我详细讲解一下 partition_point
的实现和用法。
partition_point 的定义
partition_point
是 Rust 标准库中的一个方法,其签名如下:
1
2
3
|
fn partition_point<P>(&self, pred: P) -> usize
where
P: FnMut(&T) -> bool
|
它返回第一个使得谓词(predicate)返回 false
的元素的索引。要求输入数组对于谓词必须是分段的(partitioned),即所有返回 true
的元素都在返回 false
的元素之前。
实现原理
partition_point
实际上是二分查找的一种变体。下面是其基本实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
fn partition_point<T, P>(slice: &[T], mut pred: P) -> usize
where
P: FnMut(&T) -> bool
{
let mut left = 0;
let mut right = slice.len();
while left < right {
let mid = left + (right - left) / 2;
if pred(&slice[mid]) {
left = mid + 1;
} else {
right = mid;
}
}
left
}
|
使用示例
让我们通过几个例子来理解:
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
|
fn main() {
// 示例1:找到第一个大于3的位置
let nums = vec![1, 2, 3, 4, 5, 6];
let pos = nums.partition_point(|&x| x <= 3);
println!("第一个大于3的位置: {}", pos); // 输出: 3
// 示例2:找到第一个大于等于3的位置
let pos = nums.partition_point(|&x| x < 3);
println!("第一个大于等于3的位置: {}", pos); // 输出: 2
// 示例3:在你的题目中的用法
let ages = vec![16, 17, 18];
let min_age = 15.5;
// 这里 upper_bound 获取大于 min_age 的前一个下标值
let lower = ages.partition_point(|&x| (x as f64) <= min_age);
println!("第一个大于{}的位置: {}", min_age, lower); // 输出: 0
let age = 17;
let upper = ages.partition_point(|&x| x < age + 1);
println!("第一个大于{}的位置: {}", age, upper); // 输出: 2
// upper_bound - 1 得到等于的最后一个下标值
let upper = ages.partition_point(|&x| x < age + 1) - 1;
println!("最后一个大于等于{}的位置: {}", age, upper); // 输出: 1
}
// 第一个大于3的位置: 3
// 第一个大于等于3的位置: 2
// 第一个大于15.5的位置: 0
// 第一个大于17的位置: 2
// 最后一个大于等于17的位置: 1
|
在题目中的应用
在你的题目中,我们用 partition_point
来找到满足年龄条件的范围:
-
lower = ages.partition_point(|&x| x < min_age)
- 找到第一个大于等于 min_age 的位置
- 所有小于 min_age 的元素都在左边
- 所有大于等于 min_age 的元素都在右边
-
upper = ages.partition_point(|&x| x < age + 1)
- 找到第一个大于 age 的位置
- 所有小于等于 age 的元素都在左边
- 所有大于 age 的元素都在右边
注意事项
- 输入数组必须是有序的,否则结果不可预测
- 谓词函数必须保证分段性质(前面全是true,后面全是false)
- 返回的是第一个使谓词为 false 的位置
- 如果所有元素都满足谓词,返回数组长度
- 如果没有元素满足谓词,返回0
与其他二分查找函数的对比
lower_bound
(C++)等价于 partition_point(|&x| x < target)
upper_bound
(C++)等价于 partition_point(|&x| x <= target)
这就是为什么在你的代码中:
1
2
|
let lower = ages.partition_point(|&x| x < min_age); // 找到 >= min_age 的第一个位置
let upper = ages.partition_point(|&x| x < age + 1); // 找到 > age 的第一个位置
|
这样就能得到一个左闭右开的区间 [lower, upper)
,包含了所有满足条件的年龄。
题解
这里题解用的双指针,比lower和upper的partition_point更安全,而且综合来看也只有O(n)
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class Solution {
public:
int numFriendRequests(vector<int>& ages) {
int n = ages.size();
sort(ages.begin(), ages.end());
int left = 0, right = 0, ans = 0;
for (int age: ages) {
if (age < 15) {
continue;
}
while (ages[left] <= 0.5 * age + 7) {
++left;
}
while (right + 1 < n && ages[right + 1] <= age) {
++right;
}
ans += right - left;
}
return ans;
}
};
|
计数排序+前缀和获取区间值的思路更是直接不用排序,可以直接O(n)
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class Solution {
public:
int numFriendRequests(vector<int>& ages) {
vector<int> cnt(121);
for (int age: ages) {
++cnt[age];
}
vector<int> pre(121);
for (int i = 1; i <= 120; ++i) {
pre[i] = pre[i - 1] + cnt[i];
}
int ans = 0;
for (int i = 15; i <= 120; ++i) {
if (cnt[i]) {
int bound = i * 0.5 + 8;
ans += cnt[i] * (pre[i] - pre[bound - 1] - 1);
}
}
return ans;
}
};
|
Review
如何瓦解价值数十亿美金的美国监狱行业?【TED演讲】
恶意获取监狱行业的暴利,只会让更多的人被迫持久坐牢。
就像如果卖伞的人有了下雨的权利,那天永远不会晴。
因此投诉很多不合理的暴利,从而瓦解这种恶性循环。
Tips
samba的配置文件/etc/samba/smb.conf
Share
CentOS 7.9 编译的程序在 RedHat 7.9 上运行失败分析:
实际根本原因是 RedHat 的一些依赖动态库和 CentOS 不一样。
所以 RedHat 7.9 要运行 CentOS 7.9 相似的程序,需要在 RedHat 7.9 上重新编译一次。