LeetCode 专题 :贪心算法

贪心算法,又称贪婪算法。

1、在对问题求解时,总是做出在当前看来最好的选择。即贪心算法不从整体最优上加以考虑。

2、贪心算法所作出的是在某种意义上的局部最优解。

贪心算法和动态规划算法都是由局部最优导出全局最优,二者的区别如下。

贪心算法:
1、贪心算法中,作出的每步贪心决策都无法改变,因为贪心策略是由上一步的最优解推导下一步的最优解,而上一部之前的最优解则不作保留。
2、贪心法正确的前提是:每一步的最优解一定包含上一步的最优解。

动态规划:
1、全局最优解中一定包含某个局部最优解,但不一定包含前一个局部最优解,因此需要记录之前的所有最优解;
2、动态规划的关键是确定“状态转移方程”,即如何通过已经求出的局部最优解推导出全局最优解;
3、边界条件:即最简单的,可以直接得出的局部最优解。

LeetCode 第 11 题:盛最多水的容器

给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器,且 n 的值至少为 2。

img

图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

public class Solution {

    // 指针对撞、贪心思想
    // 参考解答:https://www.cnblogs.com/zichi/p/5745992.html

    public int maxArea(int[] height) {
        int len = height.length;
        if (len < 2) {
            // 0 或者 1 的时候,不能形成区间,所以不能形成容器
            return 0;
        }
        int l = 0;
        int r = len - 1;
        int res = 0;
        while (l < r) {
            // 这里其实就是木桶原理,取决于最短的那根木板
            // [1,2,3] 3 和 1 之间的长度就是 (3-1=)2
            int area = (r - l) * Math.min(height[l], height[r]);
            res = Math.max(res, area);
            if (height[l] < height[r]) {
                l++;
            } else {
                // height[l] >= height[r]
                r--;
            }
        }
        return res;
    }


    // 暴力解法,时间复杂度太高,我们应该使用指针对撞的方法
    public int maxArea1(int[] height) {
        int len = height.length;
        if (len < 2) {
            return 0;
        }
        int res = 0;
        for (int i = 0; i < len - 1; i++) {
            for (int j = i + 1; j < len; j++) {
                res = Integer.max(res, Integer.min(height[i], height[j]) * (j - i));
            }
        }
        return res;
    }
}

LeetCode 第 435 题 :无重叠区间

传送门:435. 无重叠区间。

给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。

注意:

  1. 可以认为区间的终点总是大于它的起点。
  2. 区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。

示例 1:

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

输出: 1

解释: 移除 [1,3] 后,剩下的区间没有重叠。

示例 2:

输入: [ [1,2], [1,2], [1,2] ]

输出: 2

解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。

示例 3:

输入: [ [1,2], [2,3] ]

输出: 0

解释: 你不需要移除任何区间,因为它们已经是无重叠的了。

思路1:贪心算法。

写法1:按照起点排序:选择结尾最早的,后面才有可能接上更多的区间。

如果两个区间有重叠,应该保留那个结尾短的。即要删除的是:与之前的区间有重叠的,并且结尾还比当前结尾长的那个区间。

Python 代码:

# Definition for an interval.
class Interval:
    def __init__(self, s=0, e=0):
        self.start = s
        self.end = e


class Solution:

    # 
    # 那么
    # 关键:

    def eraseOverlapIntervals(self, intervals):
        """
        :type intervals: List[Interval]
        :rtype: int
        """
        # 特判
        size = len(intervals)
        if size <= 1:
            return 0
        intervals.sort(key=lambda x: x.start)
        # 表示去掉的区间数
        res = 0
        end = intervals[0].end

        for interval in intervals[1:]:
            # 根据题目意思,严格小,才算重叠
            if interval.start < end:
                res += 1
                end = min(end, interval.end)
            else:
                end = interval.end
        return res


if __name__ == '__main__':
    intervals = [Interval(1, 2), Interval(1, 2), Interval(1, 2)]
    solution = Solution()
    result = solution.eraseOverlapIntervals(intervals)
    print(result)

写法2:把问题转换成最多能保留多少个区间,使得它们互相不重复。则可以按照终点排序。每个区间的结尾很重要,结尾越小,则后面越有可能容纳更多的区间。

Python 代码:

# Definition for an interval.
class Interval:
    def __init__(self, s=0, e=0):
        self.start = s
        self.end = e


class Solution:
    def eraseOverlapIntervals(self, intervals):
        """
        :type intervals: List[Interval]
        :rtype: int
        """

        size = len(intervals)
        if size <= 1:
            return 0

        intervals.sort(key=lambda x: x.end)

        end = intervals[0].end
        res = 1

        for interval in intervals[1:]:
            if interval.start >= end:
                res += 1
                end = interval.end

        return size - res


if __name__ == '__main__':
    intervals = [Interval(1, 2), Interval(1, 2), Interval(1, 2)]
    solution = Solution()
    result = solution.eraseOverlapIntervals(intervals)
    print(result)

Java 代码:

/**
 * Definition for an interval.
 * public class Interval {
 *     int start;
 *     int end;
 *     Interval() { start = 0; end = 0; }
 *     Interval(int s, int e) { start = s; end = e; }
 * }
 */
class Solution {
    public int eraseOverlapIntervals(Interval[] intervals) {
        int ilen = intervals.length;
        if (ilen == 0) {
            return 0;
        }
        // 按结尾升序排序
        Arrays.sort(intervals, new Comparator() {
            @Override
            public int compare(Interval o1, Interval o2) {
                if (o1.end != o2.end) {
                    return o1.end - o2.end;
                }
                return o1.start - o2.start;
            }
        });
        int res = 1;
        // 前一个结尾区间的索引
        int pre = 0;
        for (int i = 1; i < ilen; i++) {
            if (intervals[i].start >= intervals[pre].end) {
                res++;
                pre = i;
            }
        }
        return ilen - res;
    }
}

