分组循环A

文章讨论了几种编程题目,主要涉及数组操作,如判断数组是否可排序、连续字符、交替子数组、删除字符、股票价格分析等,利用分组循环、排序和二分查找等技巧解决。
摘要由CSDN通过智能技术生成

模板

i = 0
while(i<n){
	start = i
	while( i<n && check(args) ) {
		i+=1
	}
}

1. LC 3011 判断一个数组是否可以变为有序

这题我比赛时用的并查集。看灵神视频学了个分组循环的做法。

对于每个分组,如果可以交换,则扩展分组的窗口,直至达到尽头或者不能交换为止。这样这个分组里的数都是可以任意交换的,因此就可以对这个分组进行排序。对每个分组排序后如果能使得整个数组有序,那么就成功。

import java.util.Arrays;

class Solution {
    public boolean canSortArray(int[] nums) {
        int i = 0;
        int start;
        int n = nums.length;
        while(i<n){
            start = i;
            while(i<n && check(nums[start],nums[i])){
                i++;
            }
            Arrays.sort(nums,start,i);
        }

        return inOrder(nums);
    }

    private boolean check(int num1,int num2){
        return Integer.bitCount(num1)==Integer.bitCount(num2);
    }

    private boolean inOrder(int[] nums){
        for (int i = 1; i < nums.length; i++) {
            if(nums[i-1]>nums[i]){
                return false;
            }
        }
        return true;
    }
}

2. LC 1446 连续字符

入门题。分组记录每个连续字符子串长度,维护最大值。

class Solution {
    public int maxPower(String s) {
        char[] ch = s.toCharArray();
        int i = 0;
        int n = ch.length;
        int max = 0;
        while(i<n){
            char c = ch[i];
            int start = i;
            while(i<n&&ch[i]==c){
                i++;
            }
            max = Math.max(max,i-start);
        }
        return max;
    }
}

3. LC 1869 哪种连续子字符串更长

入门题。分组记录0/1子串长度,维护最大值,最后比较。

class Solution {
    public boolean checkZeroOnes(String s) {
        int max0 = 0;
        int max1 = 1;
        char[] ch = s.toCharArray();

        int i = 0;
        int n = ch.length;
        while(i< n){
            int start = i;
            char c = ch[i];
            boolean which = c =='1';
            while(i<n && c == ch[i]){
                i++;
            }

            if(which){
                max1 = Math.max(max1,i-start);
            }else{
                max0 = Math.max(max0,i-start);
            }
        }

        return max1>max0;
    }
}

4. LC 1957 删除字符使字符串变好

入门题。分组检查连续相同子串长度,超过2就缩减到2,拼到答案里即可。

class Solution {
    public String makeFancyString(String s) {
        char[] ch = s.toCharArray();
        StringBuilder sb = new StringBuilder();

        int i = 0;
        int n = ch.length;
        while(i<n){
            int start = i;
            while(i<n && ch[start]==ch[i]){
                i++;
            }
            int cnt = Math.min(2,i-start);
            sb.append(String.valueOf(ch[start]).repeat(cnt));
        }

        return sb.toString();
    }
}

5. LC 2110 股票平滑下跌阶段的数目

入门题。分组查询每段平滑下跌阶段。贡献是(l+1)*l/2(等差数列),累加即可。

class Solution {
    public long getDescentPeriods(int[] prices) {
        int i = 0;
        int start;

        int n = prices.length;
        long ans = 0;
        while(i<n){
            start = i;
            while(i<n-1 && prices[i]==prices[i+1]+1 ){
                i++;
            }

            int cnt = i-start+1;
            ans += (long) (cnt + 1) *cnt/2;
            i++;
        }

        return ans;
    }
}

6. LC 2765 最长交替子数组

每日一题+入门题。分组查询交替子数组长度,维护最大值

class Solution {
    public int alternatingSubarray(int[] nums) {
        int n = nums.length;
        int max = -1;

        int i = 0;
        int start;

        while(i<n){
            start = i;
            int diff = 1;
            while(i<n-1 && nums[i+1]-nums[i]==diff){
                diff *= -1;
                i++;
            }

            if(i>start){
                max = Math.max(max,i-start+1);
            }
            if(!(i>start)){
                i++;
            }
        }

        return max;
    }
}

7. LC 228 汇总区间

入门题。分组查询递增1的区间,记录下来整合成目标格式的字符串即可。

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

class Solution {
    public List<String> summaryRanges(int[] nums) {
        ArrayList<List<Integer>> l = new ArrayList<>();

        int i = 0;
        int n = nums.length;

        while(i<n){
            List<Integer> tmp = new ArrayList<>();
            while(i<n-1 && nums[i]+1==nums[i+1]){
                tmp.add(nums[i]);
                i++;
            }
            tmp.add(nums[i]);
            i++;
            l.add(tmp);
        }

        return print(l);
    }

