Binary Search

我一般喜欢左闭右开,像这样,[lp,rp)

因此rp初始化要记得+1,因为取不到的。

板子:

func(args){
	int lp,rp,mid,ans;
	lp = initL;
	rp = initR+1;
	ans = initAns;
	while(lp<rp){
		mid = (rp-lp)/2 + lp; // rp-lp防溢出,可以移位卡常
		if(check(mid,...)){
			lp = mid+1;
		}
		else{
			ans = mid;
			rp = mid;
		}
	}
	return ans;
}

check(mid,...){
	// 检查逻辑
}

1. LC 275 H指数Ⅱ

这题没按板子写。但这种小细节无所谓的。二分h,检查引用次数比h大的文章数量,直至收敛。

class Solution {
    public int hIndex(int[] citations) {
        int n = citations.length;
        int lp=0,rp=n;
        while(lp<=rp){
            int mid = (lp+rp)/2;
            int count=0;
            for(int i=n-1;i>=0;i--){
                if(citations[i]>=mid){
                    count++;
                    if(count>=mid){
                        break;
                    }
                }
            }
            if(count>=mid){
                lp=mid+1;
            }else{
                rp=mid-1;
            }
        }
        return rp;
    }
}

2. LC 1283 使结果不超过阈值的最小除数

到这里板子还没写熟练,没防爆int。

二分除数,检查除完加和是否小于等于阈值,直至收敛

class Solution {
    public int smallestDivisor(int[] nums, int threshold) {
        int max = nums[0];
        for (int num : nums) {
            max = Math.max(num,max);
        }
        int lp = 1;
        int rp = max+1;
        int ans = -1;
        while(lp<rp){
            int mid = (lp+rp)/2;
            int res = div(nums, mid);
            if(res <= threshold){
                ans = mid;
                rp = mid;
            }
            else{
                lp = mid+1;
            }
        }
        return ans;
    }

    private int div(int[] nums,int dop){
        int ans = 0;
        for (int num : nums) {
            ans += (int)Math.ceil((double)num/dop);
        }
        return ans;
    }
}

3. LC 2187 完成旅途的最少时间

为了求二分上限,先对数组排序(其实无必要,很浪费时间)。很明显上限是最慢的那一趟乘以趟数,取不到记得+1。

二分时间,检查是否能完成阈值指定的旅途数量,直至收敛。

import java.util.Arrays;

class Solution {
    public long minimumTime(int[] time, int totalTrips) {
        long ans=-1;
        long lp,rp,mid;
        Arrays.sort(time);
        lp = 0L;
        rp = (long)totalTrips*time[0]+1;
        while(lp<rp){
            mid = lp+(rp-lp)/2;
            if(coverTot(mid,time,totalTrips)){
                ans = mid;
                rp = mid;
            }else{
                lp = mid+1;
            }
        }
        return ans;
    }
    
    private boolean coverTot(long ticks,int[] time,int threshHold){
        for (int i : time) {
            threshHold-= (int) (ticks/i);
            if(threshHold<=0){
                break;
            }
        }
        return threshHold<=0;
    }
}

4. LC 2226 每个小孩最多能分到多少糖果

二分最多能拿走的糖果数目。分堆其实也就是看每一堆最多能分成几堆二分结果的糖果,依次检查,直至收敛。

class Solution {
    public int maximumCandies(int[] candies, long k) {
        long sum = sumCandy(candies);
        int rp = (int) (sum / k)+1;
        int lp = 1;
        int mid;
        int ans = 0;
        while(lp<rp){
            mid = lp+(rp-lp)/2;
            if(capable(mid,candies,k)){
                ans = mid;
                lp = mid+1;
            }else{
                rp = mid;
            }
        }
        return ans;
    }

    private long sumCandy(int[] candies){
        long ans = 0;
        for (int candy : candies) {
            ans+=candy;
        }
        return ans;
    }

    private boolean capable(int candy,int[] candies,long k){
        for (int i : candies) {
            k -= i/candy;
            if(k<=0){
                break;
            }
        }
        return k<=0;
    }
}

