数据结构

本文介绍了链表的设计,包括节点初始化、插入、删除操作。接着讲解了如何两两交换链表中的节点,判断环形链表,找到相交链表的节点,以及如何进行后缀表达式求值。对于栈和队列,讨论了滑动窗口最大值和最小值的计算方法。此外,还详细阐述了二叉树的各种遍历方法,如前序、中序、后序,以及如何判断二叉树是否对称、平衡,计算路径和、最大深度等。
摘要由CSDN通过智能技术生成

链表

设计链表

//节点初始化
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;
    }
}

重排链表

在这里插入图片描述

双指针算法

  1. 先将所有节点加入到ArrayList中
  2. 使用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;
    }
}

接雨水

在这里插入图片描述

在这里插入图片描述

本题是要找到一个数左右两边第一个大于他的数

  1. 如果当前遍历的元素(柱子)高度大于栈顶元素的高度,此时就出现凹槽了

  2. 取栈顶元素,将栈顶元素弹出,这个就是凹槽的底部,也就是中间位置,下标记为mid,对应的高度为height[mid]

  3. 此时的栈顶元素st.top(),就是凹槽的左边位置,下标为st.top(),对应的高度为height[st.top()]

  4. 当前遍历的元素i,就是凹槽右边的位置,下标为i,对应的高度为height[i]

  5. 栈顶和栈顶的下一个元素以及要入栈的元素,三个元素来接水!
    5.1 雨水高度是 min(凹槽左边高度, 凹槽右边高度) - 凹槽底部高度
    int h = min(height[st.top()], height[i]) - height[mid];
    5.2 雨水的宽度是 凹槽右边的下标 - 凹槽左边的下标 - 1(因为只求中间宽度),代码为:int w = i - st.top() - 1 ;

  6. 当前凹槽雨水的体积就是: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个高频元素

在这里插入图片描述

  1. 先使用map记录每个元素出现的次数map.put(map.getOrDefault(num,0)+1) 如果存在键num则其对应的值+1,否则返回默认值0
  2. 构造大根堆,按照出现次数有多到少的顺序放入元素,最后从堆顶弹出前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;
    }
}
  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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值