    private List<String> print(List<List<Integer>> l){
        ArrayList<String> ans = new ArrayList<>();

        for (List<Integer> is : l) {
            StringBuilder sb = new StringBuilder();
            if(is.size()==1){
                sb.append(is.get(0));
            }else{
                sb.append(is.get(0)).append("->").append(is.get(is.size()-1));
            }
            ans.add(sb.toString());
        }

        return ans;
    }
}

8. LC 2760 最长奇偶子数组

入门题。分组查询满足条件的区间,维护最大值即可。

class Solution {
    public int longestAlternatingSubarray(int[] nums, int threshold) {
        int max = 0;

        int i = 0;
        int start;
        int n = nums.length;

        while(i<n){
            if(nums[i]%2!=0 || nums[i]>threshold){
                i++;
                continue;
            }

            start = i;
            while(i<n-1 && nums[i]%2!=nums[i+1]%2 && nums[i+1]<=threshold){
                i++;
            }

            max = Math.max(max,i-start+1);
            if(i==start){
                i++;
            }
        }

        return max;
    }
}

9. LC 1887 使数组元素相等的减少操作次数

这道题我没想出来分组循环的写法。就想了个最好情况下O(nlgn)的散列表写法。思路大致就是,每个数最终都要等于那个最小数。每一批相同的数都要往下逐级递减。举个例子:

[ 1 1 2 2 3 3 ]

两个3要都减少到2,然后4个2减少到1。维护一个有序列表模拟这个过程即可。

import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

class Solution {
    public int reductionOperations(int[] nums) {
        int ops = 0;

        // 最终都会减小到最小值
        TreeMap<Integer, Integer> tm = new TreeMap<>();
        for (int num : nums) {
            mAdd(tm,num);
        }

        // 累加即可
        int prefix = 0;
        Integer[] arr = tm.keySet().toArray(Integer[]::new);
        for(int i=arr.length-1;i>=0;i--){
            prefix += tm.get(arr[i]);
            ops+=prefix;
        }

        ops-=prefix;
        return ops;
    }

    private void mAdd(Map<Integer,Integer> m,int num){
        m.put(
                num,m.getOrDefault(num,0)+1
        );
    }
}

后来我发现自己sb了,这个写法完全可以改成严格的O(nlgn)的,因为减少操作的时候,跟元素的索引根本没关系。

prefix相当于每个分组到最小元素的步长。每遍历到某个分组的元素,就加上一次这个步长。如果是下一个分组就给步长+1。

import java.util.Arrays;

class Solution {
    public int reductionOperations(int[] nums) {
        int ops = 0;

        Arrays.sort(nums);
        int prefix = 0; // 相当于步长

        for(int i=1;i<nums.length;i++){
            if(nums[i]!=nums[i-1]){
                prefix++;
            }

            ops+=prefix;
        }

        return ops;
    }
}

10. LC 2038 如果相邻两个颜色均相同则删除当前颜色

少见的博弈类型的题。这道题比较简单,只要统计双方的可以操作的次数就可以了。可能会疑惑,有没有可能A操作完给了B新的操作机会?或者反过来,B给A机会?这是不可能的,因为要求连续子数组的数量大于等于3,所以删到长度≤2的时候就不能删了,就不可能给对方新的机会,这个≤2的子串会一直卡着对面操作。

统计操作次数很简单。分组统计连续子数组长度,为自己的操作次数贡献Math.max(0,l-2)次机会。

class Solution {
    public boolean winnerOfGame(String colors) {
        // 统计分别可以删几次就行
        int opA = 0;
        int opB = 0;

        char[] ch = colors.toCharArray();
        int n = ch.length;

        int i = 0;
        int start;

        while(i<n){
            start = i;
            while(i<n && ch[i]==ch[start]){
                i++;
            }

            int acc = Math.max(0,i-start-2);
            if(ch[start]=='A'){
                opA += acc;
            }else{
                opB += acc;
            }
        }

        return opA>0 && opA>opB;
    }
}

11. LC 1759 统计同质子字符串的数目

入门题。查询连续相同子串的长度,计算贡献。1+2+…+l即可。

取模想不清楚?那就直接在能取模的地方全部取模,我愿称之为取模仙人。

class Solution {
    int mod = (int)1e9+7;
    public int countHomogenous(String s) {
        char[] ch = s.toCharArray();

        long ans = 0;

        int i=0;
        int start;
        int n = ch.length;

        while(i<n){
            start = i;
            while(i<n && ch[i]==ch[start]){
                i++;
            }

            int cnt = i-start;
            ans = (ans%mod + ((long) ((cnt + 1) % mod) *(cnt%mod)/2)%mod)%mod;
        }

        return (int) (ans%mod);
    }
}