思路2:动态规划。

同样转换问题:问最多保留多少个区间,使得这些区间互相不重叠。这件事情像极了“最长上升子序列”。通常的思考方式:按照起始点进行排序。

Java 代码:

/**
 * Definition for an interval.
 * public class Interval {
 *     int start;
 *     int end;
 *     Interval() { start = 0; end = 0; }
 *     Interval(int s, int e) { start = s; end = e; }
 * }
 */
class Solution {
    public int eraseOverlapIntervals(Interval[] intervals) {
        int ilen = intervals.length;
        if (ilen == 0) {
            return 0;
        }

        Arrays.sort(intervals, new Comparator() {
            @Override
            public int compare(Interval o1, Interval o2) {
                if (o1.start != o2.start) {
                    return o1.start - o2.start;
                }
                return o1.end - o2.end;
            }
        });

        // dp[i] 表示以 intervals[i] 为结尾的区间能够成的最长不重叠的区间序列有几个
        int[] dp = new int[ilen];
        Arrays.fill(dp, 1);
        for (int i = 1; i < ilen; i++) {
            for (int j = 0; j < i; j++) {
                if (intervals[i].start >= intervals[j].end) {
                    dp[i] = Integer.max(dp[i], dp[j] + 1);
                }
            }
        }

        int res = dp[0];
        for (int i = 1; i < ilen; i++) {
            res = Integer.max(res, dp[i]);
        }
        return ilen - res;
    }
}

LeetCode 第 402 题:Remove K Digits

参考资料:https://blog.csdn.net/qq508618087/article/details/52584133

传送门:402. 移掉K位数字。

给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小。

注意:

  • num 的长度小于 10002 且 ≥ k。
  • num 不会包含任何前导零。

示例 1 :

输入: num = "1432219", k = 3
输出: "1219"
解释: 移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219。

示例 2 :

输入: num = "10200", k = 1
输出: "200"
解释: 移掉首位的 1 剩下的数字为 200. 注意输出不能有任何前导零。

示例 3 :

输入: num = "10", k = 2
输出: "0"
解释: 从原数字移除所有的数字,剩余为空就是0。

思路:肯定要移除大的数,所以遍历的时候,先移除那些左边比右边大的数,若没有移除完则直接去除后面的。

Python 代码:

class Solution:
    def removeKdigits(self, num, k):
        """
        :type num: str
        :type k: int
        :rtype: str
        """
        if len(num) == k: # 等于,直接返回‘0’
            return '0'
        stack = []
        for i in num: #遍历
            while stack and i < stack[-1] and k > 0: #当前数字小于栈顶数字,出栈。
                stack.pop()
                k -= 1
            stack.append(i)
        if k > 0:
            stack = stack[:-k] # 若还没移除K位,则直接移除后面。
        while stack and stack[0] == "0": # 移除0
            stack.pop(0)
        if not stack:
            return '0'
        else:
            return ''.join(stack)

LeetCode 第 55 题:JumpGame(是否能跳到最后一格)

传送门:55. 跳跃游戏。

给定一个非负整数数组,你最初位于数组的第一个位置。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个位置。

示例 1:

输入: [2,3,1,1,4]
输出: true
解释: 从位置 0 到 1 跳 1 步, 然后跳 3 步到达最后一个位置。

示例 2:

输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。

思路:本题用一个数 max_arrive 表示能到达的最远下标,一步步走下去,如果发现在 max_arrive 范围之内某处能达到的范围大于 max_arrive,那么我们就用更大的范围来替换掉原先的 max_arrive,这样一个局部的最优贪心策略,在全局看来也是最优的,因为局部能够到达的最大范围也是全局能够到达的最大范围。

参考资料:https://blog.csdn.net/happyaaaaaaaaaaa/article/details/51636861
Python 代码:

class Solution(object):
    def canJump(self, nums):
        """
        :type nums: List[int]
        :rtype: bool
        """
        l = len(nums)
        if l == 0:
            return True
        max_arrive = nums[0]
        for i in range(1, l):
            if i > max_arrive:
                return False
            else:
                max_arrive = max(max_arrive, i + nums[i])
        return True

LeetCode 第 12 题:整型转罗马数字

贪心算法:写好对应关系。


image-20190110155600773
image-20190110155317032
# 整形转罗马数字
class Solution(object):
    def intToRoman(self, num):
        """
        :type num: int
        :rtype: str
        """
        nums = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
        romans = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]
        index = 0
        res = ''
        while index < 13:
            # 注意:这里是等于号
            while num >= nums[index]:
                res += romans[index]
                num -= nums[index]
            index += 1
        return res

LeetCode 第 13题:罗马数字转整形

例:LVIII,编写对应关系,从左边看到右边,只要左边比右边小的,就减去两倍自己。

class Solution(object):
    def romanToInt(self, s):
        """
        :type s: str
        :rtype: int
        """

        l = len(s)
        if l == 0:
            return 0
        map = {
            'I': 1,
            'V': 5,
            'X': 10,
            'L': 50,
            'C': 100,
            'D': 500,
            'M': 1000
        }

        res = map[s[0]]
        for i in range(1, l):
            pre = map[s[i - 1]]
            cur = map[s[i]]
            if pre < cur:
                res += (cur - 2 * pre)
            else:
                res += cur
        return res

(本节完)

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