贪心算法

263 阅读7分钟

定义        

        贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择。也就是说,不从整体最优上加以考虑,做出的只是在某种意义上的局部最优解。选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关,这点和动态规划一样。贪心策略和动态规划类似,多数情况都是用来处理极值问题。

贪心算法常见问题

跳跃游戏

给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个位置。

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

来源:leetcode-cn.com/problems/ju…

var canJump = function(nums) {
    let max = 0; // 能够走到的数组下标
    for (let i = 0; i < nums.length; i++) {
        if (max < i) return false; // 当前这一步都走不到,后面更走不到了
        max = Math.max(nums[i] + i, max);
    }
    return true;
};

跳跃游戏 II

给定一个非负整数数组,你最初位于数组的第一个位置。 数组中的每个元素代表你在该位置可以跳跃的最大长度。 你的目标是使用最少的跳跃次数到达数组的最后一个位置。  

输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
     从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

来源:leetcode-cn.com/problems/ju…

var jump = function(nums) {
    let n = nums.length, count = 0, father = 0, end = 0;
    for (let i = 0; i < n - 1; i++) {
        father = Math.max(father, nums[i] + i)
        if(i === end) {
            count += 1;
            end = father;
        }
    }
    return count;
};

分发饼干

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

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

来源:leetcode-cn.com/problems/as…

var findContentChildren = function(g, s) {
    g.sort((a, b) => a - b);
    s.sort((a, b) => a - b);
    let count = 0, idx = 0;
    for (let i = 0; i < s.length; i++) {
        if(s[i] >= g[idx]) {
            count++;
            idx++;
            if(idx >= g.length) {
                break;
            }
        }
    }
    return count;
};

优势洗牌

给定两个大小相等的数组 A 和 B,A 相对于 B 的优势可以用满足 A[i] > B[i] 的索引 i 的数目来描述。 返回 A 的任意排列,使其相对于 B 的优势最大化。  

输入:A = [2,7,11,15], B = [1,10,4,11]
输出:[2,11,7,15]

来源:leetcode-cn.com/problems/ad…

var advantageCount = function(A, B) {
    //排序
    A.sort((a,b)=>a-b);
    const getMinLargeNum = function(num){
        for(let i=0;i<A.length;i++){
            //找到第一个比num大的数,返回
            if(num<A[i]){
                return i;
            }
        }
        //找不到,取第一个,因为第一个是最小的,是下等马
        return 0;
    }
    let ans = [];
    for(let i=0;i<B.length;i++){
        let idx = getMinLargeNum(B[i]);
        ans.push(A[idx]);
        A.splice(idx,1);
    }
    return ans;
};

摆动序列

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为**摆动序列。**第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。
例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。  
给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。

输入: [1,17,5,10,13,15,10,5,16,8]
输出: 7
解释: 这个序列包含几个长度为 7 摆动序列,其中一个可为[1,17,10,13,10,16,8]。

来源:leetcode-cn.com/problems/wi…

var wiggleMaxLength = function(nums) {    if(nums.length < 2) return nums.length;    let down = 1, up = 1;    for (let i = 1; i < nums.length; i++) {        if(nums[i] > nums[i - 1]) {            up = down + 1;        } else if(nums[i] < nums[i - 1]) {            down = up + 1;        }    }    return Math.max(down, up);};

不含 AAA 或 BBB 的字符串

给定两个整数 AB,返回任意字符串 S,要求满足:

  • S 的长度为 A + B,且正好包含 A 个 'a' 字母与 B 个 'b' 字母;

  • 子串 'aaa' 没有出现在 S 中;

  • 子串 'bbb' 没有出现在 S 中。  

    输入:A = 1, B = 2 输出:"abb" 解释:"abb", "bab" 和 "bba" 都是正确答案。

来源:leetcode-cn.com/problems/st…

var strWithout3a3b = function(A, B) {
    let ans = "";
    let a = 0, b = 0;
    while(A > 0 || B > 0) {
        while(A > 0 && ((A >= B && a < 2) || b == 2)) {
            ans += 'a';
            A--;
            a++;
            b = 0;
        }
        while(B > 0 && ((B >= A && b < 2) || a == 2)) {
            ans += 'b';
            B--;
            b++;
            a = 0;
        }
    }
    return ans;
};

分割数组为连续子序列

给你一个按升序排序的整数数组 num(可能包含重复数字),请你将它们分割成一个或多个子序列,其中每个子序列都由连续整数组成且长度至少为 3 。 如果可以完成上述分割,则返回 true ;否则,返回 false 。  