5. LC 1870 准时到达的列车最小时速

题面直接报出来上限了,不用求直接用就行。注意检查时最后抵达后不用向上取整。

class Solution {
    public int minSpeedOnTime(int[] dist, double hour) {
        int rp = (int) (1e7+1);
        int lp = 1;
        int mid;
        int ans = -1;
        while(lp<rp){
            mid = lp+(rp-lp)/2;
            if(onTime(mid,dist,hour)){
                ans = mid;
                rp = mid;
            }else{
                lp = mid+1;
            }
        }
        return ans;
    }

    private boolean onTime(int velocity,int[] dist,double hour){
        double cost = 0.00;
        for (int i = 0; i < dist.length; i++) {
            if(i==dist.length-1){
                cost+=(double)dist[i]/velocity;
            }
            else{
                cost+=Math.ceil((double)dist[i]/velocity);
            }
        }
        return cost<=hour;
    }

}

6. LC 1011 在D天内送达包裹的能力

二分货船载货能力,这题里面days≥1,所以上限可以取一天内送完,也就是weights的和。

import java.util.Arrays;

class Solution {
    public int shipWithinDays(int[] weights, int days) {
        int lp = Arrays.stream(weights).max().getAsInt();
        int rp = slideMaxCapability(weights)+1;
        int mid;
        int ans = lp;
        while(lp<rp){
            mid = lp + (rp-lp)/2;
            if(check(weights,mid,days)){
                ans = mid;
                rp = mid;
            }else{
                lp = mid+1;
            }
        }
        return ans;
    }

    private int slideMaxCapability(int[] weights){
        int ans = 0;
        for (int weight : weights) {
            ans+=weight;
        }
        return ans;
    }

    private boolean check(int[] weights,int capability,int threshold){
        int cost = 0;
        int rest = capability;
        for (int weight : weights) {
            if(rest>=weight){
                rest-=weight;
            }else{
                cost+=1;
                rest = capability-weight;
            }
        }
        return cost+1<=threshold;
    }

}

7. LC 875 爱吃香蕉的珂珂

二分速度,这题每堆香蕉最多1e9,最多1e4堆香蕉。也就是如果我用1e9的速度吃,每一堆最多1个h,所以总时间不超过1e4,也就≤h,因此上限取1e9。

import java.util.Arrays;

class Solution {
    public int minEatingSpeed(int[] piles, int h) {
        int rp = (int)1e9+1;
        int lp = 1;
        int mid;
        int ans = 1;
        while(lp<rp){
            mid = lp + (rp-lp)/2;
            if(check(mid,piles,h)){
                ans = mid;
                rp = mid;
            }else{
                lp = mid+1;
            }
        }
        return ans;
    }

    private boolean check(int velocity,int[] piles,int h){
        for (int pile : piles) {
            h -= (int) Math.ceil((double)pile/velocity);
            if(h<0){
                break;
            }
        }
        return h>=0;
    }

}

8. LC 300 最长递增子序列

这题两种做法,O(n²)的序列DP,和O(nlgn)的贪心二分。

维护数组d,令d[i]表示所有长度为i的严格递增子序列的末尾元素的最小值。核心思想就是让该序列上升的尽可能缓慢,这样后续元素才更有可能扩展该序列的长度。

例如,

[0,2,4] 是一个长度为3的严格递增子序列
[0,2,3] 同样
则d[3]=Math.min(4,3)=3

遍历nums,若当前num<d中的最大值,说明可以更新某个长度为j的末尾元素的最小值。

又因为d一定严格单增,可使用二分。

