# （一）栈

## 20 有效的括号——辅助栈法

class Solution {
public boolean isValid(String s) {
HashMap<Character,Character> map = new HashMap<>();
map.put('(',')');
map.put('[',']');
map.put('{','}');
for(int i=0;i<s.length();i++){
Character c = s.charAt(i);
if(map.containsKey(c)){
stack.push(c);
}else{
if(stack.isEmpty())
return false;
char top = stack.pop();//栈的方法是push和pop
if(map.get(top)==c)
continue;
else return false;
}
}
return stack.size()==0;
}
}


## 155 最小栈（单调栈思想——存在和左右比较的关系）

a>辅助栈顶，辅助栈不变；
a<=辅助栈顶，辅助栈中将a放入；

a>辅助栈顶，此时辅助栈顶是min；
a==辅助栈顶，此时辅助栈顶pop；

class MinStack {
private Stack<Integer> stack;
private Stack<Integer> help;

public MinStack() {
stack = new Stack<>();
help = new Stack<>();
}

public void push(int x) {
stack.push(x);
if(help.isEmpty()||x<=help.peek())
help.push(x);
}

public void pop() {
if(stack.pop().equals(help.peek()))//一定要注意集合中装的是对象，比较的时候用equals方法，==是不可以的
help.pop();
}

public int top() {
return stack.peek();
}

public int getMin() {
return help.peek();
}
}


## 394 字符串解码

class Solution {
public String decodeString(String s) {
stack.push(new StringBuilder(""));
for(int i=0;i<s.length();i++){
int digit=0;//
while(s.charAt(i)>='0'&&s.charAt(i)<='9'){
digit=digit*10 +s.charAt(i)-'0';
i++;
}
if(digit!=0)
stack.push(digit);

if(s.charAt(i)=='['){
stack.push('[');
stack.push(new StringBuilder(""));
}
else if(s.charAt(i)==']'){
StringBuilder temp = (StringBuilder)stack.pop();
stack.pop();//pop左括号
Integer count = (Integer)stack.pop();
String ss = temp.toString();
for(int j=0;j<count-1;j++)
temp.append(ss);
StringBuilder sb = (StringBuilder)stack.peek();
sb.append(temp);
}else{
StringBuilder sb = (StringBuilder)stack.peek();
sb.append(s.charAt(i));
}

}
return (String)stack.peek().toString();
}
}


## 739 每日温度(单调栈——需要和左右比较的时候用)

class Solution {
public int[] dailyTemperatures(int[] T) {
int[] res = new int[T.length];
for(int i=0;i<T.length;i++)
res[i] = 0;
for(int i=0;i<T.length;i++){
if(stack.isEmpty() || T[i] <= T[stack.peek()])
stack.push(i);
else{
while(!stack.isEmpty() && T[stack.peek()] < T[i]){//注意，使用stack.peek的时候确保不空才行
int index = stack.pop();
res[index] = i-index;
}
stack.push(i);
}
}
return res;
}
}


## 42 接雨水——dp 双指针 单调栈

（1)两遍遍历，用数组保存a[i]的前i个高度中的最大值（可以用动态规划思想）

class Solution {
//遍历求左右最大高度
public int trap(int[] height) {
if(height.length==0)
return 0;
int res = 0;
int[] left = new int[height.length];
//第一遍，求每个节点左边最高（含自己）
left[0] = height[0];
for(int i=1;i<height.length;i++){
left[i] = Math.max(left[i-1],height[i]);
}
//第二遍，求右边最高，并且同时比较左右最高，求出可积累雨水量
int rightmx = 0;
for(int i=height.length-1;i>=0;i--){
rightmx = Math.max(rightmx,height[i]);
res += Math.min(left[i],rightmx)-height[i];
}
return res;
}
}


