链表
设计链表
//节点初始化
class ListNode{
int val;
ListNode next;
ListNode(){} //构造器
ListNode(int val){ //构造器
this.val=val;
}
}
class MyLinkedList {
int size; //存储元素个数
ListNode head; //虚拟头节点
public MyLinkedList() {
size=0;
head=new ListNode(0);
}
//获取第index个节点的值,index从0开始
public int get(int index) {
if(index<0||index>=size){
return -1;
}
ListNode currentNode=head;//从头遍历
for(int i=0;i<=index;i++){//注意取等
currentNode=currentNode.next;
}
return currentNode.val;
}
//在第0个元素前添加
public void addAtHead(int val) {
addAtIndex(0,val);
}
//在第tail+1个元素前添加
public void addAtTail(int val) {
addAtIndex(size,val);
}
//在第index节点前添加
public void addAtIndex(int index, int val) {
if(index>size){//大于链表长度
return;
}
if(index<0){
index=0;//相当于头节点插入
}
size++;
//找到要插入节点的前驱节点
ListNode pre=head;//从头遍历
for(int i=0;i<index;i++){//没取等
pre=pre.next;
}
ListNode toAdd=new ListNode(val); //插入的节点值
toAdd.next=pre.next;
pre.next=toAdd;
}
//删除第index个节点
public void deleteAtIndex(int index) {
if(index<0||index>=size){
return ;
}
size--;
if(index==0){ //删除头节点
head=head.next;
return;
}
//找到要删除节点的前驱节点
ListNode pre=head;//从头遍历找
for(int i=0;i<index;i++){
pre=pre.next;
}
pre.next=pre.next.next;
}
}
两两交换链表中的结点
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dumyNode=new ListNode(-1); //虚拟头节点
dumyNode.next=head;
ListNode cur=dumyNode;
ListNode temp=new ListNode();
ListNode firstNode=new ListNode();
ListNode secondNode=new ListNode();
while(cur.next!=null && cur.next.next!=null){
firstNode=cur.next; //第一个结点
secondNode=cur.next.next; //第二个结点
temp=cur.next.next.next; //每一组结点的下一个结点
cur.next=secondNode; //步骤一
secondNode.next=firstNode; //步骤二
firstNode.next=temp; //步骤三
cur=firstNode; //下一次分组
}
return dumyNode.next;
}
}
回文链表
class Solution {
public boolean isPalindrome(ListNode head) {
if(head==null && head.next==null)return true;
ListNode pre; //记录分割点
ListNode slow;
ListNode fast;
pre=head;
slow=head;
fast=head;
//慢指针走一步,快指针走两步,当快指针走到末尾时,慢指针走到分割点
while(fast!=null && fast.next!=null){
pre=slow;
slow=slow.next;
fast=fast.next.next;
}
pre.next=null; //断开链表
ListNode cur1=head;
ListNode cur2=reverseList(slow);
while(cur1!=null){
if(cur1.val!=cur2.val){
return false;
}
cur1=cur1.next;
cur2=cur2.next;
}
return true;
}
public ListNode reverseList(ListNode head){
ListNode pre=null;
ListNode temp=null;
while(head!=null){
//pre-->head-->head.next(temp)-->
temp=head.next;
head.next=pre;
pre=head;
head=temp;
}
return pre;
}
}
重排链表
双指针算法
- 先将所有节点加入到ArrayList中
- 使用cnt计数,左右指针分别指向首尾,交错着给cur.next赋值
//数组实现
class Solution {
public void reorderList(ListNode head) {
ListNode cur=new ListNode();
List<ListNode>list=new ArrayList<>();
cur=head;
while(cur!=null){
list.add(cur);
cur=cur.next;
}
cur=head;
int cnt=0;
int left=1,right=list.size()-1;
while(left<=right){
if(cnt%2==0){
cur.next=list.get(right);
right--;
}else{
cur.next=list.get(left);
left++;
}
cnt++;
cur=cur.next;
}
cur.next=null;
}
}
//双端队列实现
class Solution {
public void reorderList(ListNode head) {
ListNode cur=new ListNode();
Deque<ListNode>deque=new LinkedList<>();
cur=head.next; //head不用入队了,避免重复
while(cur!=null){
deque.offer(cur);
cur=cur.next;
}
cur=head;
int cnt=0;
while(!deque.isEmpty()){
if(cnt%2==0){
cur.next=deque.pollLast(); //队尾弹出
}else{
cur.next=deque.poll(); //队首弹出
}
cur=cur.next;
cnt++;
}
cur.next=null;
}
}
环形链表 |
双指针算法:
1. fast指针一次走两步,slow指针一次走一步
2. 如果fast能和slow相遇,则存在环形
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode fast=head;
ListNode slow=head;
while(fast!=null && fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow){
return true;
}
}
return false;
}
}
环形链表 ||
假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:
那么相遇时: slow指针走过的节点数为: x + y, fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A。
因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:
(x + y) * 2 = x + y + n (y + z)
两边消掉一个(x+y): x + y = n (y + z)
因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。
所以要求x ,将x单独放在左面:x = n (y + z) - y ,
再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z
当 n为1的时候,公式就化解为 x = z,
这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。
也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。
让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast=head;
ListNode slow=head;
while(fast!=null && fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(slow==fast){ //到达相遇点
ListNode pointer1=fast; //一个指针从相遇点出发
ListNode pointer2=head; //另一个指针从开头出发
while(pointer1!=pointer2){
pointer1=pointer1.next;
pointer2=pointer2.next;
}
return pointer1; //返回相遇点
}
}
return null;
}
}
相交链表
1. 通过指针移动分别计算A,B链表的长度,如果B更长,与A交换,保证A链表更长
2. 计算出长度的差值gap,将A从头开始移动gap个单位使得A,B尾部对齐
3. 此时A,B指针同时向后移动,如果curA==curB,返回curA就是交点
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA=new ListNode();
ListNode curB=new ListNode();
curA=headA;
curB=headB;
int lenA=0,lenB=0;
while(curA!=null){
lenA++;
curA=curA.next;
}
while(curB!=null){
lenB++;
curB=curB.next;
}
//重新移动到表头
curA=headA;
curB=headB;
//保证A表更长
if(lenB>lenA){
//swap(lenA,lenB)
int temp_len=lenA;
lenA=lenB;
lenB=temp_len;
//swap(curA,curB)
ListNode temp_Node=curA;
curA=curB;
curB=temp_Node;
}
//计算差值
int gap_len=lenA-lenB;
//保证A,B尾部对齐
while(gap_len-->0){
curA=curA.next;
}
while(curA!=null){
if(curA==curB){
return curA;
}
curA=curA.next;
curB=curB.next;
}
return null;
}
}
栈
使用Deque接口实现栈和队列,创建栈的一般方式有LinkedList和ArrayDeque
Deque<Integer>stack=new LinkedList<>();
Deque<Integer>stack=new ArrayDeque<>();
//栈的方法:
push()
pop() //取得栈顶值并出栈
peek() //取得栈顶值不出栈
中缀表达式
import java.util.*;
public class Main{
public static void main(String[] args) {
Stack<Character>op=new Stack<>();
Stack<Integer>num=new Stack<>();
Map<Character,Integer> map=new HashMap<>();
//优先级
map.put('+',1);
map.put('-',1);
map.put('*',2);
map.put('/',2);
Scanner sc=new Scanner(System.in);
String str=sc.next();
for(int i=0;i<str.length();i++){
char c=str.charAt(i);//得到每一位字符
if(Character.isDigit(c)){//将字符转化为数字
int x=0,j=i;
//多位数字
while(j<str.length()&&Character.isDigit(str.charAt(j))){
x=10*x+str.charAt(j)-'0';
j++;
}
i=j-1;
num.push(x);
}else if(c=='('){
op.push(c);
}else if(c==')'){
while(op.peek()!='('){ //取出运算直到遇到左括号
eval(op,num);
}
op.pop(); //弹出左括号
}else{ //遇到运算符,判断优先级
while(!op.empty()&&op.peek()!='('&&map.get(op.peek())>=map.get(c)){
eval(op,num);
}
op.push(c);
}
}
while(!op.empty())eval(op,num); //处理剩余的
System.out.println(num.peek()); //栈顶就是最终结果
}
//取出栈中元素进行计算
public static void eval(Stack<Character> op,Stack<Integer> num){
int b = num.pop();
int a = num.pop();
char c = op.pop();
if(c == '+'){
num.push(a+b);
}else if(c == '-'){
num.push(a-b);
}else if(c == '*'){
num.push(a*b);
}else {
num.push(a/b);
}
}
}
有效的括号
1. 遇到左括号将右括号入栈,遇到右括号取出栈顶进行比较
2. 如果栈提前为空或者和栈顶不相等,则返回false
3. 最后返回栈是否为空
class Solution {
public boolean isValid(String s) {
Deque<Character>stack=new LinkedList<>();
for(int i=0;i<s.length();i++){
if(s.charAt(i)=='(')stack.push(')');
else if(s.charAt(i)=='[')stack.push(']');
else if(s.charAt(i)=='{')stack.push('}');
else if(stack.isEmpty() ||stack.peek()!=s.charAt(i))return false;
else {
stack.pop();
}
}
return stack.isEmpty();
}
}
单词对对碰
1. 使用栈保存前一个遍历过的字母
2. 每一次取出栈顶元素进行比较,如果当前值与栈顶相同,则弹出,否则压入栈
class Solution {
public String removeDuplicates(String s) {
Deque<Character>stack=new ArrayDeque<>();
for(int i=0;i<s.length();i++){
if(stack.isEmpty() || stack.peek()!=s.charAt(i)){
stack.push(s.charAt(i));
}else{
stack.pop();
}
}
String res="";
while(!stack.isEmpty()){
res=stack.pop()+res;
}
return res;
}
}
后缀表达式求值
1. 遇到运算符取出栈顶两个数字运算,再将结果入栈
2. 遇到数字直接入栈
3. 最后栈顶为最终结果
class Solution {
public int evalRPN(String[] tokens) {
Deque<Integer>stk=new LinkedList<>();
for(String s:tokens){
if(s.equals("+")){ //使用equals判断字符串是否相等
stk.push(stk.pop()+stk.pop());
}else if(s.equals("-")){
stk.push(-stk.pop()+stk.pop());
}else if(s.equals("*")){
stk.push(stk.pop()*stk.pop());
}else if(s.equals("/")){
Integer temp1=stk.pop();
Integer temp2=stk.pop();
stk.push(temp2/temp1);
}else{
stk.push(Integer.valueOf(s)); //转化为整数入栈
}
}
return stk.peek();
}
}
单调栈
找到左边第一小的数
栈不为空并且栈顶大于当前要插入的元素,则出栈,最后栈顶就是满足条件的值
import java.util.*;
public class Main{
public static void main(String[]args){
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int tt=0;
int[]st=new int[100010];
for(int i=0;i<n;i++){
int x=sc.nextInt();
while(tt!=0&&st[tt]>=x){
tt--;
}
if(tt!=0)
System.out.print(st[tt]+" ");
else
System.out.print("-1"+" ");
st[++tt]=x;
}
}
}
每日温度
1. 如果后一天的温度小于等于前一天的温度,则加入栈保存
2. 否则就是找到了第一个大于栈顶的温度,弹出栈顶直到不大于栈顶为止,记录答案
class Solution {
Deque<Integer>stack=new LinkedList<>();
public int[] dailyTemperatures(int[] temperatures) {
int len=temperatures.length;
int []res=new int[len];
stack.push(0);
for(int i=1;i<len;i++){
if(temperatures[i]<=temperatures[stack.peek()]){
stack.push(i);
}else{
while(!stack.isEmpty()&&temperatures[i]>temperatures[stack.peek()]){
res[stack.peek()]=i-stack.peek();
stack.pop();
}
stack.push(i);
}
}
return res;
}
}
下一个更大元素–双数组
给定两个数组,其中nums1是nums2的子集,要求找到nums1中的数在nums2中的下一个更大元素,如果没有则返回-1.
例如:
输入:nums1 = [4,1,2], nums2 = [1,3,4,2].
输出:[-1,3,-1]
解释:nums1 中每个值的下一个更大元素如下所述:
4 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。
1 ,用加粗斜体标识,nums2 = [1,3,4,2]。下一个更大元素是 3 。
2 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。
1. 通过哈希记录nums1中每个数对应的下标
2. 找到nums2中每个数的下一个更大的数
3. 根据映射找到在nums1中的下标index
class Solution {
Stack<Integer>stack=new Stack<>(); //保存的是下标
int []res; //保存num1中的结果
HashMap<Integer,Integer>map=new HashMap<>();
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
int len1=nums1.length,len2=nums2.length;
res=new int[len1];
Arrays.fill(res,-1);
for(int i=0;i<len1;i++){
map.put(nums1[i],i); //num1中的数与下标的映射
}
stack.add(0);
for(int i=1;i<len2;i++){ //找到nums2中的每个数的下一个更大的数,返回给子集nums1
if(nums2[i]<=nums2[stack.peek()]){
stack.push(i);
}else{
while(!stack.isEmpty()&&nums2[i]>nums2[stack.peek()]){
if(map.containsKey(nums2[stack.peek()])){
int index=map.get(nums2[stack.peek()]); //得到栈顶元素x在nums1中的下标,也就是要求的是x的下一个更大元素
res[index]=nums2[i]; //下一个更大元素是nums2[i]
}
stack.pop();
}
stack.add(i);
}
}
return res;
}
}
下一个更大元素–成环
遍历长度增长到2*lenth,并将所有的i变为i%len
class Solution {
public int[] nextGreaterElements(int[] nums) {
Stack<Integer>stack=new Stack<>();
int len=nums.length;
int []res=new int[len];
Arrays.fill(res,-1);
stack.add(0);
for(int i=1;i<2*len;i++){
if(nums[i%len]<=nums[stack.peek()]){
stack.push(i%len);
}else{
while(!stack.isEmpty() && nums[i%len]>nums[stack.peek()]){
res[stack.peek()]=nums[i%len];
stack.pop();
}
stack.add(i%len);
}
}
return res;
}
}
接雨水
本题是要找到一个数左右两边第一个大于他的数
-
如果当前遍历的元素(柱子)高度大于栈顶元素的高度,此时就出现凹槽了
-
取栈顶元素,将栈顶元素弹出,这个就是凹槽的底部,也就是中间位置,下标记为mid,对应的高度为height[mid]
-
此时的栈顶元素st.top(),就是凹槽的左边位置,下标为st.top(),对应的高度为height[st.top()]
-
当前遍历的元素i,就是凹槽右边的位置,下标为i,对应的高度为height[i]
-
栈顶和栈顶的下一个元素以及要入栈的元素,三个元素来接水!
5.1 雨水高度是 min(凹槽左边高度, 凹槽右边高度) - 凹槽底部高度
int h = min(height[st.top()], height[i]) - height[mid];
5.2 雨水的宽度是 凹槽右边的下标 - 凹槽左边的下标 - 1(因为只求中间宽度),代码为:int w = i - st.top() - 1 ; -
当前凹槽雨水的体积就是:h * w。
class Solution {
public int trap(int[] height) {
Stack<Integer>stack=new Stack<>();
int sum=0,mid=0,h=0,w=0;
stack.add(0);
for(int i=1;i<height.length;i++){
if(height[i]<height[stack.peek()]){
stack.add(i);
}else if(height[i]==height[stack.peek()]){
stack.pop();
stack.add(i); //只保留一个相同的高度
}else{
while(!stack.isEmpty() && height[i]>height[stack.peek()]){
mid=stack.peek(); //凹槽下标
stack.pop(); //弹出后栈顶为凹槽左侧
if(!stack.isEmpty()){
h=Math.min(height[i],height[stack.peek()])-height[mid];
w=i-stack.peek()-1; //宽度
sum+=h*w;
}
}
stack.add(i);
}
}
return sum;
}
}
柱状图的最大矩形面积
本题是要找到一个数左右两边第一个小于他的数
class Solution {
public int largestRectangleArea(int[] heights) {
Stack<Integer>stack=new Stack<>();
int h=0,w=0,area=0,len=heights.length;
int []newheights=new int[len+2];
//数组扩容,首尾添加0
newheights[0]=0;
newheights[newheights.length-1]=0;
for(int i=0;i<len;i++){
newheights[i+1]=heights[i];
}
//回收临时数组
heights=newheights;
stack.add(0);
for(int i=1;i<heights.length;i++){
if(heights[i]>heights[stack.peek()]){
stack.add(i);
}else if(heights[i]==heights[stack.peek()]){
stack.pop();
stack.add(i);
}else{
while(!stack.isEmpty() && heights[i]<heights[stack.peek()]){
int mid=stack.peek();
stack.pop();
if(!stack.isEmpty()){
h=heights[mid];
w=i-stack.peek()-1;
area=Math.max(area,w*h); //更新面积
}
}
stack.add(i);
}
}
return area;
}
}
队列
使用Deque接口实现队列:
Deque<Integer>deque=new LinkedList<>();
deque.add() deque.offer()//队尾添加
deque.poll() //队首弹出
deque.peek() //取出队头
滑动窗口最值
import java.util.*;
public class Main{
public static void main(String[]args){
int N=1000010;
int[]a=new int[N];
int[]q=new int[N]; //q保存数组下标
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int k=sc.nextInt();
for(int i=0;i<n;i++){
a[i]=sc.nextInt();
}
/*窗口最小值,如果队尾大于将要插入的元素,则
队尾弹出,更新队尾下标.当前窗口长度超过限定长度
K时更新队头,队头保存的就是最小值*/
int hh=0,tt=-1; //队头和队尾
for(int i=0;i<n;i++){
if(hh<=tt&&k<i-q[hh]+1)hh++;
while(hh<=tt&&a[q[tt]]>=a[i])tt--;
q[++tt]=i;
if(i+1>=k)System.out.println(a[q[hh]]);
}
/*窗口最大值*/
for(int i=0;i<n;i++){
if(hh<=tt&&k<i-q[hh]+1)hh++;
while(hh<=tt&&a[q[tt]]<=a[i])tt--;
q[++tt]=i;
if(i+1>=k)System.out.println(a[q[hh]]);
}
}
}
滑动窗口最大值
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int num=0;
int len=nums.length-k+1; //窗口个数
int []res=new int[len];
Myque myque=new Myque();
//先将前k个元素加入到队列
for(int i=0;i<k;i++){
myque.add(nums[i]);
}
//记录第一个窗口的最大值
res[num++]=myque.peek();
//处理第k个元素之后的数
for(int i=k;i<nums.length;i++){
//移除队头
myque.poll(nums[i-k]); //调用自定义方法
//后面的元素添加队尾
myque.add(nums[i]); //调用自定义方法
//保存窗口最大值--队头
res[num++]=myque.peek();
}
return res;
}
}
class Myque{
Deque<Integer>deque=new LinkedList<>();
//窗口移除元素:当队头等于当前元素时,弹出结果
public void poll(int val){
if(!deque.isEmpty() && val==deque.peek()){
deque.poll();
}
}
//当前元素>队头,就从后往前删直到队列单调递减
public void add(int val){
while(!deque.isEmpty() && val>deque.getLast()){
deque.removeLast();
}
deque.add(val);
}
//取队头,当前窗口最大值
public int peek(){
return deque.peek();
}
}
前k个高频元素
- 先使用map记录每个元素出现的次数map.put(map.getOrDefault(num,0)+1) 如果存在键num则其对应的值+1,否则返回默认值0
- 构造大根堆,按照出现次数有多到少的顺序放入元素,最后从堆顶弹出前k个元素即为前k个高频元素
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer,Integer>map=new HashMap<>();
for(Integer num:nums){
map.put(num,map.getOrDefault(num,0)+1);
}
//大根堆
PriorityQueue<int[]>priorityQueue=new PriorityQueue<>((pair1,pair2)->pair2[1]-pair1[1]);
//使用结果集遍历map,加入到大根堆
for(Map.Entry<Integer,Integer>entry:map.entrySet()){
priorityQueue.add(new int[]{entry.getKey(),entry.getValue()});
}
int []res=new int[k];
for(int i=0;i<k;i++){
res[i]=priorityQueue.poll()[0];
}
return res;
}
}
二叉树
二叉树的定义
public 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> preorderTraversal(TreeNode root) {
List<Integer>res=new ArrayList<>();
preOrder(root,res);
return res;
}
public void preOrder(TreeNode node,List<Integer>res){
if(node==null){
return;
}
res.add(node.val);
preOrder(node.left,res); //左
preOrder(node.right,res); //右
}
}
中序遍历 :左中右
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer>res=new ArrayList<>();
inOrder(root,res);
return res;
}
public void inOrder(TreeNode node,List<Integer>res){
if(node==null){
return ;
}
inOrder(node.left,res);
res.add(node.val);
inOrder(node.right,res);
}
}
后序遍历 :左右中
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer>res=new ArrayList<>();
postOrder(root,res);
return res;
}
public void postOrder(TreeNode node,List<Integer>res){
if(node==null){
return;
}
postOrder(node.left,res);
postOrder(node.right,res);
res.add(node.val);
}
}
前中后迭代遍历
前序遍历
1. 中 - 左 - 右
2. 访问的是根节点,入栈的也是根节点
3. 右孩子入栈,左孩子入栈
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer>res=new ArrayList<>();
if(root==null){
return res;
}
Stack<TreeNode>stack=new Stack<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode cur=stack.pop();
res.add(cur.val);
if(cur.right!=null){
stack.push(cur.right);
}
if(cur.left!=null){
stack.push(cur.left);
}
}
return res;
}
}
中序遍历
1.左 - 中 - 右
2. cur节点起初指向root
3. 如果cur不为空就入栈,一直往左遍历,cur=cur.left
4. 如果遇到叶节点就出栈,记录res,并递归右节点
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer>res=new ArrayList<>();
if(root==null){
return res;
}
Stack<TreeNode>stack=new Stack<>();
TreeNode cur=root;
while(cur!=null || !stack.isEmpty()){ //两个条件满足一个即可
if(cur!=null){
stack.push(cur);
cur=cur.left;
}else{
cur=stack.pop();
res.add(cur.val);
cur=cur.right;
}
}
return res;
}
}
后序遍历
1. 左 - 右 - 中
2. 前序 中 - 左 - 右 ----> 中 - 右 - 左 -----reverse----->左 - 右 - 中
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer>res=new ArrayList<>();
if(root==null){
return res;
}
Stack<TreeNode>stack=new Stack<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode cur=stack.pop();
res.add(cur.val);
if(cur.left!=null){
stack.push(cur.left);
}
if(cur.right!=null){
stack.push(cur.right);
}
}
Collections.reverse(res);
return res;
}
}
层序遍历
模板
1. path保存每一层的结果,每遍历完一层就将path加入res
2. 根节点入队列,当队列不为空时,遍历每一层,左右节点依次入队列
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>>res=new ArrayList<>();
if(root==null)return res; //特判一下
Queue<TreeNode>que=new LinkedList<>(); //队列
que.offer(root);
while(!que.isEmpty()){
List<Integer>path=new ArrayList<Integer>(); //单层结果
int len=que.size();
for(int i=0;i<len;i++){ //每一层
TreeNode cur=que.poll();
path.add(cur.val);
if(cur.left!=null)que.add(cur.left);
if(cur.right!=null)que.add(cur.right);
}
res.add(path);
}
return res;
}
}
翻转二叉树
1. 翻转整个二叉树其实只需要将每个节点的左右节点交换即可
2. 通过层序遍历,每遍历一个节点就把其左右节点交换
class Solution {
public TreeNode invertTree(TreeNode root) {
Queue<TreeNode>que=new LinkedList<>();
if(root==null)return root;
que.offer(root);
while(!que.isEmpty()){
int len=que.size();
for(int i=0;i<len;i++){
TreeNode cur=que.poll();
swap(cur);
if(cur.left!=null)que.add(cur.left);
if(cur.right!=null)que.add(cur.right);
}
}
return root;
}
public void swap(TreeNode node){
TreeNode tempNode=node.left;
node.left=node.right;
node.right=tempNode;
}
}
二叉树的最大深度
迭代 : 层序遍历时每遍历一层就更新层高
class Solution {
public int maxDepth(TreeNode root) {
Queue<TreeNode>que=new LinkedList<>();
if(root==null)return 0;
que.offer(root);
int depth=0;
while(!que.isEmpty()){
int size=que.size();
depth++;
for(int i=0;i<size;i++){
TreeNode cur=que.poll();
if(cur.left!=null)que.offer(cur.left);
if(cur.right!=null)que.offer(cur.right);
}
}
return depth;
}
}
递归:
先求它的左子树的深度,再求右子树的深度,最后取左右深度最大的数值 再+1 (加1是因为算上当前中间节点)就是目前节点为根节点的树的深度。
class Solution {
public int maxDepth(TreeNode root){
if(root==null)return 0;
int leftDepth=maxDepth(root.left); //左子树深度
int rightDepth=maxDepth(root.right); //右子树深度
int max=1+Math.max(leftDepth,rightDepth); //左右子树深度最大值 + 本身的高度1
return max;
}
}
二叉树的最小深度
遇到叶子节点就return depth
class Solution {
public int minDepth(TreeNode root) {
Queue<TreeNode>que=new LinkedList<>();
if(root==null)return 0;
que.offer(root);
int depth=0;
while(!que.isEmpty()){
int size=que.size();
depth++; //先加再入队列
for(int i=0;i<size;i++){
TreeNode cur=que.poll();
if(cur.left!=null)que.offer(cur.left);
if(cur.right!=null)que.offer(cur.right);
if(cur.left==null && cur.right==null)return depth; //遇到叶节点就返回
}
}
return depth;
}
}
完全二叉树节点数
递归 : 左子树节点数+右子树节点数+1
class Solution {
public int getNodes(TreeNode root){
if(root==null)return 0;
int leftNums=getNodes(root.left); //左子树节点数
int rightNums=getNodes(root.right); //右子树节点数
return leftNums+rightNums+1; //+上本身为1
}
public int countNodes(TreeNode root) {
return getNodes(root);
}
}
迭代
class Solution {
public int countNodes(TreeNode root) {
Queue<TreeNode>que=new LinkedList<>();
if(root==null)return 0;
que.offer(root);
int nums=0;
while(!que.isEmpty()){
int size=que.size();
for(int i=0;i<size;i++){
TreeNode cur=que.poll();
nums++;
if(cur.left!=null)que.offer(cur.left);
if(cur.right!=null)que.offer(cur.right);
}
}
return nums;
}
}
树左下角的值
更新每一层的第一个节点
class Solution {
public int findBottomLeftValue(TreeNode root) {
Queue<TreeNode>que=new LinkedList<>();
que.offer(root);
int res=0;
while(!que.isEmpty()){
int size=que.size();
for(int i=0;i<size;i++){
TreeNode cur=que.poll();
if(i==0)res=cur.val; //更新每一层的第一个节点
if(cur.left!=null)que.offer(cur.left);
if(cur.right!=null)que.offer(cur.right);
}
}
return res;
}
}
递归
n叉树的最大深度
枚举所有孩子节点,总的最大深度 = 所有孩子节点深度的最大值 + 本身高度1
class Solution {
public int maxDepth(Node root) {
if(root==null)return 0;
int depth=0;
for(Node child:root.children){
depth=Math.max(depth,maxDepth(child));
}
return depth+1;
}
}
- 队列实现
class Solution {
//队列
public boolean isSymmetric(TreeNode root){
Queue<TreeNode>que=new LinkedList<>();
//根节点的左右孩子入队列
que.offer(root.left);
que.offer(root.right);
while(!que.isEmpty()){
//取出两两比较
TreeNode leftNode=que.poll();
TreeNode rightNode=que.poll();
if(leftNode==null && rightNode==null){
continue;
}
//左右节点有一个为空 或者 节点的值不相等
if(leftNode == null || rightNode == null || leftNode.val != rightNode.val){
return false;
}
//按照左的左,右的右,左的右,右的左依次入队列
que.offer(leftNode.left);
que.offer(rightNode.right);
que.offer(leftNode.right);
que.offer(rightNode.left);
}
return true;
}
}
对称二叉树
递归法
class Solution {
public boolean compare(TreeNode leftNode,TreeNode rightNode){
//递归结束条件
//左节点为空,右节点不为空
if(leftNode==null && rightNode!=null)return false;
//左节点不为空,右节点为空
else if(leftNode!=null && rightNode==null)return false;
//左右节点同时为空,对称
else if(leftNode==null && rightNode==null)return true;
//值不相同
else if(leftNode.val != rightNode.val)return false;
//左右节点均不为空
boolean out=compare(leftNode.left,rightNode.right); //左节点的左孩子,右节点的右孩子
boolean in=compare(leftNode.right,rightNode.left); //左孩子的右孩子,右节点的左孩子
return out && in;
}
public boolean isSymmetric(TreeNode root) {
if(root==null)return true;
return compare(root.left,root.right);
}
}
平衡二叉树的判断
class Solution {
public int getHeight(TreeNode root){
if(root==null)return 0; //特判一下,防止空指针异常
int leftHeight=getHeight(root.left);
if(leftHeight==-1)return -1;
int rightHeight=getHeight(root.right);
if(rightHeight==-1)return -1;
int res;
//如果高度差绝对值>1,返回-1
if(Math.abs(leftHeight-rightHeight)>1){
res=-1;
}else{
//返回以当前节点为根节点的最大高度
res=1+Math.max(leftHeight,rightHeight);
}
return res;
}
public boolean isBalanced(TreeNode root) {
return getHeight(root)==-1?false:true;
}
}
二叉树的所有路径
1. 使用前序遍历,先加入根节点,再放入左右节点
2. 递归后立马回溯,因为path 不能一直加入节点,它还要删节点,然后才能加入新的节点。
class Solution {
public void traversal(TreeNode cur,List<Integer>paths,List<String>res){
paths.add(cur.val);
if(cur.left==null && cur.right==null){
StringBuilder sb=new StringBuilder(); //用于字符串拼接
for(int i=0;i<paths.size()-1;i++){
sb.append(paths.get(i)).append("->"); //拼接路径
}
sb.append(paths.get(paths.size()-1)); //最后一个节点后面不加"->"符号
res.add(sb.toString()); //将一条路径加入结果集
return;
}
if(cur.left!=null){
traversal(cur.left,paths,res);
paths.remove(paths.size()-1); //回溯
}
if(cur.right!=null){
traversal(cur.right,paths,res);
paths.remove(paths.size()-1); //回溯
}
}
public List<String> binaryTreePaths(TreeNode root) {
List<String>res=new ArrayList<>();
if(root == null)return res;
List<Integer>paths=new ArrayList<>();
traversal(root,paths,res);
return res;
}
}
左叶子之和
1. 左叶子判断条件:其父节点的左叶子不为空,左叶子的左右儿子均为空
2. 递归左子树,求左子树的左叶子和
3. 递归右子树,求右子树的左叶子和
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
if(root==null)return 0;
int mid=0;
//左叶子判断条件:其父节点的左叶子不为空,左叶子的左右儿子均为空
if(root.left!=null && root.left.left==null &&root.left.right==null){
mid=root.left.val; //记录左叶子的值
}
int leftSum=sumOfLeftLeaves(root.left); //递归左子树,求左子树的左叶子和
int rightSum=sumOfLeftLeaves(root.right); //递归右子树,求右子树的左叶子和
int sum=mid+leftSum+rightSum; //求和
return sum;
}
}
路径总和
1. 递归结束条件:
用count减去路径上走过的点,如果遇到叶子节点时,且count减为0,则存在路径,反则不存在
2. 单层递归:
左子树不为空,递归左子树,同时对count进行回溯;
右子树不为空,递归右子树,同时对count进行回溯;
3.递归是有返回值的!
class Solution {
public boolean traversal(TreeNode cur,int count){
//结束条件:遇到叶子节点如果count减为零,则找到路径,否则返回false
if(cur.left==null && cur.right==null && count==0)return true;
if(cur.left==null && cur.right==null)return false;
//递归,回溯
if(cur.left!=null){
count-=cur.left.val;
if(traversal(cur.left,count))return true; //递归左节点
count+=cur.left.val; //回溯
}
if(cur.right!=null){
count-=cur.right.val;
if(traversal(cur.right,count))return true; //递归右节点
count+=cur.right.val; //回溯
}
return false;
}
public boolean hasPathSum(TreeNode root, int targetSum) {
if(root==null)return false;
return traversal(root,targetSum-root.val);
}
}
并查集
集合的合并和询问
import java.util.Scanner;
public class Main{
static int N=100010;
static int []p=new int[N];//存储树结点
public static void main(String []args){
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int m=sc.nextInt();
for(int i=1;i<=n;i++)p[i]=i;//初始化每一数成为一个集合
while(m-->0){//m次操作
String s=sc.next();
int a=sc.nextInt();
int b=sc.nextInt();
if(s.equals("M")){
p[find(a)]=find(b);//将集合a的根节点合并到b集合
}else{
if(find(a)==find(b))//根节点相同
System.out.println("Yes");
else
System.out.println("No");
}
}
}
public static int find(int x){//寻找根节点节点
if(p[x]!=x)p[x]=find(p[x]);
//如果根节点不是自己,则继续递归寻找根节点,
//找到后将路径上所有集合指向根节点
return p[x];//返回根节点
}
}
连通块中点的数量
import java.util.Scanner;
public class Main{
static int N=100010;
static int []p=new int[N];//存储树结点
static int []cnt=new int [N];//存储集合中点的数量
public static void main(String []args){
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int m=sc.nextInt();
for(int i=1;i<=n;i++){
p[i]=i;//初始化每一数成为一个集合
cnt[i]=1;//初始化每个集合只有一个数
}
while(m-->0){//m次操作
String s=sc.next();
int a,b;
if(s.equals("C")){
a=sc.nextInt();
b=sc.nextInt();
a=find(a);
b=find(b);
if(a!=b){//只有当a,b不属于同一集合时才相加
p[a]=b;
cnt[b]+=cnt[a];
}
}else if(s.equals("Q1")){
a=sc.nextInt();
b=sc.nextInt();
if(find(a)==find(b))//根节点相同
System.out.println("Yes");
else
System.out.println("No");
}else{
a=sc.nextInt();
//输出a的根节点表示的集合的点数
System.out.println(cnt[find(a)]);
}
}
}
public static int find(int x){//寻找根节点节点
if(p[x]!=x)p[x]=find(p[x]);
//如果根节点不是自己,则继续递归寻找根节点,
//找到后将路径上所有集合指向根节点
return p[x];//返回根节点
}
}
哈希
同构字符串
1. 使用两个map分别保存s->t和t->s的映射
2. 如果映射不对应直接返回false
class Solution {
public boolean isIsomorphic(String s, String t) {
Map<Character,Character>map1=new HashMap<>();
Map<Character,Character>map2=new HashMap<>();
for(int i=0,j=0;i<s.length() && j<t.length();i++,j++){
if(!map1.containsKey(s.charAt(i))){ //如果还没有映射过
map1.put(s.charAt(i),t.charAt(j)); //map1保存s->t的映射
}
if(!map2.containsKey(t.charAt(j))){
map2.put(t.charAt(j),s.charAt(i)); //map2保存t->s的映射
}
if(map1.get(s.charAt(i))!=t.charAt(j) || map2.get(t.charAt(j))!=s.charAt(i)){ //映射不对应返回false
return false;
}
}
return true;
}
}
查找共用字符
1. 先计算出第一个字符串的字符频率,保存在hash数组中
2. 从第二个字符串开始,计算其他字符串的字符频率,保存在hash_otherStr中
3. 每遍历完一个字符串就取 a~ z 字符出现的最小频率,放回hash中
4. 如果最小频率>0,则放入list并输出
class Solution {
public List<String> commonChars(String[] words) {
List<String>res=new ArrayList<>();
int[]hash=new int[26];
//先保存第一个字符串的字符频率
for(int j=0;j<words[0].length();j++){
hash[words[0].charAt(j)-'a']++; //注意用charAt()访问单个字符
}
for(int i=1;i<words.length;i++){ //从第二个字符串开始
int[]Hash_OtherStr=new int[26];
for(int j=0;j<words[i].length();j++){
Hash_OtherStr[words[i].charAt(j)-'a']++; //其他字符串中的字符频率
}
for(int k=0;k<26;k++){ //取每个字符串中字符出现频率的最小值,如果最小字符频率为零,则该字符不是共用字符
hash[k]=Math.min(hash[k],Hash_OtherStr[k]);
}
}
for(int i=0;i<26;i++){ //遍历a~z
while(hash[i]!=0){ //最小频率不是0
char c=(char)(i+'a');
res.add(String.valueOf(c)); //加入list
hash[i]--;
}
}
return res;
}
}