算法

链表

反转链表

双链表

public ListNode ReverseList(ListNode head) {
    //新链表
    ListNode newHead = null;
    while (head != null) {
        //先保存访问的节点的下一个节点,保存起来
        //留着下一步访问的
        ListNode temp = head.next;
        //每次访问的原链表节点都会成为新链表的头结点,
        //其实就是把新链表挂到访问的原链表节点的
        //后面就行了
        head.next = newHead;
        //更新新链表
        newHead = head;
        //重新赋值,继续访问
        head = temp;
    }
    //返回新链表
    return newHead;
}
//栈
import java.util.Stack;
public class Solution {
public ListNode ReverseList(ListNode head) {
    Stack<ListNode> stack= new Stack<>();
    //把链表节点全部摘掉放到栈中
    while (head != null) {
        stack.push(head);
        head = head.next;
    }
    if (stack.isEmpty())
        return null;
    ListNode node = stack.pop();
    ListNode dummy = node;
    //栈中的结点全部出栈,然后重新连成一个新的链表
    while (!stack.isEmpty()) {
        ListNode tempNode = stack.pop();
        node.next = tempNode;
        node = node.next;
    }
    //最后一个结点就是反转前的头结点,一定要让他的next
    //等于空,否则会构成环
    node.next = null;
    return dummy;
}
}

链表内指定区间反转

import java.util.*;
public class Solution {
    public ListNode reverseBetween (ListNode head, int m, int n) {
        //设置虚拟头节点
        ListNode dummyNode = new ListNode(-1);
        dummyNode.next =head;
        ListNode pre = dummyNode;
        for(int i=0;i<m-1;i++){
            pre = pre.next;
        }
 
        ListNode cur = pre.next;
        ListNode curNext ;
        for(int i=0;i<n-m;i++){
            curNext = cur.next;
            cur.next = curNext.next;
            curNext .next = pre.next;
            pre.next = curNext ;
        }
        return dummyNode.next;
    }
}

链表中的节点每k个一组翻转

非递归:

每k个一组进行反转,如果不够k个就不需要反转

public ListNode reverseKGroup(ListNode head, int k) {
    //先创建一个哑节点
    ListNode dummy = new ListNode(0);
    //让哑节点的指针指向链表的头
    dummy.next = head;
    //开始反转的前一个节点,比如反转的节点范围是[link1,link2],
    //那么pre就是link1的前一个节点
    ListNode pre = dummy;
    ListNode end = dummy;
    while (end.next != null) {
        //每k个反转,end是每k个链表的最后一个
        for (int i = 0; i < k && end != null; i++)
            end = end.next;
        //如果end是空,说明不够k个,就不需要反转了,直接退出循环。
        if (end == null)
            break;
        //反转开始的节点
        ListNode start = pre.next;
        //next是下一次反转的头结点,先把他记录下来
        ListNode next = end.next;
        //因为end是这k个链表的最后一个结点,把它和原来链表断开,
        //这k个节点我们可以把他们看做一个小的链表,然后反转这个
        //小链表
        end.next = null;
        //因为pre是反转链表的前一个节点,我们把小链表[start,end]
        //反转之后,让pre的指针指向这个反转的小链表
        pre.next = reverse(start);
        //注意经过上一步反转之后,start反转到链表的尾部了,就是已经
        //反转之后的尾结点了,让他之前下一次反转的头结点即可(上面分析
        //过,next就是下一次反转的头结点)
        start.next = next;
        //前面反转完了,要进入下一波了,pre和end都有重新赋值
        pre = start;
        end = start;
    }
    return dummy.next;
}
 
//链表的反转
private ListNode reverse(ListNode head) {
    ListNode pre = null;
    ListNode curr = head;
    while (curr != null) {
        ListNode next = curr.next;
        curr.next = pre;
        pre = curr;
        curr = next;
    }
    return pre;
}

递归方式

这里并不是反转全部的节点,而是每k个节点进行反转,递归调用,直到不能完全反转为止

public ListNode reverseKGroup(ListNode head, int k) {
    //边界条件判断
    if (head == null || head.next == null)
        return head;
    ListNode tail = head;
    for (int i = 0; i < k; i++) {
        //剩余数量小于k的话,则不需要反转。
        if (tail == null)
            return head;
        tail = tail.next;
    }
    // 反转前 k 个元素
    ListNode newHead = reverse(head, tail);
    //下一轮的开始的地方就是tail
    head.next = reverseKGroup(tail, k);
    return newHead;
}
 
/*
    链表的反转,不是反转全部,只反转区间[head,tail)中间的节点,左闭右开区间
 */
private ListNode reverse(ListNode head, ListNode tail) {
    ListNode pre = null;
    ListNode next = null;
    while (head != tail) {
        next = head.next;
        head.next = pre;
        pre = head;
        head = next;
    }
    return pre;
}

合并两个排序的链表

  • 从头结点开始考虑,比较两表头结点的值,值较小的list的头结点后面接merge好的链表(进入递归了);
  • 若两链表有一个为空,返回非空链表,递归结束;
  • 当前层不考虑下一层的细节,当前层较小的结点接上该结点的next与另一结点merge好的表头就ok了;
  • 每层返回选定的较小结点就ok;
public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1==null){
            return list2;
        }
        else if(list2==null){
            return list1;
        }
        if(list2.val>list1.val){
            list1.next = Merge(list1.next,list2);
            return list1;
        }
        else{
            list2.next = Merge(list1,list2.next);
            return list2;
        }
    }
}

BM5 合并k个已排序的链表

思路: 两个两个的合并链表

注意点:

  1. 哨兵节点dummyHead的建立,next指向真正的头结点,这一步可以将头结点的处理合并到其他节点中
  2. dummyHead.next始终指向合并后链表的头结点,然后与lists的链表一个一个进行合并
  3. 初始合并的链表为null
import java.util.*;
public class Solution {
    public ListNode mergeKLists(ArrayList<ListNode> lists) {
        ListNode dummyHead=new ListNode(-1);
        for(int i=0,len=lists.size();i<len;i++){
          //第一次循环即是链表的初始化,指向数组中的第一个链表
          dummyHead.next=mergeTwoLists(dummyHead.next,lists.get(i));
        }
        return dummyHead.next;
    }
  
    //升序合并两个链表
    private ListNode mergeTwoLists(ListNode head1,ListNode head2){
        if(head1==null){
            return head2;
        }
        if(head2==null){
            return head1;
        }
        ListNode dummyHead=new ListNode(-1);
        ListNode cur=dummyHead;
        while(head1!=null&&head2!=null){
            if(head1.val<head2.val){
                cur.next=head1;
                head1=head1.next;
            }else{
                cur.next=head2;
                head2=head2.next;
            }
            cur=cur.next;
        }
        if(head1==null){
            cur.next=head2;
        }else if(head2==null){
            cur.next=head1;
        }
        return dummyHead.next;
    }
}

BM6 判断链表中是否有环

解题思路

我们都知道链表不像二叉树,每个节点只有一个val值和一个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;
    }
}