输入: [1,2,3,3,4,5]
输出: True
解释:
你可以分割出这样两个连续子序列 : 
1, 2, 3
3, 4, 5

来源:leetcode-cn.com/problems/sp…

var isPossible = function(nums) {
    const counter = {}
    for(let i of nums){
        counter[i] ? counter[i]++: counter[i] = 1
    }
    const tails = {}
    for(let item of nums) {
        if(tails[item]) {
            tails[item]--;
            tails[item +1 ] ? tails[item + 1]++ : tails[item +1 ]=1
        } else if(counter[item + 1] && counter[item + 2]) {
            counter[item + 1]--;
            counter[item + 2]--;
            tails[item + 1] ? tails[item + 1]++: tails[item + 1]=1
        } else {
            return false
        }
        counter[item]--;
    }
    return true;
};

划分字母区间

字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。

输入:S = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca", "defegde", "hijhklij"。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。

来源:leetcode-cn.com/problems/pa…

var partitionLabels = function(S) {
    let res = [];
    let map = new Map(), start = 0, end = 0;
    for (let i = 0; i < S.length; i++) {
        map[S[i]] = i;
    }
    for (let j = 0; j < S.length; j++) {
        end = Math.max(end, map[S[j]]);
        if(j === end) {
            res.push(end - start + 1);
            start = j + 1;
        }
    }
    return res;
};

非递增顺序的最小子序列

给你一个数组 nums,请你从中抽取一个子序列,满足该子序列的元素之和 严格 大于未包含在该子序列中的各元素之和。
如果存在多个解决方案,只需返回 长度最小 的子序列。如果仍然有多个解决方案,则返回 元素之和最大 的子序列。
与子数组不同的地方在于,「数组的子序列」不强调元素在原数组中的连续性,也就是说,它可以通过从数组中分离一些(也可能不分离)元素得到。
注意,题目数据保证满足所有约束条件的解决方案是 唯一 的。同时,返回的答案应当按 非递增顺序 排列。

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

来源:leetcode-cn.com/problems/mi…

var minSubsequence = function(nums) {
    let arr = new Array(101).fill(0);
    let i, sum = 0, s = 0;
    for (let i = 0; i < nums.length; ++i) {
        sum += nums[i];
        arr[nums[i]]++;
    }
    let ans = [];
    for (i = 100; i >= 0; --i) {
        while(arr[i]--) {
            if(s + i > sum - i) {
                ans.push(i)
                return ans;
            } else {
                s += i;
                sum -= i;
                ans.push(i)
            }
        }
    }
    return ans;
};

玩筹码

数轴上放置了一些筹码,每个筹码的位置存在数组 chips 当中
你可以对 任何筹码 执行下面两种操作之一(不限操作次数,0 次也可以):
将第 i 个筹码向左或者右移动 2 个单位,代价为 0
将第 i 个筹码向左或者右移动 1 个单位,代价为 1
最开始的时候,同一位置上也可能放着两个或者更多的筹码。返回将所有筹码移动到同一位置(任意位置)上所需要的最小代价。

输入:chips = [1,2,3]
输出:1
解释:第二个筹码移动到位置三的代价是 1,第一个筹码移动到位置三的代价是 0,总代价为 1。

来源:leetcode-cn.com/problems/mi…

var minCostToMoveChips = function(chips) {
    let odd = 0, even = 0;
    for (let i = 0; i < chips.length; i++) {
        if(chips[i] % 2 == 0) {
            even++
        } else {
            odd++
        }
    }
    return Math.min(even, odd)
};

 K 次取反后最大化的数组和

给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。(我们可以多次选择同一个索引 i。) 以这种方式修改数组后,返回数组可能的最大和。  

输入:A = [3,-1,0,2], K = 3
输出:6
解释:选择索引 (1, 2, 2) ,然后 A 变为 [3,1,0,2]

来源:leetcode-cn.com/problems/ma…

var largestSumAfterKNegations = function(A, K) {
    A.sort((a, b) => a - b);
    for (let i = 0; i < K; i++) {
        A[0] = -A[0];
        A.sort((a, b) => a - b);
    }
    let sum = A.reduce((prev, cur) => {
        return prev + cur
    })
    return sum;
};

柠檬水找零

在柠檬水摊上,每一杯柠檬水的售价为 5 美元。 顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。 每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。 注意,一开始你手头没有任何零钱。 如果你能给每位顾客正确找零,返回 true ,否则返回 false 。  

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

来源:leetcode-cn.com/problems/le…