(2)最高效的方法——（双指针），时间O（n），空间O(1)
left指针：从左往右当前下标
right指针：从右向左当前下标
left_max:left指针左边能找的最大值（不含left自己）——这个值一定是当前left的左堤坝
right_max:right指针右边能找到的最大值（不含right）——这个值一定是当前right的优堤坝

class Solution {
public int trap(int[] height) {
if(height.length<=2)
return 0;
int res = 0;
int left = 0,right = height.length-1;
int leftMax = 0,rightMax = 0;
while(left <= right){//注意结束条件，当left==right时，当前节点依然可以和左右最高值计算存雨量
if(leftMax < rightMax){//左指针可计算存储量了
res += Math.max(leftMax-height[left],0);
leftMax = Math.max(leftMax,height[left]);
left += 1;
}else{
res += Math.max(rightMax-height[right],0);
rightMax = Math.max(rightMax,height[right]);
right--;
}
}
return res;
}
}


（3）用单调栈，找到每次发生上升的位置，只有此时才可能出现囤积雨水的现象。

public class Solution{
int trap(int[] height){
int n = height.length;
if(n<=2) return 0;
int res =0;//结果
int curLeftMax=0;//当前节点左边最大的那个高度（不含当前）
for(int i=0;i<n;i++){
int cur = height[i];

if(cur>=curLeftMax){//如果当前节点是新的制高点
while(!stackHeight.isEmpty()){//更新递减栈，以原来的左边最高为短板，计算雨水
int h = stackHeight.pop();
int index = stackIndex.pop();
if(stackHeight.isEmpty()) break;//最后一个元素，也就是那个原来的左边最高被pop了，可直接退出
res += (index-stackIndex.peek())*(curLeftMax-h);//雨水=长*高，高是和左最高的差，长是递减栈距离自己左边第一个元素的距离。
}
curLeftMax = cur;
stackHeight.push(cur);
stackIndex.push(i);
}else{//当前节点很普通
while(stackHeight.peek()<=cur){
int h = stackHeight.pop();
int index = stackIndex.pop();
res += (index - stackIndex.peek())*(cur-h);
}
stackHeight.push(cur);
stackIndex.push(i);
}
}
return res;
}
}


# （二）堆

## 剑指offer41 数据流中的中位数

• 添加元素x
• Left的大小==Right的大小，将x放入Right，然后Right新的堆顶放回Left
• Left的大小>Right的大小，x放入Left，Left新的堆顶放回Right
• 查找中位数
• Left的大小==Right的大小，中位数是堆顶的算数平均
• Left的大小>Right的大小，中位数是Left的堆顶

class MedianFinder{
Queue<Integer> Left,Right;
public MedianFinder(){
Left = new PriorityQueue<>((x,y)->y-x);//大顶堆，放小的那一半
Right = new PriorityQueue<>();//小顶堆
}
//添加元素
if(Left.size() == Right.size()){
}else{
}
}
//找到中位数
public double findMedian(){
if(Left.size() == Right.size())
return (Left.peek()+Right.peek())/2;
else
return Left.peek();
}
}


## 23 合并k个升序链表(三种方法)




在这里插入代码片


在这里插入代码片


## 215 数组的第K个最大的元素

（1）如何用快排找第k大的元素

