用javascript分类刷leetcode4.贪心(图文视频讲解)

news2024/9/24 1:22:05

什么是贪心算法

贪心法,又称贪心算法,贪婪算法,在对问题求解时,总是做出在当前看来最好的选择,期望通过每个阶段的局部最优选择达到全局最优,但结果不一定最优

适用场景:简单的说,问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解,就能用贪心算法的到最后的最优解,这种子问题最优解称为最优子结构

贪心算法与动态规划的不同点在于它对每个子问题的解决方案都做出当前的最优选择,不能回退,而动态规划会保留之前的运算结果,并根据之前的结果进行选择,有回退的功能,贪心是动态规划的理想化的情况。

621. 任务调度器 (medium)

给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表。其中每个字母表示一种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。在任何一个单位时间,CPU 可以完成一个任务,或者处于待命状态。

然而,两个 相同种类 的任务之间必须有长度为整数 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。

你需要计算完成所有任务所需要的 最短时间 。

示例 1:

输入:tasks = [“A”,“A”,“A”,“B”,“B”,“B”], n = 2
输出:8
解释:A -> B -> (待命) -> A -> B -> (待命) -> A -> B
在本示例中,两个相同类型任务之间必须间隔长度为 n = 2 的冷却时间,而执行一个任务只需要一个单位时间,所以中间出现了(待命)状态。
示例 2:

输入:tasks = [“A”,“A”,“A”,“B”,“B”,“B”], n = 0
输出:6
解释:在这种情况下,任何大小为 6 的排列都可以满足要求,因为 n = 0
[“A”,“A”,“A”,“B”,“B”,“B”]
[“A”,“B”,“A”,“B”,“A”,“B”]
[“B”,“B”,“B”,“A”,“A”,“A”]

诸如此类
示例 3:

输入:tasks = [“A”,“A”,“A”,“A”,“A”,“A”,“B”,“C”,“D”,“E”,“F”,“G”], n = 2
输出:16
解释:一种可能的解决方案是:
A -> B -> C -> A -> D -> E -> A -> F -> G -> A -> (待命) -> (待命) -> A -> (待命) -> (待命) -> A

提示:

1 <= task.length <= 104
tasks[i] 是大写英文字母
n 的取值范围为 [0, 100]

ds_191

  • 思路:先排个数最多的任务A,在A的冷却时间内插入其他任务,先计算前n-1行n的间隔的时间大小,再计算和最大次数相同的字母个数,然后累加进ret。最后在tasks的长度和ret中取较大的一个
  • 复杂度:时间复杂度O(n),空间复杂度O(1)

js:

function leastInterval(tasks, n) {
    let arr = Array(26).fill(0);
    for (let c of tasks) {
        //统计各个字母出现的次数
        arr[c.charCodeAt() - "A".charCodeAt()]++;
    }
    let max = 0;
    for (let i = 0; i < 26; i++) {
        //找到最大次数
        max = Math.max(max, arr[i]);
    }
    let ret = (max - 1) * (n + 1); //计算前n-1行n的间隔的时间大小
    for (let i = 0; i < 26; i++) {
        //计算和最大次数相同的字母个数,然后累加进ret
        if (arr[i] == max) {
            ret++;
        }
    }
    return Math.max(ret, tasks.length); //在tasks的长度和ret中取较大的一个
}

881. 救生艇 (medium)

给定数组 people 。people[i]表示第 i 个人的体重 ,船的数量不限,每艘船可以承载的最大重量为 limit。

每艘船最多可同时载两人,但条件是这些人的重量之和最多为 limit。

返回 承载所有人所需的最小船数 。

示例 1:

输入:people = [1,2], limit = 3
输出:1
解释:1 艘船载 (1, 2)
示例 2:

输入:people = [3,2,2,1], limit = 3
输出:3
解释:3 艘船分别载 (1, 2), (2) 和 (3)
示例 3:

输入:people = [3,5,3,4], limit = 5
输出:4
解释:4 艘船分别载 (3), (3), (4), (5)

提示:

1 <= people.length <= 5 * 104
1 <= people[i] <= limit <= 3 * 104

