# 498.对角线遍历

# 题目

给你一个大小为 m x n 的矩阵 mat ,请以对角线遍历的顺序,用一个数组返回这个矩阵中的所有元素。

输入:mat = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,4,7,5,3,6,8,9]

链接:https://leetcode-cn.com/problems/diagonal-traverse/ (opens new window)

# 答案

该题目需要总结出:遍历的方向由层数(横纵坐标之和)决定的,偶右上奇左下,这个规律。之后通过遍历总的个数,和控制当前行和列的位置来完成数组的构成。其中需要处理一些临界点,例如向右上方遍历的时候会存在 上面 和 右面 两个临界位置。

/**
 * @param {number[][]} mat
 * @return {number[]}
 */
var findDiagonalOrder = function(mat) {
  if (!mat || !mat.length) {
    return [];
  }

  let r = 0;
  let c = 0;
  const rows = mat.length;
  const cols = mat[0].length;
  const res = [];
  
  // 遍历的次数等于总个数
  for (let i = 0; i < rows * cols; i++) {
    res[i] = mat[r][c];
    // 遍历的方向由层数(横纵坐标之和)决定的,偶右上奇左下
    if ((r + c) % 2 === 0) {
      if (c === cols - 1) {
        // 往下移动,准备向左下遍历
        r++;
      } else if (r === 0) {
        // 往右移动,准备向左下遍历
        c++;
      } else {
        // 右上方移动
        c++;
        r--;
      }
    } else {
      if (r === rows - 1) {
        // 向右移动,准备向右上方遍历
        c++;
      } else if (c === 0) {
        // 向下移动,准备向右上方遍历
        r++;
      } else {
        // 左下方移动
        c--;
        r++;
      }
    }
  }

  return res;
};

# 80.删除有序数组中的重复项

# 题目

给你一个有序数组 nums,请你原地删除重复出现的元素,使每个元素最多出现两次,返回删除后数组的新长度。不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

输入:nums = [1,1,1,2,2,3]
输出:5, nums = [1,1,2,2,3]
解释:函数应返回新长度 length = 5,并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 不需要考虑数组中超出新长度后面的元素。

链接:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array-ii/ (opens new window)

# 答案

题目要求每个元素最多出现两次,那么对于前两个数字我们可以直接保留。现在我们考虑第三个数的情况,由于数组是有序的,如果第三个数和第一个数一样,那么第二个数必定也是一样的,这样就会有三个一样的数字,不符合题意了。所以只要我们保证第一个和第三个数不一样,那么即使第一个数和第二个数一样也是符合题意的。接着我们从第三个数字起遍历,每找到一个与 当前指针前 - 2 位置不同的值就赋值到当前指针位置,并将指针往前移动一位。

/**
 * @param {number[]} nums
 * @return {number}
 */
var removeDuplicates = function (nums) {
  let len = 0;
  for (num of nums) {
    if (len < 2 || nums[len - 2] !== num) {
      nums[len++] = num;
    }
  }
  return len;
};

# 502.IPO

# 题目

假设力扣(LeetCode)即将开始 IPO。为了以更高的价格将股票卖给风险投资公司,力扣 希望在 IPO 之前开展一些项目以增加其资本。由于资源有限,它只能在 IPO 之前完成最多 k 个不同的项目。帮助力扣设计完成最多 k 个不同项目后得到最大总资本的方式。

给你 n 个项目。对于每个项目 i,它都有一个纯利润 profits[i],和启动该项目需要的最小资本 capital[i]。

最初,你的资本为 w。当你完成一个项目时,你将获得纯利润,且利润将被添加到你的总资本中。

总而言之,从给定项目中选择 最多 k 个不同项目的列表,以最大化最终资本 ,并输出最终可获得的最多资本。

示例:

输入:k = 2, w = 0, profits = [1,2,3], capital = [0,1,1]
输出:4
解释:
由于你的初始资本为 0,你仅可以从 0 号项目开始。
在完成后,你将获得 1 的利润,你的总资本将变为 1。
此时你可以选择开始 1 号或 2 号项目。
由于你最多可以选择两个项目,所以你需要完成 2 号项目以获得最大的资本。
因此,输出最后最大化的资本,为 0 + 1 + 3 = 4。