12. LC 1578 使绳子变成彩色的最短时间

入门题。查询连续相同的子串,记录需要最长时间删除的代价,当前分组总体时间减去该最长时间即为分组的最短时间,每个分组的最短时间加起来就是总体的最短时间。

class Solution {
    public int minCost(String colors, int[] neededTime) {
        char[] ch = colors.toCharArray();

        int n = ch.length;
        int i = 0;
        int start;
        int ans = 0;

        while(i<n){
            start = i;
            int max = neededTime[start];
            int tmp = 0;
            while(i<n && ch[start]==ch[i]){
                max = Math.max(max,neededTime[i]);
                tmp+=neededTime[i];
                i++;
            }

            if(i!=start+1){
                ans += tmp - max;
            }
        }

        return ans;
    }
}

13. LC 1839 所有元音按顺序排布的最长子字符串

入门题。对于每个分组记录长度,维护当前最大的字符,要求后续的全部大于这个字符。同时记录所有出现过的字符,如果到最后5个元音字符全部出现过,那么维护最大值。

import java.util.Arrays;
import java.util.HashSet;

class Solution {
    static char[] vowels = new char[]{'a','e','i','o','u'};
    public int longestBeautifulSubstring(String word) {
        char[] ch = word.toCharArray();
        int n = ch.length;

        int i = 0;
        int max = 0;
        while(i<n){
            HashSet<Character> vs = new HashSet<>();
            int start = i;
            char last = ch[i];
            while(i<n && ch[i]>=last){
                last = ch[i];
                vs.add(ch[i]);
                i++;
            }

            if(check(vs)){
                max = Math.max(max,i-start);
            }
        }
        return max;
    }

    private boolean check(HashSet<Character> vs){
        return vs.size()==5;
    }
}

14. LC 826 安排工作以达到最大收益

首先可以看一个例子:

difficult = [85,47,57] profit = [24,66,99]

很明显第一个任务又难钱又少,这样的任务谁还选?肯定做那个难度低并且钱多的嘛。

所以我们的任务就是,让profit随着difficult的增加而增加,也就是类似于:

difficult = [2,4,6,8,10] profit = [10,20,30,40,50]

profit随着difficult单调增,很明显是吧difficult当作x轴了,所以我们按照difficult排序,同时如果difficult相同,把profit更大的排在前面(这样是为了分组循环时,让profit更大的收割后面又难钱又少的任务,或者说一样难但钱更少的任务)

分组循环

每一轮以当前遍历到的位置为基点,如果后面的某个位置难度≥当前位置的难度,但是钱≤当前位置的钱,就可以跳过他了。否则不能跳过

二分查找

分组循环后,profit随着difficult单调增。这个时候就变成了一个很trivial二分题目了。

具体实现上,分组循环时不需要额外开第二个数组,可以原地更新,这是因为遍历的速度一定快于或等于数组大小增长的速度。

import java.util.Arrays;
import java.util.Comparator;

class Solution {
    public int maxProfitAssignment(int[] difficulty, int[] profit, int[] worker) {
        int m,n;
        m = worker.length;
        n = difficulty.length;

        int[][] rec = new int[n][2];

        for (int i = 0; i < n; i++) {
            rec[i][0] = difficulty[i];
            rec[i][1] = profit[i];
        }

        Arrays.sort(rec, (o1, o2) -> {
            if(o1[0]==o2[0]){
                return -Integer.compare(o1[1],o2[1]);
            }

            return Integer.compare(o1[0],o2[0]);
        });

        int size = 0;
        int i = 0;

        while(i<n){
            int j = i;
            while(i<n){
                if(rec[j][1]>=rec[i][1] && rec[j][0]<=rec[i][0]){
                    i++;
                }else{
                    break;
                }
            }
            rec[size++] = rec[j];
        }

        int ans = 0;
        for (int j = 0; j < m; j++) {
            ans += max_profit(rec,size,worker[j]);
        }

        return ans;
    }

    private int max_profit(int[][] rec,int size,int ability){
        int l = 0;
        int r = size;
        int mid,ans;

        ans = 0;

        while(l<r){
            mid = ((r-l)>>>1)+l;
            if(rec[mid][0]<=ability){
                ans = rec[mid][1];
                l = mid+1;
            }else{
                r = mid;
            }
        }

        return ans;
    }
}

空间复杂度O(n),时间复杂度O((m+n)logn),瓶颈在排序。

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值