LeetCode 1482

242 阅读3分钟

LeetCode 1482

链接:leetcode.com/problems/mi…

方法1:二分答案

时间复杂度:O(nlogn) 想法:这是当时周赛的时候现场交的一个解法,对天数进行二分。当然这里如果你想到二分答案这个做法的话基本上这道题就做完了,因为反正二分答案写起来代码比较简单。 代码:

class Solution {
    public int minDays(int[] bloomDay, int m, int k) {
        int n = bloomDay.length;
        if (n < m * k) return -1;
        int left = 1, right = 1;
        for (int num : bloomDay) {
            right = Math.max(right, num);
        }
        
        while (left + 1 < right) {
            int mid =  left + (right - left) / 2;
            if (canMake(bloomDay, mid, m, k)) {
                right = mid;
            }
            else {
                left = mid;
            }
        }
        
        if (canMake(bloomDay, left, m, k)) {
            return left;
        }   
        return right;
    }
    
    private boolean canMake(int[] bloomDay, int level, int m, int k) {
        int n = bloomDay.length;
        
        int num = 0, nowHas = 0;
        for (int i = 0; i < n; i++) {
            if (nowHas == k - 1 && bloomDay[i] <= level) {
                num++;
                nowHas = 0;
                continue;
            }
            if (bloomDay[i] > level) {
                nowHas = 0;
                continue;
            }
            if (bloomDay[i] <= level && nowHas < k - 1) {
                nowHas++;
            }
        }
        
        return num >= m;
    }
}

但在这里我想顺带复习一下什么样的题目可以用二分答案来做,因为很多情况下大家二分答案的题没做出来主要是没想到要采用这个做法。

二分答案小总结:

  1. 一般涉及两个重要变量。在这个题里是天数和m,这个题的k从这个角度上来说应该算常量。
  2. 如果你要二分的那个量是自变量x,而另一个量是变量y的话,y应该对x具有单调性。在这个题里,给的天数越大,显然可能凑出的花束数就越多,因此满足这个性质。这也是为什么我们有时候说所谓“最大xx值的最小值”,“最小xxx值的最大值”很可能是二分答案的题目。
  3. 具有二段性。这个跟2)有一点点重合,但这一点是说,假设说题目要求满足条件y的最值,那么随着x单调变化的时候,y一定是有一段是不满足条件,然后在x变成某个值之后,y突然满足了条件,然后之后y一直满足条件。单调性和二段性也是所有二分能够进行的基础。

方法2:维护区间

时间复杂度:O(nlogn) 想法:这是yxc大佬的解法,当时周赛结束后他在b站直播讲的。基本想法是,维护区间。我们先用一个数组记录一堆点Point, Point携带的信息是输入的bloomDay这个数组里面每个元素的值和下标。然后对新的数组排序,按照元素的值(花开的天数)排序。然后当我去遍历这个数组的时候,它们的花开天数是递增的,相当于每遍历到一个地方,花就开一朵。我们需要维护下标组成的区间,每个区间代表了连续盛开的一些花。用两个数组维护区间,分别叫l和r,就是说这个点原本index所在的区间,它的左边界是什么,右边界是什么。因为要连续的k个花才能做一个花束,所以说当有m个长度至少为k的区间的时候,就可以了,当前遍历到的这个点对应的花开时间就是最少所需要的天数。我们想用l[i] == 0来判定这个对于下标i,它之前有没有被放进区间里,那么存进Point数组的时候就得存下标+1。当我把一个点的下标x放进区间的时候,要考虑合并的问题,主要就是看一看l[x - 1]和r[x + 1],然后分情况更新l和r就行了。因为我们之前存下标+1,后来每个下标考虑r[x + 1],因此对r会有r[n + 1]的访问,因此l和r全开成n + 2的长度。

代码:

class Point {
    public int index, value;
    
    public Point(int index, int value) {
        this.index = index;
        this.value = value;
    }
}

class PointComparator implements Comparator<Point> {
    public int compare(Point p1, Point p2) {
        if (p1.value != p2.value) {
            return p1.value - p2.value;
        }
        return p1.index - p2.index;
    }
}

class Solution {
    public int minDays(int[] bloomDay, int m, int k) {
        int n = bloomDay.length;
        if (m * k > n) return -1;
        
        Point[] points = new Point[n];
        
        for (int i = 0; i < n; i++) {
            points[i] = new Point(i + 1, bloomDay[i]);
        }
        Arrays.sort(points, new PointComparator());
        
        int[] l = new int[n + 2];
        int[] r = new int[n + 2];
        int res = 0;
        
        for (Point p : points) {
            int x = p.index;
            if (l[x - 1] != 0 && r[x + 1] != 0) {
                res = res - get(l[x - 1], x - 1, k) - get(x + 1, r[x + 1], k) + get(l[x - 1], r[x + 1], k);
                l[r[x + 1]] = l[x - 1];
                r[l[x - 1]] = r[x + 1];
            }
            else if (l[x - 1] != 0) {
                res = res - get(l[x - 1], x - 1, k) + get(l[x - 1], x, k);
                l[x] = l[x - 1];
                r[l[x - 1]] = x;
            }
            else if (r[x + 1] != 0) {
                res = res - get(x + 1, r[x + 1], k) + get(x, r[x + 1], k);
                r[x] = r[x + 1];
                l[r[x + 1]] = x;
            }
            else {
                res += get(x, x, k);
                l[x] = x;
                r[x] = x;
            }
            
            if (res >= m) {
                return p.value;
            }
        }
        
        return -1;
    }
    
    private int get(int l, int r, int k) {
        return (r - l + 1) / k;
    }
}

听说这个做法来自LeetCode 352,我现在还没做过,有空写一下。