Java贪心算法——leetcode简单题总结

目录

1、贪心算法介绍

2、leetcode题分析

2.1、最长回文串

题目要求

题目分析

代码演示

2.2、分发饼干

题目要求

题目分析

代码演示

2.3、数组拆分

题目要求

题目分析

代码演示

2.4、种花问题

题目要求

题目分析

代码演示


1、贪心算法介绍

1)贪婪算法(贪心算法)是指在对问题进行求解时,在每一步选择中都采取最好或者最优(即最有利)的选择,从而希望能够导致结果是最好或者最优的算法。

2)贪婪算法所得到的结果不一定是最优的结果(有时候会是最优解),但是都是相对近似(接近)最优解的结果。

2、leetcode题分析

2.1、最长回文串

题目要求

        给定一个包含大写字母和小写字母的字符串 s ,返回通过这些字母构造成的 最长的回文串。在构造过程中,请注意区分大小写 。比如 "Aa" 不能当做一个回文字符串。例如:

输入:s = "abccccdd"

输出:7

解释:

我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。

题目分析

        已知回文串的是指正着读和反着读都一样的字符串,我们可以将其看作是一个对称模型。在一个回文串中,只有最多一个字符出现奇数次,其余的字符都只能是偶数次。

        若字符串s中每种字符的数量都是偶数,那么必然能形成回文串,因此直接输出字符串s的长度即可;

        若字符串s中有字符的数量为奇数,为了能够凑成回文串,我们可以先默认奇数没办法形成回文串,用 “字符串s的长度” 减去 “数量为奇数次的字符种类数” ,亦可理解为将奇数次的字符数量减一变成偶数次后重新计算字符串s的长度。这么做会多出数个字符,我们可以取其一将其作为回文串对称模型的中心,即在上述的结果加一得到最长的回文串。

代码演示

public static int longestPalindrome2(String s) {
    // 找出可以构成最长回文串的长度
    int[] arr = new int[128];
    for(char c : s.toCharArray()) {
        //记录每一种字符的数量
        arr[c]++;
    }
    int j = 0;
    for (int i : arr) {
        j += (i % 2);
    }
   /*
        只要有一个字符的数量是奇数,那么总数就一定是奇数
        由于奇数没法放两边形成回文串,因此全部减1形成偶数,即减去奇数的种类数 s.length() - j
        但由于是奇数,可以取一个字符放回文串中间,因此长度为s.length() - j + 1
    */

    return j == 0 ? s.length() : (s.length() - j + 1);
}

2.2、分发饼干

题目要求

        假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。 对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。例如:

输入:

g = [1,2,3],s = [1,1]

输出:1

解释::你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。 虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。 所以你应该输出1。

题目分析

        目的是为了尽可能满足越多数量的孩子,由题意可知胃口值越小的孩子越容易满足,因此优先满足胃口值小的孩子,即对数组g进行从小到大排序。根据贪心算法原理,我们应该选择可以满足这个孩子的胃口且尺寸最小的饼干,因此饼干的数组s也需要从小到大排序。

代码演示

public static int findContentChildren(int[] g, int[] s) {
        int gk = 0;
        int count = 0;
        //从小到大排序
        Arrays.sort(g);
        Arrays.sort(s);
        for (int i = 0; i < s.length; i++) {
            //拿出最小的饼干,从胃口最小的孩子开始遍历
            //由于尽可能满足最多孩子,因此将胃口由小到大的孩子喂饱最容易满足要求
            if(s[i] >= g[gk]){
                count++;
                gk++;
            }
            if(gk > g.length-1){
                break;
            }
        }
        return count;
    }

2.3、数组拆分

题目要求

        给定长度为 2n 的整数数组 nums ,你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), ..., (an, bn) ,使得从 1 到 n 的 min(ai, bi) 总和最大。 返回该最大总和 。例如:

输入:nums = [1,4,3,2]

输出:4

解释:所有可能的分法(忽略元素顺序)为:

1、(1, 4), (2, 3) -> min(1, 4) + min(2, 3) = 1 + 2 = 3

