zl程序教程

您现在的位置是:首页 >  其它

当前栏目

一排正方形,有红色绿色两种颜色,将左边全变红色,右边全变绿色,最少需要染色的个数是多少

需要 两种 颜色 多少 个数 红色 绿色 最少
2023-09-11 14:15:38 时间

一排正方形,有红色绿色两种颜色,将左边全变红色,右边全变绿色,最少需要染色的个数是多少?

提示:空间换时间,提前准备前缀统计量,在遍历i的过程中,o(1)速度拿到左右G/R的数量,这样就完成优化。


题目

一排正方形数组arr,有红色R,绿色G两种颜色,将左边全变红色,右边全变绿色,最少需要染色的方块个数是多少?
目标就是让所有红色方块离左边近,所有绿色方块离右边近。


一、审题

示例:
arr=RGRGR
变这样:
arr=RRRGG
至少变2个
在这里插入图片描述
可能也有别的方案,放在想办法让左边全是R,右边全是G


暴力解,来到i,让0–i,全变R,让i+1–N-1全变G,每次暴力查找统计量,整体o(n^2)复杂度

每次来到i位置,都去数一下0–i上有多少个G:N1个
每次来到i位置,都去数一下i=1–N-1上有多少个R:N2个
这样的话,左边让G变R,右边让R变G,整体至少需要变:N1+N2个
这就是结果
如果每次都暴力索引查找的话,每一个i是N种情况
内部索引查找又是o(n)
整体就是o(n*n)=o(n^2)复杂度,不可取!

在这里插入图片描述

数组,范围上统计数量,范围上求累加和等等,完全可以通过提前准备前缀信息搞定,比如:前缀累加和,前缀数量等等
利用空间换时间来加速算法的优化!

本题就这么干!!!


左边让G变R,右边让R变G,利用前缀统计量count加速:o(n)复杂度

其实左边R,右边G是没有明确的一个分界线的
我们就枚举分界线,来到i位置
以i为分界线,左边:0–i,G全变R,右边:i+1–N-1的R全变G
看看每一个i,左边有多少G变R,右边有多少R变G?左右变化的和,就是咱们至少要变的数量
整个0–N-1所有分界线都有一个变数,更新给min就是咱们的结果!

在这里插入图片描述
以i为分界线,左边:0–i,G全变R,右边:i+1–N-1的R全变G
(1)countG:从左往右统计G的个数,目的是想让左边的全变R,所以G的个数,就是需要变为R的个数
(2)countR:从右往左统计R的个数,目的是想让右边的全变G,所以R的个数,就是需要变为G的个数
(3)当分界线i=-1时,意味着让所有arr都变为G,所以只看R有多少个?则需要变这么多个方块:countR[i+1]=countR[0]
(4)当分界线i=N-1时,意味着让所有arr都变为R,所以只看G有多少个?则需要变这么多个方块:countG[i]=countR[N-1]
(5)其余i位置,左边让G变R,变这么多个:countG[i],右边让R变G,变这么多个:countR[i+1],即两者错位相加,看上图,最终变整体个数:countG[i]+countR[i+1],每一个i为分界线的情况,直接更新给min即可
(6)返回min

本题优化的本质:
利用countG/R额外信息数组,加速在i位置,两头瞬间秒杀拿到G和R的个数,一下子就求出来整体需要变多少个了,这种做法我们在优化算法时常用的,空间换时间
最后一遍o(n)复杂度搞定,速度够快吧!