var lemonadeChange = function(bills) {
    var five = 0, ten = 0, len = bills.length;
    for(let i = 0; i < bills.length; i++) {
        if(bills[i] == 5) {
            five++
        } else if(bills[i] == 10) {
            if(five == 0) {
                return false;
            }
            five--;
            ten++;
        } else if(bills[i] == 20) {
            if(ten > 0 && five > 0) {
                ten--;
                five--
            } else if(five >= 3) {
                five -= 3;
            } else {
                return false;
            }
        }
    }
    return true;
};

最后一块石头的重量

有一堆石头,每块石头的重量都是正整数。
每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 xy,且 x <= y。那么粉碎的可能结果如下:

  • 如果 x == y,那么两块石头都会被完全粉碎;
  • 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x

最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0

输入:[2,7,4,1,8,1]
输出:1
解释:
先选出 78,得到 1,所以数组转换为 [2,4,1,1,1],
再选出 24,得到 2,所以数组转换为 [2,1,1,1],
接着是 21,得到 1,所以数组转换为 [1,1,1],
最后选出 11,得到 0,最终数组转换为 [1],这就是最后剩下那块石头的重量。

来源:leetcode-cn.com/problems/la…

var lastStoneWeight = function(stones) {
    let len = stones.length;
    while(len > 1) {
        stones.sort((a, b) => a - b);
        let x = stones[len - 2], y = stones[len - 1];
        if(x != y) {
            stones.pop();
            len = stones.length;
            stones[len - 1] = y - x;
        } else {
            stones.pop();
            stones.pop();
            len = stones.length;
        }
    }
    return len < 1 ? 0 : stones[0]
};

加油站

在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。 如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -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 可为起始索引。

来源:leetcode-cn.com/problems/ga…

var canCompleteCircuit = function(gas, cost) {
    let n = gas.length;
    let total_tank = 0;
    let cur_tank = 0;
    let starting_station = 0;
    for (let i = 0; i < n; i++) {
        total_tank += gas[i] - cost[i];
        cur_tank += gas[i] - cost[i];
        if(cur_tank < 0) {
            starting_station = i + 1;
            cur_tank = 0
        }
    }
    return total_tank >= 0 ? starting_station : -1;
};

分发糖果

老师想给孩子们分发糖果,有N个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。
你需要按照以下要求,帮助老师给这些孩子分发糖果:

  • 每个孩子至少分配到 1 个糖果。
  • 相邻的孩子中,评分高的孩子必须获得更多的糖果。

那么这样下来,老师至少需要准备多少颗糖果呢?

输入: [1,0,2]
输出: 5
解释: 你可以分别给这三个孩子分发 2、1、2 颗糖果。

来源:leetcode-cn.com/problems/ca…

var candy = function(ratings) {
    var left = new Array(ratings.length).fill(1);
    var right = new Array(ratings.length).fill(1);
    for (var i = 1; i < ratings.length; i++) {
        if(ratings[i] > ratings[i-1])
            left[i] = left[i-1] + 1;
    }
    let count = left[ratings.length - 1];
    for (let j = ratings.length - 2; j >= 0; j-- ) {
        if(ratings[j] > ratings[j+1]) {
            right[j] = right[j+1] + 1;
        }
        count += Math.max(left[j], right[j])
    }
    return count;
};

灌溉花园的最少水龙头数目

在 x 轴上有一个一维的花园。花园长度为 n,从点 0 开始,到点 n 结束。 花园里总共有 n + 1 个水龙头,分别位于 [0, 1, ..., n] 。 给你一个整数 n 和一个长度为 n + 1 的整数数组 ranges ,其中 ranges[i] (下标从 0 开始)表示:如果打开点 i 处的水龙头,可以灌溉的区域为 [i - ranges[i], i + ranges[i]] 。 请你返回可以灌溉整个花园的 最少水龙头数目 。如果花园始终存在无法灌溉到的地方,请你返回 -1 。

输入:n = 5, ranges = [3,4,1,1,0,0]
输出:1
解释:
点 0 处的水龙头可以灌溉区间 [-3,3]
点 1 处的水龙头可以灌溉区间 [-3,5]
点 2 处的水龙头可以灌溉区间 [1,3]
点 3 处的水龙头可以灌溉区间 [2,4]
点 4 处的水龙头可以灌溉区间 [4,4]
点 5 处的水龙头可以灌溉区间 [5,5]
只需要打开点 1 处的水龙头即可灌溉整个花园 [0,5]

来源:leetcode-cn.com/problems/mi…