BM7 链表中环的入口结点

  1. 这题我们可以采用双指针解法,一快一慢指针。快指针每次跑两个element,慢指针每次跑一个。如果存在一个圈,总有一天,快指针是能追上慢指针的。
  2. 如下图所示,我们先找到快慢指针相遇的点,p。我们再假设,环的入口在点q,从头节点到点q距离为A,q p两点间距离为B,p q两点间距离为C。
  3. 因为快指针是慢指针的两倍速,且他们在p点相遇,则我们可以得到等式 2(A+B) = A+B+C+B. (感谢评论区大佬们的改正,此处应为:*如果环前面的链表很长,而环短,那么快指针进入环以后可能转了好几圈(假设为n圈)才和慢指针相遇。但无论如何,慢指针在进入环的第一圈的时候就会和快的相遇。等式应更正为 2(A+B)= A+ nB + (n-1)C)*
  4. 由3的等式,我们可得,C = A。
  5. 这时,因为我们的slow指针已经在p,我们可以新建一个另外的指针,slow2,让他从头节点开始走,每次只走下一个,原slow指针继续保持原来的走法,和slow2同样,每次只走下一个。
  6. 我们期待着slow2和原slow指针的相遇,因为我们知道A=C,所以当他们相遇的点,一定是q了。
  7. 我们返回slow2或者slow任意一个节点即可,因为此刻他们指向的是同一个节点,即环的起始点,q。
public class Solution {
    public ListNode EntryNodeOfLoop(ListNode pHead)
    {
        if(pHead == null || pHead.next == null){
            return null;
        }
        ListNode fast = pHead;
        ListNode slow = pHead;
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow){
                ListNode slow2 = pHead;
                while(slow2 != slow){
                    slow2 = slow2.next;
                    slow = slow.next;
                }
                return slow2;
            }
        }
        return null;
    }
}

BM8 链表中倒数最后k个结点

快慢指针

step 1:准备一个快指针,从链表头开始,在链表上先走k步。

step 2:准备慢指针指向原始链表头,代表当前元素,则慢指针与快指针之间的距离一直都是k。

step 3:快慢指针同步移动,当快指针到达链表尾部的时候,慢指针正好到了倒数k个元素的位置。

public ListNode FindKthToTail(ListNode pHead, int k) {
    if (pHead == null)
        return pHead;
    ListNode first = pHead;
    ListNode second = pHead;
    //第一个指针先走k步
    while (k-- > 0) {
        if (first == null)
            return null;
        first = first.next;
    }
    //然后两个指针在同时前进
    while (first != null) {
        first = first.next;
        second = second.next;
    }
    return second;
}

BM9 删除链表的倒数第n个节点

非递归解决

  • 先求出链表的长度length
  • 找到要删除链表的前一个节点,让他的前一个结点指向要删除结点的下一个结点即可
public ListNode removeNthFromEnd(ListNode head, int n) {
    ListNode pre = head;
    int last = length(head) - n;
    //如果last等于0表示删除的是头结点
    if (last == 0)
        return head.next;
    //这里首先要找到要删除链表的前一个结点
    for (int i = 0; i < last - 1; i++) {
        pre = pre.next;
    }
    //然后让前一个结点的next指向要删除节点的next
    pre.next = pre.next.next;
    return head;
}
 
//求链表的长度
private int length(ListNode head) {
    int len = 0;
    while (head != null) {
        len++;
        head = head.next;
    }
    return len;
}

BM10 两个链表的第一个公共结点

set解法

这是一种「从前往后」找的方式。

使用 Set 数据结构,先对某一条链表进行遍历,同时记录下来所有的节点。

然后在对第二链条进行遍历时,检查当前节点是否在 Set 中出现过,第一个在 Set 出现过的节点即是交点。

import java.util.*;
public class Solution {
    public ListNode FindFirstCommonNode(ListNode a, ListNode b) {
        Set<ListNode> set = new HashSet<>();
        while (a != null) {
            set.add(a);
            a = a.next;
        }
        while (b != null && !set.contains(b)) b = b.next;
        return b;
    }
}

差值法

由于两条链表在相交节点后面的部分完全相同,因此我们可以先对两条链表进行遍历,分别得到两条链表的长度,并计算差值 d

让长度较长的链表先走 d 步,然后两条链表同时走,第一个相同的节点即是节点。

import java.util.*;
public class Solution {
    public ListNode FindFirstCommonNode(ListNode a, ListNode b) {
        int c1 = 0, c2 = 0;
        ListNode ta = a, tb = b;
        while (ta != null && c1++ >= 0) ta = ta.next;
        while (tb != null && c2++ >= 0) tb = tb.next;
        int d = c1 - c2;
        if (d > 0) {
            while (d-- > 0) a = a.next;
        } else if (d < 0) {
            d = -d;
            while (d-- > 0) b = b.next;
        }
        while (a != b) {
            a = a.next;
            b = b.next;
        }
        return a;
    }
}

BM11 链表相加(二)

申请两个栈空间和一个标记位,然后将两个栈中内容依次相加,将结果倒插入新节点中。

public class Solution {
    public ListNode addInList (ListNode head1, ListNode head2) {
        LinkedList<Integer> list1 = new LinkedList<>(); //list1栈
        LinkedList<Integer> list2 = new LinkedList<>(); //list2栈
        putData(list1, head1); //入栈
        putData(list2, head2);
        ListNode newNode = null;
        ListNode head = null;
        int carry = 0; //标记进位
        while(!list1.isEmpty() || ! list2.isEmpty() || carry != 0) {
            int x = (list1.isEmpty()) ? 0 : list1.pop();  //依次从栈中取出
            int y = (list2.isEmpty()) ? 0 : list2.pop();
            int sum = x + y + carry; //与进位一起相加
            carry = sum / 10; //更新进位
            //将计算值放入节点
            newNode = new ListNode(sum % 10);
                        //更新下一个节点的指向
            newNode.next = head;
            head = newNode;
        }
        return head;
 
    }
    private static void putData(LinkedList<Integer> s1,ListNode head1) {
        if (s1 == null) s1 = new LinkedList<>();
                //遍历节点将其插入栈中
        while(head1 != null) {
            s1.push(head1.val);
            head1 = head1.next;
        }
    }
}

BM12 单链表的排序

public class Solution {
    public ListNode sortInList (ListNode head) {
      ListNode cur = head;
      ListNode nextNode = null;
      int temp = 0;
      while (cur.next != null) {
        nextNode = cur.next;
        while (nextNode != null) {
          if (cur.val > nextNode.val) {
            temp = cur.val;
            cur.val = nextNode.val;
            nextNode.val = temp;
          }
          nextNode = nextNode.next;
        }
        cur = cur.next;
      }
      return head;
    }
}

BM13 判断一个链表是否为回文结构

双指针

使用两个指针,一个最左边一个最右边,两个指针同时往中间靠,判断所指的字符是否相等

public boolean isPail(ListNode head) {
    ListNode fast = head, slow = head;
    //通过快慢指针找到中点
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
    }
    //如果fast不为空,说明链表的长度是奇数个
    if (fast != null) {
        slow = slow.next;
    }
    //反转后半部分链表
    slow = reverse(slow);
 
    fast = head;
    while (slow != null) {
        //然后比较,判断节点值是否相等
        if (fast.val != slow.val)
            return false;
        fast = fast.next;
        slow = slow.next;
    }
    return true;
}
 
//反转链表
public ListNode reverse(ListNode head) {
    ListNode prev = null;
    while (head != null) {
        ListNode next = head.next;
        head.next = prev;
        prev = head;
        head = next;
    }
    return prev;
}

使用栈

我们知道栈是先进后出的一种数据结构,这里还可以使用栈先把链表的节点全部存放到栈中,然后再一个个出栈,这样就相当于链表从后往前访问了,通过这种方式也能解决,看下代码

public boolean isPail(ListNode head) {
    ListNode temp = head;
    Stack<Integer> stack = new Stack();
    //把链表节点的值存放到栈中
    while (temp != null) {
        stack.push(temp.val);
        temp = temp.next;
    }
 
    //然后再出栈
    while (head != null) {
        if (head.val != stack.pop()) {
            return false;
        }
        head = head.next;
    }
    return true;
}

BM14 链表的奇偶重排

