【算法思维】-- 贪心算法

OJ须知:

  • 一般而言,OJ在1s内能接受的算法时间复杂度:10e8 ~ 10e9之间(中值5*10e8)。在竞赛中,一般认为计算机1秒能执行 5*10e8 次计算
时间复杂度 取值范围
o(log2n) 大的离谱
O(n) 10e8
O(nlog(n)) 10e6
O(nsqrt(n))) 10e5
O(n^2) 5000
O(n^3) 300
O(2^n) 25
O(3^n) 15
O(n!)

11

时间复杂度排序:o(1) < o(log2n) < o(n) < o(nlog2n) < o(n^2) < o(n^3) < o(2^n) < o(2^n) < o(3^n) < o(n!)


目录

算法思想

算法局限性

过程

案例

平衡分割字符串⭐

方法一:贪心

复杂度分析

买股票的最佳时机2⭐⭐

方法一:贪心 

复杂度分析

跳跃游戏⭐⭐

方法一:贪心(nums[i] == 0为核心)

复杂度分析

方法二:贪心(nums[i]为核心)


算法思想

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

融汇贯通的理解:

        贪心算法是不会考虑全局的,其每一步都会确定当前其所看到的场景下的最优的一个解  —— 整个贪心问题的解:每一步的最优解叠加之后的结果。

        有一个概念,就叫做最优子结构:每一个子问题,其的最优解最终叠加之后,能够作为最终问题的解,那就是最优子结构