class Solution {
    public int lengthOfLIS(int[] nums) {
        // O(nlgn)
        int n = nums.length;
        if(n==0){
            return 0;
        }
        int[] d = new int[n+1];
        int len = 1;
        d[len] = nums[0];
        for(int i=1;i<n;i++){
            if(nums[i]>d[len]){
                d[++len] = nums[i];
            }else{
                // 二分更新
                int l=1;int r = len+1;int pos = 0;
                while(l<r){
                    int mid = l + (r-l)/2;
                    if(d[mid]<nums[i]){
                        l = mid+1;
                    }else{
                        pos = mid;
                        r = mid;
                    }
                }
                d[pos] = nums[i];
            }
        }
        return len;
    }
}

9. LC 1954 收集足够苹果的最小花园周长

考虑半边长(边长的一半),设为k。则共有2(k+1)(2k²+k)个苹果,对k进行二分即可。 k的上限可以写个程序判断,也即上式>=1e15的值。算出来是62996。

class Solution {
    public long minimumPerimeter(long neededApples) {
        long lp,rp,ans,mid;
        lp = 1;
        rp = 62997+1;
        ans = -1;
        while(lp<rp){
            mid = (rp-lp)/2 + lp;
            if(check(mid,neededApples)){
                ans = mid;
                rp = mid;
            }else{
                lp = mid+1;
            }
        }
        return 8*ans;
    }

    private boolean check(long mid,long need){
        return 2*(mid+1)*(2*mid*mid+mid)>=need;
    }

}

10. LC 1898 可移除字符的最大数目

这题灵神标的分数是1913。已经是K级别的题目了,但如果看出来二分板子的话其实没有任何思维量。这题的难点是能不能看出来单调性质,周赛场上基本会被降智20%,临场是否能看出来单调性是胜负手。

题面已知p一定是s的子序列,假设我们根据removable中前k个删除了s中的k个字符而导致p不再是s的子序列,那么删除>k个字符p也一定不是s的子序列。这个可以轻易反证。

同时,如果删除了前k个removable中标记出来的字符,p仍然是s的子序列,那么这样删除<k个字符p也一定是s的子序列,同样可以轻易反证。

根据这两个性质可知,删的越多,p就越不可能是s的子序列。既然有此单调性质,二分k即可。

检查时我也没啥优雅的检查方法,看别人交的也都那个思路:列一个bool数组,根据二分结果标记出所有的删除字符,然后线搜s,跳过bool数组标记的同时检查p的字符是否仍都在s中(线搜自带顺序了)。

import java.util.Arrays;

class Solution {
    public int maximumRemovals(String s, String p, int[] removable) {
        int lp,rp,mid,ans;
        ans = -1;
        lp = 0;rp = removable.length+1;
        char[] sch = s.toCharArray();
        char[] pch = p.toCharArray();
        while(lp<rp){
            mid = ((rp-lp)>>>1)+lp;
            if(check(sch,pch,removable,mid)){
                ans = mid;
                lp = mid+1;
            }else{
                rp = mid;
            }
        }
        return ans;
    }

    private boolean check(char[] sch,char[] pch,int[] removable,int mid){
        boolean[] exist = new boolean[sch.length];
        Arrays.fill(exist,true);
        for (int i = 0; i < mid; i++) {
            exist[removable[i]] = false;
        }
        int pp = 0;
        for (int i = 0; i < sch.length; i++) {
            if(exist[i]){
                if(sch[i]==pch[pp]){
                    pp++;
                    if(pp==pch.length){
                        return true;
                    }
                }
            }
        }
        return false;
    }
}

11. LC 1482 制作m束花所需的最少天数

