一)删除字符串中所有相邻字符的重复项
1047. 删除字符串中的所有相邻重复项 - 力扣(LeetCode)
算法原理:栈结构+模拟,只是需要遍历所有字符串中的字符,一次存放到栈里面即可,也是可以使用数组来模拟一个栈结构的:
class Solution { public String removeDuplicates(String s) { Stack
stack=new Stack<>(); String ret=""; for(char ch:s.toCharArray()){ if(stack.isEmpty()) stack.push(ch); else{ char top=stack.peek(); if(top==ch){ stack.pop(); }else{ stack.push(ch); } } } for(char ch:stack){ ret+=ch; } return ret; } } class Solution { public String removeDuplicates(String s) { StringBuilder result=new StringBuilder(); for(char ch:s.toCharArray()){ if(result.length()==0){ result.append(ch); }else{ if(result.charAt(result.length()-1)==ch){ result.deleteCharAt(result.length()-1); }else{ result.append(ch); } } } return result.toString(); } }
二)比较含退格的字符串
844. 比较含退格的字符串 - 力扣(LeetCode)
算法原理:如果遇到空格,那么直接消掉前面的字符即可,如果当前的第一个字符是#,那么不会抵消前面的任何一个字符,仍然是一个空字符串
class Solution { public String changeStr(String str){ Stack
stack=new Stack<>(); String ret=""; for(char ch:str.toCharArray()){ if(ch=='#'){ if(!stack.isEmpty()) stack.pop(); else continue; } else stack.push(ch); } for(char ch:stack) ret+=ch; return ret.toString(); } public boolean backspaceCompare(String s, String t) { return changeStr(s).equals(changeStr(t)); } }
三)基本运算器(2)
224. 基本计算器 - 力扣(LeetCode)
算法原理:
1)由于乘除运算优于加减运算,因此我们不妨先考虑先进行所有的乘除运算,并将这些乘除运算后的整数值放回到原表达式的位置,然后随后将所有的乘除运算都计算完成之后,随后整个表达式里面的值就是就是相当于是整个一系列整数相加减的值
2)因此我们可以使用一个栈,保存这些进行乘除运算之后的整数的值,对于加减号的数字可以直接压入到栈里面,对于乘除号的数字,可以直接和栈顶元素进行计算,并替换栈顶元素作为计算之后的结果
3)prev变量是保存我们遍历到的任意一个数字前面的符号
class Solution { public int calculate(String s) { Stack
stack=new Stack<>(); char[] array=s.toCharArray(); char prev='+'; int index=0; while(index ='0'&&ch<='9'){ int result=0; while(index ='0'&&array[index]<='9'){ result=result*10+Integer.parseInt(array[index]+""); index++; } if(prev=='+') stack.push(result); else if(prev=='-') stack.push(-result); else if(prev=='*'){ int num=stack.pop(); stack.push((result)*num); } else if(prev=='/'){ int num=stack.pop(); stack.push(num/(result)); } }else{ prev=ch; index++; } } int sum=0; for(int num:stack) sum+=num; return sum; } }
四)字符串解码
394. 字符串解码 - 力扣(LeetCode)
算法原理:1[a2[b3[c]]]
1)本题和列竖式和带有括号数式的计算是相同的,我们都是先进行括号里面的,然后再来进行计算括号外边的,所以初步构想一下本题还是使用栈来进行解决
2)从前向后进行遍历这个字符串,当我们遍历到整个字符串的时候,是根本不知道后面的字符串是什么东西,后面本质上是一个黑盒,当我们遇到1的时候,是不能将后面的东西贸然重复一次,应该先让里面的东西解码,所以需要使用一个数据结构将这个字符1的信息保存起来
3)保存信息是从前向后的顺序,当遇到右括号的时候要将最近的字符进行出栈
class Solution { public String decodeString(String s) { char[] array=s.toCharArray(); Stack
NumStack=new Stack<>(); Stack StringStack=new Stack<>(); StringStack.push(""); int index=0; int len=array.length; while(index ='0'&&array[index]<='9'){ int result=0; while(index ='0'&&array[index]<='9'){ result=result*10+Integer.parseInt(array[index]+""); index++; } NumStack.push(result); }else if(array[index]>='a'&&array[index]<='z'){ //2.如果遇到的是单独一个字符串,那么直接提取出来拼接到栈顶元素的后面 String result=""; while(index ='a'&&array[index]<='z'){ result+=array[index]; index++; } String topString=StringStack.pop(); topString+=result; StringStack.push(topString); }else if(array[index]=='['){ //3.如果遇到的是左括号那么就直接提取后面的字符,左括号后面一定是字符串 String result=""; index++; while(index ='a'&&array[index]<='z') { result += array[index]; index++; } StringStack.push(result);//注意这个代码此时就不能取出栈顶元素的字符串进行拼接了 }else if(array[index]==']'){ //4.如果遇到的是右括号,那么直接提取出数字栈和字符串栈中的数字进行拼接 int num=NumStack.pop(); String str=StringStack.pop(); String temp=""; for(int i=0;i
五)验证栈序列
946. 验证栈序列 - 力扣(LeetCode)
算法原理:
1)让元素一直进栈即可
2)进栈的同时,判断是否出栈即可
class Solution { public boolean validateStackSequences(int[] pushed, int[] popped) { int index1=0; int index2=0; Stack
stack=new Stack<>(); while(index1
六)N叉树的层序遍历
429. N 叉树的层序遍历 - 力扣(LeetCode)
为什么层序遍历会使用到队列这种数据结构呢?当我们进行遍历到当前层的时候是需要上一层的所有节点的第一个节点的,开始向后进行拓展,要使用一个先进先出的数据结构
class Solution { public List
> levelOrder(Node root) { List
> result=new ArrayList<>(); if(root==null) return result; Queue
queue=new LinkedList<>(); queue.add(root); while(!queue.isEmpty()){ int size=queue.size(); List tempList=new ArrayList<>(); for(int i=0;i
N叉树的前序遍历:
class Solution { List
ret=new ArrayList<>(); public void dfs(Node root){ if(root==null) return; ret.add(root.val); for(Node node:root.children){ dfs(node); } } public List preorder(Node root) { dfs(root); return ret; } } N叉树的后序遍历:
class Solution { List
ret=new ArrayList<>(); public void dfs(Node root){ if(root==null) return; for(Node node:root.children){ dfs(node); ret.add(node.val); } } public List postorder(Node root) { dfs(root); if(root!=null) ret.add(root.val); return ret; } } N叉数的最大深度
class Solution { public int maxDepth(Node root) { if(root==null) return 0; int temp=0; List
childrens=root.children; for(Node node:childrens){ temp=Math.max(maxDepth(node),temp); //找到每一个孩子结点的最大深度 } return temp+1; } }
七)二叉树的锯齿形层序遍历
103. 二叉树的锯齿形层序遍历 - 力扣(LeetCode)
增加一个标记位,让偶数行的信息逆序即可
算法原理:在我们写层序遍历的代码的时候,只是需要稍微进行一下修改即可,我们可以记录一个flag值,当flag是偶数的时候就将这一层的list进行逆序一下就可以了,当我们们拿到这一层的结果之后存储到最终的结果之前,直接进行逆序即可
解法1:使用一个LinkedList,如果是奇数层,那么就采用尾插法,如果是偶数层,那么就采用头插法
class Solution { public List
> zigzagLevelOrder(TreeNode root) { List
> ret=new ArrayList<>(); Queue
queue=new LinkedList<>(); if(root==null) return ret; queue.add(root); while(!queue.isEmpty()){ int size=queue.size(); LinkedList tempList=new LinkedList<>(); for(int i=0;i 解法2:立flag
class Solution { public List
> zigzagLevelOrder(TreeNode root) { List
> ret=new ArrayList<>(); Queue
queue=new LinkedList<>(); if(root==null) return ret; queue.add(root); boolean flag=true; while(!queue.isEmpty()){ int size=queue.size(); List tempList=new ArrayList<>(); for(int i=0;i
八)二叉树的最大宽度
662. 二叉树最大宽度 - 力扣(LeetCode)
解法1:让队列存储空节点,统计每一层的最大宽度,从而计算最大值
最大宽度的计算其实本质上就是两个有效值之间节点个数的计算,时间超时
时间超时的原因就是如果树的高度太高,存储空间点过多,内存还会超时
如果二叉树像这样的话,二叉树的高度是1500,那么最后一层List就会存储非常多的空节点,此时内存都会超时
class Solution { public int widthOfBinaryTree(TreeNode root) { if(root==null) return 0; Queue
queue=new LinkedList<>(); List > result=new ArrayList<>(); queue.add(root); int maxWidth=0; while(!queue.isEmpty()){ int size=queue.size(); List
tempList=new ArrayList<>(); for(int i=0;i 解法2:利用数组存储二叉树的方式给节点编号
1)可以画一个二叉树,将所有结点的下标值都列出来,之前我们写层序遍历的时候是每一行进行存储的是二叉树每一层结点的值,现在存储的是二叉树每一层节点的下标值
2)因为我们要保存每一个节点的下标值,以方便计算左孩子的下标和有孩子的下标,所以我们想队列中存放的就不能单单的存放一个根节点了,而是存放hash
3)所以此时就不要再进行关心空节点了,此时只是需要关心下标即能计算出二叉树的宽度
4)注意一个细节问题:节点的下标是有可能存在溢出的,因为对于一棵二叉树来说,题目中给定的数据范围是3000个节点,那么如果这棵树只是有左右两分支,况且每一个分支的高度是1500个,那么此时有的结点的下标可能就达到2^1500-1,此时我们在JAVA中学过的所有的数据类型都是无法存下这个数字的,但是结论是当我们相减之后,即使溢出,那么结果也是正确的,因为数据的存储是环形的,但是别超过1圈;
import sun.reflect.generics.tree.Tree; import java.util.*; class Solution { static 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; } } public static int widthOfBinaryTree(TreeNode root) { if(root==null) return -1; Queue
> queue=new LinkedList<>(); HashMap topmap=new HashMap<>(); topmap.put(root,0); int maxWidth=0; queue.add(topmap); while(!queue.isEmpty()){ List tempList=new ArrayList<>(); int size=queue.size(); for(int i=0;i hash=queue.poll(); for(Map.Entry entry: hash.entrySet()){ int index=entry.getValue(); TreeNode node=entry.getKey(); tempList.add(index); if(node.left!=null){ HashMap map=new HashMap<>(); map.put(node.left,2*index+1); queue.add(map); } if(node.right!=null){ HashMap map=new HashMap<>(); map.put(node.right,2*index+2); queue.add(map); } } } System.out.println(tempList); if(tempList.size()>0) maxWidth=Math.max(maxWidth, tempList.get(tempList.size()-1)-tempList.get(0)+1); } return maxWidth; } public static void main(String[] args) { TreeNode root=new TreeNode(10); root.left=new TreeNode(100); root.right=new TreeNode(1000); System.out.println(widthOfBinaryTree(root)); } } class Solution { public int widthOfBinaryTree(TreeNode root) { //1.使用数组模拟队列 List
> queue=new LinkedList<>(); queue.add(new Pair (root,0)); int widthLength=0;//记录最终结果 //2.进入循环 while(!queue.isEmpty()){ //2.1先更新一下这一层的宽度 Pair firstNode=queue.get(0); Pair tailNode=queue.get(queue.size()-1); widthLength=Math.max(widthLength, tailNode.getValue()-firstNode.getValue()+1); //2.2再让下一层入队 List > newQueue=new LinkedList<>(); for(Pair temp:queue){ TreeNode node=temp.getKey(); int index=temp.getValue(); if(node.left!=null){ newQueue.add(new Pair (node.left,2*index+1)); } if(node.right!=null){ newQueue.add(new Pair (node.right,2*index+2)); } } queue=newQueue; } return widthLength; } } class Solution { public int widthOfBinaryTree(TreeNode root) { if(root==null) return -1; Queue
queue=new LinkedList<>(); root.val=0; queue.add(root); int maxLen=0; while(!queue.isEmpty()){ int size=queue.size(); List tempList=new ArrayList<>(); for(int i=0;i
解法3:深度优先遍历
hash
哈希表中存放的就是层数和每一层的第一个节点对应的坐标值 1)使用一个哈希表来存放进行深度优先遍历过程中的最左边的节点,就是每一层的开始节点
2)当我们进行深度优先遍历的过程中,如果发现是最左边的节点(哈希表中没有出现过),那么就将它加入到哈希表中,如果发现哈希表中已经存在节点了,那么就哈希表中的节点拿出来进行计算一下最大值;
class Solution { HashMap
result=new HashMap<>(); int WidthMax=1; public void dfs(TreeNode root,int level,int index){ if(root==null) return; if(!result.containsKey(level)){ result.put(level,index); }else{ WidthMax=Math.max(WidthMax,index-result.get(level)+1); } dfs(root.left,level+1,2*index+1); dfs(root.right,level+1,2*index+2); } public int widthOfBinaryTree(TreeNode root) { if(root==null) return 0; dfs(root,0,0); return WidthMax; } }
九)最后一块石头的重量
1046. 最后一块石头的重量 - 力扣(LeetCode)
class MyPriorityQueue { int[] array=null; int len=0; public MyPriorityQueue(int[] array){ this.array=array; this.len=array.length; for(int i=(array.length-1-1)/2;i>=0;i--){ DownAdjust(array,array.length,i); } } public void DownAdjust(int[] array,int len,int i){ int parent=i; int child=2*i+1; while(child
array[parent]){ int temp=array[child]; array[child]=array[parent]; array[parent]=temp; parent=child; child=2*parent+1; }else{ break; } } } public int poll(){ System.out.println(len); int result=array[0]; int temp=array[0]; array[0]=array[len-1]; array[len-1]=temp; len--; DownAdjust(array,len,0); return result; } public void offer(int data){ if(len>=array.length){ this.array=Arrays.copyOf(array,2*array.length); } len++; array[len-1]=data; //此时要进行向上调整 upAdjust(array,len); } public void upAdjust(int[] array,int len){ int child=len-1; int parent=(len-1)/2; while(child>=0){ if(array[child]>array[parent]){ int temp=array[child]; array[child]=array[parent]; array[parent]=temp; child=parent; parent=(child-1)/2; }else { break; } } } public int GetSize(){ return len; } } class Solution { public int lastStoneWeight(int[] stones) { if(stones.length==2) return Math.abs(stones[1]-stones[0]); MyPriorityQueue queue=new MyPriorityQueue(stones); while(queue.GetSize()>1){ int a=queue.poll(); int b=queue.poll(); int result=Math.abs(a-b); if(result!=0) queue.offer(result); } if(queue.GetSize()==0) return 0; return queue.poll } }
十)数据流中的第K大元素
703. 数据流中的第 K 大元素 - 力扣(LeetCode)
关于TopK问题,一共有两种解决方法:
1)第一种:堆,时间复杂度是N*logK,是用来一个一个来处理数据的
2)第二种就是快速选择算法,时间复杂度是O(N)考的多
当进行解决TopK问题的时候要想明白两个问题?
1)找到前K大的元素是使用大根堆还是使用小根堆呢?
2)为什么要使用大根堆或者是小根堆呢
3)如何找到第K大的元素?如何找到第K小的元素?
class KthLargest { PriorityQueue
queue=null; int k=0; //默认建立的就是一个小堆 public KthLargest(int k, int[] nums) { this.k=k; this.queue=new PriorityQueue<>(k); for(int i=0;i 1)如果在遍历数组元素的过程中,数组中的某一个值比堆顶元素小,那么这个数组中元素肯定不是前K个元素中最大的值
2)如果数组中的某一个值比堆顶元素大,那么这个元素可能是最终的结果,但是此时的堆顶元素(堆顶元素是这个堆中,也是前K个元素中最小的元素)一定不是最终我们想要的结果,直接弹出堆顶元素即可,将数组中的这个元素入堆,此时如果将数组元素全部遍历完,那么这个小根堆中的元素,一定是数组中最大的k个数,此时堆顶元素就是前K大的元素
3)小根堆是数组中前K个最大的值,堆顶元素是这K个最大的值中最小的元素,当遍历到数组元素大于堆顶元素的时候说明此时堆顶的元素一定不是前K个元素中最大的元素;
十一)前K个高频单词:
692. 前K个高频单词 - 力扣(LeetCode)
首先这个题是按照单词出现的频率来进行比较的,从高到低进行比较,当有两个单词出现频率相同的时候,那么此时是按照这两个单词的字典顺序来进行比较的,从低到高;
算法原理:利用堆来解决TopK问题:我们之前的TopK问题纯纯比较的就是数的大小,所以当时就直接进行比较了,但是这道题比较的是每一个单词出现的次数是多少,所以需要首先搞定的是每一个单词出现的次数是多少
1)所以首先预处理处理一下原始的字符串数组,使用一个哈希表来统计每一个单词出现的频次即可;
2)先创建一个大小为K的小堆,循环处理数组元素,让元素依次进堆,然后进行判断如果数组元素比堆顶元素大,那么直接进堆,将堆顶元素直接进行删除即可,当数组全部遍历完成之后,堆里面就是我们想要的结果;
class Solution { public List
topKFrequent(String[] words, int k) { //1.先进行统计一下每一个单词出现的频次,key是单词本身,value是单词出现的次数 HashMap map=new HashMap<>(); for(String word:words){ map.put(word,map.getOrDefault(word,0)+1); } System.out.println(map); //2.建立一个大小为K的小堆 PriorityQueue > queue=new PriorityQueue<>(k,new Comparator >() { @Override public int compare(Map.Entry o1, Map.Entry o2) { Integer Count1=o1.getValue(); Integer Count2=o2.getValue(); if(Count1!=Count2) return Count1-Count2;//根据出现频次建立一个小堆 else{ return o2.getKey().compareTo(o1.getKey());//根据单词字典顺序建立一个大堆 } } }); //3.将数组中的所有元素入堆 for(Map.Entry entry: map.entrySet()){ if(queue.size() peek= queue.peek(); if(count>peek.getValue()|| (count==peek.getValue()&&word.compareTo(peek.getKey())<=0)){ queue.poll(); queue.offer(entry); } } } //3.统计最终结果 List result=new ArrayList<>(); while(!queue.isEmpty()){ result.add(queue.poll().getKey()); } Collections.reverse(result); return result; } }
十二)数据流的中位数
295. 数据流的中位数 - 力扣(LeetCode)
解法1:直接进行排序:每一次放一个数就进行排序,每一次放一个数就进行排序,时间复杂度O(logN)
class MedianFinder { private List
ret; public MedianFinder() { this.ret=new ArrayList<>(); } public void addNum(int num) { ret.add(num); } public double findMedian() { Collections.sort(ret); int len=ret.size(); if(len%2==0){ return (ret.get(len/2)+ret.get((len/2)-1))/2.0; }else{ return ret.get(len/2); } } } 解法2:直接插入排序:每当我们进行插入一个元素的时候先找到元素插入的位置,然后再整体向后移动,但是每一次都需要从后向前找到这个元素要插入的位置,最坏情况下要扫描数组一便,但是找元素个数时间复杂度是O(N),但是如果每一个元素都是插入到最前面的位置,一直执行查找移动区间的操作,时间复杂度就会飙升,来一个数找一个位置放进去,来一个数找一个位置放进去
class MedianFinder { List
ret=null; public MedianFinder() { this.ret=new ArrayList<>(); } public void addNum(int num) { if(ret.isEmpty()){ ret.add(num); }else{ int i=ret.size()-1; ret.add(0); for(;i>=0;i--){ if(ret.get(i)>=num){ ret.set(i+1,ret.get(i)); } else{ break; } } ret.set(i+1,num);//防止没有进入循环的元素无法进行设置 } } public double findMedian() { int len=ret.size(); if(len%2==0){ return (ret.get(len/2)+ret.get((len/2)-1))/2.0; }else{ return ret.get(len/2); } } }
解法三:使用大小堆来进行维护数据流的中位数
算法原理:
1)大体思路:小根堆里面的所有元素都是大于大根堆的
2)ADD函数思路:我们再进行添加元素的时候,需要动态地维护大根堆和小根堆的元素信息
class MedianFinder { PriorityQueue
bigHeap=null; PriorityQueue smallHeap=null; public MedianFinder() { this.bigHeap=new PriorityQueue<>((a,b)-> b-a); this.smallHeap=new PriorityQueue<>((a,b)->a-b); } public void addNum(int num) { Integer peek=bigHeap.peek(); if(bigHeap.size()==smallHeap.size()){ if(bigHeap.isEmpty()||num<=peek){ bigHeap.offer(num); }else{ smallHeap.offer(num); int temp=smallHeap.poll(); bigHeap.offer(temp); } }else{ if(num<=peek){ bigHeap.offer(num); int temp=bigHeap.poll(); smallHeap.offer(temp); }else{ smallHeap.offer(num); } } } public double findMedian() { if(bigHeap.size()==smallHeap.size()) return (bigHeap.peek()+smallHeap.peek())/2.0; else return bigHeap.peek(); } } class MedianFinder { PriorityQueue
bigHeap=null; PriorityQueue smallHeap=null; int bigLen=0; int smallLen=0; public MedianFinder() { this.bigHeap=new PriorityQueue<>((a,b)-> b-a); this.smallHeap=new PriorityQueue<>((a,b)->a-b); } public void addNum(int num) { Integer peek=bigHeap.peek(); if(bigLen==smallLen){ if(bigLen==0||peek>=num){ bigHeap.offer(num); bigLen++; }else{ smallHeap.offer(num); smallLen++; int temp=smallHeap.poll(); bigHeap.offer(temp); bigLen++; smallLen--; } }else{ if(num<=peek){ bigHeap.offer(num); bigLen++; int temp=bigHeap.poll(); bigLen--; smallHeap.offer(temp); smallLen++; }else{ smallHeap.offer(num); smallLen++; } } } public double findMedian() { if(bigLen==smallLen) return (bigHeap.peek()+smallHeap.peek())/2.0; else return bigHeap.peek(); } }
class Solution { public List
preorderTraversal(TreeNode root) { TreeNode[] array=new TreeNode[100]; List list=new ArrayList<>(); int index=0; TreeNode current=root; while(current!=null||index>0){ while(current!=null){ list.add(current.val); System.out.println(current.val); array[index]=current; index++; current=current.left; } if(index>0){ TreeNode node=array[index-1]; index--; current=node.right; } } return list; } }