ds_155

  • 思路:题意是一条船只能坐2人,要求尽可能的用少的船装下这些人。所以可以用贪心策略。让更多的人组成2人组,而且这些2人组的两人重量加起来不超过船的载重。所以可以先排序people,用双指针从两边向中间遍历,让重的人和轻的人组成2人组,如果当前最重的人和最轻的人的重量和超过了载重,那只能让重的人先乘一条船。
  • 复杂度:时间复杂度O(nlogn),排序的复杂度。空间复杂度O(logn),排序的栈空间

js:

var numRescueBoats = function (people, limit) {
    people.sort((a, b) => (a - b));
    let ans = 0,
        left = 0,//左指针初始化在0的位置
        right = people.length - 1 //右指针初始化在people.length - 1的位置
    while (left <= right) {//两指针向中间靠拢 遍历
        //当people[left] + people[right--]) <= limit 表示左右两边的人可以一起坐船 然后让left++ right--
        //如果两人坐不下,那只能让重的人先坐一条船 也就是让right--
        if ((people[left] + people[right--]) <= limit) {
            left++
        }

        ans++
    }
    return ans
};

55. 跳跃游戏 (medium)

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

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

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

示例 1:

输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
示例 2:

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

提示:

1 <= nums.length <= 3 * 104
0 <= nums[i] <= 105

方法1.动态规划
  • 思路:dp[i]表示能否到达位置i,对每个位置i判断能否通过前面的位置跳跃过来,当前位置j能达到,并且当前位置j加上能到达的位置如果超过了i,那dp[i]更新为ture,便是i位置也可以到达。
  • 复杂度:时间复杂度O(n^2),空间复杂度O(n)

js:

function canJump(nums) {
    let dp = new Array(nums.length).fill(false); //初始化dp
    dp[0] = true; //第一项能到达
    for (let i = 1; i < nums.length; i++) {
        for (let j = 0; j < i; j++) {
            //当前位置j能达到,并且当前位置j加上能到达的位置如果超过了i,那dp[i]更新为ture,便是i位置也可以到达
            if (dp[j] && nums[j] + j >= i) {
                dp[i] = true;
                break;
            }
        }
    }

    return dp[nums.length - 1];
}
方法2.贪心

ds_147

  • 思路:不用考虑每一步跳跃到那个位置,而是尽可能的跳跃到最远的位置,看最多能覆盖的位置,不断更新能覆盖的距离。
  • 复杂度:时间复杂度O(n),遍历一边。空间复杂度O(1)

js:

var canJump = function (nums) {
    if (nums.length === 1) return true; //长度为1 直接就是终点
    let cover = nums[0]; //能覆盖的最远距离
    for (let i = 0; i <= cover; i++) {
        cover = Math.max(cover, i + nums[i]); //当前覆盖距离cover和当前位置加能跳跃的距离中取一个较大者
        if (cover >= nums.length - 1) {
            //覆盖距离超过或等于nums.length - 1 说明能到达终点
            return true;
        }
    }
    return false; //循环完成之后 还没返回true 就是不能达到终点
};

134. 加油站(medium)

在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

给定两个整数数组 gas 和 cost ,如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。

示例 1:

输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2]
输出: 3
解释:
从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
因此,3 可为起始索引。
示例 2:

输入: gas = [2,3,4], cost = [3,4,3]
输出: -1
解释:
你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。
我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油
开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油
开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油
你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。
因此,无论怎样,你都不可能绕环路行驶一周。

提示:

gas.length == n
cost.length == n
1 <= n <= 105
0 <= gas[i], cost[i] <= 104

ds_173

  • 思路:首先判断总油量是否小于总油耗,如果是则肯定不能走一圈。如果否,那肯定能跑一圈。接下来就是循环数组,从第一个站开始,计算每一站剩余的油量,如果油量为负了,就以这个站为起点从新计算。如果到达某一个点为负,说明起点到这个点中间的所有站点都不能到达该点。
  • 复杂度:时间复杂度O(n),空间复杂度O(1)

js:

var canCompleteCircuit = function (gas, cost) {
    let totalGas = 0;
    let totalCost = 0;
    for (let i = 0; i < gas.length; i++) {
        totalGas += gas[i];
        totalCost += cost[i];
    }
    if (totalGas < totalCost) {//总油量小于总油耗 肯定不能走一圈
        return -1;
    }

    let currentGas = 0;
    let start = 0;
    for (let i = 0; i < gas.length; i++) {
        currentGas = currentGas - cost[i] + gas[i];
        if (currentGas < 0) {//如果到达下一站的时候油量为负数 就以这个站为起点 从新计算
            currentGas = 0;
            start = i + 1;
        }
    }

    return start;
};