var minTaps = function(n, ranges) {
    let land = Array.from(new Array(n).fill(0));
    for (let i = 0; i < ranges.length; i++) {
        let l = Math.max(i - ranges[i], 0);
        let r = Math.min(i + ranges[i], n);
        for (let j = l; j < r; j++) {
            land[j] = Math.max(land[j], r)
        }
    }
    let count = 0, cur = 0;
    while(cur < n) {
        if(land[cur] == 0) return -1;
        cur = land[cur];
        count++;
    }
    return count; 
};

无重叠区间

给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
注意:可以认为区间的终点总是大于它的起点。区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。

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

来源:leetcode-cn.com/problems/no…

var eraseOverlapIntervals = function(intervals) {
    if (intervals.length === 0) return 0;
    intervals.sort((a, b) => a[1] - b[1]);
    var cnt = 1;
    var end = intervals[0][1];
    for(let i=1;i<intervals.length;i++) {
        if(intervals[i][0] < end) {
            continue;
         }
        end = intervals[i][1];
        cnt++;
    }
    return intervals.length - cnt;
};

种花问题

假设你有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花卉不能种植在相邻的地块上,它们会争夺水源,两者都会死去。 给定一个花坛(表示为一个数组包含0和1,其中0表示没种植花,1表示种植了花),和一个数 n 。能否在不打破种植规则的情况下种入 n 朵花?能则返回True,不能则返回False。  

输入: flowerbed = [1,0,0,0,1], n = 1
输出: True
输入: flowerbed = [1,0,0,0,1], n = 2
输出: False

来源:leetcode-cn.com/problems/ca…

var canPlaceFlowers = function(flowerbed, n) {
    let num = 0, count = 1;
    for  (let i = 0; i < flowerbed.length; i++) {
        if(flowerbed[i] === 0) {
            count++;
        } else {
            count = 0;
        }
        if(count === 3) {
            num++;
            count = 1;
        }
    }
    if(count === 2) {
        num++;
    }
    return n <= num;
};

用最少数量的箭引爆气球

在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。 一支弓箭可以沿着 x 轴从不同点完全垂直地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。 给你一个数组 points ,其中 points [i] = [xstart,xend] ,返回引爆所有气球所必须射出的最小弓箭数。  

输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:对于该样例,x = 6 可以射爆 [2,8],[1,6] 两个气球,以及 x = 11 射爆另外两个气球

来源:leetcode-cn.com/problems/mi…

var findMinArrowShots = function(points) {
    if(points.length === 0) return 0;
    points.sort((a, b) => a[1] - b[1]);
    let pos = points[0][1];
    let ans = 1;
    for (let ballon of points) {
        if(ballon[0] > pos) {
            pos = ballon[1];
            ans++;
        }
    }
    return ans;
};

 划分字母区间

字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。

输入:S = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca", "defegde", "hijhklij"。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。

来源:leetcode-cn.com/problems/pa…

var partitionLabels = function(S) {
    let res = [];
    let map = new Map(), start = 0, end = 0;
    for (let i = 0; i < S.length; i++) {
        map[S[i]] = i;
    }
    for (let j = 0; j < S.length; j++) {
        end = Math.max(end, map[S[j]]);
        if(j === end) {
            res.push(end - start + 1);
            start = j + 1;
        }
    }
    return res;
};

根据身高重建队列

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。 请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。  

输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
解释:
编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 01 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0123 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。

来源:leetcode-cn.com/problems/qu…

var reconstructQueue = function(people) {
    let ans = [];
    people.sort((a,b) => {
        return a[0] === b[0] ? a[1] - b[1] :  b[0] - a[0];
    });
    for(let i = 0 ; i < people.length; i++) {
        ans.splice(people[i][1],0,people[i]);
    }
    return ans;
};

非递减数列

给你一个长度为 n 的整数数组,请你判断在 最多 改变 1 个元素的情况下,该数组能否变成一个非递减数列。 我们是这样定义一个非递减数列的: 对于数组中所有的 i (0 <= i <= n-2),总满足 nums[i] <= nums[i + 1]。

输入: nums = [4,2,3]
输出: true
解释: 你可以通过把第一个4变成1来使得它成为一个非递减数列。

来源:leetcode-cn.com/problems/no…

var checkPossibility = function(nums) {
    let i, cnt = 0;
    for (i = 0; i < nums.length; i++) {
        if(nums[i] > nums[i+1]) {
            if(i == 0 || nums[i - 1] <= nums[i + 1]) {
                nums[i] = nums[i + 1]
            } else if (nums[i - 1] > nums[i + 1]) {
                nums[i + 1] = nums[i]
            }
            ++cnt;
            if( cnt > 1) {
                return false;
            }
        }
    }
    return true;
};