• 如果划分好后的位置下标是 n-k,返回之
• 如果划分好后的位置下标在n-k前面，也就是说第k大的元素在它后面，递归调用 右边
• 如果划分后的位置下标>n-k,递归调用左边
时间复杂度：O（nlogn）,
空间复杂度：用栈，是logn
class Solution {
public int findKthLargest(int[] nums, int k) {
return quicksort(nums,0,nums.length-1,k);
}
private int quicksort(int[] a,int left,int right,int k){
if(left>right)
return -1;
int base=a[left],i=left,j=right;
while(i<j){
while(a[j]>=base && j>i)
j--;
while(a[i]<=base && j>i)//都是带等号的，否则出错
i++;
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
a[left] = a[i];
a[i] = base;
if(i==a.length-k)
return a[i];
else if(i<a.length-k)
return quicksort(a,i+1,right,k);
else
return quicksort(a,left,i-1,k);

}
}


（2）经典做法——堆

PriorityQueue<Integer> heap = new PriorityQueue(n,(a,b)->b-a);//大顶堆要写倒过来


public class Solution{
public int findKthLargest(int[] nums,int k){
return heapsort(nums,k);
}
public int heapsort(int[] a,int  k){
int len = a.length;
for(int i = len/2-1;i>=0;i--)//初始化堆
heapajust(a,i,len);
swap(a,0,--len);//找到最大的元素了
if(k==1)
return a[len];
while(len>0){//当未排序长度>0时
heapajust(a,0,len);
swap(a,0,--len);
if(a.length-k==len)
return a[len];
}
return a[0];
}
private void heapajust(int[] a,int index,int len){
int left = index*2+1;
while(left<len){
int larger = left;
if(left+1<len && a[left+1]>a[left])
larger = left+1;
if(a[index]<a[larger]){//更大和和当前节点交换，并且当前节点变成交换后的位置，注意更新左孩子left
swap(a,index,larger);
index = larger;
left = index*2+1;
}else{
break;
}
}
}
private void swap(int[] s,int a,int b){
int temp = s[a];
s[a] = s[b];
s[b] = temp;
}
}


## 347 前K个高频元素（hash+快排/堆排）

quick(a[],left,right)，将a从小道大排序，以a[left]为基准元素，将a[]分成两部分，左边都<a[left],右边都>=a[left]，得到基准元素的位置

class Solution{
public int[] topKeyFrequent(int[] nums ,int k){
//哈希表放着num数组元素出现的次数
Map<Integer,Integer> map = new HashMap<Integer,Integer>();
for(int item:nums)
map.put(item,map.getOrDefault(item,0)+1);

//次数数组,如{[1,3]，[2,1]，[3,2]}代表元素1出现3次，元素2出现1次，元素3出现2次
List<int[]> list = new ArrayList<int[]>();
for(Map.Entry<Integer,Integer> entry : map.entrySet()){
int key = entry.getKey();
int count = entry.getValue();
}
int[] res = new int[k];//结果集
quicksort(list,0,list.size()-1,res,0,k);
return res;
}
//快排求出list的出现次数前k做多的元素，放在res中
public void quicksort(List<int[]>,int left,int right,int[] res,int index,int k){
int base = list.get(left)[1];
int i = left,j = right;
while(i<j){
while(i<j && list.get(j)[1] >= base)
j--;
while(i<j && list.get(i)[1] <= base)
i++;
int[] temp = list.get(i);
list.set(i,list.get(j));
list.set(j,temp);
}
int[] temp = list.get(left);
list.set(left,list.get(i));
list.set(i,temp);
if(i==list.size()-k){
while(i<list.size()){
res[index] = list.get(i)[0];
index++;
i++;
}
}else if(i<list.size()-k){
quicksort(list,i+1,right,res,0,k);
}else{
quicksort(list,left,i-1,res,0,k);
}
}
}


class Solution{
public int[] topKFrequent(int[] nums,int k){
//哈希表放着num数组元素出现的次数
Map<Integer,Integer> map = new HashMap<Integer,Integer>();
for(int item:nums)
map.put(item,map.getOrDefault(item,0)+1);
//最小堆
PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>(){
public int compare(int[] m,int[] n){
return m[1]-n[1];
}
});
//遍历维护这个最小跟堆
for(Map.Entry<Integer,Integer> entry : map){
int key = entry.getKey();
int count = entry.getValue();
if(queue.size()==k){
if(queue.peek()[1] < count){
queue.poll();
queue.offer(new int[]{key,count});
}
}else{
queue.offer(new int[]{key,count});
}
}
//从堆中得到返回值
int[] res = new int[k];
for(int i=0;i<k;i++)
res[i] = queue.poll()[0];
return res;
}
}