双指针

  • 设置first指针和last分别位于前后相邻的位置,一次向后遍历两步,则得到的frst走的为偶数位,last奇数位
  • 同时考虑null指针的情况
import java.util.*;
public class Solution {
    public ListNode oddEvenList (ListNode head) {
       
        if(head == null) return null;
        
        ListNode fast = head.next;
        ListNode last = head;
        ListNode result1 = last;
        ListNode result2 = fast;
        
        if(fast == null) return head;
       
        while(fast.next != null && fast.next.next != null){
            last.next = last.next.next;
            last = last.next;
            fast.next = fast.next.next;
            fast = fast.next;
        }
        if(fast.next == null){
            last.next = result2;
        }else{
            last.next = last.next.next;
            last = last.next;
            fast.next = null;
            last.next = result2;
        }
        return result1;
    }
}

BM15 删除有序链表中重复的元素-I

public class Solution {
    public ListNode deleteDuplicates (ListNode head) {
        if(head == null) return null;
        ListNode fast = head;
        while(fast.next != null) {
            if (fast.val == fast.next.val) {
                fast.next = fast.next.next;
            } else {
                fast = fast.next;
            }
        }
        return head;
    }
}

BM16 删除有序链表中重复的元素-II

public static ListNode deleteDuplicates(ListNode head) {
  ListNode node = new ListNode(0);
  node.next = head;
  ListNode next;
  ListNode cur = head;
  ListNode pre = node;
  while (cur != null) {
    next = cur.next;
    boolean tmp = false;
    while (next != null && cur.val == next.val) {
      next = next.next;
      pre.next = next; // 前一个不同节点指向下一个不同节点
      tmp = true;
    }
    if (!tmp) { // 如果以前没有相同的节点,则pre指向当前节点,保证下次遇到相同的节点设置next
      pre = cur;
    }
    cur = next;
  }
  return node.next;
}

二分查找/排序

BM17 二分查找-I

import java.util.*;
public class Solution {
  public int search (int[] nums, int target) { 
    //定义了target在[left,right]区间内
    int left = 0;
    int right = nums.length-1;
    //数组从小到大排序
    while(right>=left){
      //定义中间值的下角标	
      int middle = (left + right)/2;s
      //如果中间值大于目标值,目标值在左半部分,下一轮二分查找[left,middle-1]
      if (nums[middle] > target){
        right = middle -1;
        //如果中间值小于目标值,目标值在右半部分,下一轮二分查找[middle+1,right]
      }else if(nums[middle] < target){
        left = middle + 1;   
        //如果左右两边都没有,那就是中间值
      }else {
        return middle;
      } 
    }
    //没有找到目标值,返回-1
    return -1;
  }
}

BM18 二维数组中的查找

从左下找

对于左下角的值 m,m 是该行最小的数,是该列最大的数
每次将 m 和目标值 target 比较:

  1. 当 m < target,由于 m 已经是行最大的元素,想要更大只有从列考虑,取值右移一位
  2. 当 m > target,由于 m 已经是该列最小的元素,想要更小只有从行考虑,取值上移一位
  3. 当 m = target,找到该值,返回 true

用某行最小或某列最大与 target 比较,每次可剔除一整行或一整列

public class Solution {
    public boolean Find(int target, int [][] array) {
        int rows = array.length;
        if(rows == 0){
            return false;
        }
        int cols = array[0].length;
        if(cols == 0){
            return false;
        }
        // 左下
        int row = rows-1;
        int col = 0;
        while(row>=0 && col<cols){
            if(array[row][col] < target){
                col++;
            }else if(array[row][col] > target){
                row--;
            }else{
                return true;
            }
        }
        return false;
    }
}

BM19 寻找峰值

二分法

上坡一定有波峰,下坡不一定有波峰

import java.util.*;
public class Solution {
   
    public int findPeakElement (int[] nums) {
   //关键思想:下坡的时候可能找到波峰,但是可能找不到,一直向下走的
  //上坡的时候一定能找到波峰,因为题目给出的是nums[-1] = nums[n] = -∞
        int left = 0;
        int right = nums.length-1;
        while(left<right){
            int mid = left+(right-left)/2;
            //证明右边的路是下坡路,不一定有坡峰
            if(nums[mid]>nums[mid+1]){
                right = mid;
            }
            else{
                //这里是右边的路是上坡路
                left = mid + 1;
            }
        }
        return right;
    }
}

找最大值

根据题目的意思,两个端点值是-∞(且元素不重复),我只需要一直找最大的值,那么这个值一定是波峰

public int findPeakElement(int[] nums) {
  int idx = 0;
  for (int i = 1; i < nums.length; ++i) {
    if (nums[i] > nums[idx]) {
      idx = i;
    }
  }
  return idx;
}

BM20 数组中的逆序对

  • 划分阶段:将待划分区间从中点划分成两部分,两部分进入递归继续划分,直到子数组长度为1
  • 排序阶段:使用归并排序递归地处理子序列,同时统计逆序对,因为在归并排序中,我们会依次比较相邻两组子数组各个元素的大小,并累计遇到的逆序情况。而对排好序的两组,右边大于左边时,它大于了左边的所有子序列,基于这个性质我们可以不用每次加1来统计,减少运算次数。
  • 合并阶段:将排好序的子序列合并,同时累加逆序对。
int num = 0;
public int InversePairs(int [] array) {
  //在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
  int []temp = new int[array.length];
  sort(array,0,array.length-1,temp);
  return num;
}
private  void sort(int[] array,int left,int right,int []temp){
  if(left<right){
    int mid = (left+right)/2;
    sort(array,left,mid,temp);//左边归并排序,使得左子序列有序
    sort(array,mid+1,right,temp);//右边归并排序,使得右子序列有序
    merge(array,left,mid,right,temp);//将两个有序子数组合并操作
  }
}
private  void merge(int[] array,int left,int mid,int right,int[] temp){
  int i = left;//左序列指针
  int j = mid+1;//右序列指针
  int t = 0;//临时数组指针
  while (i<=mid && j<=right){
    if(array[i]<=array[j]){
      temp[t++] = array[i++];
    }else {
      temp[t++] = array[j++];
      num = (num + mid -i + 1)%1000000007;
    }
  }

  while(i<=mid){//将左边剩余元素填充进temp中
    temp[t++] = array[i++];
  }
  while(j<=right){//将右序列剩余元素填充进temp中
    temp[t++] = array[j++];
  }
  t = 0;
  //将temp中的元素全部拷贝到原数组中
  while(left <= right){
    array[left++] = temp[t++];
  }
}
}

BM21 旋转数组的最小数字

通过二分的方法,不断去更新存在于两个子数组(两个非递减排序子数组)中的下标。时间复杂度是O(log(n))

public int minNumberInRotateArray(int[] array) {
  if (array.length == 0) {
    return 0;
  }
  int l = 0;
  int r = array.length - 1;
  while (l < r - 1) {
    int mid = (l + r) >> 1;
    if (array[mid] >= array[l]) {
      l = mid; /// 说明mid所在的位置是在第一个非递减子数组中
    } else if (array[mid] <= array[r]) {
      r = mid; /// 说明mid所在的位置是在第二个非递减子数组中
    }
  }
  return array[r];
}

BM22 比较版本号

public static int compare (String version1, String version2) {

  String[] arr1 = version1.split("\\.");
  String[] arr2 = version2.split("\\.");

  int n = Math.max(arr1.length, arr2.length);
  for (int i = 0; i < n; i++) {

    int value1 = (i > arr1.length - 1) ? 0 : Integer.parseInt(arr1[i]);
    int value2 = (i > arr2.length - 1) ? 0 : Integer.parseInt(arr2[i]);

    if (value1 > value2) {
    	return 1;
    } else  if (value1 < value2) {
    	return -1;
    }
  }
  return 0;
}

