LeetCode刷题之贪心算法

贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。

贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。

贪心算法基本思路
  • 建立数学模型来描述问题
  • 把求解的问题分成若干个子问题
  • 对每个子问题求解,得到子问题的局部最优解
  • 把子问题的解局部最优解合成原来问题的一个解

1221. 分割平衡字符串

在一个「平衡字符串」中,'L' 和 'R' 字符的数量是相同的。
给出一个平衡字符串 s,请你将它分割成尽可能多的平衡字符串。
返回可以通过分割得到的平衡字符串的最大数量。

示例 1:

输入:s = "RLRRLLRLRL"
输出:4
解释:s 可以分割为 "RL", "RRLL", "RL", "RL", 每个子字符串中都包含相同数量的 'L' 和 'R'。

题解:
class Solution {
        public int balancedStringSplit(String s) {
        String restStr = s;
        ArrayList list = new ArrayList<>();
        while (restStr.length()>0) {
            restStr = getPinghengStr(restStr)[1];  //获取剩余字符串循环处理
            list.add(getPinghengStr(restStr)[0]);  //存储各个生成的平衡字符串
        }

        return list.size();
    }

    /**
     * 传入一个字符串,截取平衡字符串,同时返回剩余字符串
     * @param str
     * @return
     */
    private String[] getPinghengStr(String str) {
        String jugeStr;
        for (int i=1;i

944. 删列造序

给定由 N 个小写字母字符串组成的数组 A,其中每个字符串长度相等。
你需要选出一组要删掉的列 D,对 A 执行删除操作,使 A 中剩余的每一列都是 非降序 排列的,然后请你返回 D.length 的最小可能值。

删除 操作的定义是:选出一组要删掉的列,删去 A 中对应列中的所有字符,形式上,第 n 列为 [A[0][n], A[1][n], ..., A[A.length-1][n]])。(可以参见 删除操作范例)

示例 1:

输入:["cba", "daf", "ghi"]
输出:1
解释:
当选择 D = {1},删除后 A 的列为:["c","d","g"] 和 ["a","f","i"],均为非降序排列。
若选择 D = {},那么 A 的列 ["b","a","h"] 就不是非降序排列了。

题解:
class Solution {

    /**
     * 思路:
     * 将字符串数组排成字符矩阵,
     * 需要满足的条件是每一列从上到下需要按照字母顺序排列,如果不是字母顺序,就记录该列
     * 返回记录的列数
     * @param A
     * @return
     */
    public int minDeletionSize(String[] A) {
        int count = 0;
        a: for (int c=0;c

1403. 非递增顺序的最小子序列

给你一个数组 nums,请你从中抽取一个子序列,满足该子序列的元素之和 严格 大于未包含在该子序列中的各元素之和。

如果存在多个解决方案,只需返回 长度最小 的子序列。如果仍然有多个解决方案,则返回 元素之和最大 的子序列。

与子数组不同的地方在于,「数组的子序列」不强调元素在原数组中的连续性,也就是说,它可以通过从数组中分离一些(也可能不分离)元素得到。

注意,题目数据保证满足所有约束条件的解决方案是 唯一 的。同时,返回的答案应当按 非递增顺序 排列。

示例 1:

输入:nums = [4,3,10,9,8]
输出:[10,9]
解释:子序列 [10,9] 和 [10,8] 是最小的、满足元素之和大于其他各元素之和的子序列。但是 [10,9] 的元素之和最大。

题解:
class Solution {
    /**
     * 思路:
     * 1.对nums数组进行从小到大排序
     * 2.遍历从nums中从尾端依次取出数到sublist集合中,如果sublist中数的和大于剩余数的和,则返回该sublist,否则继续取数
     * @param nums
     * @return
     */
    public List minSubsequence(int[] nums) {
        List subList = new ArrayList<>();  //抽出来的子序列

        Arrays.sort(nums); //对nums排序

        int total = 0; //计算nums数组的总大小
        int subNum = 0;  //计算抽出来数的和的值
        for (int num : nums) {
            total += num;
        }

        for (int i=nums.length-1;i>=0;i--) {
            //从最后一个数开始抽取
            subList.add(nums[i]);
            subNum += nums[i];

            if (subNum > total/2) {
                //判断抽出的数的和是否比剩下的数的和大
                return subList;
            }
        }

        return subList;
    }
}

1518. 换酒问题

小区便利店正在促销,用 numExchange 个空酒瓶可以兑换一瓶新酒。你购入了 numBottles 瓶酒。

如果喝掉了酒瓶中的酒,那么酒瓶就会变成空的。

请你计算 最多 能喝到多少瓶酒。

示例 1:


输入:numBottles = 9, numExchange = 3
输出:13
解释:你可以用 3 个空酒瓶兑换 1 瓶酒。
所以最多能喝到 9 + 3 + 1 = 13 瓶酒。

题解:
class Solution {
    public int numWaterBottles(int numBottles, int numExchange) {
        //把现有买来的酒喝完
        int drinkCount = numBottles;  //现有喝酒数量
        int bottleCount = numBottles;  //手里现有酒瓶数量
        //开始兑换,判断空瓶数量是否大于等于numExchange
        while (bottleCount >= numExchange) {
            int curExchangeCount = bottleCount/numExchange;  //本次换酒数量
            //计算是否有剩余换不了的几个酒瓶 restBottleCount

分发饼干

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。

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

题解:
class Solution {
    /**
     * 思路:先将胃口和饼干从小到大排序
     * 优先满足小胃口的孩子,给当前这个小胃口孩子一个最小尺寸的饼干,且能满足该孩子
     * 用两个下标记录各个孩子和各个饼干
     */
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);
        int gPos = 0;
        int sPos = 0;
        int count = 0;
        while(gPos=g[gPos]) {
                count++;
                //该孩子得到满足
                sPos++;
                gPos++;
            } else {
                sPos++; //仅饼干往后移
            }
        }
        return count;
    }
}

数组拆分

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

返回该 最大总和 。

题解:
class Solution {
    /**
     * 假设排完序的结果为a1<=b1<=a2<=b2<=...<=an<=bn
     * 那么a1应该跟谁一组呢?
     *
     * a1作为全局最小值,无论谁跟a1搭档都是无效的,都是炮灰,
     * 那倒不如取一个最小的,把大的留给后面做搭档,才能保证后面的累计和尽可能地大,即给a1找一个“最小的搭档”b1。
     *
     * 当a1、b1被处理之后,a2同a1同理分析,给a2找个最小的b2
     * 所以,最终选择a1,a2,...,an会得到最好的结果。
     */
    public int arrayPairSum(int[] nums) {
        Arrays.sort(nums);
        int total = 0;
        for (int i=0;i

柠檬水找零

在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。

每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。

注意,一开始你手头没有任何零钱。

给你一个整数数组 bills ,其中 bills[i] 是第 i 位顾客付的账。如果你能给每位顾客正确找零,返回 true ,否则返回 false 。

题解:
class Solution {
    /**
     * 思路:只管5、10元够不够找零,20元不用管,因为20元不能找
     * 那么记录5、10元的张数,如果是5,则直接存储,如果是10,则5元-1张,10元+1张
     * 如果是20元,那么先找5、10元各减一张,如果不能,则5元减3张,如果还不行,则返回false
     */
    public boolean lemonadeChange(int[] bills) {
        int fiveNum = 0, tenNum = 0;
        for (int i=0;i=1) {
                        fiveNum--;
                        tenNum++;
                    } else {
                        return false;
                    }
                    break;
                case 20:
                    if (fiveNum>=1 && tenNum>=1) {
                        //5、10元各减一张
                        fiveNum--;
                        tenNum--;
                    } else if (fiveNum >=3) {
                        fiveNum -= 3;
                    } else {
                        return false;
                    }
                    break;
            }
        }
        return true;
    }
}

去除重复字母

给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)。

题解:
class Solution {
    /**
     * 解题思路:
     * 1.第一个条件:去重字母
     * 2.第二个条件:保证字典序从小到大
     * 3.第三个条件:保证字母存在至少一个
     * @param s
     * @return
     */
    public String removeDuplicateLetters(String s) {
        //第三个条件:保证字母存在至少一个
        //做法:给每一种字母维护一个计数器,先遍历获取其数量,如果数量为1,则在第二个条件中不要弹出栈,否则可以弹出栈
        int[] charCount = new int[256];
        for (int i=0;i stk = new Stack<>();
        boolean[] inStack = new boolean[256]; //用于存储这个字符是否已经记录过
        // 第一个条件:去重字母
        for(char c : s.toCharArray()) {
            // 每遍历过一个字符,都将对应的计数减一
            charCount[c]--;

            if (inStack[c]) {
                continue;
            }

            //第二个条件:保证字典序从小到大,在插入字母之前,判断与前一个字母的字典序,
            // 如果比前一个小,则循环将字符弹出栈顶,清除在栈中的记录
            while(!stk.empty() && stk.peek() > c) {
                if (charCount[stk.peek()] == 0) {
                    break;
                }
                inStack[stk.pop()] = false;
            }
            stk.push(c);
            inStack[c] = true;
        }
        StringBuilder sb = new StringBuilder();
        while (!stk.empty()) {
            sb.append(stk.pop());
        }
        return sb.reverse().toString();
    }
}

你可能感兴趣的:(LeetCode刷题之贪心算法)