860. 柠檬水找零 (easy)

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

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

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

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

示例 1:

输入:bills = [5,5,5,10,20]
输出:true
解释:
前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
由于所有客户都得到了正确的找零,所以我们输出 true。
示例 2:

输入:bills = [5,5,10,10,20]
输出:false
解释:
前 2 位顾客那里,我们按顺序收取 2 张 5 美元的钞票。
对于接下来的 2 位顾客,我们收取一张 10 美元的钞票,然后返还 5 美元。
对于最后一位顾客,我们无法退回 15 美元,因为我们现在只有两张 10 美元的钞票。
由于不是每位顾客都得到了正确的找零,所以答案是 false。

提示:

1 <= bills.length <= 105
bills[i] 不是 5 就是 10 或是 20

  • 思路:优先找面额大的
  • 复杂度:时间复杂度O(n),空间复杂度O(1)

js:

var lemonadeChange = function (bills) {
    let five = 0, ten = 0;
    for (const bill of bills) {
        if (bill === 5) {//面值为5 直接可以兑换柠檬水
            five += 1;
        } else if (bill === 10) {//面值为10 兑换柠檬水 还需要找5元
            if (five === 0) {
                return false;
            }
            five -= 1;
            ten += 1;
        } else {//面值为20 兑换柠檬水 需要找3个5元或一个10元一个5元
            if (five > 0 && ten > 0) {
                five -= 1;
                ten -= 1;
            } else if (five >= 3) {
                five -= 3;
            } else {
                return false;
            }
        }
    }
    return true;
};

435. 无重叠区间 (medium)

给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠 。

示例 1:

输入: intervals = [[1,2],[2,3],[3,4],[1,3]]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。
示例 2:

输入: intervals = [ [1,2], [1,2], [1,2] ]
输出: 2
解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。
示例 3:

输入: intervals = [ [1,2], [2,3] ]
输出: 0
解释: 你不需要移除任何区间,因为它们已经是无重叠的了。

提示:

1 <= intervals.length <= 105
intervals[i].length == 2
-5 * 104 <= starti < endi <= 5 * 104

方法1.动态规划

ds_143

  • 思路:dp[i]表示前i个区间中最大不重合区间的个数,首先将区间数组按左边界排序,找出intervals中最多有多少个不重复的区间,动态规划方程dp[i] = Math.max(dp[i], dp[j] + 1)。intervals的长度减去最多的不重复的区间 就是最少删除区间的个数
  • 复杂度:时间复杂度O(n^2),两层嵌套循环leetcode执行超时 复杂度过高。空间复杂度O(n),dp数组的空间

js:

//leetcode执行超时 复杂度过高
var eraseOverlapIntervals = function (intervals) {
    if (!intervals.length) {
        return 0;
    }

    intervals.sort((a, b) => a[0] - b[0]); //按左边界排序
    const n = intervals.length;
    const dp = new Array(n).fill(1); //初始化dp数组

    for (let i = 1; i < n; i++) {
        for (let j = 0; j < i; j++) {
            //循环i,j找出intervals中最多有多少个不重复的区间
            //j的右边界小于i的左边界 相当于多出了一个不重合区间
            if (intervals[j][1] <= intervals[i][0]) {
                dp[i] = Math.max(dp[i], dp[j] + 1); //更新dp[i]
            }
        }
    }
    return n - Math.max(...dp); //n减去最多的不重复的区间 就是最少删除区间的个数
};
方法2.贪心

ds_142

  • 思路:intervals按右边界排序,然后从左往右遍历,右边界结束的越早,留给后面的区间的空间就越大,不重合的区间个数就越多,intervals的长度减去最多的不重复的区间 就是最少删除区间的个数
  • 复杂度:时间复杂度O(nlogn),数组排序O(nlogn),循环一次数组O(n)。空间复杂度O(logn),排序需要的栈空间

js:

var eraseOverlapIntervals = function (intervals) {
    if (!intervals.length) {
        return 0;
    }

    //按右边界排序,然后从左往右遍历,右边界结束的越早,留给后面的区间的空间就越大,不重合的区间个数就越多
    intervals.sort((a, b) => a[1] - b[1]);

    const n = intervals.length;
    let right = intervals[0][1]; //right初始化为第一个区间的右边界
    let ans = 1; //最多的不重合区间的个数
    for (let i = 1; i < n; ++i) {
        //循环区间数组
        if (intervals[i][0] >= right) {
            //当区间的左边界大于上一个区间的右边界的时候 说明是一对不重合区间
            ++ans; //ans加1
            right = intervals[i][1]; //更新right
        }
    }
    return n - ans; //intervals的长度减去最多的不重复的区间 就是最少删除区间的个数
};

能不能用贪心算法需要满足贪心选择性,贪心算法正确的的证明可以用反证法

以这一题为例:

  • 我们的思路是保留最多的不重合的区间,所以按照区间结尾排序,区间结尾结束的越早且和前一个区间不重叠的,就加入最多不重复的区间中,我们称为算法a,假如算法a中的某一个步骤是选择区间[a, b],我们称为区间A。
  • 假设这个选择不正确,也就是说算法a得到的不是最优解。
  • 我们假设存在另一个算法c能得到最优解,算法c中的一个步骤是选择区间[c, d],我们称为区间C,使得它是最优解中的一个区间,其中d>b,因为算法a选择的是结尾最先结束且不重合的区间,如果算法a不正确,又因为区间数组中的区间是固定的,则其他算法c肯定存在d>b的情况。
  • 我们用区间A替换区间C完全不影响算法c的结果,因为b<d,所以不影响区间C后面区间的结果。所以我们选择了区间A也构成了一个最优解。而我们假设的是选择区间A不是最优解,所以和之前的假设矛盾,所以算法a是正确的贪心算法

452. 用最少数量的箭引爆气球 (medium)

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。

一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足  xstart ≤ x ≤ xend,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。

给你一个数组 points ,返回引爆所有气球所必须射出的 最小 弓箭数 。

示例 1:

输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:气球可以用2支箭来爆破:
-在x = 6处射出箭,击破气球[2,8]和[1,6]。
-在x = 11处发射箭,击破气球[10,16]和[7,12]。
示例 2:

输入:points = [[1,2],[3,4],[5,6],[7,8]]
输出:4
解释:每个气球需要射出一支箭,总共需要4支箭。
示例 3:

输入:points = [[1,2],[2,3],[3,4],[4,5]]
输出:2
解释:气球可以用2支箭来爆破:

  • 在x = 2处发射箭,击破气球[1,2]和[2,3]。
  • 在x = 4处射出箭,击破气球[3,4]和[4,5]。

提示:

1 <= points.length <= 105
points[i].length == 2
-231 <= xstart < xend <= 231 - 1

ds_163

  • 思路:区间按照结尾从小到大排序,循环数组,如果后面一个区间的开始大于前一个区间的结尾 就需要新增一支箭。
  • 复杂度:时间复杂度O(nlogn),排序的复杂度O(nlogn),循环数组的复杂度O(n)。空间复杂度O(logn),排序栈空间

js:

var findMinArrowShots = function (points) {
    if (!points.length) {
        return 0;
    }

    points.sort((a, b) => a[1] - b[1]); //按照区间结尾排序
    let pos = points[0][1];
    let ans = 1;
    for (let balloon of points) {
        if (balloon[0] > pos) {
            //如果后面一个区间的开始大于前一个区间的结尾 就需要新增一支箭
            pos = balloon[1]; //更新pos为新的区间的结尾
            ans++;
        }
    }
    return ans;
};

455. 分发饼干 (easy)

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

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

示例 1:

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

输入: g = [1,2], s = [1,2,3]
输出: 2
解释:
你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
你拥有的饼干数量和尺寸都足以让所有孩子满足。
所以你应该输出2.

ds_141

  • 思路:大尺寸的饼干既可以满足胃口大的孩子也可以满足胃口小的孩子,那么就应该优先满足胃口大的。排序两个数组,从右到左遍历,用大饼干首先满足胃口大的小孩
  • 复杂度:时间复杂度O(mlogm + nlogn)。空间复杂度O(logm + logn)