链接:https://leetcode-cn.com/problems/ipo/ (opens new window)

# 答案

这里使用 java 代码编写,原因是 js 没有 PriorityQueue 这类数据结构,使用排序的话时间复杂度高,再加上 js 性能本来就不行,在 leetcode 上直接超时了。。

分析:由于每完成一个任务都会使得总资金 w 增加或不变。因此对于所选的第 i 个任务而言,应该在所有「未被选择」且启动资金不超过 w 的所有任务里面选利润最大的。因此每次决策前,将启动资金不超过当前总资金的任务加入集合,再在里面取出利润最大的任务,与我们当前的总资金 w 相加,循环求解 k 次即为答案。

class Solution {
  public int findMaximizedCapital(int k, int w, int[] profits, int[] capital) {
    int n = profits.length;
    // 构建和排序启动资金和利润的二维数组,并排序,时间复杂度为 O(nlogn)
    List<int[]> list = new ArrayList<>();
    for (int i = 0; i < n; i++) {
      list.add(new int[]{ capital[i], profits[i] });
    }
    Collections.sort(list, (a, b) -> a[0] - b[0]);
    // 构建优先级队列(大根堆),poll 操作的时间复杂度为 O(logn)
    PriorityQueue<Integer> q = new PriorityQueue<>((a, b) -> b - a);
    // 由于之前 list 根据启动资金排序过,这里只需要遍历到不符合为止,不需要每次从头到尾遍历
    int i = 0;
    while (k-- > 0) {
      while (i < n && list.get(i)[0] <= w) {
        q.add(list.get(i)[1]);
        i++;
      }
      if (q.isEmpty()) break;
      w += q.poll();
    }
    return w;
  }
}

# 503.下一个更大元素

# 题目

给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。
示例:

输入: [1,2,1]
输出: [2,-1,2]
解释: 第一个 1 的下一个更大的数是 2;
数字 2 找不到下一个更大的数; 
第二个 1 的下一个最大的数需要循环搜索,结果也是 2。

链接:https://leetcode-cn.com/problems/next-greater-element-ii/ (opens new window)

# 答案

刚接触这道题的时候直接使用双 for 循环暴力破解,结果太费时间了。。现在考虑优化的情况,我们注意到,暴力解法中,如果数组的前半部分是单调不增的,那么会有很大的计算资源的浪费。比如说 [6,5,4,3,8],对于前面的 [6,5,4,3] 等数字都需要向后遍历,当寻找到元素 8 时才找到了比自己大的元素;而如果已知元素 6 向后找到元素 8 才找到了比自己的大的数字,那么对于元素 [5,4,3] 来说,它们都比元素 6 更小,所以比它们更大的元素一定是元素 8,不需要单独遍历对 [5,4,3] 向后遍历一次。

根据上面的分析可知,可以遍历一次数组,如果元素是单调递减的(则他们的「下一个更大元素」相同),我们就把这些元素保存,直到找到一个较大的元素;把该较大元素逐一跟保存了的元素比较,如果该元素更大,那么它就是前面元素的「下一个更大元素」,在实现上,我们可以使用「单调栈」来实现。

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var nextGreaterElements = function(nums) {
  const n = nums.length;
  const res = new Array(n).fill(-1);
  // 用一个栈保存值递减的下标(单调递减栈)
  const stk = [];

  for (let i = 0; i < n * 2 - 1; i++) {
    // 检查栈顶对应值与当前值
    // 如果大于则不处理,后续执行 push 后保持单调递减的特性
    // 如果小于则需要循环处理单调栈,直到栈顶对应值大于当前值,那么处理前的栈顶到处理后的栈顶的最近最大值即为当前值
    while (stk.length && nums[stk[stk.length - 1]] < nums[i % n]) {
      res[stk[stk.length - 1]] = nums[i % n];
      stk.pop();
    }
    stk.push(i % n);
  }

  return res;
};