融汇贯通的理解:

        所以,如果想用贪心算法,首先就要去确定一下,这个问题是不是最优子结构(将问题进行一定的缩小,查看缩小版是否符合最优子结构

算法局限性

        用贪心算法的时候,其如果不是一个最优子结构,那我们得到的这一个解,其不一定是全局的一个最优解,有可能是一个局部的最优解。

  • 不能保证求得的最后解是最佳的
  • 不能用来求最大值最小值的问题
  • 只能求满足某些约束条件可行解的范围比

过程

  1. 建立数学模型来描述问题
  2. 把求解的问题分成若干个子问题
  3. 对每一子问题求解,得到子问题的局部最优解
  4. 把子问题的局部最优解合成原来解问题的一个解

        说白了就是:假设一个问题比较复杂,暂时找不到全局最优解,那么我们可以考虑把原问题拆成几个小问题(分而治之的思想),分别求每个小问题的最优解,再把这些 “局部最优解” 叠起来,就 “当作” 整个问题的最优解了。

案例

        在我们排序所学知识中,选择排序就很好的诠释了贪心算法的使用。

【算法思维】-- 贪心算法_第1张图片

        其每一次的排序都是选择未排序的区间内的最小值。 所以这个地方每一个子问题,其的最优解最终叠加之后,能够作为最终问题的解,所以是一个最优子结构

//直接插入排序
// 对比 插入排序,谁更小。
void SelectSort(int* a, int n)
{
	assert(a);
	int begin_i = 0, end_i = n - 1;
	while (begin_i < end_i)
	{
        // 贪心算法: 每次从未排序数组中找到最小值
		int min_i = begin_i;
		for (int i = begin_i + 1; i <= end_i; ++i)
		{
			if (a[i] < a[min_i])
				min_i = i;
		}

		Swap(&a[begin_i], &a[min_i]);
		++begin_i;
	}
}

        贪心算法:每次找当前情况的最优解,不用考虑上一步是什么 / 下一步是什么。

平衡分割字符串⭐

1221. 分割平衡字符串 - 力扣(LeetCode)


平衡字符串 中,'L' 和 'R' 字符的数量是相同的。

给你一个平衡字符串 s,请你将它分割成尽可能多的子字符串,并满足:

        每个子字符串都是平衡字符串。
返回可以通过分割得到的平衡字符串的 最大数量 。

提示:

  • 2 <= s.length <= 1000
  • s[i] = 'L' 或 'R'
  • s 是一个 平衡 字符串

方法一:贪心

抽象题中线索:

  • 整体策略:不能让平衡字符串嵌套
  • 当前情况决策:当前情况下的字符串,是否符合平衡分割字符串
class Solution {
public:
    int balancedStringSplit(string s) {
        int ret = 0;
        int balance = 0;
        for(auto ch : s)
        {
            // 当前情况决策: 当前情况下的字符串,是否符合平衡分割字符串
            ch == 'L' ? balance++ : balance--;
            if(balance == 0) ret++;
        }
        return ret;
    }
};

复杂度分析

  • 时间复杂度:O(n)。
  • 空间复杂度:O(1)。

买股票的最佳时机2⭐⭐

122. 买卖股票的最佳时机 II


给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你

也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润 。

提示:

  • 1 <= prices.length <= 3 * 10e4
  • 0 <= prices[i] <= 104

方法一:贪心 

抽象题中线索:

  • 整体策略:不能股票的购买售出嵌套
  • 当前情况决策:当前情况下的股票以最低价买入,然后以高价售出

【算法思维】-- 贪心算法_第2张图片

就是判断趋势:

  • 连续上涨:上涨的最低点买入,上涨的最高点卖出 —— 上涨区间中买卖绝对利润相同。
  • 连续下跌:不进行买卖。
class Solution {
public:
    int maxProfit(vector& prices) {
        int profit = 0;
        int buy = prices[0];
        int sell = 0;
        for(int i = 1; i < prices.size(); i++)
        {
            // 判断趋势
            if(prices[i] > prices[i - 1]) profit += prices[i] - prices[i - 1];
        }
        return profit;
    }
};

复杂度分析

  • 时间复杂度:O(n)。
  • 空间复杂度:O(1)。

跳跃游戏⭐

55. 跳跃游戏 - 力扣(LeetCode)


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

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

判断你是否能够到达最后一个下标。

提示:

  • 1 <= nums.length <= 3 * 10e4
  • 0 <= nums[i] <= 105

方法一:贪心(nums[i] == 0为核心

抽象题中线索:

  • 整体策略:不要嵌套nums[i] == 0。
  • 当前情况决策:当前情况下有nums[i] == 0,其前面的最大值是否跨的过

需要注意:最后一个数据为0,不用提出,已经到最后了。

class Solution {
public:
    bool canJump(vector& nums) {
        int max = 0;
        for(int i = 0; i < nums.size() - 1; i++)
        {
            // 当前情况决策: 当前情况下有nums[i] == 0,其前面的最大值是否跨的过
            if(nums[i] + i > max) max = nums[i] + i;
            if(nums[i] == 0)
                if(max > i) 
                    continue;
                else 
                    return false;
        }
        return true;
    }
};

        max_i 位置很有可能已经完全足够到达最后,所以我们可以增加以一次判断。

class Solution {
public:
    bool canJump(vector& nums) {
        int max_i = 0;
        for(int i = 0; i < nums.size() - 1; i++)
        {
            // 当前情况决策: 当前情况下有nums[i] == 0,其前面的最大值是否跨的过
            if(nums[i] + i > max_i)
                if((max_i = nums[i] + i) >= nums.size() - 1) return true;
            if(nums[i] == 0)
                if(max_i > i) 
                    continue;
                else 
                    return false;
        }
        return true;
    }
};

复杂度分析

  • 时间复杂度:O(n)。
  • 空间复杂度:O(1)。

方法二:贪心(nums[i]为核心

抽象题中线索:

  • 整体策略:不要嵌套nums[i + 1]。
  • 当前情况决策:当前位置 i 前述最大值是否可以到达。
class Solution {
public:
    bool canJump(vector& nums) {
        int max_i = 0;
        for(int i = 0; i < nums.size(); i++)
        {
            // 当前情况决策: 当前位置 i 前述最大值是否可以到达。
            if(max_i >= i)
            {
                max_i = max(max_i, nums[i] + i);
                // max_i 位置已经完全足够到达最后
                if(max_i >= nums.size() - 1) return true;
            }
            else return false;
        }
        return true;
    }
};

你可能感兴趣的:(算法思维,算法,贪心算法,c++)