• 算法


    链表

    反转链表

    双链表

    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;
        }
    
  • 相关阅读:
    Nginx配置虚拟主机
    TCP有限状态机
    一次完整的HTTP请求过程
    129 爬虫 requests request 爬图片
    算法: 二分查找 冒泡 插入 选择排序
    121 monogdb安装, 增删改查, mongodb中的update修改器 pymomgo
    119 websocket 群聊 单聊 websocket的握手 加密解密
    118 falsk智能机器人 语音合成 语音识别
    117 flask中的上下文实现原理 偏函数 线程 setattr
    python基础中的内置方法:
  • 原文地址:https://www.cnblogs.com/liyefei/p/16047559.html
Copyright © 2020-2023  润新知