leetcode刷题-贪心

562 阅读2分钟

leetcode刷题-贪心

保证每次操作都是局部最优的,并且最后得到的结果是全局最优的

1. 分发饼干

题目

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

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

思路

典型的贪心算法

证明:假设在某次选择中,贪心策略选择给当前满足度最小的孩子分配第M个饼干,第M个饼干为可以满足该孩子的最小饼干; 假设存在一种最优策略,给该孩子分配第N个饼干,并且M<N,可以发现,经过这一轮的分配,贪心策略分配后剩下的饼干一定有一个比最优策略来得大。因此,在后续的分配中,贪心策略一定能够满足更多的孩子,也就是说贪心策略也就是最优策略。

var findContentChildren = function(g, s) {
  g.sort((a, b) => a - b);
  s.sort((a, b) => a - b);

  let gi = 0, si = 0;
  
  while(gi < g.length && si < s.length) {
    if (g[gi] <= s[si]) {
      gi++;
    }
    si++;
  }

  return gi;
};

2. 无重叠区间

题目

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

注意:

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

思路

  • 先计算最多能组成的不重叠的区间个数,然后用区间总个数减去不重叠的区间的个数
  • 如何计算出最多的不重叠区间的个数? 贪心:每次选择结尾最小的区间,因为选择的区间结尾越小,留给后面的区间的空间越大,后面能够选择的区间个数也越大
var eraseOverlapIntervals = function(intervals) {
  intervals.sort((a, b) => a[1] - b[1]);
  let count = 1;
  let end = intervals[0][1];

  for (let i = 1; i < intervals.length; i++) {
    const item = intervals[i];
    if (item[0] < end) {
      continue;
    }
    count++;
    end = item[1];
  }

  return intervals.length - count;
};

3. 用最少数量的箭引爆气球

题目

在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。

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

给你一个数组 points ,其中 points [i] = [xstart,xend] ,返回引爆所有气球所必须射出的最小弓箭数。

思路

第二题的变种

var findMinArrowShots = function(points) {
  points.sort((a, b) => a[1] - b[1]);

  let count = 1;
  let minEnd = points[0][1];

  for (let i = 1; i < points.length; i++) {
    const item = points[i];

    if (item[0] <= minEnd) {
      continue;
    }

    count++;
    minEnd = item[1];
  }

  return count;
};

4. 根据身高重建队列

题目

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。

请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。

思路

身高降序,K值升序,然后按排好序的顺序插入队列的第K个位置中

var reconstructQueue = function(people) {
  people.sort((a, b) => a[0] === b[0] ? a[1] - b[1] : b[0] - a[0])
  const resArr = [];

  for (let i = 0; i < people.length; i++) {
    const item = people[i];
    resArr.splice(item[1], 0, item);
  }

  return resArr;
};

5. 划分字母区间

题目

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

var partitionLabels = function (s) {
  const letterArr = initArr(s);
  letterArr.sort((a, b) => a[0] - b[0]);
  let index = 0;

  for (let i = 0; i < letterArr.length; i++) {
    if (letterArr[i][0] !== -1) {
      index = i;
      break;
    }
  }

  let begin = letterArr[index][0];
  let end = letterArr[index][1];
  const res = [];

  for (let i = index + 1; i < letterArr.length; i++) {
    const item = letterArr[i];
    if (item[0] < end) {
      end = Math.max(end, item[1]);
    } else {
      res.push(end - begin + 1);
      begin = item[0];
      end = item[1];
    }
  }

  res.push(end - begin + 1);

  return res;
};

function initArr(s) {
  const letterArr = new Array(26);
  for (let i = 0; i < 26; i++) {
    letterArr[i] = [-1, -1];
  }
  const lettera = 'a'.charCodeAt();
  const sArr = s.split('');
  for (let i = 0; i < s.length; i++) {
    const code = sArr[i].charCodeAt() - lettera;
    if (letterArr[code][0] === -1) {
      letterArr[code][0] = i;
    }
    letterArr[code][1] = i;
  }

  return letterArr;
}

6、种花问题

题目

假设有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花不能种植在相邻的地块上,它们会争夺水源,两者都会死去。

给你一个整数数组 flowerbed 表示花坛,由若干 0 和 1 组成,其中 0 表示没种植花,1 表示种植了花。另有一个数 n ,能否在不打破种植规则的情况下种入 n 朵花?能则返回 true ,不能则返回 false。

var canPlaceFlowers = function(flowerbed, n) {
  if (n === 0) return true; 

  for (let i = 0, len = flowerbed.length; i < len; i++) {
    if (flowerbed[i] === 1) { // 等于1的时候,直接跳2格
      i += 1;
    } else {
      // 等于0且能够种花
      if ((i + 1 === len) || (i + 1 < len && flowerbed[i + 1] === 0)) {
        n--;
        if (n === 0) return true;
        i += 1;
      } else {
        // 等于0的时候但是不能种花,直接跳2个格
        i += 2;
      }
    }
  }

  return n === 0;
};

7、判断子序列

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。

var isSubsequence = function(s, t) {
  const m = s.length, n = t.length;
  let i = 0, j = 0;

  while(i < m && j < n) {
    if (s[i] === t[j]) {
      i++;
    }
    j++;
  }

  return i === m;
};

8、非递减数列(重要)

给你一个长度为 n 的整数数组,请你判断在 最多 改变 1 个元素的情况下,该数组能否变成一个非递减数列。

我们是这样定义一个非递减数列的: 对于数组中任意的 i (0 <= i <= n-2),总满足 nums[i] <= nums[i + 1]。

var checkPossibility = function(nums) {
  const n = nums.length;
  let count = 0;

  for (let i = 0; i < n; i++) {
    const x = nums[i], y = nums[i + 1];
    if (x > y) {
      count++;
      if (count > 1) return false;
      if (i > 0 && y < nums[i - 1]) {
        nums[i + 1] = x;
      }
    }
  }

  return true;
};

9、买卖股票的最佳时机 II

给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

var maxProfit = function(prices) {
  let sum = 0;

  for (let i = 0; i < prices.length; i++) {
    if (i + 1 && prices[i + 1] > prices[i]) {
      sum += (prices[i + 1] - prices[i]);
    }
  }

  return sum;
};

10、最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

思路

动态规划:f(i)表示以i元素为结尾的连续子数组的最大和

f(i) = Math.max(f(i-1) + x, x);
var maxSubArray = function(nums) {
  let res = nums[0];
  let arr = new Array();
  arr[0] = nums[0];

  for (let i = 1; i < nums.length; i++) {
    arr[i] = Math.max(arr[i - 1] + nums[i], nums[i]);
    res = Math.max(res, arr[i]);
  }

  return res;
};

11、买卖股票的最佳时机

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

var maxProfit = function(prices) {
  let res = 0;
  let minArr = new Array(prices.length).fill(10001);
  minArr[0] = prices[0];

  for(let i = 1; i < prices.length; i++) {
    minArr[i] = Math.min(minArr[i - 1], prices[i]);
  }

  for (let i = 0; i < prices.length; i++) {
    res = Math.max(prices[i] - minArr[i], res);
  }

  return res;
};