手撕代码:

    //复习:
    //**以i为分界线,左边:0--i,G全变R,右边:i+1--N-1的R全变G**
    public static int changeNumAtLeast(String s){
        if(s.compareTo("") == 0 || s.length() == 0) return 0;
        int N = s.length();
        //(1)countG:从**左往右**统计G的个数,目的是想让左边的全变R,所以G的个数,就是需要变为R的个数
        //(2)countR:从**右往左**统计R的个数,目的是想让右边的全变G,所以R的个数,就是需要变为G的个数
        int[] countG = new int[N];
        int[] countR = new int[N];
        char[] str = s.toCharArray();
        countG[0] = str[0] == 'G' ? 1 : 0;
        countR[N - 1] = str[N - 1] == 'R' ? 1 : 0;
        for (int i = 1; i < N; i++) {
            countG[i] = countG[i - 1] + (str[i] == 'G' ? 1 : 0);//看i处的G,括号加上,优先级在括号内
        }
        for (int i = N - 2; i >= 0; i--) {
            countR[i] = countR[i + 1] + (str[i] == 'R' ? 1 : 0);//看i处的R,括号加上,优先级在括号内
        }
        //(3)当分界线i=-1时,意味着让所有arr都变为G,所以只看R有多少个?
        // 则需要变这么多个方块:countR[i+1]=countR[0]
        int min = countR[0];
        //(4)当分界线i=N-1时,意味着让所有arr都变为R,所以只看G有多少个?
        // 则需要变这么多个方块:countG[i]=countR[N-1]
        min = Math.min(min, countG[N - 1]);
        //(5)其余i位置,左边让G变R,变这么多个:countG[i],右边让R变G,
        // 变这么多个:countR[i+1],即两者错位相加,看上图,
        // 最终变整体个数:countG[i]+countR[i+1],每一个i为分界线的情况,直接更新给min即可
        for (int i = 0; i < N - 1; i++) {
            min = Math.min(min, countG[i] + countR[i + 1]);
        }
        //(6)返回min
        return min;
    }

问题不大:
测试一把:


    public static void test(){
        String s  = "RGRGR";
        String s1  = "RGRGR";
        System.out.println(changeRG(s));
        System.out.println(changeRG2(s1));
        System.out.println(changeNumAtLeast(s1));
    }

    public static void main(String[] args) {
        test();
    }
}
2
2
2

当然,可以省掉数组,咱们这样搞,拿left统计整个0–i上的G的个数,拿countR来统计i–N-1上的R的个数
这样还可以省掉一个数组
代码可以这么写,不过不是重点
重点还是上面的思想!

下面的看不看随意:

    public static int changeRG2(String s){
        //显然方法2省略了一个数组,省空间,复杂度都是差不多的
        if (s == null || s.length() == 1) return 0;

        char[] str = s.toCharArray();
        int min = Integer.MAX_VALUE;
        int N = str.length;
        int[] R = new int[N];//统计i--N-1上R的个数,从右往左统计
        R[N - 1] = str[N - 1] == 'R' ? 1 : 0;//起点
        for (int i = N - 2; i >=0; i--) {
            R[i] = R[i + 1] + (str[i] == 'R' ? 1 : 1);
        }
        //收集信息--与此同时统计0--i上G的个数
        int countG = 0;//最开始分界线i==-1,左边没有G
        min = R[0];//这个时候最小值,就是让右边全部N个都变为G,统计R【0--N-1】上的个数
        for (int i = 0; i < N - 1; i++) {
            countG += str[i] == 'G' ? 1 : 0;//累加G的个数
            min = Math.min(min, countG + R[i + 1]);//看0--i上G多少个,i+1--N-1上R多少个
        }
        //最后分界线为N时,左边全变R,看左边G多少个
        min = Math.min(min, countG + (str[N - 1] == 'G' ? 1 : 0));//这里单纯滴统计G多少个???
        return min;
    }

测试结果,上面已经给出了


总结

提示:重要经验:

1)总之都要让i做分界线,左边找G的个数N1,右边找R个数N2,整体左边变R,右边变G,就是N1+N2,每个i得到的至少要变的个数,更新给min就行
2)不管暴力解,还是优化解,都是要这么做的,而本题优化的的关键就是空间换时间,提前准备前缀统计量,在遍历i的过程中,o(1)速度拿到左右G/R的数量,这样就完成优化。
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。