1. 最多能完成排序的块 II
这个问题和“最多能完成排序的块”相似,但给定数组中的元素可以重复,输入数组最大长度为2000,其中的元素最大为10**8。
arr是一个可能包含重复元素的整数数组,我们将这个数组分割成几个“块”,并将这些块分别进行排序。之后再连接起来,使得连接的结果和按升序排序后的原数组相同。我们最多能将数组分成多少块?
分析:
排序块的充分条件是:此块中最大数字为 headhead , 若此块后面的所有数字都 >= head ,则此块为排序块。
方法一:辅助栈
可以遍历一遍数组arr,动态判断到目前数字 num 为止最多能分出多少排序块,并保存每个排序块的最大值 head 。每遍历到下个数字 num,动态判断前面所有的排序块是否成立,并更新所有排序块:
- 当某排序块 num < head :将此排序块[A]与 num 合并,形成新排序块[A | num],最大值仍为 head;
- 当某排序快num>=head:原排序块保留,并新加排序块 [num]。
对于整个数组的排序块,其 head大小是从左到右递增的。因此,若给数组尾部加入一个随机正整数 n ,尾部的排序块更容易被合并(最先满足 num < head )。当 n 值较小时( < 前面多个排序块的 head ),则需按尾部到首部的顺序合并多个排序块。使用 栈 保存排序块最大值 head 。在遍历过程中,通过维护栈的 head 序列,实现排序块的动态更新。
class Solution {
public int maxChunksToSorted(int[] arr) {
LinkedList<Integer> stack = new LinkedList<Integer>();
for(int num : arr) {
if(!stack.isEmpty() && num < stack.getLast()) {
int head = stack.removeLast();
while(!stack.isEmpty() && num < stack.getLast()) stack.removeLast();
stack.addLast(head);
}
else stack.addLast(num);
}
return stack.size();
}
}
贪心+前缀最大值+后缀最小值
- 如果前缀 [… i) 的最大值 <= 后缀 [i …] 的最小值,则意味着 i 及其右侧的元素都能“守得住”,即:原数组排序后,[i] 无需跨到左侧,此时增加一个分块,对排序结果无影响;
- 否则,意味着 [i] 位置“守不住”,在原数组排序后,需要将其移到左侧,因此它不能单独成一个分块,此时分块不能增加;
class Solution {
public int maxChunksToSorted(int[] arr) {
if (arr.length == 0) return 0;
int[] rightMin = new int[arr.length];
int leftMax = arr[0];
rightMin[arr.length-1] = arr[arr.length-1];
for (int i = arr.length-2; i >= 0 ; i--) {
rightMin[i] = Math.min(arr[i], rightMin[i+1]);
}
int res = 1;
for (int i = 1; i < arr.length; i++) {
if (leftMax <= rightMin[i]) res++;
leftMax = Math.max(leftMax, arr[i]);
}
return res;
}
}
2. 岛屿数量
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
分析:
DFS。扫描整个二维网格。如果一个位置为 1,则以其为起始节点开始进行深度优先搜索。在深度优先搜索的过程中,每个搜索到的 1 都会被重新标记为 0。最终岛屿的数量就是我们进行深度优先搜索的次数。
class Solution {
public int numIslands(char[][] grid) {
if (grid.length == 0 || grid[0].length == 0) return 0;
int lines = grid.length;
int rows = grid[0].length;
int ans = 0;
for (int i = 0; i < lines; i++) {
for (int j = 0; j < rows; j++) {
if (grid[i][j] == '1')
{
traceback(grid, i, j);
ans++;
}
}
}
return ans;
}
private void traceback(char[][] grid, int i, int j) {
if (i<0 || i>=grid.length || j<0 || j>=grid[0].length || grid[i][j] == '0') return;
grid[i][j] = '0';
traceback(grid, i-1, j);
traceback(grid, i+1, j);
traceback(grid, i, j-1);
traceback(grid, i, j+1);
}
}
3. 二叉树的前序遍历
给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
分析:
DFS。
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
PreOrder(root, res);
return res;
}
private void PreOrder(TreeNode root, List<Integer> res) {
if (root == null) return;
res.add(root.val);
PreOrder(root.left, res);
PreOrder(root.right, res);
}
}