# （三）队列

## 剑指offer59I 滑动窗口的最大值（单调队列）

1. 原来窗口的最大值，看看是否被移出
2. 原来窗口的次最大值
3. 当前新元素

class solution{
public int[] maxSlidingWindow(int[] nums,int k){
//边界
if(nums.length == 0 || k==0)
return new int[0];
//我们写队列最好是用Deque这个API

//先构造第一个窗口
for(int i=0;i<k;i++){
//后面出现的元素如果比队列尾部的一些元素小，会覆盖他们变成新的次大元素
while(!deque.isEmpty() && deque.peekLast() < nums[i])
deque.removeLast();
}
res[0] = deque.peekFirst();

//下面是滑动窗口阶段
for(int i=k;i<nums.length; i++){
if(deque.peekFirst() == nums[i-k])
deque.removeFirst();
while(!deque.isEmpty() && deque.peekLast() < nums[i])
deque.removeLast();
res[i-k+1] = dequeue.peekFirst();
}
return res;
}
}



## 剑指offer59II 队列的最大值

max的对头始终是当前队列的最大值，

• 插入元素时，如果新元素是的东西，不管他，直接放进入，队伍前面有大家伙撑着；如果是大家伙，max就要更新了，将队伍前面新来的小的家伙淘汰掉，再将这个大家伙放进入当升职当长老。
• poll元素的时候，如果和max顶相同也要一起poll，否则不管max
class MaxQueue{
Queue<Integer> queue;
Deque<Integer> max;
public MaxQueue(){
}
//入队
public void push_back(int value){
while(!max.isEmpty() && max.peekLast() < value)
max.removeLast();
}
//出队
public int pop_front(){
if(!max.isEmpty() && queue.peek().equals(max.peekFirst()))
max.removeFirst();
return queue.size()==0?-1:queue.poll();
}
//最大值
public int max_value(){
return max.size()==0?-1:max.peek();
}
}


## 621 任务调度器（用优先队列太麻烦了，填桶策略）

• 任务种类很少的时候，填不满n+1
• 任务种类很多的时候，桶子放不下，一行放不同可以尽情的放超过n+1都可以执行
• 我们就计算第一种就行了，和任务长度间取一个最大值做返回值
class Solution{
int[] counts = new int[26];//存放的是每种任务数量
counts[c-'A']+=1;
int max = 0;
for(int count : counts) max = Math.max(max,count);
int maxCount = 0;		//最后一行还剩的元素数量
for(int count:counts)
if(count == max)
maxCount++;
}
}


# （四）并查集（一般用于判断图中是否有环，也可以用于求连通分量个数）

## 128最长连续序列——（字节原题，并查集和哈希表法）

（1）哈希表+优化

class Solution {
public int longestConsecutive(int[] nums) {
Set<Integer> set = new HashSet<>();
for(int a:nums)
int maxlen = 0;
for(int a:nums){
if(a==Integer.MIN_VALUE || !set.contains(a-1)){//小心边界
int curlen = 1;
int curnum = a;
while(a != Integer.MAX_VALUE && set.contains(curnum+1)){//小心边界
curnum++;
curlen++;
}
maxlen = Math.max(maxlen,curlen);
}
}
return maxlen;
}
}



