二叉树的中序遍历
给定一个二叉树,返回它的中序 遍历。
递归解法
时间复杂度O(n);
空间复杂度最坏情况下O(n);
public List inorderTraversal(TreeNode root) {
List vals = new ArrayList<>();
recursion(vals, root);
return vals;
}
public void recursion(List vals, TreeNode node){
if(node != null){
if(node.left != null){
recursion(vals, node.left);
}
vals.add(node.val);
if(node.right != null){
recursion(vals, node.right);
}
}
}
栈
1)针对每个current节点,while循环找到最左节点,遍历路径上的所有节点入栈;
2)出栈,输出current节点的值,current节点指向当前节点的右子树;
3)重复1)
时间复杂度O(n);
空间复杂度O(n);
public List inorderTraversal2(TreeNode root) {
List vals = new ArrayList<>();
TreeNode current = root;
Stack stack = new Stack<>();
while (!stack.isEmpty() || current!=null){
while (current != null){
stack.push(current);
current = current.left;
}
current = stack.pop();
vals.add(current.val);
current = current.right;
}
return vals;
}
莫里斯遍历
若current没有左子节点
a. 将current添加到输出
b. 进入右子树,亦即, current = current.right
否则
a. 在current的左子树中,令current成为最右侧节点的右子节点
b. 进入左子树,亦即,current = current.left
举例而言:
1
/ \
2 3
/ \ /
4 5 6
首先,1 是根节点,所以将 current 初始化为 1。1 有左子节点 2,current 的左子树是
2
/ \
4 5
在此左子树中最右侧的节点是 5,于是将 current(1) 作为 5 的右子节点。令 current = cuurent.left (current = 2)。
现在二叉树的形状为:
2
/ \
4 5
\
1
\
3
/
6
对于 current(2),其左子节点为4,我们可以继续上述过程
4
\
2
\
5
\
1
\
3
/
6
由于 4 没有左子节点,添加 4 为输出,接着依次添加 2, 5, 1 。节点 3 有左子节点 6,故重复以上过程,,令3成为6的右子节点,然后添加6,3。
最终的结果是 [4,2,5,1,6,3]。
public List < Integer > inorderTraversal(TreeNode root) {
List < Integer > res = new ArrayList < > ();
TreeNode curr = root;
TreeNode pre;
while (curr != null) {
if (curr.left == null) {
res.add(curr.val);
curr = curr.right; // move to next right node
} else { // has a left subtree
pre = curr.left;
while (pre.right != null) { // find rightmost
pre = pre.right;
}
pre.right = curr; // put cur after the pre node
TreeNode temp = curr; // store cur node
curr = curr.left; // move cur to the top of the new tree
temp.left = null; // original cur left be null, avoid infinite loops
}
}
return res;
}
两两交换链表中的节点(https://leetcode-cn.com/problems/swap-nodes-in-pairs/)
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
给定 1->2->3->4, 你应该返回 2->1->4->3.
递归
递归三要素:
- 找终止条件
- 找返回值
- 单次过程
public ListNode swapPairs(ListNode head) {
// 终止条件
if(head == null || head.next == null){
return head;
}
// 返回值
ListNode ret = head.next;
// 单次过程
ListNode temp = ret.next;
ret.next = head;
head.next = swapPairs(temp);
return ret;
}
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
}
二叉树的最近公共祖先(https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/)
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
说明:
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉树中。
递归解法
本题递归的关键在于如何定义递归方法的返回值。我们定义返回值为:当前节点的子树中是否包含p节点或q节点;那么当前节点是否是最近公共祖先即可表示为:
(inLeftSubTree && inRightSubTree) || (inCurrentNode && (inLeftSubTree || inRightSubTree))
inLeftSubTree表示当前节点的左子树是否包含p节点或q节点;
inRightSubTree表示当前节点的右子树是否包含p节点或q节点;
inCurrentNode 表示当前节点是否是p节点或q节点;
private static TreeNode ans;
public static TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null){
return null;
}
recursion(root, p, q);
return ans;
}
// 当前子树是否包含p或q
public static boolean recursion(TreeNode root, TreeNode p, TreeNode q){
if(root == null){
return false;
}
boolean inCurrentNode = root.val == p.val || root.val == q.val;
boolean inLeftSubTree = recursion(root.left, p, q);
boolean inRightSubTree = recursion(root.right, p, q);
// 判断当前节点是否是最深公共祖先
if((inLeftSubTree && inRightSubTree) || (inCurrentNode && (inLeftSubTree || inRightSubTree))){
ans = root;
}
return inCurrentNode || inLeftSubTree || inRightSubTree;
}
找到两个节点到根节点的路径
另外一种直观的解法就是找到两个节点到根的路径,然后重合的第一个节点就是最近公共祖先。
可以通过HashMap保存每个节点的父节点,从而找到两个节点到根的路径。
public static TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
Map parent = new HashMap<>();
dfs(root, parent);
List pVisited = new ArrayList<>();
while (p != null){
pVisited.add(p.val);
p = parent.get(p.val);
}
while (q != null){
if(pVisited.contains(q.val)){
return q;
}
q = parent.get(q.val);
}
return root;
}
public static void dfs(TreeNode root, Map parent){
if(root == null){
return;
}
if(root.left != null){
parent.put(root.left.val, root);
dfs(root.left, parent);
}
if(root.right != null){
parent.put(root.right.val, root);
dfs(root.right, parent);
}
}
也可以直接通过递归找到每个节点到根的路径。
public static TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null){
return null;
}
List pNodes = new ArrayList<>();
pNodes.add(root);
finish = false;
getAncList(root, p, pNodes);
List qNodes = new ArrayList<>();
qNodes.add(root);
finish = false;
getAncList(root, q, qNodes);
int n = Math.min(pNodes.size(), qNodes.size());
for(int i=1; i nodes){
if(root != null && root.val == p.val){
finish = true;
return;
}
if(root == null){
return;
}
if(!finish && root.left != null){
nodes.add(root.left);
getAncList(root.left, p, nodes);
if(!finish){
nodes.remove(nodes.size()-1);
}
}
if(!finish && root.right != null){
nodes.add(root.right);
getAncList(root.right, p, nodes);
if(!finish){
nodes.remove(nodes.size()-1);
}
}
return;
}
岛屿的最大面积(https://leetcode-cn.com/problems/max-area-of-island/)
给定一个包含了一些 0 和 1 的非空二维数组 grid 。
一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 。)
示例 1:
[[0,0,1,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,1,1,0,1,0,0,0,0,0,0,0,0],
[0,1,0,0,1,1,0,0,1,0,1,0,0],
[0,1,0,0,1,1,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,0,0,0,0,0,0,1,1,0,0,0,0]]
对于上面这个给定矩阵应返回 6。注意答案不应该是 11 ,因为岛屿只能包含水平或垂直的四个方向的 1 。
示例 2:
[[0,0,0,0,0,0,0,0]]
对于上面这个给定的矩阵, 返回 0。
此题既可以用深度优先算法也可以用广度优先算法。这里介绍深度优先算法(dfs)的两种实现,递归解法和非递归解法(栈)。递归解法一般比较巧妙,代码简洁,但是比较难想;非递归解法,容易复合常规思路,代码相对复杂。
对于此类统计最大连接面积的题型,可以使用沉岛思想。
深度优先(递归解法)
每次面积加1之后,将对应的网格置为0,避免重复计算面积,这种思想叫做沉岛思想。
public static int maxAreaOfIsland(int[][] grid) {
int m = grid.length;
if (m == 0) {
return 0;
}
int n = grid[0].length;
if (n == 0) {
return 0;
}
int maxArea = 0;
for(int i=0; i=grid.length || j>=grid[0].length || grid[i][j]==0){
return 0;
}
int num = 1;
grid[i][j] = 0;
num += dfs(grid, i-1, j);
num += dfs(grid, i+1, j);
num += dfs(grid, i, j-1);
num += dfs(grid, i, j+1);
return num;
}
深度优先(栈解法)
深度优先可以使用栈实现,碰到值为1的网格,入栈,并且其周围的值为1的网格入栈;
public static int maxAreaOfIsland(int[][] grid) {
int m = grid.length;
if (m == 0) {
return 0;
}
int n = grid[0].length;
if (n == 0) {
return 0;
}
int maxArea = 0;
Stack stack = new Stack<>();
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
int area = 0;
stack.push(new int[]{i,j});
while (!stack.isEmpty()){
int[] pos = stack.pop();
int r = pos[0];
int c = pos[1];
if(grid[r][c] == 1){
area++;
grid[r][c] = 0;
if (r - 1 >= 0 && grid[r-1][c] == 1) {
stack.push(new int[]{r - 1, c});
}
if (r + 1 < m && grid[r+1][c] == 1) {
stack.push(new int[]{r + 1, c});
}
if (c - 1 >= 0 && grid[r][c-1] == 1) {
stack.push(new int[]{r, c - 1});
}
if (c + 1 < n && grid[r][c+1] == 1) {
stack.push(new int[]{r, c + 1});
}
}
}
maxArea = Math.max(maxArea, area);
}
}
}
return maxArea;
}
岛屿数量(https://leetcode-cn.com/problems/number-of-islands/)
深度优先:沉岛法
我们可以将二维网格看成一个无向图,竖直或水平相邻的 1 之间有边相连。
为了求出岛屿的数量,我们可以扫描整个二维网格。如果一个位置为 1,则以其为起始节点开始进行深度优先搜索。在深度优先搜索的过程中,每个搜索到的 11都会被重新标记为 0。
最终岛屿的数量就是我们进行深度优先搜索的次数。
这里我们使用递归方式实现深度优先算法。
public static int numIslands(char[][] grid) {
int m = grid.length;
if (m == 0) {
return 0;
}
int n = grid[0].length;
if (n == 0) {
return 0;
}
int island = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if(grid[i][j] == '1'){
dfs(grid, i, j);
island ++;
}
}
}
return island;
}
public static void dfs(char[][] grid, int row, int col) {
int m = grid.length;
int n = grid[0].length;
if (row < 0 || row >= m || col < 0 || col >= n || grid[row][col] == '0') {
return;
}
grid[row][col] = '0';
dfs(grid, row + 1, col);
dfs(grid, row - 1, col);
dfs(grid, row, col + 1);
dfs(grid, row, col - 1);
}
此题同样可以使用广度优先算法实现,使用队列,关键思想仍然是沉岛思想。
简化路径
栈
1)考虑到有../回到前一个目录的操作,联想到使用栈
2)使用/作为分隔符,对路径进行分隔,过滤到空字符和.字符。..出栈,否则入栈。
public static String simplifyPath(String path) {
String[] segs = path.split("/");
Stack stack = new Stack();
for(String seg : segs){
if("".equals(seg.trim()) || ".".equals(seg)){
continue;
}
if("..".equals(seg)){
if(!stack.isEmpty()){
stack.pop();
}
}else {
stack.push(seg);
}
}
if(stack.isEmpty()){
return "/";
}
StringBuilder sb = new StringBuilder();
while (!stack.isEmpty()){
sb.insert(0, "/" + stack.pop());
}
return sb.toString();
}
字符串解码
栈
考虑到有括号的匹配问题,联想到使用栈。
本题中可能出现括号嵌套的情况,比如 2[a2[bc]],这种情况下我们可以先转化成 2[abcbc],在转化成 abcbcabcbc。我们可以把字母、数字和括号看成是独立的 TOKEN,并用栈来维护这些 TOKEN。具体的做法是,遍历编码字符串:
1)如果当前的字符为数位,解析出一个数字(连续的多个数位)并进栈
2)如果当前的字符为字母或者左括号,直接进栈
3)如果当前的字符为右括号,开始出栈,一直到左括号出栈,出栈序列反转后拼接成一个字符串,此时取出栈顶的数字,就是这个字符串应该出现的次数,我们根据这个次数和字符串构造出新的字符串并进栈
4)重复如上操作,最终将栈中的元素按照从栈底到栈顶的顺序拼接起来,就得到了答案。
public static String decodeString(String s) {
StringBuilder ans = new StringBuilder();
Stack stack = new Stack();
int n = s.length();
int ptr = 0;
while (ptr < n) {
char c = s.charAt(ptr);
if (isNumber(c)) {
StringBuilder num = new StringBuilder(String.valueOf(c));
while (++ptr < n && isNumber(s.charAt(ptr))) {
num.append(s.charAt(ptr));
}
stack.push(num.toString());
} else if (c == ']') {
StringBuilder word = new StringBuilder();
String pop = stack.pop();
while (!"[".equals(pop)) {
word.insert(0, pop);
pop = stack.pop();
}
int k = Integer.valueOf(stack.pop());
StringBuilder decode = new StringBuilder();
for (int i = 0; i < k; i++) {
decode.append(word);
}
stack.push(decode.toString());
ptr++;
} else {
stack.push(String.valueOf(c));
ptr++;
}
}
while (!stack.isEmpty()){
ans.insert(0, stack.pop());
}
return ans.toString();
}
public static boolean isNumber(char c) {
return c >= '0' && c <= '9';
}
每日温度
栈
1)假设输出数组为ans[n]。ans[i]的值为i后第一个大于T[i]的数的下标决定的,后面的数决定前面的数的答案,则联想到使用栈;
2)输出值为下标的差值,所以栈里面存的应该是数的下标,而非数本身。
3)具体做法如下:
public int[] dailyTemperatures(int[] T) {
Stack stack = new Stack();
int n = T.length;
int[] ans = new int[n];
stack.push(0);
for(int i=1; i T[stack.peek()]){
int index = stack.pop();
ans[index] = i - index;
}
stack.push(i);
}
// 剩下的未出栈的元素, 全都为零, 由于数组初始化为零, 所以不需要处理剩下未出栈的元素
// while (!stack.isEmpty()){
// int index = stack.pop();
// ans[index] = 0;
// }
return ans;
}
朋友圈
深度优先搜索
思路如下:
一次深度优先搜索就将一个连通图的所有顶点遍历完成;所以最后深度优先搜索的次数就是连通图的个数,也就是朋友圈的个数。
public static int findCircleNum(int[][] M) {
int n = M.length;
if (n == 0) {
return 0;
}
int ans = 0;
boolean[] visited = new boolean[n];
for (int i = 0; i < n; i++) {
if(!visited[i]){
dfs(M, visited, i);
ans ++;
}
}
return ans;
}
public static void dfs(int[][] M, boolean[] visited, int i){
for(int j = 0; j < M[i].length; j++){
if(!visited[j] && M[i][j] == 1){
visited[j] = true;
dfs(M, visited, j);
}
}
}
移掉K位数字
栈
1)依序遍历每一个数字,每当后一个数小于前一个数时,应该移除前面一个数,否则不可能得到更小的数。因为相同位数的数字,高位数越小,整体就越小。
2)后面的数决定前一个数的去留,联想到使用栈。
public String removeKdigits(String num, int k) {
if(k >= num.length()){
return "0";
}
Stack stack = new Stack<>();
int index = 0;
while (k > 0 && index < num.length()){
char c = num.charAt(index++);
while (!stack.isEmpty() && k > 0 && c < stack.peek()){
stack.pop();
k --;
}
stack.push(c);
// 不能以0开头
if(stack.size() == 1 && c == '0'){
stack.pop();
}
}
while (k > 0){
stack.pop();
k--;
}
StringBuilder sb = new StringBuilder();
while (!stack.isEmpty()){
sb.insert(0, stack.pop());
}
if(index < num.length()){
sb.append(num.substring(index));
}
return "".equals(sb.toString()) ? "0" : sb.toString();
}
水壶问题
深度优先
此题的关键在于建模。深度优先(栈、递归)和广度优先(队列)均可解决。本题使用深度优先算法,使用栈实现,因为递归可能会超过Java默认递归层数限制,所以能用栈实现就优先用栈。注意使用HashSet保存状态时,要重写equals方法和hashcode方法。
public static class Water {
public int x;
public int y;
public Water(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o == null || !(o instanceof Water)) {
return false;
}
Water oo = (Water) o;
return x == oo.x && y == oo.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}
public static boolean canMeasureWater(int x, int y, int z) {
if (z < 0 || z > x + y) {
return false;
}
Set set = new HashSet<>();
Stack stack = new Stack<>();
stack.push(new Water(0, 0));
set.add(new Water(0,0));
while (!stack.isEmpty()){
Water water = stack.pop();
if(water.x == z || water.y == z || water.x + water.y == z){
return true;
}
// 将y倒入x 至x满或者y空
if(water.y > 0 && water.x < x && water.y + water.x >= x){
int nextX = x;
int nextY = water.y + water.x - x;
Water nextWater = new Water(nextX, nextY);
if(!set.contains(nextWater)){
stack.push(nextWater);
set.add(nextWater);
}
}else if(water.y > 0 && water.x < x && water.y + water.x < x){
int nextX = water.x + water.y;
int nextY = 0;
Water nextWater = new Water(nextX, nextY);
if(!set.contains(nextWater)){
stack.push(nextWater);
set.add(nextWater);
}
}
// 将x倒入y 至y满或者x空
if(water.x > 0 && water.y < y && water.y + water.x >= y){
int nextX = water.x + water.y - y;
int nextY = y;
Water nextWater = new Water(nextX, nextY);
if(!set.contains(nextWater)){
stack.push(nextWater);
set.add(nextWater);
}
}else if(water.x > 0 && water.y < y && water.y + water.x < y){
int nextX = 0;
int nextY = water.y + water.x;
Water nextWater = new Water(nextX, nextY);
if(!set.contains(nextWater)){
stack.push(nextWater);
set.add(nextWater);
}
}
// 将x装满
if(water.x < x){
int nextX = x;
int nextY = water.y;
Water nextWater = new Water(nextX, nextY);
if(!set.contains(nextWater)){
stack.push(nextWater);
set.add(nextWater);
}
}
// 将y装满
if(water.y < y){
int nextX = water.x;
int nextY = y;
Water nextWater = new Water(nextX, nextY);
if(!set.contains(nextWater)){
stack.push(nextWater);
set.add(nextWater);
}
}
// 将x倒光
if(water.x > 0){
int nextX = 0;
int nextY = water.y;
Water nextWater = new Water(nextX, nextY);
if(!set.contains(nextWater)){
stack.push(nextWater);
set.add(nextWater);
}
}
// 将y倒光
if(water.y > 0){
int nextX = water.x;
int nextY = 0;
Water nextWater = new Water(nextX, nextY);
if(!set.contains(nextWater)){
stack.push(nextWater);
set.add(nextWater);
}
}
}
return false;
}
数学方法
如果数学基础好的人,通过贝祖定理告诉我们,只需要找到 x, y的最大公约数并判断 z是否是它的倍数即可。所以本题就成了使用辗转相除法求解x,y的最大公约数。gcd(a,b) = gcd(b,a mod b)。
public static boolean canMeasureWater(int x, int y, int z) {
if(z < 0 || z > x + y){
return false;
}
if(x == 0 || y == 0){
return z == 0 || z == Math.max(x, y);
}
int gcd = getGCD(x, y);
if(z % gcd == 0){
return true;
}
return false;
}
public static int getGCD(int x, int y){
int a = Math.min(x, y);
int b = Math.max(x, y);
int mod = b % a;
while (mod != 0){
int temp = mod;
mod = a % mod;
a = temp;
}
return a;
}
字典序排数
递归
1)按顺序把以1开头、2开头、...、9开头的小于等于n的所有数加入到list中
2)对于以1开头的,递归地将以0开头、1开头、2开头、...、9开头的数加入到list中;其它的都是这样。
public static List lexicalOrder(int n) {
List list = new ArrayList<>();
for(int i=1; i<=9; i++){
dfs(list, i, n);
}
return list;
}
public static void dfs(List list, int currentValue, int n){
if(currentValue <= n){
list.add(currentValue);
}else {
return;
}
for(int nextBit = 0; nextBit <= 9; nextBit ++){
dfs(list, currentValue * 10 + nextBit, n);
}
return;
}
求根到叶子节点数字之和
递归
- 每层的数求和
- 每一层的每个数:截止上一层得到的数乘10加上自身的值
public int sumNumbers(TreeNode root) {
if(root == null){
return 0;
}
return recursion(root, 0);
}
public int recursion(TreeNode node, int preValue){
if(node == null){
return 0;
}
int temp = preValue * 10 + node.val;
if(node.left == null && node.right == null){
return temp;
}
return recursion(node.left, temp) + recursion(node.right, temp);
}
回溯
- 比较容易和直观地想法是将所有根到叶子节点的路径求得,在求和。
- 这种方法稍慢,因为路径共同的部分,计算就重复了
public int sumNumbers(TreeNode root) {
if(root == null){
return 0;
}
List> numbers = new ArrayList<>();
backtrace(numbers, new ArrayList<>(), root);
int sum = 0;
for(List number : numbers){
sum += getNumber(number);
}
return sum;
}
public void backtrace(List> numbers, List number, TreeNode node){
if(node.left == null && node.right == null){
List newNumber = new ArrayList<>(number);
newNumber.add(node.val);
numbers.add(newNumber);
return;
}
number.add(node.val);
if(node.left != null){
backtrace(numbers, number, node.left);
}
if(node.right != null){
backtrace(numbers, number, node.right);
}
number.remove(number.size()-1);
}
public int getNumber(List bits){
int number = 0;
for(Integer bit : bits){
number = number * 10 + bit;
}
return number;
}
有序链表转换二叉搜索树
递归算法
- 通过list获取链表所有元素,从而可以使用下标获取中间元素
- 递归建立平衡二叉树即可
public TreeNode sortedListToBST(ListNode head) {
List numbers = new ArrayList<>();
while (head != null){
numbers.add(head.val);
head = head.next;
}
return genTree(numbers, 0, numbers.size()-1);
}
public TreeNode genTree(List numbers, int start, int end){
if(start == end){
TreeNode treeNode = new TreeNode(numbers.get(start));
return treeNode;
}
if(start > end){
return null;
}
int med = (start + end) / 2;
TreeNode treeNode = new TreeNode(numbers.get(med));
treeNode.left = genTree(numbers, start, med - 1);
treeNode.right = genTree(numbers, med + 1, end);
return treeNode;
}
树的子结构
递归
public boolean isSubStructure(TreeNode A, TreeNode B) {
if(A == null || B == null){
return false;
}
return contains(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B);
}
public boolean contains(TreeNode A, TreeNode B){
if(B == null){
return true;
}
if((A == null && B != null) || A.val != B.val){
return false;
}
return contains(A.left, B.left) && contains(A.right, B.right);
}
132模式
单调栈
- left,med,right三者,判断满足132模式,至少需要两次比较:right>left && med>right
- 所以我们需要找到一个med,使得med>right,并且这个right必定大于med左边的任意一个数;
- 基于上述的思路,我们可以考虑从后向前遍历数组;因为我们要判断right是否大于med左边的任意一个数,所以联想到维护一个最小值前缀数组;我们还要判断med是否大于med之后的任意一个数,常规来说这需要O(N)的时间复杂度,为了降低时间复杂度,我们联想到维护一个单调递减栈,这样med只需要和栈顶的最小数进行比较即可做出判断。
- 技巧使用:维护单调递减栈的方法是只有小于栈顶元素才可以入栈;维护单调递增栈的方法是只有大于栈顶元素才可以入栈;
- 此方法时间复杂度为O(N)
public boolean find132pattern(int[] nums) {
Stack stack = new Stack<>();
int n = nums.length;
if(n < 3){
return false;
}
int[] min = new int[n];
min[0] = nums[0];
for (int i = 1; i < n; i++) {
min[i] = Math.min(nums[i], min[i - 1]);
}
for (int j = n - 1; j >= 0; j--) {
if (nums[j] > min[j]) { // 保证入栈的right候选者必定大于任意一个左边的数
while (!stack.isEmpty() && stack.peek() <= min[j]) { // 维护单调递减栈
stack.pop();
}
if (!stack.isEmpty() && nums[j] > stack.peek()) {
return true;
}
stack.push(nums[j]);
}
}
return false;
}
常规遍历
public boolean find132pattern(int[] nums) {
int left = 0;
for (int med = left + 1; med < nums.length - 1; med++) {
if (nums[med] > nums[left] + 1) {
for (int right = med + 1; right < nums.length; right++) {
if(nums[right] < nums[med] && nums[right] > nums[left]){
return true;
}
}
}else if(nums[med] < nums[left]){
left = med;
}
}
return false;
}
验证栈序列
使用栈模拟过程
- 可以从入栈数组出发解题,也可以从出栈数组出发解题,代码如下
// 从入栈数组出发解题
public boolean validateStackSequences(int[] pushed, int[] popped) {
int N = pushed.length;
Stack stack = new Stack();
int j = 0;
for (int x: pushed) {
stack.push(x);
while (!stack.isEmpty() && j < N && stack.peek() == popped[j]) {
stack.pop();
j++;
}
}
return j == N;
}
// 从出栈数组出发解题
public boolean validateStackSequences(int[] pushed, int[] popped) {
Stack stack = new Stack<>();
int n = pushed.length;
int pushIndex = 0;
for (int popIndex = 0; popIndex < n; popIndex++) {
int targetPop = popped[popIndex];
if(!stack.isEmpty() && stack.peek() == targetPop){
stack.pop();
continue;
}
while (pushIndex < n && pushed[pushIndex] != targetPop) {
stack.push(pushed[pushIndex]);
pushIndex++;
}
if(pushIndex < n){
pushIndex++;
}else if(stack.pop() != targetPop){
return false;
}
}
return true;
}
求1+2+…+n
递归
public int sumNums(int n) {
return n == 0 ? 0 : n + sumNums(n-1);
}
下一个更大元素 II
单调栈
- 先不考虑环形问题,寻找下一个更大元素,我们维护一个递减栈(不增栈),若当前元素大于栈顶元素,此时栈顶元素出栈,对应下标的下一个更大元素已经确定就是当前元素;若当前元素小于等于栈顶元素,入栈。当遍历一遍之后,能够确定答案的只有一部分。剩下没有确定答案的数组下标还在栈中。所以我们需要再遍历一遍。
- 对于环形问题,一般的解决技巧是利用求模运算得到下标;所以遍历两遍数组,只需要将数组的边界扩大一倍即可。
- 由于要遍历两遍数组,对于第二遍,已经确定答案的,就不需要再进行计算了,所以维护了一个visit数组。
int[] arr = {1,2,3,4,5};
int n = arr.length, index = 0;
while (true) {
print(arr[index % n]);
index++;
}
所以此题的代码如下:
public int[] nextGreaterElements(int[] nums) {
int n = nums.length;
int[] ans = new int[n];
if(n == 0){
return ans;
}
boolean[] visit = new boolean[n];
Stack stack = new Stack<>();
stack.push(0);
for (int i = 1; i < 2 * n; i++) {
int ind = i % n;
while (!stack.isEmpty() && nums[ind] > nums[stack.peek()]) {
int popInd = stack.pop();
if(!visit[popInd]){
ans[popInd] = nums[ind];
visit[popInd] = true;
}
}
stack.push(ind);
}
while (!stack.isEmpty()){
int popInd = stack.pop();
if(!visit[popInd]){
ans[popInd] = -1;
visit[popInd] = true;
}
}
return ans;
}
基本计算器 II
public int calculate(String s) {
Stack numStack = new Stack<>();
char lastOp = '+';
char[] arr = s.toCharArray();
for(int i = 0; i < arr.length; i ++){
if(arr[i] == ' ') continue;
if(Character.isDigit(arr[i])){
int tempNum = arr[i] - '0';
while(++i < arr.length && Character.isDigit(arr[i])){
tempNum = tempNum * 10 + (arr[i] - '0');
}
i--;
if(lastOp == '+') numStack.push(tempNum);
else if(lastOp == '-') numStack.push(-tempNum);
else numStack.push(res(lastOp, numStack.pop(), tempNum));
} else lastOp = arr[i];
}
int ans = 0;
for(int num : numStack) ans += num;
return ans;
}
private int res(char op, int a, int b){
if(op == '*') return a * b;
else if(op == '/') return a / b;
else if(op == '+') return a + b; //其实加减运算可以忽略
else return a - b;
}