js:

var findContentChildren = function (g, s) {
    g = g.sort((a, b) => a - b);
    s = s.sort((a, b) => a - b); //排序数组
    let result = 0;
    let index = s.length - 1;
    for (let i = g.length - 1; i >= 0; i--) {
        //从胃口大的小孩开始满足
        if (index >= 0 && s[index] >= g[i]) {
            result++; //结果加1
            index--;
        }
    }
    return result;
};

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

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

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

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

示例 1:

输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
  随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。
总利润为 4 + 3 = 7 。
示例 2:

输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
  总利润为 4 。
示例 3:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0 。

提示:

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

方法1.动态规划

ds_67

  • 思路:根据题意只能持有一只股票,不限制交易次数,我们可以用动态规划来做,首先定义状态,题中有两个状态,一个是天数,一个是是否持有股票,所以我们定义dp[i][0]表示第 i天交易完后手里没有股票的最大利润,dp[i][1] 表示第 i天交易完后手里持有一支股票的最大利润,接下来就是定义状态转移方程:

    1. 假如当前的状态是dp[i][0],表示手中没股票,则可由前一天的两种情况转移过来,第一种是dp[i-1][0],表示前一天手里没股票,而且今天没做任何操作。第二种是dp[i-1][1],表示前一天持有股票,但是今天卖了,所以收益是dp[i-1][1]+prices[i],我们需要求出这两种情况下的最大值就是最大利润,状态转移方程就是:

      dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);

    2. 假如当前的状态是dp[i][1],表示手中有股票,则可由前一天的两种情况转移过来,第一种是dp[i−1][1],表示前一天手中有股票,即是今天没做任何操作。第二种是dp[i−1][0],表示前一天没有股票,但是今天买进了,所以收益是dp[i-1][1]-prices[i],我们需要求出这两种情况下的最大值就是最大利润,状态转移方程就是:

      dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);

    由上面的状态转移方程我们知道,当前天的最大收益,只与前一天的状态相关,所以我们可以不用定义二维数组来存放状态,只需要将dp[i - 1][0]dp[i - 1][1]存放在变量中。

  • 复杂度分析:时间复杂度:O(n),n是数组长度,每天有持有股票或者没持有两种状态,一共2n的状态转移次数,时间复杂度就是O(2n),时间复杂度和常系数无关,所以时间复杂度就是O(n)。空间复杂度O(n),因为要开辟n的空间存放状态,虽然是二维数组,但是第二维是常数。如果进行了状态压缩,空间复杂度可以优化到O(1)

js:

var maxProfit = function (prices) {
    const n = prices.length;
    const dp = new Array(n).fill(0).map((v) => new Array(2).fill(0)); //初始化状态数组
    (dp[0][0] = 0), (dp[0][1] = -prices[0]); //3.定义初始值
    for (let i = 1; i < n; ++i) {
        //1.确定状态
        //2.推导状态转移方程
        //当前没持有股票,可由前一天的两种状态转移过了,
          //1是前一天没持有,今天不动,2是前一天持有,今天卖掉,求这两种情况的较大值
        dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
        //当前持有股票,可由前一天的两种状态转移过了,
          //1是前一天持有,今天不动,2是前一天没持有,今天买入,求这两种情况的较大值
        dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
    }
    //4.确定输出值
    return dp[n - 1][0]; //返回第n-1天的最大值
};

//空间压缩
var maxProfit = function (prices) {
    const n = prices.length;
    let dp0 = 0,
        dp1 = -prices[0];
    for (let i = 1; i < n; ++i) {
        let newDp0 = Math.max(dp0, dp1 + prices[i]);
        let newDp1 = Math.max(dp1, dp0 - prices[i]);
        dp0 = newDp0;
        dp1 = newDp1;
    }
    return dp0;
};
方法2.贪心

ds_56

  • 思路:因为不限制交易次数,只要今天价格比昨天高,就交易,利润为正累加,最后的和就是最大的利润,注意第一天是没有利润的,这道题之所以可以用贪心是因为局部最优:收集每天的正利润,可以推导出,全局最优:求得最大利润
  • 复杂度分析:时间复杂度O(n),n是数组的长度。空间复杂度是O(1)

js:

var maxProfit = function (prices) {
    let ans = 0;
    let n = prices.length;
    for (let i = 1; i < n; ++i) {
        //今天价格和昨天的差值是否为正,如果为正累加进去,为负则加0
        ans += Math.max(0, prices[i] - prices[i - 1]);
    }
    return ans;
};

视频讲解:传送门

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/140919.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

手把手YOLOv5输出热力图

环境要求 我的版本是YOLOV5 7.0 先看结果&#xff1a; 结果仅供参考 具体步骤一&#xff1a; 首先配置好YOLO V5环境 这个采用pip install requirements即可 具体配置环境可以看我其他的博客有详细介绍 GPU环境自己配置 步骤二&#xff1a; 运行YOLO 没问题&#xff0c;输…

Excel表格的导入导出——EasyExcel

参考视频 csdn参考地址 一、导入依赖 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.0.5</version> </dependency>二、实体类 方式一&#xff1a;Excel Property&#xff08;&…

Kubernetes Pod 底层实现原理

文章目录前言一、探索 Container1.1 设置实验环境&#xff08;playground&#xff09;1.2 探索容器的 namespace1.3 探索容器的 cgroupsCheck the memory limit.二、探索 Pod2.1 设置实验环境&#xff08;playground&#xff09;2.2 探索 Pod 的容器2.3 探索 Pod 的命名空间2.4…

UDP协议重点总结(附实例)

文章目录前言一、网络的原生情况二、UDP协议2.1 UDP的特点2.1.1 不可靠性2.1.2 无连接&#xff08;不是缺点&#xff09;2.1.3 面向数据报&#xff08;优点&#xff09;2.1.4 缓冲区2.1.5 大小受限2.2 UDP协议端格式2.3 关于校验和2.4 基于UDP的应用层协议三、UDP总结&#xff…

P2279 [HNOI2003]消防局的设立

[HNOI2003]消防局的设立题目描述2020 年&#xff0c;人类在火星上建立了一个庞大的基地群&#xff0c;总共有 n 个基地。起初为了节约材料&#xff0c;人类只修建了 n-1 条道路来连接这些基地&#xff0c;并且每两个基地都能够通过道路到达&#xff0c;所以所有的基地形成了一个…

HTML5本地存储详解

html5 本地存储。前言一、localStorage 对象二、sessionStorage 对象三、localstorage 与 cookie 的区别四、localStorage 和 sessionStorage 二者的区别总结前言 ☀️本地存储是指在客户端存储数据&#xff0c;HTML5 为我们提供了两种 API&#xff0c;分别是 localStorage 与 …

算法是如何炼成的?

一、算 法 简 史算法可以追溯到古代埃及人和古希腊人使用的算术方法。在古代埃及&#xff0c;人们使用简单的加减法来解决基本的数学问题&#xff0c;而在古希腊&#xff0c;人们开始使用更加复杂的算术方法&#xff0c;比如平方、立方、平方根和立方根。随着数学的发展&#x…

css元素转换(旋转函数、rotateX 和 rotateY 的使用、移动函数、缩放函数、过渡、动画)详解

文章目录旋转函数rotateX 和 rotateY 的使用移动函数缩放函数过渡transition-timing-function 属性动画旋转函数 在 CSS3 中&#xff0c;使用 rotate 函数能够让指定的元素对象绕原点旋转&#xff0c;主要在二维空间内进行操作。 其语法格式如下所示&#xff1a; transform: …

【C++常用算法】STL基础语法学习 | 查找算法

目录 ●find ●find_if ●adjacent_find ●binary_ search ●count ●count_if ●find 1.功能描述&#xff1a; 查找指定元素&#xff0c;如果找到则放回指定元素的迭代器&#xff0c;若未找到则返回结束迭代器。 2.查看find定义下底层代码的函数原型&#xff1a; 3.…

Day849.ThreadLocal线程本地存储模式 -Java 性能调优实战

ThreadLocal线程本地存储模式 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于ThreadLocal线程本地存储模式的内容。 民国年间某山东省主席参加某大学校庆演讲&#xff0c;在篮球场看到十来个人穿着裤衩抢一个球&#xff0c;观之实在不雅&#xff0c;于是怒斥学校的…

用于安全医疗保健系统的基于机器学习的可伸缩区块链架构

