设计类的题目,需要考虑如何合理的运用数据结构。系统的功能要求可以分为两个部分,一个是要求按照给出的优先级返回对应的内容,一个是两个无返回值的操作,借出和还回。
很明显这里是需要维护多个优先队列来实现给定优先级的对应内容的。比较难的点在于,如何处理借出的书籍和还回来的书籍。
方案一:lazy tag,懒标记的思想是借鉴了线段树的操作。并不真正的执行删除操作,而是给东西加上了一个标记,在遍历遇到的时候再删除。对于删除类的题目还是比较有效的,但是对于这个题目,因为我们除了需要删除,还有可能需要添加。因此一个物品存在多个状态,标记删除实际删除,标记删除实际未删除。这两个状态的东西,在我们还东西的时候,需要不同处理,前者只需要修改标记,后者就需要加入队列了。因此我们需要至少两个lazy tag,可能较为麻烦。
方案二:timeStamp,时间戳方法。我们使用一个map维护了物品的最新的timestamp,然后可以将物品的 [shopId, movieId, price]信息重复的放入队列。当队列弹出的时候,我们只需要比对弹出的物品的时间戳与map中保存的时间戳是不是一样就可以了。
class MovieRentingSystem {
PriorityQueue<int[]>[] movieList = new PriorityQueue[10010];
// 四元组,shopid,movieid,price,timestamp
PriorityQueue<int[]> rentList = new PriorityQueue<>(new Comparator<int[]>(){
@Override
public int compare(int[] a, int[] b){
if (a[2]!=b[2]) {
return a[2]-b[2];
}
if (a[0] != b[0]) {
return a[0]-b[0];
}
return a[1]-b[1];
}
});
Map<Pair<Integer, Integer>, Integer> timeStep = new HashMap<>();
Map<Pair<Integer, Integer>, Integer> price = new HashMap<>();
int step = 0;
public MovieRentingSystem(int n, int[][] entries) {
for(int[] e:entries){
Pair<Integer, Integer> pair = new Pair<>(e[0],e[1]);
timeStep.put(pair, 0);
price.put(pair, e[2]);
if(movieList[e[1]] == null){
movieList[e[1]] = new PriorityQueue<>((a, b) -> {
if (a[2]!=b[2]) {
return a[2]-b[2];
}
return a[0]-b[0];
});
}
movieList[e[1]].offer(new int[]{
e[0], e[1], e[2], 0});
}
}
public List<Integer> search(int movie) {
PriorityQueue<int[]> cur = movieList[movie];
List<Integer> ans = new ArrayList<>();
List<int[]> temp = new ArrayList<>();
if(cur == null) return ans;
for(int i = 0;i<5 && !cur.isEmpty();i++){
int[] curM = cur.poll();
if(curM[3] != timeStep.get(new Pair<>(curM[0],curM[1]))){
i--;
continue;
}
temp.add(curM);
ans.add(curM[0]);
}
for(int[] t:temp){
cur.offer(t);
}
return ans;
}
public void rent(int shop, int movie) {
step++;
timeStep.put(new Pair<>(shop, movie),step);
rentList.offer(new int[]{
shop, movie, price.get(new Pair<>(shop, movie)), step});
}
public void drop(int shop, int movie) {
step++;
timeStep.put(new Pair<>(shop, movie),step);
movieList[movie].offer(new int[]{
shop, movie, price.get(new Pair<>(shop, movie)), step});
}
public List<List<Integer>> report() {
PriorityQueue<int[]> cur = rentList;
List<List<Integer>> ans = new ArrayList<>();
List<int[]> temp = new ArrayList<>();
for(int i = 0;i<5 && !cur.isEmpty();i++){
int[] curM = cur.poll();
if(curM[3] != timeStep.get(new Pair<>(curM[0],curM[1]))){
i--;
continue;
}
List<Integer> ccur = new ArrayList<>();
ccur.add(curM[0]);
ccur.add(curM[1]);
ans.add(ccur);
temp.add(curM);
}
for(int[] t:temp){
cur.offer(t);
}
return ans;
}
}
这道题目猛地一看是一道区间搜索题目,很难下手想到好的解法。
我们可以慢慢破题,首先题目说了是要求差值的绝对值最小,那我们可以想一下什么情况下差值的绝对值最小。一定是相邻的两个元素,换句话说,如果我们可以让区间的元素有序,然后依次比较相邻的两个数字就可以得到答案。
然后是第二个问题,如何低复杂度的让区间的数字排序,这个其实无法做到的。但是我们发现了一个数据量上的暗示,1
那解决的方法就是,我们维护presum[i][1-100]
表示区间[0-i]
上每个数字的出现次数,这是一种前缀和的思想。这样我们经过 O(100*n)
的复杂度的预处理,就可以在后续O(1)
的得到任意数字在区间内的出现情况。继而可以计算相邻值的差值,得到差值绝对值的最小值。
class Solution {
public int[] minDifference(int[] nums, int[][] queries) {
int n = nums.length;
int[][] presum = new int[n+1][120];
for (int i = 1;i<=n;i++){
for(int j = 1;j<=100;j++){
presum[i][j] = presum[i-1][j];
}
presum[i][nums[i-1]]++;
}
int m = queries.length;
int[] ans = new int[m];
for(int i = 0;i<m;i++){
int l = queries[i][0];
int r = queries[i][1]+1;
int cur = Integer.MAX_VALUE;
int last = 0;
for (int j = 1;j<=100;j++){
int res = presum[r][j] - presum[l][j];
// 计算相邻值,当前数字区间内有且前一个数字也有。
if(res != 0 && last != 0){
cur = Math.min(cur, j-last);
}
if(res!=0) last = j;
}
ans[i] = cur == Integer.MAX_VALUE?-1:cur;
}
return ans;
}
}
其实说白了就是交替加减,前一个是加后一个只能不选择或者是减。前一个是减,后一个可以不要或者是加。因此有点类似打家劫舍的思路。dp[i][0], dp[i][1]
分别表示了两个状态,一个是当前可以加元素,一个是当前可以减元素。然后在逻辑里实现,不需要其他元素。
class Solution {
public long maxAlternatingSum(int[] nums) {
int n = nums.length;
long[][] dp = new long[n+1][2];
// 当前元素为加
dp[0][1] = 0;
// 当前元素减
dp[0][0] = Integer.MIN_VALUE/2;
for(int i = 1;i<=n;i++){
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1]+nums[i-1]);
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0]-nums[i-1]);
}
return Math.max(dp[n][0], dp[n][1]);
}
}