2、(1, 3), (2, 4) -> min(1, 3) + min(2, 4) = 1 + 2 = 3

3、(1, 2), (3, 4) -> min(1, 2) + min(3, 4) = 1 + 3 = 4

所以最大总和为 4

题目分析

        min(a,b)方法即取a、b中的最小值。让数组中的数字两两组合取最小值,我们应该尽可能保证组合的值最为接近,这样才能保证相加后返回的是最大总和。因此将数组进行从小到大排序后进行组合。由于组合后取出的是最小值,那么只需要取偶数下标的数进行相加即可。

代码演示

    public static int arrayPairSum(int[] nums) {
        /**
         *  数组:1 2 2 5 6 6
         *  下标:0 1 2 3 4 5
         *       *   *   *
         *       1+2+6 = 9
         *  从小到大排序后取偶数下标
         */
        Arrays.sort(nums);
        int sum = 0;
        for (int i = 0; i < nums.length; i++) {
            if(i % 2 != 1){
                sum+=nums[i];
            }
        }
        return sum;
    }

2.4、种花问题

题目要求

        假设有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花不能种植在相邻的地块上,它们会争夺水源,两者都会死去。

        给你一个整数数组 flowerbed 表示花坛,由若干 0 和 1 组成,其中 0 表示没种植花,1 表示种植了花。另有一个数 n ,能否在不打破种植规则的情况下种入 n 朵花?能则返回 true ,不能则返回 false。

示例一

输入:

flowerbed = [1,0,0,0,1], n = 1

输出:true

示例二

输入:

flowerbed = [1,0,0,0,1], n = 2

输出:false

题目分析

(1)对于中间的0区间:

        1~2个0:可种0朵;

        3~4个:可种1朵;

        5~6个:可种2朵;

        ...    

        count个:可种 (count-1)/2 朵 

(2)对于两头的0区间,由于左边、右边分别没有1的限制,可种花朵数稍有不同。为了代码流程的统一,可以在数组最左边、数组最右边分别补1个0,意味着花坛左边、右边没有花。这样公式就跟(1)相同了。

代码演示

示例一

    public static boolean canPlaceFlowers2(int[] flowerbed, int n) {
        if (flowerbed == null || flowerbed.length == 0) return n == 0;

        int countOfZero = 1; // 当前全0区段中连续0的数量,刚开始预设1个0,因为开头花坛的最左边没有花,可以认为存在一个虚无的0
        int canPlace = 0; // 可以种的花的数量
        for (int bed : flowerbed) {
            if (bed == 0) { // 遇到0,连续0的数量+1
                countOfZero++;
            } else { // 遇到1,结算上一段连续的0区间,看能种下几盆花:(countOfZero-1)/2
                canPlace += (countOfZero-1)/2;
                if (canPlace >= n) return true;
                countOfZero = 0; // 0的数量清零,开始统计下一个全0分区
            }
        }
        // 最后一段0区还未结算:
        countOfZero++; // 最后再预设1个0,因为最后花坛的最右边没有花,可以认为存在一个虚无的0
        canPlace += (countOfZero-1)/2;

        return canPlace >= n;
    }

示例二——暴力罗列每一种情况,仅供参考

 public static boolean canPlaceFlowers(int[] flowerbed, int n) {
        int count = 0;
        int length = flowerbed.length;
        if(length ==1){
            if(flowerbed[0] == 0){
                count++;
            }
        }else {
            if(flowerbed[0] == 0 && flowerbed[1] == 0){
                count++;
                flowerbed[0] = 1;
            }
            if(flowerbed[length-1] == 0 && flowerbed[length-2] == 0){
                count++;
                flowerbed[length-1] = 1;
            }
        }
        for (int i = 0; i < length; i++) {
            if(flowerbed[i] == 0 && i != 0 && i != length-1 && length!=1){
                if(flowerbed[i-1] == 0 && flowerbed[i+1] == 0){
                    count++;
                    flowerbed[i] = 1;
                }
            }
        }
        if(n <= count ){
            return true;
        }else {
            return false;
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梅川库紫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值