灵神给这题标的1946分,妥妥的K。做了之后我觉得我也行了(笑。但扪心自问一下我临场应该看不出是二分的,这题不标二分我真一点不会往二分上想。

单调性:对于任意的整数p

  1. 假设等待了p天之后,能够制作m束花,那么等待任意>p天之后,也一定能制作m束花
  2. 假设等待了p天之后,还是不能制作m束花,那么等待任意<p天之后,也一定不能制作m束花。

下限:等1天(不可能一天都不等的,最早开花要过1天)

上限:题目标了,bloomDay的范围在1e9之内,所以无脑取1e9即可。

检查:线搜统计连续的长度≥k的块,个数应≥m。

import java.util.Arrays;

class Solution {
    public int minDays(int[] bloomDay, int m, int k) {
        int n = bloomDay.length;
        if(m*k>n){
            return -1;
        }
        int lp,rp,mid,ans;
        ans = -1;
        lp = 1;
        rp = (int) (1e9+1);
        while(lp<rp){
            mid = ((rp-lp)>>>1)+lp;
            if(check(bloomDay,m,k,mid)){
                ans = mid;
                rp = mid;
            }else{
                lp = mid+1;
            }
        }
        return ans;
    }
    
    private boolean check(int[] bloomDay,int m,int k,int mid){
        int cm = 0;
        int con = 0;
        for (int j : bloomDay) {
            if (j <= mid) {
                con++;
                if (con == k) {
                    con = 0;
                    cm++;
                }
            } else {
                con = 0;
            }
        }
        return cm>=m;
    }
}

12. LC 1276 不浪费原料的汉堡制作方案

这题不在灵神题单里,刷每日一题的时候看到的。

特判:由于巨无霸和小皇堡的番茄片数目都为偶数,偶数加偶数仍为偶数,所以如果给出的番茄片数量不为偶数,就可以直接返回空列表了。

单调性:对于任意数量的小皇堡B(其中B显然小于等于给出的芝士片数量)

  1. 若制作B个小皇堡会导致番茄片不够用,则显然制作<B个也会导致番茄片不够用,这是因为如果少做小皇堡,那么就会多做巨无霸,巨无霸对番茄片的消耗更大。
  2. 若制作B个小皇堡导致番茄片有剩余,则制作>B个也会导致番茄片剩余。这是因为如果多做小皇堡,那么就会少做巨无霸,对番茄片的消耗更不够。

题目要求将两种材料恰好用完,我选择直接对小皇堡数量二分,检查二分结果是否能使得材料用完。

下限:不做小皇堡,0。

上限:最多做Math.min(tomatoSlices/2,cheeseSlices)个小皇堡

检查:番茄片剩余数量

题外话:其实这是道鸡兔同笼的题目,但我最近刷二分题单刷魔怔了,就用二分了。

import java.util.ArrayList;
import java.util.List;

class Solution {
    public List<Integer> numOfBurgers(int tomatoSlices, int cheeseSlices) {
        ArrayList<Integer> ans = new ArrayList<>();
        if(tomatoSlices%2!=0){
            return ans;
        }
        int lp,rp,mid,s,check;
        s = -1;
        lp = 0;
        rp = Math.min(tomatoSlices/2,cheeseSlices)+1;
        while(lp<rp){
            mid = ((rp-lp)>>>1)+lp;
            check = check(tomatoSlices, cheeseSlices, mid);
            if(check==0){
                s= mid;
                break;
            }else if(check>0){
                rp = mid;
            }else{
                lp = mid+1;
            }
        }
        if(s!=-1){
            ans.add(cheeseSlices-s);
            ans.add(s);
        }
        return ans;
    }

    private int check(int tomato,int cheese,int mid){
        return (tomato-mid*2-(cheese-mid)*4);
    }
}

13. LC 1642 可以到达的最远建筑

这题灵神标的1962,接近2000分的题了。这道题确实可以用二分做,但并不是一个较好的解法,较好的解法是贪心。但这里是二分题解,就不放贪心了。

单调性:对于任意下标dest

  1. 若使用给出的bricks和ladders可以到达dest,则必然也能到达<dest的位置(大不了梯子砖块用不完)。
  2. 若使用给出的bricks和ladders不可以到达dest,则必然也到不了>dest的位置。

下限:0(不一定能到下标1的)

上限:n-1

检查:检查用的贪心。正是因为可以用贪心检查,所以就没必要二分。一切搜索算法都是把求解型问题转换为了判定型问题,但既然能用贪心直接求解,为什么还要搜索判定?所以这是这道题不适合二分的点。

贪心检查的思路是:对于小的高度差用砖块,对于大的高度差用梯子。这是一个很直观的结论,用反证法也能轻易证明。这里出于直观就不写严谨的数学证明了。

假设存在一个策略,其中存在至少一次在使用梯子和使用砖块时,前者对应的高度差小于后者。形如(b,x)和(l,y),其中x>y。那么我们交换在这两次中使用梯子和使用砖块的时机,也即(b,y)和(l,x),既然y个砖块能满足后者的高度差,那么对于x>y显然能满足。而梯子本身是对任意高度差都适用的,因此对于前者,交换后仍满足。

因此对于小的高度差用砖块,对于大的高度差用梯子,一定不比反过来差。但反之不亦然。则贪心思路正确。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.PriorityQueue;

class Solution {
    public int furthestBuilding(int[] heights, int bricks, int ladders) {
        int lp,rp,mid,ans;
        ans = 0;
        lp = 0;
        int n = heights.length;
        rp = n;
        int[] diff = new int[n];
        diff[0] = 0;
        for (int i = 1; i < heights.length; i++) {
            diff[i] = Math.max(heights[i] - heights[i-1],0);
        }
        while(lp<rp){
            mid = ((rp-lp)>>>1)+lp;
            if(check(diff,mid,bricks,ladders)){
                ans = mid;
                lp = mid+1;
            }else{
                rp = mid;
            }
        }
        return ans;
    }

    private boolean check(int[] diff,int mid,int b,int l){
        PriorityQueue<Integer> pq = new PriorityQueue<>(Integer::compare);
        for(int i=1;i<=mid;i++){
            if(diff[i]!=0){
                pq.offer(diff[i]);
            }
        }
        while(!pq.isEmpty()){
            Integer poll = pq.poll();
            if(poll<=b){
                b-=poll;
            }else{
                l--;
            }
        }
        return l>=0;
    }
}

这里小根堆插入时间复杂度nlgn,外面套一层O(lgn)的二分,总体复杂度O(n(lgn)²),复杂度太高了,表现很差。

14. 周赛378 T3 LC 100184 找出出现至少三次的最长特殊子字符串Ⅱ

不知道这题是不是二分才是最优解。我没想出来O(n)的写法。最后过了一个O(nlgn)的。

PS:这把真的,前三题难度都很低,结果我一看榜第四题基本没人做出来,就写个T3题解润了。榜上雪景式、别问问就是数学、张晴川、erutan这帮大佬在11:20左右也都没做出来。灵神一如既往的强,34minAK了。

求最值可转为判定。显然这个特殊子字符串长度越长越不可能出现3次。单调性如下:

对于任意窗口长度m

  1. 若m能够对应至少一个符合条件的特殊子字符串,那么<m也能(m对应的那个/些子字符串就是)
  2. 若m不能对应任何满足条件的子字符串,那么>m也不能(显然)

上限:n-mid+1

下限:1(没有长度为0的字符串这一说)

检查:

O(n)的检查。遍历字符串,对于某一位置,建立flag,后续所有字符应和他一致,直到遍历到不一致的停止。给该字符串带来Math.max(end-start+1-mid+1,0)的增量。判断是否≥3即可。字符串由于字符都一致,可以开个26size的哈希表维护。

class Solution {
    public int maximumLength(String s) {
        int n = s.length();
        int lp,rp,mid,ans;
        ans = -1;
        lp = 1;
        rp = n-3+1+1;
        while(lp<rp){
            mid = ((rp-lp)>>>1)+lp;
            if(check(s,mid,n)){
                ans = mid;
                lp = mid+1;
            }else{
                rp = mid;
            }
        }
        return ans;
    }

    private boolean check(String s,int mid,int n){
        int[] cnt = new int[26]; // 直接把字符串当字符就行
        char[] ch = s.toCharArray();
        for(int i=0;i<n;i++){
            char flag = ch[i];
            int tmp = i;
            while(i+1<n && ch[i+1]==flag){
                i++;
            }
            cnt[flag-'a']+=Math.max(0,i-tmp+1-mid+1);
            if(cnt[flag-'a']>=3){
                return true;
            }
        }
        return false;
    }

}

15. LC 33 搜索旋转排序数组

这题是真的汗流浃背乐。应该是一道基础题,但我就是想了很久(基本功不扎实)。

二分一定要在保证单调性的区间使用。而旋转后的数组本身并不是有序的,但可以视为两段数组拼在一起,其中每一个都是有序的。因此这里的思路是“两次二分”,或者叫做分治二分。

例如,现在有个数组:

[ 9,10,11,0,1,2,3,4,5,6,7,8 ]

我们并不知道上述两个有序数组的分界点的索引(这里就是11对应的索引,2),那么只能选择粗暴的从中间切开:

[ 9,10,11,0,1,2 ]
[ 3,4,5,6,7,8 ]

可以证明这样切割一定是有至少一个数组有序。既然该数组有序,我们就可以对其二分查找。具体到这个例子,假设target = 10吧。

  1. [3,8]区间里没有10,不用看了,直接找左半边
  2. 左半边并不是单调的,因此先分割
[9,10,11]
[0,1,2]
  1. [9,11]里包含了10,去左半边找
  2. 由于左半边单调,一直二分下去,就能找到10

这里每次都要二分两次,先对检索区间二分,然后找到target可能在的区间,如果它有序,就可以一直二分下去了,如果无序,那么就需要再二分,直到找到有序区间为止。而有序无序既然都要二分,在实现时合在一起即可。

class Solution {
    public int search(int[] nums, int target) {
        int lp,rp,mid,ans;
        ans = -1;
        lp = 0;
        rp = nums.length;
        while(lp<rp){
            mid = ((rp-lp)>>>1)+lp;
            if(nums[mid]==target){
                return mid;
            }
            // 整个数组分为两段
            // [lp,mid] (mid,rp)
            // 至少有一段是单增的
            if(nums[lp]<nums[mid]){
                // 左半段单调
                if(nums[lp]<=target && target<nums[mid]){
                    // 在左半段,左半段二分
                    rp = mid;
                }else{
                    // 不在左半段,那只能找右半段了
                    lp = mid+1;
                }
            }else{
                // 右半段单调
                if(nums[mid]<target && target<=nums[rp-1]){
                    // 在右半段,右半段二分
                    lp = mid+1;
                }else{
                    rp = mid;
                }
            }
        }
        return ans;
    }
}

16. LC 100200 标记所有下标的最早秒数Ⅰ

周赛386T3。

  1. 单调性:

    1. 若m秒可以将所有元素标记,显然>m秒也可以。
    2. 若m秒无法将所有元素标记,显然<m秒也不可以。
  2. 下界:1

  3. 上界:changeIndices.length

  4. 检查:这个比较有意思,考细节的。我们可以这么想,倒着遍历changeIndices,看每个位置的最晚结束时间是多少(也就是最后一次能标记该位置的时间点),只要能在这个最晚结束时间点之前标记上就可以。所以我先统计所有位置的最晚结束时间。然后按照该时间排序。ddl早的先进行减1和标记,这是因为总归要在ddl之前把该位置上的元素递减到0。另外记得标记一个位置也需要1个单位的时间。

    我采用的方式是每次新的ddl出现时,相当于给整个任务新增了一段可用的时间,所以就在rest中记录新增的时间。然后把元素减为0并标记需要nums[i]+1个时间。检查有没有超时就可以了。

    import java.util.Arrays;
    import java.util.Comparator;
    
    class Solution {
        public int earliestSecondToMarkIndices(int[] nums, int[] changeIndices) {
            int l,r,mid,ans;
            ans = -1;
            l = 1;
            r = changeIndices.length+1;
    
            while(l<r){
                mid = ((r-l)>>>1)+l;
                if(check(nums,changeIndices,mid)){
                    ans = mid;
                    r =  mid;
                }else{
                    l = mid+1;
                }
            }
            return ans;
        }
    
        private boolean check(int[] nums,int[] changeIndices,int mid){
            int n = nums.length;
            int[][] ddl = new int[n][2];
    
            for (int i = 0; i < n; i++) {
                ddl[i][0] = nums[i];
                ddl[i][1] = -1;
            }
    
            for(int i=mid;i>=1;i--){
                if(ddl[changeIndices[i-1]-1][1]==-1){
                    ddl[changeIndices[i-1]-1][1] = i;
                }
            }
    
            Arrays.sort(ddl, Comparator.comparingInt(o -> o[1]));
    
            int rest = 0;
            int prev = 0;
            for (int[] arr : ddl) {
                if(arr[0]>arr[1]){
                    return false;
                }
                rest += arr[1]-prev;
                prev = arr[1];
                rest -= arr[0]+1;
                if(rest<0){
                    return false;
                }
            }
    
            return true;
        }
    }
    

    检查的瓶颈在于排序,是nlgn的,二分lgm的,所以总体O(nlgmlgn)的

17. LC 100246 将元素分配到两个数组中Ⅱ

第一次AK。周赛387T4。

这题可以这么想:维护arr1和arr2的两个有序列表。每次只需要二分查找第一个比nums[i]大的位置,然后根据列表长度计算有多少个比nums[i]大的元素即可。

复杂度方面,二分是O(logn)的,有序列表插入也是二分,也是O(logn)的,所以总体O(nlogn),对于1e5的数据,是可以过的。

但是java tmd没有有序列表,所以还得自己手写一个。

import java.util.ArrayList;

class SortedList{
    public ArrayList<Integer> list;

    public SortedList(){
        list = new ArrayList<>();
    }

    public void insert(int element) {
        int index = findInsertionIndex(element);
        list.add(index, element);
    }

    private int findInsertionIndex(int element) {
        int left = 0;
        int right = list.size() - 1;
        int index = -1;

        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (list.get(mid).compareTo(element) < 0) {
                index = mid;
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }

        return index + 1;
    }
    public int findFirstIndexGreaterThan(int target) {
        int left = 0;
        int right = list.size() - 1;
        int firstIndex = list.size(); // 初始化索引为列表长度,表示没有找到大于target的元素

        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (list.get(mid).compareTo(target) > 0) {
                // 如果mid位置的元素大于target,则更新firstIndex并继续在左半部分搜索
                firstIndex = mid;
                right = mid - 1;
            } else {
                // 如果mid位置的元素小于或等于target,则在右半部分搜索
                left = mid + 1;
            }
        }

        return firstIndex;
    }
}