文章目录背景相关技术简介区块链扩张性电子病历数据安全安全医疗保健的架构基于可扩展区块链架构的机器学习概述基于可扩展区块链架构的机器学习工作流程小结摘要从3.0到4.0的工业革命已经改变了医疗保健环境。患者电子健康记录(EHR)与医学研究机构共享&#xff0c;用于临床研究…

12月榜单丨B站UP主排行榜(飞瓜数据B站)发布!

飞瓜轻数发布2022年12月飞瓜数据UP主排行榜&#xff08;B站平台&#xff09;&#xff0c;通过充电数、涨粉数、成长指数三个维度来体现UP主账号成长的情况&#xff0c;为用户提供B站号综合价值的数据参考&#xff0c;根据UP主成长情况用户能够快速找到运营能力强的B站UP主。飞瓜…

Python:python简介

1&#xff1a;特点 一种解释型&#xff0c;面向对象&#xff0c;动态数据类型的开源高级程序设计语言 其特点就是&#xff1a;优雅&#xff0c;明确&#xff0c;简单&#xff0c;完善的基础代码库和大量的第三方库。 2&#xff1a;解释VS解释 3&#xff1a;应用场景 python…

基于androidstudio校园快递APP系统的设计与实现

1.课题背景及研究的目的和意义 1.1 课题背景 在其发展速度可谓一日千里的电子商务时代&#xff0c;大学生群体已成为网络购物群体中不可或缺的一部分。因此&#xff0c;高校师生对网购的需求也愈来愈强烈&#xff0c;校园快递的问题也成为了焦点&#xff0c;其中校园快递代理…

98%的数据被浪费,企业该如何释放数据价值?

在数字经济时代&#xff0c;对于广大企业来说&#xff0c;数据就是生产资料&#xff0c;算力则是生产力。飞速增长的业务数据&#xff0c;为现代企业提供了最具价值的资产。然而另一方面&#xff0c;如何存储、清理、管理、挖掘、运用数据&#xff0c;也给广大企业提出了艰巨的…

4天带你上手HarmonyOS ArkUI开发——《HarmonyOS ArkUI入门训练营之健康生活实战》

《HarmonyOS ArkUI入门训练营之健康饮食应用》是面向入门开发者打造的实战课程系列。特邀华为终端BG高级开发工程师作为本次训练营讲师&#xff0c;以健康饮食为例&#xff0c;开展技术教学及实战案例分享&#xff0c;助力入门开发者快速提升技能实力进阶。 目标学员 入门开发者…

apache httpClient关于cookie解析的报错处理

报错信息&#xff1a;o.a.h.c.p.ResponseProcessCookies - Invalid cookie header: "Set-Cookie: account"xxxxx"; expiresFri, 03 Feb 2023 06:02:40 GMT; httponly; Path/". Invalid expires attribute: Fri, 03 Feb 2023 06:02:40 GMThttpClient版本&am…

4年翻4倍年薪30W+的测试工程师个人成长之路

欢迎同行来交流&#xff0c;wx 群二维码应该过不了审核&#xff0c;私聊要把。税收图保证真实性。 一、何为测试 简单做一下科普。测试简而言之就是应用上线前&#xff0c;验证应用是否存在bug&#xff0c;是否满足产品的需求。大家津津乐道的程序员&#xff0c;也就是开发&am…

stm32 的 ESP8266 wifi 模块 (ESP - 12s) 的使用

1. ESP8266 的器件介绍 2. ESP2866外设 的引脚 3. 我所用的的ESP2866 的引脚图 4. 代码 编程的串口 5.wifi 的指令 1. AT 测试指令 2. ATRST 重启模块 3. ATGMR 查看版本信息 4. ATRESTORE 恢复出厂设置 5. ATUART115200,8,1,0,0 串口设置 串口号&#xff…

【SpringBoot应用篇】SpringBoot 业务代码中常用技巧

【SpringBoot应用篇】SpringBoot 业务代码中常用技巧自定义拦截器自定义过滤器过滤器和拦截器的区别获取Spring容器对象BeanFactoryAware接口ApplicationContextAware接口ApplicationListener接口全局异常处理类型转换器参数解析器Import导入配置普通类配置类ImportSelectorImp…