栈
- 有效的括号E
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
class Solution {
public boolean isValid(String s) {
if(s == null) return true;
Stack<Character> stack = new Stack<>();
for(char c : s.toCharArray()){
if(c == '('){
stack.push(')');
}else if(c == '['){
stack.push(']');
}else if(c == '{'){
stack.push('}');
}else if(stack.empty() || c != stack.pop()){//若s还未遍历完,栈已空,说明没有元素和s剩下的匹配
return false; //若栈非空,但栈顶并非那个匹配的元素
}
}
if(stack.empty()){
return true;
}
return false;
}
}
class Solution {
public int largestRectangleArea(int[] heights) {
int len = heights.length;
if(len == 0) return 0;
if(len == 1) return heights[0];
int res = 0;
int[] newHeights = new int[len + 2];//创建一个新数组,在原数组左右各加了一个高度为0的柱子
newHeights[0] = 0;
System.arraycopy(heights, 0, newHeights, 1, len);//(待拷贝数组,起始位(包含),拷贝到的数组, 起始位, 拷贝长度)
newHeights[len + 1] = 0;
len += 2;
heights = newHeights;
Deque<Integer> stack = new ArrayDeque<>(len);
stack.addLast(0);
for(int i = 1; i < len; i++){
while(heights[i] < heights[stack.peekLast()]){
int curHeight = heights[stack.pollLast()];
int curWeith = i - stack.peekLast() - 1;
res = Math.max(res, curHeight * curWeith);
}
stack.addLast(i);
}
return res;
}
}
如果遇到左括号我们就把他的下标压栈,如果遇到右括号说明和栈顶元素匹配,那么栈顶元素比如m出栈,用当前元素的下标减去新的栈顶元素的值,为什么减去新的栈顶元素值,这是因为新的栈顶元素还没匹配成功,之前的栈顶元素m到现在元素的下标之间构成了有效的括号
class Solution {
public int longestValidParentheses(String s) {
int max = 0;
Stack<Integer> stack = new Stack<>();
stack.push(-1);//防止一开始就遇到')',栈中没有可弹出元素
for(int i = 0; i < s.length(); i++){
if(s.charAt(i) == '('){
stack.push(i);
}else{
stack.pop();
if(stack.isEmpty()){
stack.push(i);//防止遇到')'时栈中没有可弹出元素
}else{
max = Math.max(max, i - stack.peek());
}
}
}
return max;
}
}
class Solution {
public int findUnsortedSubarray(int[] nums) {
Stack<Integer> stack = new Stack<>();
int l = nums.length, r = 0;
for(int i = 0; i < nums.length; i++){
while(!stack.isEmpty() && nums[i] < nums[stack.peek()]){//本该是升序,若当前元素小于栈顶元素,
l = Math.min(l, stack.pop()); //说明没有升序,将重新定义左边界
}
stack.push(i);
}
stack.clear();
for(int i = nums.length - 1; i >= 0; i--){
while(!stack.isEmpty() && nums[i] > nums[stack.peek()]){//本该是降序,否则重新定义右边界
r = Math.max(r, stack.pop());
}
stack.push(i);
}
if(r-l > 0){
return r - l + 1;
}
return 0;
}
}
双指针
- 接雨水 H
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
class Solution {
public int trap(int[] height) {
int left_max = 0, right_max = 0, res = 0;
int left = 0, right = height.length - 1;
while(left < right){
if(height[left] < height[right]){
if(height[left] >= left_max){
left_max = height[left];
}else{
res += (left_max - height[left]);
}
left++;
}else{
if(height[right] >= right_max){
right_max = height[right];
}else{
res += (right_max - height[right]);
}
right--;
}
}
return res;
}
}
- 二叉树的中序遍历 E
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
List<Integer> list = new ArrayList<>();
while(root != null || !stack.isEmpty()){
while(root != null){
stack.add(root);
root = root.left;
}
if(!stack.isEmpty()){
root = stack.pop();
list.add(root.val);
root = root.right;
}
}
return list;
}
}
- 最小栈 E
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) —— 将元素 x 推入栈中。
pop() —— 删除栈顶的元素。
top() —— 获取栈顶元素。
getMin() —— 检索栈中的最小元素。
class MinStack {
/** initialize your data structure here. */
Stack<Integer> stack, min_stack;
public MinStack() {
stack = new Stack<>();
min_stack = new Stack<>();
}
public void push(int val) {
stack.add(val);
if(min_stack.isEmpty() || val <= min_stack.peek()){//这里是<=防止有重复的最小值却只记录了一次
min_stack.add(val);
}
}
public void pop() {
if(min_stack.peek().equals(stack.pop())){
min_stack.pop();
}
}
public int top() {
return stack.peek();
}
public int getMin() {
return min_stack.peek();
}
}
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(val);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.getMin();
*/
- 字符串解码 M
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
示例 1:
输入:s = “3[a]2[bc]”
输出:“aaabcbc”
本题难点在于括号内嵌套括号,需要从内向外生成与拼接字符串,这与栈的先入后出特性对应。
class Solution {
public String decodeString(String s) {
LinkedList<Integer> stack_multi = new LinkedList<>();
LinkedList<String> stack_res = new LinkedList<>();
StringBuilder res = new StringBuilder();
int multi = 0;
for(Character c : s.toCharArray()){
if(c >= '0' && c <= '9'){//如果是数字,就存入multi
multi = multi*10 + Integer.parseInt(c + "");//如果是多位数,高位就要*10,向左移
}
if(c >= 'a' && c <= 'z'){//如果是字母,就存入res
res.append(c);
}
if(c == '['){//如果是'[',将multi,res分比如入栈,之后重置multi,res
stack_multi.addLast(multi);
stack_res.addLast(res.toString());
multi = 0;
res = new StringBuilder();
}
if(c == ']'){//如果是']',res = last_res + cur_multi * res
StringBuilder tmp = new StringBuilder();
int cur_multi = stack_multi.removeLast();
for(int i = 0; i < cur_multi; i++){
tmp.append(res);
}
res = new StringBuilder(stack_res.removeLast() + tmp);
}
}
return res.toString();
}
}
- 每日温度 M
请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。
例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。
用单调栈,这是一个递减的栈
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int len = temperatures.length;
int arr[] = new int[len];
Deque<Integer> stack = new LinkedList<>();
for(int i = 0; i < len; i++){
int temperature = temperatures[i];//记录下该位置的值,因为下面的循环可能弹出多个值
while(!stack.isEmpty() && temperature > temperatures[stack.peek()]){//当前元素大于栈顶元素,
//则两者下标之差即是天数
int preIndex = stack.pop();//弹出栈顶元素
arr[preIndex] = i - preIndex;//计算下标差
}
stack.push(i);//否则,该元素入栈,继续循环,直至找到下一个大于栈顶元素的值
}
return arr;
}
}
- 盛最多水的容器 M
设置双指针 i,j分别位于容器壁两端,根据规则移动指针(后续说明),并且更新面积最大值 res,直到 i == j 时返回 res
class Solution {
public int maxArea(int[] height) {
int i = 0, j = height.length - 1, res = 0;
while(i < j){
if(height[i] < height[j]){
res = Math.max(res, (j-i)*height[i]);
i++;
}else{
res = Math.max(res, (j-i)*height[j]);
j--;
}
}
return res;
}
}
二叉树
- 二叉树展开为链表 M
将左子树插入到右子树的地方
将原来的右子树接到左子树的最右边节点
考虑新的右子树的根节点,一直重复上边的过程,直到新的右子树为 null
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public void flatten(TreeNode root) {
while(root != null){
if(root.left == null){//左子树为空,直接到右子树
root = root.right;
} else{
TreeNode per = root.left;
while(per.right != null){//找到左子树最右边的节点
per = per.right;
}
per.right = root.right;//将右子树接到左子树最右边节点后面
root.right = root.left;//将左子树接到右子树原来的位置
root.left = null;//root的左边清空
root = root.right;//继续下一个节点
}
}
}
}
递归
- 合并两个有序链表 E
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1 == null) return l2;//l1排完之后,后面直接全是l2
if(l2 == null) return l1;//同上
if(l1.val < l2.val){
l1.next = mergeTwoLists(l1.next,l2);
return l1;
}else{
l2.next = mergeTwoLists(l1,l2.next);
return l2;
}
}
}
- 合并K个升序链表 H
给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。
这个题目与上一题相似,将链表数组转化为两两合并
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if(lists.length == 0) return null;
return merge(lists, 0, lists.length-1);
}
public ListNode merge(ListNode[] lists, int l, int r){
if(l == r){//左右相等,即只有一条链表,没人和它合并,直接返回自己
return lists[l];
}
int mid = l+(r-l)/2;
ListNode n1 = merge(lists, l, mid);//分治
ListNode n2 = merge(lists, mid+1, r);
return recur(n1, n2);
}
public ListNode recur(ListNode n1, ListNode n2){
if(n1 == null) return n2;
if(n2 == null) return n1;
if(n1.val < n2.val){
n1.next = recur(n1.next, n2);
return n1;
}else{
n2.next = recur(n1, n2.next);
return n2;
}
}
}
贪心
- 跳跃游戏 M
给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。
依次遍历数组中的每一个位置,并实时维护 最远可以到达的位置。对于当前遍历到的位置 xx,如果它在 最远可以到达的位置 的范围内,那么我们就可以从起点通过若干次跳跃到达该位置,因此我们可以用 x + \textit{nums}[x]x+nums[x] 更新 最远可以到达的位置。
在遍历的过程中,如果 最远可以到达的位置 大于等于数组中的最后一个位置,那就说明最后一个位置可达,我们就可以直接返回 True 作为答案。反之,如果在遍历结束后,最后一个位置仍然不可达,我们就返回 False 作为答案。
class Solution {
public boolean canJump(int[] nums) {
int max_len = 0;//记录最远能够跳到的距离
for(int i = 0; i < nums.length; i++){
if(i <= max_len){//i<max_len说明i可达
max_len = Math.max(max_len, i+nums[i]);
if(max_len >= nums.length - 1){//最远距离大于数组长度,说明可达
return true;
}
}
}
return false;
}
}
分治
- 数组中的第K个最大元素 M
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
基于快排思想将数组排序
import java.util.Random;
class Solution {
private static Random random = new Random(System.currentTimeMillis());
public int findKthLargest(int[] nums, int k) {
int len = nums.length;
int left = 0;
int right = len - 1;
int target = len - k;//第k大就是下标len-k
while (true) {
int index = partition(nums, left, right);
if (index == target) {
return nums[index];
} else if (index < target) {
left = index + 1;
} else {
right = index - 1;
}
}
}
private int partition(int[] nums, int left, int right){
if(right > left){
//选取随机下标作为标定点,范围[left,right]
int randomIndex = random.nextInt(right - left ) + 1 + left;
swap(nums, left, randomIndex);
}
int pivot = nums[left];
int i = left+1, j = right;
while(true){
while(i <= j && nums[i] < pivot){
i++;
}
while(i <= j && nums[j] > pivot){
j--;
}
if(i > j) break;
swap(nums, i, j);
i++;
j--;
}
swap(nums, left, j);
return j;
}
private void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
}
双指针
- 回文链表 E
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
List<Integer> list = new ArrayList<>();
ListNode cur = head;
while(cur != null){
list.add(cur.val);//将链表转换为数组
cur = cur.next;
}
int front = 0;//双指针分别从头尾遍历
int back = list.size() - 1;
while(front < back){
if(list.get(front).equals(list.get(back))){
front++;
back--;
}else{
return false;
}
}
return true;
}
}