二叉树

BM23 二叉树的前序遍历

什么是二叉树的前序遍历?简单来说就是“根左右”。

递归解决

终止条件: 当子问题到达叶子节点后,后一个不管左右都是空,因此遇到空节点就返回。 返回值:每次处理完子问题后,就是将子问题访问过的元素返回,依次存入了数组中。 本级任务:每个子问题优先访问这棵子树的根节点,然后递归进入左子树和右子树。

  • 准备数组用来记录遍历到的节点值,Java可以用List,C++可以直接用vector。
  • 从根节点开始进入递归,遇到空节点就返回,否则将该节点值加入数组。
  • 依次进入左右子树进行递归。
public class Solution {
    public int[] preorderTraversal (TreeNode root) {
        List<Integer> list=new ArrayList<>();
        dfs(list,root);
        int[] res=new int[list.size()];
        for (int i = 0; i < list.size(); i++) {
            res[i]=list.get(i);
        }
        return res;
    }
    public void dfs(List<Integer> list,TreeNode root){
        if(root!=null){
            list.add(root.val);
            dfs(list,root.left);
            dfs(list,root.right);
        }
    }
}

BM24 二叉树的中序遍历

什么是二叉树的中序遍历,简单来说就是“左根右”

终止条件:当子问题到达叶子节点后,后一个不管左右都是空,因此遇到空节点就返回。

返回值:每次处理完子问题后,就是将子问题访问过的元素返回,依次存入了数组中。

本级任务:每个子问题优先访问左子树的子问题,等到左子树的结果返回后,再访问自己的根节点,然后进入右子树。

  • 准备数组用来记录遍历到的节点值,Java可以用List
  • 从根节点开始进入递归,遇到空节点就返回,否则优先进入左子树进行递归访问
  • 左子树访问完毕再回到根节点访问。
  • 最后进入根节点的右子树进行递归。
public class Solution {
    ArrayList<Integer> list=new ArrayList<>();
    public int[] inorderTraversal (TreeNode root) {
       ArrayList<Integer> helpList=inorder(root,list);
       int[] intArr = list.stream().mapToInt(Integer::intValue).toArray();
       return intArr;
    }
    public ArrayList<Integer> inorder(TreeNode root, ArrayList<Integer> list){
        if(root==null){
            return list;
        }
        inorder(root.left,list);
        list.add(root.val);
        inorder(root.right,list);
        return list;
    }
}

BM25 二叉树的后序遍历

什么是二叉树的后续遍历,简单来说就是“左右根”

终止条件:当子问题到达叶子节点后,后一个不管左右都是空,因此遇到空节点就返回。

返回值:每次处理完子问题后,就是将子问题访问过的元素返回,依次存入了数组中。

本级任务:对于每个子问题,优先进入左子树的子问题,访问完了再进入右子树的子问题,最后回到父问题访问根节点。

  • 准备数组用来记录遍历到的节点值,Java可以用List
  • 从根节点开始进入递归,遇到空节点就返回,否则优先进入左子树进行递归访问
  • 左子树访问完毕再进入根节点的右子树递归访问。
  • 最后回到根节点,访问该节点。
public class Solution {
    List<Integer> list=new ArrayList<>();
    public int[] postorderTraversal (TreeNode root) {
        postOrder(root);
        int[] res= new int[list.size()];
        for(int i=0;i<list.size();i++){
            res[i]=list.get(i);
        }
        return res;
         
    }
    void postOrder(TreeNode root){
        if(root!=null){
            postOrder(root.left);
            postOrder(root.right);
            list.add(root.val);
        }
    }
}

非递归

ArrayList<Integer> list=new ArrayList<>();
TreeNode cur=root,pre=null;
Stack<TreeNode> s=new Stack<>();
while(cur!=null||!s.isEmpty()){
  while(cur!=null){
    s.push(cur);
    cur=cur.left;
  }

  cur=s.get(s.size()-1);
  if(cur.right==null||pre==cur.right){
    s.pop();
    list.add(cur.val);
    pre=cur;
    cur=null;
  }else{
    cur=cur.right;  
  }
}

int[] res=new int[list.size()];
for(int i=0;i<list.size();i++){
  res[i]=list.get(i);
}
return res;
}

BM26 求二叉树的层序遍历

  • 首先判断二叉树是否为空,空树没有遍历结果。
  • 建立辅助队列,根节点首先进入队列。不管层次怎么访问,根节点一定是第一个,那它肯定排在队伍的最前面。
  • 每次进入一层,统计队列中元素的个数。因为每当访问完一层,下一层作为这一层的子节点,一定都加入队列,而再下一层还没有加入,因此此时队列中的元素个数就是这一层的元素个数。
  • 每次遍历这一层这么多的节点数,将其依次从队列中弹出,然后加入这一行的一维数组中,如果它们有子节点,依次加入队列排队等待访问。
  • 访问完这一层的元素后,将这个一维数组加入二维数组中,再访问下一层。
public ArrayList<ArrayList<Integer>> levelOrder (TreeNode root) {
  ArrayList<ArrayList<Integer>> result = new ArrayList<>();
  if (root == null) {
    return result;
  }
  // 队列,用于存储元素
  Queue<TreeNode> queue = new LinkedList<>();
  // 根节点先入队
  queue.offer(root);
  // 当队列不为空的时候
  while(!queue.isEmpty()) {
    // 队列的大小就是这一层的元素数量
    int size = queue.size();
    ArrayList<Integer> list = new ArrayList<>();
    // 开始遍历这一层的所有元素
    for (int i = 0; i < size; i ++) {
      TreeNode node = queue.poll();
      // 如果左节点不为空,则入队,作为下一层来遍历
      if(node.left != null) {
        queue.offer(node.left);
      }
      // 同上
      if (node.right != null) {
        queue.offer(node.right);
      }
      // 存储一层的节点
      list.add(node.val);
    }
    // 将一层所有的节点汇入到总的结果集中
    result.add(list);
  }
  return result;
}

BM27 按之字形顺序打印二叉树

  • 首先判断二叉树是否为空,空树没有打印结果。
  • 建立辅助队列,根节点首先进入队列。不管层次怎么访问,根节点一定是第一个,那它肯定排在队伍的最前面,初始化flag变量。
  • 每次进入一层,统计队列中元素的个数,更改flag变量的值。因为每当访问完一层,下一层作为这一层的子节点,一定都加入队列,而再下一层还没有加入,因此此时队列中的元素个数就是这一层的元素个数。
  • 每次遍历这一层这么多的节点数,将其依次从队列中弹出,然后加入这一行的一维数组中,如果它们有子节点,依次加入队列排队等待访问。
  • 访问完这一层的元素后,根据flag变量决定将这个一维数组直接加入二维数组中还是反转后再加入,然后再访问下一层。
import java.util.LinkedList;
public class Solution {
    public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        LinkedList<TreeNode> q = new LinkedList<>();
        ArrayList<ArrayList<Integer>> res = new ArrayList<>();
        boolean rev = true;
        q.add(pRoot);
        while(!q.isEmpty()){
            int size = q.size();
            ArrayList<Integer> list = new ArrayList<>();
            for(int i=0; i<size; i++){
                TreeNode node = q.poll();
                if(node == null){continue;}
                if(rev){
                    list.add(node.val);
                }else{
                    list.add(0, node.val);
                }
                q.offer(node.left);
                q.offer(node.right);
            }
            if(list.size()!=0){res.add(list);}
            rev=!rev;
        }
        return res;
    }
}

