关于leetcode:用javascript分类刷leetcode4贪心图文视频讲解

32次阅读

共计 11532 个字符,预计需要花费 29 分钟才能阅读完成。

什么是贪婪算法

贪婪法,又称贪婪算法,贪心算法,在对问题求解时,总是做出在以后看来最好的抉择,冀望通过每个阶段的部分最优抉择达到全局最优,但后果不肯定最优

实用场景:简略的说,问题可能分解成子问题来解决,子问题的最优解能递推到最终问题的最优解,就能用贪婪算法的到最初的最优解,这种子问题最优解称为最优子结构

贪婪算法与动静布局的不同点在于它对每个子问题的解决方案都做出以后的最优抉择,不能回退,而动静布局会保留之前的运算后果,并依据之前的后果进行抉择,有回退的性能,贪婪是动静布局的理想化的状况。

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. 动静布局

  • 思路:依据题意只能持有一只股票,不限度交易次数,咱们能够用动静布局来做,首先定义状态,题中有两个状态,一个是天数,一个是是否持有股票,所以咱们定义 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. 贪婪

  • 思路:因为不限度交易次数,只有明天价格比昨天高,就交易,利润为正累加,最初的和就是最大的利润,留神第一天是没有利润的,这道题之所以能够用贪婪是因为 部分最优:收集每天的正利润,能够推导出,全局最优:求得最大利润
  • 复杂度剖析:工夫复杂度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;
};

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

  • 思路:题意是一条船只能坐 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
};

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.

  • 思路:大尺寸的饼干既能够满足胃口大的孩子也能够满足胃口小的孩子,那么就应该优先满足胃口大的。排序两个数组,从右到左遍历,用大饼干首先满足胃口大的小孩
  • 复杂度:工夫复杂度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;
};

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

  • 思路:区间依照结尾从小到大排序,循环数组,如果前面一个区间的开始大于前一个区间的结尾 就须要新增一支箭。
  • 复杂度:工夫复杂度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;
};

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]

  • 思路:先排个数最多的工作 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 中取较大的一个
}

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

  • 思路:首先判断总油量是否小于总油耗,如果是则必定不能走一圈。如果否,那必定能跑一圈。接下来就是循环数组,从第一个站开始,计算每一站残余的油量,如果油量为负了,就以这个站为终点从新计算。如果到达某一个点为负,阐明终点到这个点两头的所有站点都不能到达该点。
  • 复杂度:工夫复杂度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;
};

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. 贪婪

  • 思路:不必思考每一步跳跃到那个地位,而是尽可能的跳跃到最远的地位,看最多能笼罩的地位,不断更新能笼罩的间隔。
  • 复杂度:工夫复杂度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 就是不能达到起点
};

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. 动静布局

  • 思路: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. 贪婪

  • 思路: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 是正确的贪婪算法

视频解说:传送门

正文完
 0