class Solution {
//并查集类
class UnionFind{
Map<Integer, Integer> parents;
public UnionFind(int[] arr) {//初始化，map<自己，祖先>
parents = new HashMap<>();
for (int i : arr) {
parents.put(i, i);
}
}
public Integer find(int x) {//查找x的祖宗
if (!parents.containsKey(x)) return null;
int t = parents.get(x);
if(x != t)
parents.put(x, find(t));//如果x的祖先不是自己，更新x的祖先
return parents.get(x);//最后返回x的祖先
}
public boolean union(int x, int y) {//将x和y变成一个集合关系（具有祖先子孙关系）
Integer rootX = find(x), rootY = find(y);
if (rootX == null || rootY == null) return false;
if(rootX.equals(rootY)) return false;
parents.put(rootX, rootY);
return true;
}
}
//-------------------------------------------
//解题
public int longestConsecutive(int[] nums) {
if (nums.length == 0) return 0;
UnionFind u = new UnionFind(nums);
for (int num : nums) {//构造并查集，每次合并num 和 num+1
u.union(num, num + 1);
}
int max = 1;
for (int num : nums) {
max = Math.max(max,u.find(num) - num + 1);
}
return max;
}
}


## 547 朋友圈——（并查集）

int fa[1000];
int find(int x){//查找x的祖先节点
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}
//查找连通分量个数
int findCircleNum(int** M, int MSize, int* MColSize){
for(int i=0;i<MSize;i++)   fa[i]=i;
int ans=MSize;//连通分量默认有MSize个
for(int i=0;i<MSize;i++)//遍历行
{
for(int j=0;j<i;j++)//遍历列
{
if(M[i][j]==0)
continue;
if(find(fa[i])!=find(fa[j]))//如果该两个点间不同祖先
{
fa[find(i)]=fa[find(j)];//祖先合并为一家
ans--;//连通分量--
}
}
}
return ans;
}


## 被围绕的区域

• 将各个坐标映射到一维，范围在[0, mn-1]，同时定义一个超级源点src在最后位置mn，这个源点与所有边缘的’O’相连
• 对于在里边的’O’来说，将其与上下左右四个方向的’O’连接起来
• 最后在遍历一遍矩阵，将没有与src连接在一块的’O’置为’X’
class Solution {
// 定义并查集——很简单，并查集都是这么定义的
class UnionFind{
int[] parents;
public UnionFind(int size){
parents = new int[size];
for(int i = 0; i < size; i++)
parents[i] = i;
}
//找x的祖先
public int find(int x){
if(parents[x] == x)
return x;
return parents[x] = find(parents[x]);
}
//合并x和y
public void union(int x, int y){
int px = find(x);
int py = find(y);
if(px == py)
return ;
parents[px] = py;
}
//x和y是否在同一个连通图里
public boolean isConnect(int x, int y){
return find(x) == find(y);
}
}

int[][] d = new int[][]{{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
int m;
int n;
//求解
public void solve(char[][] board) {
m = board.length;
if(m == 0) return;
n = board[0].length;
int size = m * n + 1;
int src = m * n;
UnionFind u = new UnionFind(size);
//遍历这个图，处理并查集
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(board[i][j] == 'X')
continue;
if(i == 0 || i == m-1 || j == 0 || j == n-1)// 是边缘的'O'与超级源点src相连接
u.union(src, i * n + j);
else{//不是边缘的‘O’
for(int k = 0; k < 4; k++){ // 将周围的'O'与(i, j)连接
int x = i + d[k][0];
int y = j + d[k][1];
if(board[x][y] == 'O')
u.union(i * n + j , x * n + y);
}
}
}
}
//最后根据并查集的结果处理图，不和周围的‘O’相联的O都改成“X”
for(int i = 1; i < m-1; i++){
for(int j = 1; j < n-1; j++){
if(board[i][j] == 'O' && !u.isConnect(src, i * n + j)){
board[i][j] = 'X';
}
}
}
}
}


• 点赞
• 评论
• 分享
x

海报分享

扫一扫，分享海报

• 收藏
• 打赏

打赏

青椒炒肉小郎君

你的鼓励将是我创作的最大动力

C币 余额
2C币 4C币 6C币 10C币 20C币 50C币
• 举报
• 一键三连

点赞Mark关注该博主, 随时了解TA的最新博文
04-29 36
08-21 34

06-30 27
10-09 38
03-20 62