class Solution {
    public int[] resultArray(int[] nums) {
        SortedList sarr1 = new SortedList();
        SortedList sarr2 = new SortedList();

        ArrayList<Integer> arr1 = new ArrayList<>();
        ArrayList<Integer> arr2 = new ArrayList<>();

        arr1.add(nums[0]);
        sarr1.insert(nums[0]);

        arr2.add(nums[1]);
        sarr2.insert(nums[1]);

        for (int i = 2; i < nums.length; i++) {
            int s1 = sarr1.list.size()-sarr1.findFirstIndexGreaterThan(nums[i]);
            int s2 = sarr2.list.size()-sarr2.findFirstIndexGreaterThan(nums[i]);

            if(s1>s2){
                arr1.add(nums[i]);
                sarr1.insert(nums[i]);
            }else if(s1<s2){
                arr2.add(nums[i]);
                sarr2.insert(nums[i]);
            }else{
                if(arr1.size()<=arr2.size()){
                    arr1.add(nums[i]);
                    sarr1.insert(nums[i]);
                }else {
                    arr2.add(nums[i]);
                    sarr2.insert(nums[i]);
                }
            }
        }

        int[] ans = new int[nums.length];
        for (int i = 0; i < arr1.size(); i++) {
            ans[i] = arr1.get(i);
        }
        for (int i = 0; i < arr2.size(); i++) {
            ans[i+arr1.size()] = arr2.get(i);
        }
        return ans;
    }
}

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值