BM28 二叉树的最大深度

递归

public int maxDepth(TreeNode root) {
  return root==null? 0 : Math.max(maxDepth(root.left), maxDepth(root.right))+1;
}

BFS

public int maxDepth(TreeNode root) {
    if (root == null)
        return 0;
    //创建一个队列
    Deque<TreeNode> deque = new LinkedList<>();
    deque.push(root);
    int count = 0;
    while (!deque.isEmpty()) {
        //每一层的个数
        int size = deque.size();
        while (size-- > 0) {
            TreeNode cur = deque.pop();
            if (cur.left != null)
                deque.addLast(cur.left);
            if (cur.right != null)
                deque.addLast(cur.right);
        }
        count++;
    }
    return count;
}

BM29 二叉树中和为某一值的路径(一)

递归

public boolean hasPathSum(TreeNode root, int sum) {
    //如果根节点为空,或者叶子节点也遍历完了也没找到这样的结果,就返回false
    if (root == null)
        return false;
    //如果到叶子节点了,并且剩余值等于叶子节点的值,说明找到了这样的结果,直接返回true
    if (root.left == null && root.right == null && sum - root.val == 0)
        return true;
    //分别沿着左右子节点走下去,然后顺便把当前节点的值减掉,左右子节点只要有一个返回true,
    //说明存在这样的结果
    return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
}

非递归解决

public boolean hasPathSum(TreeNode root, int sum) {
    if (root == null)
        return false;
    Stack<TreeNode> stack = new Stack<>();
    stack.push(root);//根节点入栈
    while (!stack.isEmpty()) {
        TreeNode cur = stack.pop();//出栈
        //累加到叶子节点之后,结果等于sum,说明存在这样的一条路径
        if (cur.left == null && cur.right == null) {
            if (cur.val == sum)
                return true;
        }
        //右子节点累加,然后入栈
        if (cur.right != null) {
            cur.right.val = cur.val + cur.right.val;
            stack.push(cur.right);
        }
        //左子节点累加,然后入栈
        if (cur.left != null) {
            cur.left.val = cur.val + cur.left.val;
            stack.push(cur.left);
        }
    }
    return false;
}

BM30 二叉搜索树与双向链表

  • 创建两个指针,一个指向题目中要求的链表头(head),一个指向当前遍历的前一结点(pre)。
  • 首先递归到最左,初始化head与pre。
  • 然后处理中间根节点,依次连接pre与当前结点,连接后更新pre为当前节点。
  • 最后递归进入右子树,继续处理。
  • 递归出口即是节点为空则返回。
public class Solution {
    TreeNode pre= null;
    TreeNode root=null;
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree ==null) return null;
        Convert(pRootOfTree.left);
        if(root == null){
            root=pRootOfTree;
        }
        if(pre!=null){
            pRootOfTree.left=pre;
            pre.right=pRootOfTree;
        }
        pre=pRootOfTree;
        Convert(pRootOfTree.right);
        return root;
    }
}

BM31 对称的二叉树

前序遍历的时候我们采用的是“根左右”的遍历次序,如果这棵二叉树是对称的,即相应的左右节点交换位置完全没有问题,那我们是不是可以尝试“根右左”遍历,按照轴对称图像的性质,这两种次序的遍历结果应该是一样的。

我们使用 0x3f3f3f3f 作为无效值,并建立占位节点 emptyNode 用来代指空节点(emptyNode.val = 0x3f3f3f3f)。

一个朴素的做法是:使用「层序遍历」的方式进行「逐层检查」,对于空节点使用 emptyNode 进行代指,同时确保不递归 emptyNode 对应的子节点。

具体做法如下:

  1. 起始时,将 root 节点入队;
  2. 从队列中取出节点,检查节点是否为 emptyNode 节点来决定是否继续入队:
    • 当不是 emptyNode 节点时,将其左/右儿子进行入队,如果没有左/右儿子,则用 emptyNode 代替入队;
    • 当是 emptyNode 节点时,则忽略
  3. 在进行流程 img 的同时使用「临时列表」记录当前层的信息,并检查当前层是否符合 “对称” 要求;
  4. 循环流程 imgimg,直到整个队列为空。
import java.util.*;
class Solution {
    int INF = 0x3f3f3f3f;
    TreeNode emptyNode = new TreeNode(INF);
    boolean isSymmetrical(TreeNode root) {
        if (root == null) return true;

        Deque<TreeNode> d = new ArrayDeque<>();
        d.add(root);
        while (!d.isEmpty()) {
            // 每次循环都将下一层拓展完并存到「队列」中
            // 同时将该层节点值依次存入到「临时列表」中
            int size  = d.size();
            List<Integer> list = new ArrayList<>();
            while (size-- > 0) {
                TreeNode poll = d.pollFirst();
                if (!poll.equals(emptyNode)) {
                    d.addLast(poll.left != null ? poll.left : emptyNode);
                    d.addLast(poll.right != null ? poll.right : emptyNode);
                }
                list.add(poll.val);
            }

            // 每一层拓展完后,检查一下存放当前层的该层是否符合「对称」要求
            if (!check(list)) return false;
        }
        return true;
    }

    // 使用「双指针」检查某层是否符合「对称」要求
    boolean check(List<Integer> list) {
        int l = 0, r = list.size() - 1;
        while (l < r) {
            if (!list.get(l).equals(list.get(r))) return false;
            l++;
            r--;
        }
        return true;
    }
}

BM32 合并二叉树

  • 首先判断t1与t2是否为空,若为则用另一个代替,若都为空,返回的值也是空。
  • 然后依据前序遍历的特点,优先访问根节点,将两个根点的值相加创建到新树中。
  • 两棵树再依次同步进入左子树和右子树
import java.util.*;
public class Solution {
    public TreeNode mergeTrees (TreeNode t1, TreeNode t2) {
        //总体思想(递归):以t1为根本,将t2拼接到t1中,具体分一下几种情况:
        //(1)t1不为空,t2为空
        if(t1!=null && t2 == null){
            return t1;
        }
        //(2)t1为空,t2不为空
        if(t1==null && t2!=null){
            return t2;
        }
        //(3)t1与t2都不为空
        if(t1 != null && t2 != null){
            t1.val += t2.val;//合并数据
            t1.left = mergeTrees(t1.left,t2.left);//递归左子树
            t1.right = mergeTrees(t1.right,t2.right);//递归右子树
        }
        return t1;
    }
}

BM33 二叉树的镜像

遍历每一个节点,然后交换他的两个子节点,一直循环下去,直到所有的节点都遍历完为止

public TreeNode Mirror(TreeNode root) {
    //如果为空直接返回
    if (root == null)
        return null;
    //队列
    final Queue<TreeNode> queue = new LinkedList<>();
    //首先把根节点加入到队列中
    queue.add(root);
    while (!queue.isEmpty()) {
        //poll方法相当于移除队列头部的元素
        TreeNode node = queue.poll();
        //交换node节点的两个子节点
        TreeNode left = node.left;
        node.left = node.right;
        node.right = left;
        //如果当前节点的左子树不为空,就把左子树
        //节点加入到队列中
        if (node.left != null) {
            queue.add(node.left);
        }
        //如果当前节点的右子树不为空,就把右子树
        //节点加入到队列中
        if (node.right != null) {
            queue.add(node.right);
        }
    }
    return root;
}

BM34 判断是不是二叉搜索树

利用二叉搜索树的特性:中序遍历为升序,遍历二叉树即可。

每次记录一下前驱节点的值,判断当前节点是否比前驱节点大,如果比前驱小,则遍历结束。

如果遍历到最后一个节点还是满足则为二叉搜索树。

