学习时间 2022-9-4
学习内容
1、LeetCode 一道困难题
这道题先使用滑动窗口,在51/51案例时超时了,有点可惜
附上自己的算法
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int L = 0;
int R = k-1;
int[] results = new int[nums.length-k+1];
int max = findMaxInRange(nums,L,R);
results[0] = max;
while(L < results.length-1){
int out = nums[L++];
int in = nums[++R];
if(out == max && in < max){
max = findMaxInRange(nums,L,R);
}
if(in > max){
max = in;
}
results[L] = max;
}
return results;
}
public int findMaxInRange(int[] nums,int L,int R){
//左闭右闭
int max = nums[L];
for(int i = L+1;i<=R;i++){
max = Math.max(max,nums[i]);
}
return max;
}
}
大致思路如下:当进一个和出一个时,更新max,如果出去的那个值是max并且进来的值比出去的max小,则重新寻找max,否则max可以保持不变 这个解法超时了,问题就在这个寻找的过程,有O(N)的复杂度,导致整体复杂度偏高,超时
其实我在优化的时候想到了用大根堆,但是没有做出来,题解中使用了大根堆,用堆去更新最大值,每次去取顶部即可,最大的问题是如何让元素移除?因为移除一个元素需要知道他的index,而堆如何去存储index也是一个问题,看了题解才知道,他把大根堆做成了一个数组,存放num和index。大根堆代码如下:
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
PriorityQueue<int[]> pq = new PriorityQueue<int[]>(new Comparator<int[]>() {
public int compare(int[] pair1, int[] pair2) {
return pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1];
}
});
for (int i = 0; i < k; ++i) {
pq.offer(new int[]{nums[i], i});
}
int[] ans = new int[n - k + 1];
ans[0] = pq.peek()[0];
for (int i = k; i < n; ++i) {
pq.offer(new int[]{nums[i], i});
while (pq.peek()[1] <= i - k) {
pq.poll();
}
ans[i - k + 1] = pq.peek()[0];
}
return ans;
}
}
对于这个题,我应该思考以下几点:
- PriorityQueue的compare为啥这样去填写?
- peek()[1] <= i-k 为何就可以确定移除元素?
- 对于求最大值,我们需要去维护最大值以外的值吗?在这个题中我被限制了,一直在想如何在堆中去remove出去的元素,实则不必
2、《操作系统概念》同步
为什么需要同步
现在假设有两个进程,都有一个整形变量counter,假设现在counter=5,生产者进程每次增加一项时,counter++,消费者进程每消费一项时,让counter–,现在让生产者和消费者并发执行,counter会出现4 5 6 三种答案,但其实只有5是对的。
这两个赋值可以看作(经典机器上的机器语言):
register1 = counter;
register1 = register1 + 1;
counter = register1;
register2 = counter;
register2 = register2 - 1;
counter = register2;
但并发执行,会让上述的语句按任意的顺序执行,所以可以出现:
register1 = counter;//生产者 register1 = 5
register1 = register1 + 1;//生产者 register1 = 6
register2 = counter;//消费者 register1 = 5
register2 = register2 - 1;//消费者 register1 = 4
counter = register2;//消费者 counter = 4
counter = register1;//生产者 counter = 6
最后输出的结果就是6,这就是问题所在
临界区问题
现在假设某个系统有n个进程。每个进程有一段代码,称为临界区(critical section),进程在该区域可以改变公共变量。这个区的特征是:当一个进程在临界区时,不允许其他进程也在临界区,也就是说,没有两个进程可以在他们的临界区内同时执行。
在进入临界区前,每个进程应请求许可。实现这一请求的代码区段称为进入区,临界区之后有退出区,其他代码为剩余区。
do{
//进入区
//临界区
//退出区
//剩余区
}while(true)
临界区问题应该满足三个要求:
- 互斥 两个进程不能同时在临界区执行
- 进步 当有进程需要进入临界区时,不能是在剩余区执行的进程
- 有限等待 在进入区的进程的请求不能是无限的
Peterson经典解决方案
这是基于软件的临界区问题解决方案
这些方案都必须满足临界区的三个要求
假设有Pj和Pi两个进程
do{
//进入区
flag[i] = true;
turn = j;
while(flag[j] && turn == j);//如果进不去则等待
//临界区
//xxxx
//退出区
flag[i] = false;
//剩余区
}while(true);
1、互斥:turn只能有一个值 满足互斥
2、进步:Pj进程修改turn后,Pi依然可以重新进入临界区,满足进步
3、有限等待 最多一次即可进入
硬件同步
这种方式主要通过加锁实现
对单处理器内核
在修改共享变量时禁止中断出现
对多处理器
使用指令让公共部分的flag改变,如何通过这个flag来锁、释放资源
一共有两种
test_and_set(&lock) 查看当前锁状态,并附加true 比如现在为false了,输出false,并让lock重新等于true
compare_and_swap(&lock,except_value,new_value) 拿到当前锁状态,如果和期望值一样,则改成new_value,比如拿到lock=0,当前能使用锁,与期望值0相同,则把锁改成1,并往后执行临界区
这种算法可以满足互斥,但是不能满足有限等待,因为可能会让其他进程无限的等待,解决办法是使用一个waiting数组,这个数组可以记录下一个执行的进程是谁,这样就可以保证每一个进程都能执行到
boolean lock;
boolean waiting[n];