public class Solution {
    boolean isVa;
    boolean first;
    int min;
    public boolean isValidBST (TreeNode root) {
        midorder(root);
        return !isVa;
    }
    public void midorder(TreeNode root){
        if(!isVa&&root!=null){
            midorder(root.left);
            if(!first){
                min = root.val;
                first = !first;
            }
            else {
                if(min>=root.val) isVa = !isVa;
                else min = root.val;
            }
            midorder(root.right);
        }
    }
}

BM35 判断是不是完全二叉树

  • 先判断空树一定是完全二叉树。
  • 初始化一个队列辅助层次遍历,将根节点加入。
  • 逐渐从队列中弹出元素访问节点,如果遇到某个节点为空,进行标记,代表到了完全二叉树的最下层,若是后续还有访问,则说明提前出现了叶子节点,不符合完全二叉树的性质。
  • 继续加入左右子节点进入队列排队,等待访问。
import java.util.*;
 
public class Solution {
 
    public boolean isCompleteTree (TreeNode root) {
      if (root == null) return false;
      Queue<TreeNode> q = new LinkedList<>();
      q.offer(root);
      boolean ended = false;
       
      while(!q.isEmpty()) {
        TreeNode pop = q.poll();
        if (pop == null) {
          ended = true;
        } else {
          if (q != null && ended) return false;
          q.offer(pop.left);
          q.offer(pop.right);
        }
      }
      return true;
    }
}

BM36 判断是不是平衡二叉树

思路:

从题中给出的有效信息:

  • 左右两个子树的高度差的绝对值不超过1
  • 左右两个子树都是一棵平衡二叉树

故此 首先想到的方法是使用递归的方式判断子节点的状态

方法一:dfs

具体做法:
如果一个节点的左右子节点都是平衡的,并且左右子节点的深度差不超过 1,则可以确定这个节点就是一颗平衡二叉树。

public class Solution {
    public boolean IsBalanced_Solution(TreeNode root) {
        if (root == null) return true;
        //判断左子树和右子树是否符合规则,且深度不能超过2
        return IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right) && Math.abs(deep(root.left) - deep(root.right)) < 2;
    }
    //判断二叉树深度
    public int deep(TreeNode root) {
        if (root == null) return 0;
        return Math.max(deep(root.left), deep(root.right)) + 1;
    }
}

BM37 二叉搜索树的最近公共祖先

  • 根据二叉搜索树的性质,从根节点开始查找目标节点,当前节点比目标小则进入右子树,当前节点比目标大则进入左子树,直到找到目标节点。这个过程成用数组记录遇到的元素。
  • 分别在搜索二叉树中找到p和q两个点,并记录各自的路径为数组。
  • 同时遍历两个数组,比较元素值,最后一个相等的元素就是最近的公共祖先。

非二叉搜索树

public class Solution {
    public TreeNode commonAncestor (TreeNode root, int p, int q) {
        if (null == root) return null;
        if (root.val == p || root.val == q) return root;
        // 通过递归假设我们知道了运算结果 题目含义是不会出现重复节点
        TreeNode left = commonAncestor(root.left, p, q);
        TreeNode right = commonAncestor(root.right, p, q);
        if (left == null) return right;
        else if (right == null) return left;
        else return root;
    } 
    public int lowestCommonAncestor (TreeNode root, int p, int q) {
        return commonAncestor(root, p, q).val;
    }
}

利用二叉树性质

public class Solution {
    public TreeNode commonAncestor (TreeNode root, int p, int q) {
        if (null == root) return null;
        if (root.val == p || root.val == q) return root;
        // 通过递归假设我们知道了运算结果 题目含义是不会出现重复节点
        if (p < root.val && q < root.val) return commonAncestor(root.left, p, q);
        else if (p > root.val && q > root.val) return commonAncestor(root.right, p, q);
        else return root;
    } 
    public int lowestCommonAncestor (TreeNode root, int p, int q) {
        // write code here
        return commonAncestor(root, p, q).val;
    }
}

堆/栈/队列

BM42 用两个栈实现队列

栈是后进先出,队列是先进先出,想要用栈实现队列,需要把一个栈中的元素挨个pop()出来,再push到另一个栈中。

import java.util.Stack;
public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();
 
    //入栈操作
    public void push(int node) {
        stack1.push(node);
    }
 
    //出栈操作
    public int pop() {
        if(stack2.size()<=0){
            while(stack1.size()!=0){
                stack2.push(stack1.pop());
            }
        }
    return stack2.pop();
    }
}

BM43 包含min函数的栈

step 1:使用一个栈记录进入栈的元素,正常进行push、pop、top操作。 step 2:使用另一个栈记录每次push进入的最小值。 step 3:每次push元素的时候与第二个栈的栈顶元素比较,若是较小,则进入第二个栈,若是较大,则第二个栈的栈顶元素再次入栈,因为即便加了一个元素,它依然是最小值。于是,每次访问最小值即访问第二个栈的栈顶。

import java.util.Stack;
public class Solution {
    Stack<Integer> stackTotal = new Stack<Integer>();
    Stack<Integer> stackLittle = new Stack<Integer>();

    public void push(int node) {
        stackTotal.push(node);
        if(stackLittle.empty()){
            stackLittle.push(node);
        }else{
            if(node <= stackLittle.peek()){
                stackLittle.push(node);
            }else{
                stackLittle.push(stackLittle.peek());
            }
        }
    }

    public void pop() {
        stackTotal.pop();
        stackLittle.pop();
    }

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

    public int min() {
        return stackLittle.peek();
    }
}

BM44 有效括号序列

先进后出 的 栈

public class Solution {
    public boolean isValid (String s) {
        if(s == null){
            return false;
        }
        Stack<Character> temp = new Stack<>();
        for(char item :s.toCharArray()){
            if(item == '['){
                temp.push(']');
            }else if(item == '{'){
                temp.push('}');
            }else if(item == '('){
                temp.push(')');
            }else if(temp.isEmpty() || temp.pop() != item){
                //如果 还有数据 并且不是 [ { (  ,那么temp就是空的,不符合要求,或者弹出的元素不等于当前的 也不是
                return false;
            }
        }
      return temp.isEmpty();
    }
}

BM45 滑动窗口的最大值

使用大顶堆

import java.util.*;
//思路:用一个大顶堆,保存当前滑动窗口中的数据。滑动窗口每次移动一格,就将前面一个数出堆,后面一个数入堆。
public class Solution {
    //大顶堆
    public PriorityQueue<Integer> maxQueue = new PriorityQueue<Integer>((o1,o2)->o2-o1);
    //保存结果
    public ArrayList<Integer> result = new ArrayList<Integer>();
  
    public ArrayList<Integer> maxInWindows(int [] num, int size)
    {
        if(num==null || num.length<=0 || size<=0 || size>num.length){
            return result;
        }
        int count=0;
        for(;count<size;count++){//初始化滑动窗口
            maxQueue.offer(num[count]);
        }
       //对每次操作,找到最大值(用优先队列的大顶堆),然后向后滑动(出堆一个,入堆一个)
        while(count < num.length){
            result.add(maxQueue.peek());
            maxQueue.remove(num[count-size]);
            maxQueue.add(num[count]);
            count++;
        }
        result.add(maxQueue.peek());//最后一次入堆后没保存结果,这里额外做一次即可

        return result;
    }
}

BM46 最小的K个数

优先队列

 int top = 3;
 int[] arr = new int[]{1,23,15,6,7,1,4,7,8};

List<Integer> result = new ArrayList<>();
PriorityQueue<Integer> maxQueue = new PriorityQueue<>(Collections.reverseOrder());
for (int num : arr) {
maxQueue.offer(num);
if (maxQueue.size() > top) {
maxQueue.poll();
}
}

while (!maxQueue.isEmpty()) {
result.add(maxQueue.poll());
}

BM48 数据流中的中位数

import java.util.ArrayList;
import java.util.Collections;
public class Solution {
 
    ArrayList<Integer> list = new ArrayList<>();//创建一个数组列表(list)
    public void Insert(Integer num) {
        list.add(num);//往数组列表(list)中添加元素
    }
 
    public Double GetMedian() {
        Collections.sort(list);//集合工具类(Collections)对数组列表排序
        int length = list.size();
        //求中位数
        if(length % 2 != 0){//从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值
            return (double)list.get(length/2);
        }else{//从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值
            return (double)(list.get(length/2-1) + list.get(length/2))/2;
        }
    }
 
 
}

哈希

BM50 两数之和

import java.util.*;
public class Solution {
    public int[] twoSum (int[] numbers, int target) {
        HashMap<Integer, Integer> map = new HashMap<>();
        //遍历数组
        for (int i = 0; i < numbers.length; i++) {
            //将不包含target - numbers[i],装入map中,包含的话直接返回下标
            if(map.containsKey(target - numbers[i]))
                return new int[]{map.get(target - numbers[i])+1, i+1};
            else
                map.put(numbers[i], i);
        }
        throw new IllegalArgumentException("No solution");
    }
}

BM53 缺失的第一个正整数

public class Solution {
     public int minNumberDisappeared (int[] nums) {
        // write code here
        HashMap<Integer,Integer> maps=new HashMap<>();
        for(int i=0;i<nums.length;i++){
            maps.put(nums[i], maps.getOrDefault(nums[i], 0)+1);
        }
        int index=1;
        int res=1;
        while(true){
            if(!maps.containsKey(index)){
                res=index;
                break;
            }else{
                index++;
            }
        }
        return res;
    }
}

BM54 三数之和

import java.util.*;
public class Solution {
  public ArrayList<ArrayList<Integer>> threeSum(int[] num) {
    //存放最终答案的二维数组
    ArrayList<ArrayList<Integer>> ans = new ArrayList<>();
    int len = num.length;
    //特判:长度<3的数组不满足条件
    if(len<3){
      return ans;
    }
    //排序O(nlogn)
    Arrays.sort(num);

    for(int i=0;i<len;i++){
      //如果nums[i]已经大于0,就没必要继续往后了,因为和就是0啊
      if(num[i]>0){
        return ans;
      }
      //注意考虑越界i>0,主要功能是排除重复值
      if(i>0 && num[i]==num[i-1]){
        continue;
      }
      //声明指针
      int cur = num[i];
      int left = i+1;
      //从尾部开始
      int right =len-1;
      while(left<right){
        //满足条件的三数和
        int tp_ans = cur+num[left]+num[right];
        //如果已经找到和为0
        if(tp_ans==0){
          //创建一个数组,并将满足条件的三元素放进去
          ArrayList<Integer> list = new ArrayList<>();
          list.add(cur);
          list.add(num[left]);
          list.add(num[right]);
          //将最终的结果存入答案数组ans中
          ans.add(list);
          //判断是left指针指向是否重复
          while(left<right && num[left]==num[left+1]){
            left++;
          }
          //判断是right指针指向是否重复
          while(left<right && num[right]==num[right-1]){
            right--;
          }
          //移动指针
          left++;
          right--;
        }else if(tp_ans<0){
          left++;
        }else{
          right--;
        } 
      }
    }
    return ans;
  }
}

递归/回溯

BM55 没有重复项数字的全排列

public ArrayList<ArrayList<Integer>> permute(int[] num) {
  // 存一种排列
  LinkedList<Integer> list = new LinkedList<>();
  // 递归进行
  backTrack(num,list);
  return res2;
}

public void backTrack(int[] num, LinkedList<Integer> list){
  // 当list中的长度等于数组的长度,则证明此时已经找到一种排列了
  if(list.size() == num.length){
    // add进返回结果集中
    res2.add(new ArrayList<>(list));
    return;
  }
  // 遍历num数组
  for(int i = 0; i < num.length; i++){
    // 若当前位置中的数已经添加过了则跳过
    if(list.contains(num[i]))
      continue;
    // 选择该数
    list.add(num[i]);
    // 继续寻找
    backTrack(num,list);
    // 撤销最后一个
    list.removeLast();
  }
}

BM56 有重复项数字的全排列

public class Solution {
    ArrayList<ArrayList<Integer>> result = new ArrayList<>();
    public ArrayList<ArrayList<Integer>> permuteUnique(int[] num) {
        //排序
        Arrays.sort(num);
        boolean[] mark = new boolean[num.length];
        LinkedList<Integer> track = new LinkedList<>();
        backTrack(num,mark,track);
        return result;
    }
 
    public void backTrack(int[] num, boolean[] mark, LinkedList<Integer> track) {
        if(track.size() == num.length){
            result.add(new ArrayList<Integer>(track));
            return;
        }
        for(int i=0;i<num.length;i++){
            //该数已经标记过,遍历下一个数
            if(mark[i]){
                continue;
            }
 
            //之前重复色数据没有被使用
            if(i>0 && num[i] == num[i-1] && !mark[i-1]){
                continue;
            }
 
            //符合条件的数据添加进来
            mark[i] = true;
            track.add(num[i]);
 
            //递归调用
            backTrack(num,mark,track);
            //回溯
            track.removeLast();
            mark[i] = false;
        }
 
    }
}

BM57 岛屿数量

dfs 深度优先

public void dfs(char[][] grid, int r, int c) {
        int nr = grid.length;
        int nc = grid[0].length;

        if (r < 0 || c < 0 || r >= nr || c >= nc || grid[r][c] == '0') {
            return;
        }

        grid[r][c] = '0';
        dfs(grid, r - 1, c);
        dfs(grid, r + 1, c);
        dfs(grid, r, c - 1);
        dfs(grid, r, c + 1);
    }

    public int solve(char[][] grid) {
        if (grid == null || grid.length == 0) {
            return 0;
        }

        int nr = grid.length;
        int nc = grid[0].length;
        int num_islands = 0;
        for (int r = 0; r < nr; ++r) {
            for (int c = 0; c < nc; ++c) {
                if (grid[r][c] == '1' ) {
                    num_islands++;
                    dfs(grid, r, c);
                }
            }
        }

        return num_islands;
    }

动态规划

跳台阶

class Solution {
public:
    int f[50]{0};
    int jumpFloor(int number) {
        if (number <= 1) return 1;
        if (f[number] > 0) return f[number];
        return f[number] = (jumpFloor(number-1)+jumpFloor(number-2));
    }
};

BM64 最小花费爬楼梯

import java.util.*; 
public class Solution {
    public int minCostClimbingStairs (int[] cost) {
        // write code here
        int n = cost.length;
        int[] dp = new int[n];
        dp[0] = cost[0];
        dp[1] = cost[1];
        for(int i = 2;i < n;i ++) {
            dp[i] = Math.min(dp[i-1],dp[i-2])+cost[i];
        }
        return Math.min(dp[n-1],dp[n-2]);
    }
}

BM66 最长公共子串

public String LCS(String str1, String str2) {
    int maxLenth = 0;//记录最长公共子串的长度
    //记录最长公共子串最后一个元素在字符串str1中的位置
    int maxLastIndex = 0;
    int[][] dp = new int[str1.length() + 1][str2.length() + 1];
    for (int i = 0; i < str1.length(); i++) {
        for (int j = 0; j < str2.length(); j++) {
            //递推公式,两个字符相等的情况
            if (str1.charAt(i) == str2.charAt(j)) {
                dp[i + 1][j + 1] = dp[i][j] + 1;
                //如果遇到了更长的子串,要更新,记录最长子串的长度,
                //以及最长子串最后一个元素的位置
                if (dp[i + 1][j + 1] > maxLenth) {
                    maxLenth = dp[i + 1][j+1];
                    maxLastIndex = i;
                }
            } else {
                //递推公式,两个字符不相等的情况
                dp[i + 1][j+1] = 0;
            }
        }
    }
    //最字符串进行截取,substring(a,b)中a和b分别表示截取的开始和结束位置
    return str1.substring(maxLastIndex - maxLenth + 1, maxLastIndex + 1);
}

BM69 把数字翻译成字符串

思路:可以分为两种情况,一种情况是第i位可以独立编码,另一种情况是第i位可以和第i-1位字符组合进行编码。

import java.util.*;

public class Solution {
    public int solve (String nums) {
        if(nums==null ||nums.length()==0) return 0;
        int[] dp = new int[nums.length()+1];
        dp[0]=1;
        dp[1]=nums.charAt(0)=='0'?0:1;
        for(int i=2;i<dp.length;i++){
            //无法独立编码也无法组合编码
            if(nums.charAt(i-1)=='0' && (nums.charAt(i-2)=='0' || nums.charAt(i-2)>'2')){
                return 0;
            //只能组合编码
            }else if(nums.charAt(i-1)=='0'){
                dp[i] = dp[i-2];
            //只能独立编码
            }else if(nums.charAt(i-2)=='0' || nums.charAt(i-2)>'2' || nums.charAt(i-2)=='2'&& nums.charAt(i-1)>'6' ){
                dp[i] = dp[i-1];
            //两种编码方式都可以
            }else{
                dp[i] = dp[i-1]+dp[i-2];
            }
        }
        return dp[nums.length()];
    }
}

BM80 买卖股票的最好时机(一)

import java.util.*;
public class Solution {
    public int maxProfit (int[] prices) {
        int len = prices.length;
        int minPrices = Integer.MAX_VALUE;
        int ans = 0;
        for(int i=0;i<len;i++){
            //寻找最低点
            if(prices[i]<minPrices){
                minPrices = prices[i];
            }else if(prices[i]-minPrices>ans){
                //更新答案(最大利润)
                ans = prices[i]-minPrices;
            }
        }
        return ans;
    }
}

字符串

字符串变形

public String trans(String s, int n) {
    
    String[] strArray = s.split(" ", -1);
    StringBuilder strbuild = new StringBuilder();

    for (int i = strArray.length - 1; i >= 0; i--) {
        strbuild.append(reverse(strArray[i])); //数组转换为字符串
        //最后一个字符串后面不再附加空格
        if(i==0) {
            break;
        }
        //字符串之间附加空格
        strbuild.append(" ");
    }
    return strbuild.toString();
}

//大小写转换
    private String reverse(String s){
        StringBuilder res= new StringBuilder();
        for(char ch:s.toCharArray()){
            if(Character.isLowerCase(ch)){
                res.append(Character.toUpperCase(ch));
                continue;
            }
            if(Character.isUpperCase(ch)){
                res.append(Character.toLowerCase(ch));
                continue;
            }
        }
        return res.toString();
    }

双指针

BM87 合并两个有序的数组

import java.util.*;
public class Solution {
    public void merge(int A[], int m, int B[], int n) {
        int p1 = 0, p2 = 0;
        //新开一个M+n大小的数组
        int[] sorted = new int[m + n];
        int cur;
        //循环选择
        while (p1 < m || p2 < n) {
            if (p1 == m) {
                cur = B[p2++];
            } else if (p2 == n) {
                cur = A[p1++];
            } else if (A[p1] < B[p2]) {
                cur = A[p1++];
            } else {
                cur = B[p2++];
            }
            sorted[p1 + p2 - 1] = cur;
        }
        //移动
        for (int i = 0; i != m + n; ++i) {
            A[i] = sorted[i];
        }
    }
}

BM88 判断是否为回文字符串

    import java.util.*;

    public class Solution {

        public boolean judge (String str) {
            // 判断特殊情况
            if (str == null || str.length() == 0) return false; 
            // 定义双指针,不相同则不是回文串
            for (int i = 0, j = str.length()-1; i < j; i++, j--)
                if (str.charAt(i) != str.charAt(j)) return false;
            return true;
        }
    }

BM92 最长无重复子数组

    public int maxLength(int[] arr) {
        int maxLen = 0;
        Set<Integer> set = new HashSet<>();
        int left = 0, right = 0;
        while (right < arr.length) {
            while (set.contains(arr[right]))
                set.remove(arr[left++]);
            set.add(arr[right++]);
            maxLen = Math.max(maxLen, right - left);
        }
        return maxLen;
    }

排序

冒泡排序

public static int[] bubbleSort(int[] array) {
        if (array.length == 0)
            return array;
        for (int i = 0; i < array.length; i++)
            for (int j = 0; j < array.length - 1 - i; j++)
                if (array[j + 1] < array[j]) {
                    int temp = array[j + 1];
                    array[j + 1] = array[j];
                    array[j] = temp;
                }
        return array;
    }

选择排序

public static int[] selectionSort(int[] array) {
        if (array.length == 0)
            return array;
        for (int i = 0; i < array.length; i++) {
            int minIndex = i;
            for (int j = i; j < array.length; j++) {
                if (array[j] < array[minIndex]) //找到最小的数
                    minIndex = j; //将最小数的索引保存
            }
            int temp = array[minIndex];
            array[minIndex] = array[i];
            array[i] = temp;
        }
        return array;
    }

插入排序

 public static int[] insertionSort(int[] array) {
        if (array.length == 0)
            return array;
        int current;
        for (int i = 0; i < array.length - 1; i++) {
            current = array[i + 1];
            int preIndex = i;
            while (preIndex >= 0 && current < array[preIndex]) {
                array[preIndex + 1] = array[preIndex];
                preIndex--;
            }
            array[preIndex + 1] = current;
        }
        return array;
    }

希尔排序

public static int[] ShellSort(int[] array) {
        int len = array.length;
        int temp, gap = len / 2;
        while (gap > 0) {
            for (int i = gap; i < len; i++) {
                temp = array[i];
                int preIndex = i - gap;
                while (preIndex >= 0 && array[preIndex] > temp) {
                    array[preIndex + gap] = array[preIndex];
                    preIndex -= gap;
                }
                array[preIndex + gap] = temp;
            }
            gap /= 2;
        }
        return array;
    }

快速排序

    public static int[] QuickSort(int[] array, int start, int end) {
        if (array.length < 1 || start < 0 || end >= array.length || start > end) return null;
        int smallIndex = partition(array, start, end);
        if (smallIndex > start)
            QuickSort(array, start, smallIndex - 1);
        if (smallIndex < end)
            QuickSort(array, smallIndex + 1, end);
        return array;
    }
    /**
     * 快速排序算法——partition
     * @param array
     * @param start
     * @param end
     * @return
     */
    public static int partition(int[] array, int start, int end) {
        int pivot = (int) (start + Math.random() * (end - start + 1));
        int smallIndex = start - 1;
        swap(array, pivot, end);
        for (int i = start; i <= end; i++)
            if (array[i] <= array[end]) {
                smallIndex++;
                if (i > smallIndex)
                    swap(array, i, smallIndex);
            }
        return smallIndex;
    }

    /**
     * 交换数组内两个元素
     * @param array
     * @param i
     * @param j
     */
    public static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
posted @ 2022-03-23 23:23  温故方能知新  阅读(60)  评论(0)    收藏  举报