Skip to content

Latest commit

 

History

History
2713 lines (2207 loc) · 77.9 KB

牛客top100刷题.md

File metadata and controls

2713 lines (2207 loc) · 77.9 KB

算法学习方法:

  1. 每道题控制一下时间,如果时间过了很久没思路,直接看题解,然后手敲,如果中途依然没思路,继续手敲;
  2. 分析执行结果,时间复杂度+空间复杂度,要求必须100%打败对手,写完之后看评论,学习更多的思路,做到一题多解;
  3. 代码中写清楚注释;
  4. 写过的代码一定要保存在github上面 【平常写的代码等文件一定要保存好】;
  5. 刷了大概几百到题;
  6. 大厂面试至少80%都是原题;
  7. 刷力扣+剑指offer;
  8. 多刷+狂刷

01 链表

BM1 反转链表

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
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 res=node;
        while(!stack.isEmpty()){
            node.next=stack.pop();
            node=node.next;
        }
        node.next=null;
        return res;
        

    }
}

BM2 链表内指定区间反转

import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 * }
 */

public class Solution {

    public ListNode reverseBetween (ListNode head, int m, int n) {
        ListNode virHead = new ListNode(-1);
        virHead.next=head;
//      一定要加这句!!
        ListNode pre = virHead;
        for (int i = 0; i < m - 1; i++) {
            pre = pre.next;
        }

        ListNode rightNode = pre;
        for (int i = 0; i < n - m + 1; i++) {
            rightNode = rightNode.next;
        }

        ListNode leftNode = pre.next;
        ListNode cur = rightNode.next;

        pre.next = null;
        rightNode.next = null;

        reverseLinekedList(leftNode);

        pre.next = rightNode;
        leftNode.next = cur;
        return virHead.next;
    }
    private void reverseLinekedList(ListNode head) {
        ListNode cur = head;
        ListNode pre = null;
        while (cur != null) {
            ListNode cur_next = cur.next;
            cur.next=pre;
            pre = cur;
            cur = cur_next;
        }
    }
}

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

import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 * }
 */

public class Solution {
    /**
     *
     * @param head ListNode类
     * @param k int整型
     * @return ListNode类
     */
    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++) {
            if (tail == null)
//     如果数量不够k个,那么直接返回子链表的头指针即可,自动返回整个链表
                return head;
            tail = tail.next;
        }
        ListNode newHead = reverse(head, tail);
        head.next = reverseKGroup(tail, k);
        return newHead;
    }

    private ListNode reverse(ListNode head, ListNode tail) {
        ListNode next = null;
        ListNode pre = null;
        while (head != tail) {
            next = head.next;
            head.next = pre;
            pre = head;
            head = next;
        }
        return pre;
    }

}

BM4 合并两个排序的链表

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode Merge(ListNode list1, ListNode list2) {
//         开始节点
        ListNode resList = new ListNode(0);
//         将首指针赋给node链表
        ListNode res=resList;
        while (list1 != null && list2 != null) {
            if (list1.val <= list2.val) {
                resList.next = list1;
                list1 = list1.next;
            } else {
                resList.next = list2;
                list2 = list2.next;
            }
            resList = resList.next;
        }
        while (list1 != null) {
            resList.next = list1;
            list1 = list1.next;
            resList = resList.next;
        }
        while (list2 != null) {
            resList.next = list2;
            list2 = list2.next;
            resList = resList.next;
        }
        return res.next;
//         return resList;
    }
}

BM5 合并k个已排序的链表

// 分治思想
import java.util.*;
public class Solution {
    public ListNode mergeKLists(ArrayList<ListNode> lists) {
        return mergeList(lists,0,lists.size()-1);
    }
//     合并列表
    public ListNode mergeList(ArrayList<ListNode> lists, int left, int right) {
//         如果只有一个链表,则从数组中取出该数组!!
        if(left==right) return lists.get(left);
        if(left>right) return null;
        int mid=left+(right-left)/2;
        return merge(mergeList(lists,left,mid),mergeList(lists,mid+1,right));

    }
//     就是简单的两个链表合并
    public ListNode merge(ListNode list1, ListNode list2) {
        //         开始节点
        ListNode resList = new ListNode(1);
//         将首指针赋给node链表
        ListNode res = resList;
        while (list1 != null && list2 != null) {
            if (list1.val <= list2.val) {
                resList.next = list1;
                list1 = list1.next;
            } else {
                resList.next = list2;
                list2 = list2.next;
            }
            resList = resList.next;
        }
        while (list1 != null) {
            resList.next = list1;
            list1 = list1.next;
            resList = resList.next;
        }
        while (list2 != null) {
            resList.next = list2;
            list2 = list2.next;
            resList = resList.next;
        }
        return res.next;


    }
}

==BM6 判断链表中是否有环==

import java.util.HashSet;
public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode pos = head;
        HashSet<ListNode> visitedNode = new HashSet<>();
        while (pos != null) {
            if (visitedNode.contains(pos)) {
                return true;
            }
            visitedNode.add(pos);
            pos = pos.next;
        }
        return false;

    }
}

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

// 1 栈 时间复杂度 O(n),空间复杂度 O(1)。
import java.util.*;

public class Solution {
  
    public ListNode FindKthToTail (ListNode pHead, int k) {
        if(pHead==null || k==0 )
            return null;
        Stack<ListNode> stack = new Stack<>();
        ListNode node = new ListNode(0);
        while (pHead != null) {
            stack.push(pHead);
            pHead = pHead.next;
        }
        if (stack.size() < k)
            return null;
        for (int i = 0; i < k; i++) {
            node = stack.pop();
        }

        return node;
    }
}
// 2 快慢指针  时间复杂度 O(n),空间复杂度 O(n)。

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

import java.util.*;
public class Solution {
    /**
     * @param head ListNode类
     * @param n int整型
     * @return ListNode类
     */
    public ListNode removeNthFromEnd (ListNode head, int n) {
// head是空的头指针,不带数据
        ListNode pos = head;
//         此处必须将head赋值给q
        ListNode q = head;
        int len = 0;
        while (head != null) {
            len++;
            head = head.next;
        }
        if (n == len) {
            return q.next;
        }
//         for (int i = 0; i < len; i++) {
//             i++;
//             if (i == len - n) {
//                 pos.next = pos.next.next;
//             }
//             pos = pos.next;
//         }

        int i = 0;
        while (pos != null) {
            i++;
            if (i == len - n) {
                pos.next = pos.next.next;
            }
            pos = pos.next;
        }
        return q;
    }
}

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


public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode p1=pHead1;
        ListNode p2=pHead2;
        
        while(p1!=p2){
            p1=(p1!=null)?p1.next:pHead2;
            p2=(p2!=null)?p2.next:pHead1;
        }
        return p1;
    }
}

BM11 链表相加(二)

import java.util.*;

public class Solution {
    /**
     *
     * @param head1 ListNode类
     * @param head2 ListNode类
     * @return ListNode类
     */
    public ListNode addInList (ListNode head1, ListNode head2) {
        if(head1==null)
            return head2;
        if(head2==null)
            return head1;
        head1 = reverse(head1);
        head2 = reverse(head2);

        ListNode head = new ListNode(-1);
        ListNode newHead = head;
//         注意此处进位是全局变量!!

        int tmp = 0;
        while (head1 != null || head2 != null) {
            int val = tmp;
            if (head1 != null) {
                val += head1.val;
                head1 = head1.next;
            }

            if (head2 != null) {
                val += head2.val;
                head2 = head2.next;
            }
            tmp = val / 10;
            newHead.next = new ListNode(val % 10);
            newHead = newHead.next;
        }
//         注意此处最后进位的处理
        if (tmp > 0) {
            newHead.next = new ListNode(tmp);
        }
        return reverse(head.next);

    }

    ListNode reverse(ListNode head) {
        ListNode cur = head;
        ListNode  pre = null;
        while (cur != null) {
            ListNode tail = cur.next;
            cur.next = pre;
            pre = cur;
            cur = tail;
        }
//         注意此处返回值
        return pre;
    }
}

BM12 单链表的排序

import java.util.*;

public class Solution {

    public ListNode sortInList (ListNode head) {
        ArrayList tmpArr = new ArrayList();
        while (head != null) {
            tmpArr.add(head.val);
            head = head.next;
        }
        Collections.sort(tmpArr);
        ListNode tmp = new ListNode(-1);
        ListNode res = tmp;
// foreach循环
        for (Object i : tmpArr) {
            tmp.next = new ListNode(Integer.parseInt(String.valueOf(i)));
            tmp = tmp.next;
        }
        return res.next;
    }
}

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

import java.util.*;

public class Solution {

    public boolean isPail (ListNode head) {
//         复制原链表
        ListNode tmp_head = head;
        ListNode oldHeadList = new ListNode(-1);
        ListNode oldHead= oldHeadList;
        while (tmp_head != null) {
            oldHead.next=new ListNode(tmp_head.val);
//             这样改变指针还是引用,并没有进行新的创建
//             oldHead.next=tmp_head;
            tmp_head=tmp_head.next;
            oldHead=oldHead.next;
        }

        ListNode res = reverseList(head);
        while (oldHeadList.next != null && res != null) {
            if (oldHeadList.next.val != res.val)
                return false;
            oldHeadList = oldHeadList.next;
            res = res.next;
        }
        return true;
    }

    public ListNode reverseList(ListNode head) {
        ListNode pre = null, cur = head, next = null;
        while (cur != null ) {
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
}

BM14 链表的奇偶重排

import java.util.*;

public class Solution {
    public ListNode oddEvenList (ListNode head) {
        if (head == null) return null;
        ListNode Node1 = head;
        ListNode node1 = head;
        ListNode Node2 = head.next;
        ListNode node2 = head.next;
        while (node1.next != null && node2.next != null) {
            node1.next = node2.next;
            node1=node1.next;
            node2.next = node1.next;
            node2=node2.next;
        }
        node1.next = Node2;
        return Node1;
    }
}

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

import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 * }
 */

public class Solution {
    /**
     *
     * @param head ListNode类
     * @return ListNode类
     */
    public ListNode deleteDuplicates (ListNode head) {
        // write code here
        if (head == null || head.next == null)
            return head;
        ListNode cur = head;
//         操作时对cur进行操作
        while (cur.next != null) {
            if (cur.val == cur.next.val) {
                cur.next = cur.next.next;
            } else
                cur = cur.next;
        }
        return head;
    }
}

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

思想:

  1. 要求:空间复杂度 O(n)O(n),时间复杂度 O(n)O(n)

    增加辅助空间set,然后遍历原链表,构建新的链表即可

  2. 进阶:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)

import java.util.*;
public class Solution {

    public ListNode deleteDuplicates (ListNode head) {
        // write code here
        if (head == null)
            return head;
        Set set = new HashSet();
        ListNode cur = new ListNode(-1);
        cur.next = head;
        ListNode p = cur;
        ListNode tmp = head;
        while (tmp.next != null) {
            if (tmp.val == tmp.next.val) {
                set.add(tmp.val);
            } else
                tmp = tmp.next;
        }
        p = cur;
        while (p.next != null) {
            if (set.contains(p.next.val)) {
                p.next = p.next.next;
            } else
                p = p.next;
        }
        return cur.next;
    }

}

==BM17 二分查找-I==

==O(logn)== 【重要!!】

import java.util.*;

public class Solution {
   
    public int search (int[] nums, int target) {
        // write code here
        int low=0,high=nums.length-1;
        for(int i=0;i<nums.length;i++){
            int mid=(low+high)/2;
            if(target<nums[mid]){
               high=mid-1;
            }
            else if(target>nums[mid]){
                low=mid+1;
            }else{
                return mid;
            }
        }
        return -1;
    }
}

==BM18 二维数组中的查找==

public class Solution {
    public boolean Find(int target, int [][] array) {
        int m=array.length;
        if(m==0)
            return false;
        int n=array[0].length;
        if(n==0)
            return false;
        int row=0,columm=n-1;
        while(row<m && columm>=0){
            if(target==array[row][columm])
                return true;
            else if(target>array[row][columm]){
                row++;
            }
            else
                columm--;
        }
        return false;

    }
}

BM19 寻找峰值

复杂度:o(n)

import java.util.*;
public class Solution {
    public int findPeakElement (int[] nums) {
        // write code here
        if (nums.length == 2 && nums[0] > nums[1]) {
            return 0;
        } else if (nums.length == 2 && nums[0] < nums[1]) {
            return 1;
        }
        for (int i = 1; i < nums.length - 1; i++) {
            if (nums[i] > nums[i - 1] && nums[i] > nums[i + 1])
                return i;
            else if (nums[nums.length - 1] > nums[nums.length - 2])
                return nums.length - 1;
        }
        return 0;
    }
}

O(logN)

import java.util.*;
public class Solution {
    public int findPeakElement (int[] nums) {
        int n = nums.length;
        if (n == 1)
            return 0;
        if (n == 2 ) {
            int peek = nums[0] > nums[1] ? 0 : 1;
            return peek;
        }
        int low = 0, high = n - 1, mid = 0;
        while (low < high) {
            mid = low + (high - low) / 2;
            // 峰值在右侧
            if (nums[mid] < nums[mid + 1]) {
                low = mid + 1;
            }
            //峰值在左侧
            else if (nums[mid] > nums[mid + 1]) {
                high = mid;
            }
        }
        return low;
    }
}

==BM20 数组中的逆序对 【难!!】==

【存在一定的问题!】

归并算法 O(nlogn)

「归并排序」与「逆序对」是息息相关的。归并排序体现了 “分而治之” 的算法思想,具体为:

分: 不断将数组从中点位置划分开(即二分法),将整个数组的排序问题转化为子数组的排序问题; 治: 划分到子数组长度为 1 时,开始向上合并,不断将 较短排序数组 合并为 较长排序数组,直至合并至原数组时完成排序;

image-20210623223031128

public class Solution {
//     这个思想一定要结合左侧的图示
    int count = 0;
    public int InversePairs(int [] array) {
        if (array.length < 2)
            return 0;
        mergeSort(array, 0, array.length - 1);
        return count;
    }

    public void mergeSort(int [] array, int left, int right) {
        int mid = left + (right - left) / 2;
        if (left < right) {
// 先分
            mergeSort(array, left, mid);
            mergeSort(array, mid + 1, right);
// 后合
            merge(array, left, mid, right);
        }
    }

    public void merge(int [] array, int left, int mid, int right) {

        int[] arr = new int[right - left + 1];
        int l = left;
        int r = mid + 1;
        int c = 0;
		//注意此处s的取值
        int s = left;
        while (l <= mid && r <= right) {
            if (array[l] < array[r]) {
                arr[c++] = array[l++];
                //没有重复数字所以可以这样!!
            } else {
                arr[c++] = array[r++];
				//这个条件不会写?
                //左右两侧都是升序,所以mid右侧是右侧序列最小值,因此可以这样计算
                count += mid + 1 - l;
                count %= 1000000007;
            }
        }
        while (l <= mid)
            arr[c++] = array[l++];
        while (r <= right)
            arr[c++] = array[r++];

        for (int num : arr) {
            array[s++] = num;
        }
    }
}

==BM21 旋转数组的最小数字==

import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if (array.length == 0) return 0;
        int low = 0, high = array.length - 1;
        
        while (low < high) {
//             终止条件
            if (array[low] < array[high]) {
                return array[low];
            }
            int mid = low + (high - low) / 2;
            if (array[mid] > array[high])
                low = mid + 1;
            else if (array[mid] < array[high]) {
                high = mid ;
            } else {
                high--;
            }
        }
        return array[low];

    }
}

==BM22 比较版本号==

import java.util.*;
public class Solution {
    public int compare (String version1, String version2) {
//         注意转义处理
        String[] str1 = version1.split("\\.");
        String[] str2 = version2.split("\\.");
        int i;
        int maxLen = (str1.length > str1.length) ? str1.length : str2.length;

        for ( i = 0; i < str1.length || i < str1.length; i++) {
            int x = 0, y = 0;
            if (i < str1.length) {
                x = Integer.parseInt(str1[i]);
            }
            if (i < str2.length) {
                y = Integer.parseInt(str2[i]);
            }
            if (x > y) {
                return 1;
            }
            if (x < y) {
                return -1;
            }
        }
        return 0;
    }
}

BM23 二叉树的前序遍历

定义 preorder(root) 表示当前遍历到 ==root 节点==的答案。按照定义,我们只要首先将 root 节点的值加入答案,然后递归调用 ==preorder(root.left) 来遍历 root 节点的左子树,最后递归调用 preorder(root.right) 来遍历 root 节点的右子树即可==,递归终止的条件为==碰到空节点==。

import java.util.*;

public class Solution {

    public int[] preorderTraversal (TreeNode root) {

        ArrayList<Integer> list = new ArrayList<>();
        preorder ( root, list);
        int[] res = new int[list.size()];
        for (int i = 0; i < list.size(); i++) {
            res[i] = list.get(i);
        }
        return res;

    }

    public void preorder (TreeNode root, ArrayList list) {
        if (root != null) {
            list.add(root.val);
            preorder(root.left, list);
            preorder(root.right, list);
        }
    }
}

BM24 二叉树的中序遍历

import java.util.*;

/*
 * public class TreeNode {
 *   int val = 0;
 *   TreeNode left = null;
 *   TreeNode right = null;
 *   public TreeNode(int val) {
 *     this.val = val;
 *   }
 * }
 */

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param root TreeNode类
     * @return int整型一维数组
     */
    public int[] inorderTraversal (TreeNode root) {
        // write code here
        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 list, TreeNode root) {
        if (root != null) {
            dfs(list, root.left);
            list.add(root.val);
            dfs(list, root.right);
        }
    }
}

02 二叉树

==BM26 求二叉树的层序遍历==

import java.util.*;
public class Solution {
    public ArrayList<ArrayList<Integer>> levelOrder (TreeNode root) {
        ArrayList<ArrayList<Integer>> res = new ArrayList<>();
        if (root == null)
            return res;
        //队列作用:遍历所有元素
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            ArrayList<Integer> list = new ArrayList<>();
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                list.add(node.val);
                if (node.left != null)
                    queue.offer(node.left);
                if (node.right != null)
                    queue.offer(node.right);
            }
            res.add(list);
        }
        return res;
    }
}

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

方法一:层序遍历 + 双端队列(奇偶层逻辑分离) 方法一代码简短、容易实现;但需要判断每个节点的所在层奇偶性,即冗余了 NN 次判断。 通过将奇偶层逻辑拆分,可以消除冗余的判断。 算法流程:

BFS 循环: 循环打印奇 / 偶数层,当 deque 为空时跳出; 奇数层: 队首出节点,队尾先加左节点后加右节点 偶数层: 队尾出节点,队首先加右节点后加左节点

import java.util.*;
public class Solution {
    public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> res = new ArrayList<>();
        if(pRoot==null)
            return res;
        Deque<TreeNode> queue = new LinkedList<>();
        queue.add(pRoot);
        int level = 1;
        while (!queue.isEmpty()) {
            ArrayList<Integer> list = new ArrayList<>();
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                if (level % 2 != 0) {
                    TreeNode node = queue.pollFirst();
                    list.add(node.val);
                    if (node.left != null)
                        queue.addLast(node.left);
                    if (node.right != null)
                        queue.addLast(node.right);
                } else {
                    TreeNode node = queue.pollLast();
                    list.add(node.val);
                    if (node.right != null)
                        queue.addFirst(node.right);
                    if (node.left != null)
                        queue.addFirst(node.left);
                }
            }
            level++;
            res.add(list);
        }
        return res;
    }
}

方法二:层序遍历 + 双端队列 利用双端队列的两端皆可添加元素的特性,设打印列表(双端队列) tmp ,并规定: 奇数层 则添加至 tmp 尾部 , 偶数层 则添加至 tmp 头部 。

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();
        List<List<Integer>> res = new ArrayList<>();
        if(root != null) queue.add(root);
        while(!queue.isEmpty()) {
            LinkedList<Integer> tmp = new LinkedList<>();
            for(int i = queue.size(); i > 0; i--) {
                TreeNode node = queue.poll();
                // 偶数层 -> 队列头部
                if(res.size() % 2 == 0) tmp.addLast(node.val); 
                // 奇数层 -> 队列尾部
                else tmp.addFirst(node.val); 
                if(node.left != null) queue.add(node.left);
                if(node.right != null) queue.add(node.right);
            }
            res.add(tmp);
        }
        return res;
    }
}

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

要求:空间复杂度 O(n),时间复杂度 O(n)

进阶:空间复杂度 O(树的高度)O(树的高度),时间复杂度 O(n)

import java.util.*;
public class Solution {
    public boolean hasPathSum (TreeNode root, int sum) {
        if (root == null)
            return false;
        return hasPath(root, sum,0);
    }
    
    public boolean hasPath(TreeNode root, int sum,int res){
        if (root == null)
            return false;
//         要对累加值进行处理,否则递归错误
        res += root.val;
        if (root.left == null && root.right == null && res == sum)
            return true;
        return hasPath(root.left,sum,res) || hasPath(root.right,sum,res);
    }
}

==BM30 **二叉搜索树与双向链表 【难!!】 **==

==【看到二叉搜索树一定要想到中序遍历!!!】==

要求:空间复杂度O(1)(即在原树上操作),时间复杂度 O(n)

因此二叉搜索树的==中序遍历==就是一个递增序列,我们只要对它中序遍历就可以组装称为递增双向链表。

1、特殊情况,二叉树为空,则直接返回 null

2、创建 保留上一个结点 pre,返回链表结点 root

3、递归遍历左子树;root = pRootOfTree

4、遍历当前结点,并修改为双向链表 pRootOfTree.left=pre; pre.right=pRootOfTree;

5、更新 pre = pRootOfTree

6、递归遍历右子树

7、递归结束返回 root

public class Solution {
    TreeNode cur = null, head = null;
    public TreeNode Convert(TreeNode pRootOfTree) {
        if (pRootOfTree == null)
            return pRootOfTree;
        Convert( pRootOfTree.left);

        if (cur == null) {
            cur = pRootOfTree;
            head = pRootOfTree;
        } else {
            cur.right=pRootOfTree;
            pRootOfTree.left=cur;
            cur=pRootOfTree;
        }

        Convert( pRootOfTree.right);
        
        return head;
    }
}

==BM31 对称的二叉树==

可以用递归和迭代两种方法解决这个问题

解题思路: 对称二叉树定义: 对于树中 任意两个对称节点 LL 和 RR ,一定有: L.val=R.val :即此两对称节点值相等。 L.left.val=R.right.val :即 LL 的 左子节点 和 RR 的 右子节点 对称; L.right.val = R.left.val :即 LL 的 右子节点 和 RR 的 左子节点 对称。

根据以上规律,考虑从顶至底递归,判断每对节点是否对称,从而判断树是否为对称二叉树。

算法流程: isSymmetric(root) :

特例处理: 若根节点 root 为空,则直接返回 truetrue 。 返回值: 即 recur(root.left, root.right) ; recur(L, R) :

终止条件: 当 LL 和 RR 同时越过叶节点: 此树从顶至底的节点都对称,因此返回 true ; 当 LL 或 RR 中只有一个越过叶节点: 此树不对称,因此返回 false ; 当节点 LL 值 不等于节点 RR 值: 此树不对称,因此返回 false ; 递推工作: 判断两节点 L.left和 R.right是否对称,即 recur(L.left, R.right) ; 判断两节点 L.right 和 R.left是否对称,即 recur(L.right, R.left) ; 返回值: 两对节点都对称时,才是对称树,因此用与逻辑符 && 连接。

Picture1.png

public class Solution {
    boolean isSymmetrical(TreeNode pRoot) {
        return pRoot==null?true:recur(pRoot.left,pRoot.right);
    }
    
    boolean recur(TreeNode L,TreeNode R){
//         终止条件
        if( L==null && R==null) return true;
//         注意或的条件顺序!!
        if(L==null || R==null || L.val!=R.val) return false;
//         递推公式
        return recur( L.left, R.right) && recur( L.right, R.left);
    }
}

==BM32 合并二叉树==

进阶:空间复杂度 O(1),时间复杂度 O(n)

递归

时间复杂度:O(N) 空间复杂度:O(N)

import java.util.*;
public class Solution {
    public TreeNode mergeTrees (TreeNode t1, TreeNode t2) {
        //终止条件
        if (t1 == null) return t2;
        if (t2 == null) return t1;
        //递推工作
            //合并根节点
        t1.val = t1.val + t2.val;
            //合并左子树
        t1.left = mergeTrees ( t1.left,  t2.left);
            //合并右子树
        t1.right = mergeTrees ( t1.right,  t2.right);
        
        return t1;
    }
}

==BM33 二叉树的镜像==

要求: 空间复杂度 O(n) 。

本题也有原地操作,即空间复杂度 O(1)的解法,时间复杂度 O(n)

二叉树镜像定义: 对于二叉树中任意节点 root ,设其左 / 右子节点分别为 left, rightleft,right ;则在二叉树的镜像中的对应 rootroot 节点,其左 / 右子节点分别为 right, leftright,left 。

Picture1.png

方法一:递归法 根据二叉树镜像的定义,考虑递归遍历(dfs)二叉树,交换每个节点的左 / 右子节点,即可生成二叉树的镜像。 递归解析 终止条件

当节点 root 为空时(即越过叶节点),则返回 null ; 递推工作: 初始化节点 tmp ,用于暂存 root 的左子节点; 开启递归 右子节点 mirrorTree(root.right),并将返回值作为 root的 左子节点 。 开启递归 左子节点 mirrorTree(tmp) ,并将返回值作为 root 的 右子节点 。 返回值:

返回当前节点 root ;

import java.util.*;
public class Solution {
    public TreeNode Mirror (TreeNode pRoot) {
//     终止条件
        if (pRoot == null) return null;
//     递推工作
        TreeNode tmp = pRoot.left;
        pRoot.left = pRoot.right;
        pRoot.right = tmp;

        pRoot.left = Mirror (pRoot.left);
        pRoot.right = Mirror (pRoot.right);
        return pRoot;
    }
}

是否要增加新的recur函数取决于,原函数的参数是否够用,如果不够用,则要新开辟函数!!

==BM34 判断是不是二叉搜索树==

方法一:中序遍历

import java.util.*;
public class Solution {
    /**中序遍历得到list,然后遍历list比较前后的值*/
    public boolean isValidBST (TreeNode root) {
        List<Integer> list = new ArrayList<>();
        inOrder(list, root);

        int[] res = new int[list.size()];
        for (int i = 0; i < list.size() - 1; i++) {
            if (list.get(i) >= list.get(i + 1))
                return false;
        }
        return true;
    }

    public void inOrder(List list, TreeNode root) {
        if (root != null) {
            dfs(list, root.left);
            list.add(root.val);
            dfs(list, root.right);
        }
    }
}

方法二:递归

import java.util.*;

public class Solution {
    public boolean isValidBST (TreeNode root) {
        return recur(root, Long.MIN_VALUE, Long.MAX_VALUE);
    }
    public boolean recur (TreeNode root, long min, long max) {
		// 终止条件
        if (root == null) return true;
        if (root.val <= min || root.val >= max) return false;
        
        return recur (root.left, min, root.val) && recur (root.right, root.val, max);
    }
}

==BM35 判断是不是完全二叉树==

思想:层序遍历

import java.util.*;
public class Solution {
    public boolean isCompleteTree (TreeNode root) {
        if (root == null) return false;

        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        boolean flag = false;

        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            if (node == null)
                flag = true;
            else {
                if (!queue.isEmpty() && flag) {
                    return false;
                }
                queue.offer(node.left);
                queue.offer(node.right);
            }
           
        }
        return true;
    }
}

==BM36 判断是不是平衡二叉树 【难!】==

import java.util.*;
public class Solution {
    public boolean IsBalanced_Solution(TreeNode root) {
        return depth(root) != -1;
    }

// 求二叉树的最大高度:自底向上
    public int depth(TreeNode root) {
        if (root == null)
            return 0;
        int ldep = depth(root.left);
        if (ldep == -1) return -1;
        int rdep = depth(root.right);
        if (rdep == -1) return -1;
        int sub = Math.abs(ldep - rdep);
        if (sub > 1) return -1;
        
        return Math.max(ldep, rdep) + 1;
    }
}

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

方法二:一次遍历 思路与算法

在方法一中,我们对从根节点开始,通过遍历找出到达节点 pp 和 qq 的路径,一共需要两次遍历。我们也可以考虑将这两个节点放在一起遍历。

整体的遍历过程与方法一中的类似:

我们从根节点开始遍历;

如果当前节点的值大于 pp 和 qq 的值,说明 pp 和 qq 应该在当前节点的左子树,因此将当前节点移动到它的左子节点;

如果当前节点的值小于 pp 和 qq 的值,说明 pp 和 qq 应该在当前节点的右子树,因此将当前节点移动到它的右子节点;

如果当前节点的值不满足上述两条要求,那么说明当前节点就是「分岔点」。此时,pp 和 qq 要么在当前节点的不同的子树中,要么其中一个就是当前节点。

可以发现,如果我们将这两个节点放在一起遍历,我们就省去了存储路径需要的空间。

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
import java.util.*;
public class Solution {
   
    public int lowestCommonAncestor (TreeNode root, int p, int q) {
        TreeNode ancestor = root;
        while (true) {
            if (ancestor.val > p && ancestor.val > q)
                ancestor = ancestor.left;
            else if (ancestor.val < p && ancestor.val < q)
                ancestor = ancestor.right;
            else
                return ancestor.val;
        }
    }
}

==BM38 在二叉树中找到两个节点的最近公共祖先==

根据以上定义,若 rootroot 是 p, qp,q 的 最近公共祖先 ,则只可能为以下情况之一:

pp 和 qq 在 rootroot 的子树中,且分列 rootroot 的 异侧(即分别在左、右子树中); p = root,且 qq 在 rootroot 的左或右子树中; q = root,且 pp 在 rootroot 的左或右子树中;

考虑通过递归对二叉树进行先序遍历,当遇到节点 pp 或 qq 时返回。从底至顶回溯,当节点 p, qp,q 在节点 rootroot 的异侧时,节点 rootroot 即为最近公共祖先,则向上返回 rootroot 。

递归解析: 终止条件: 当越过叶节点,则直接返回 null ; 当 rootroot 等于 p, q,则直接返回 root ; 递推工作: 3.若不为1, 2中情况,说明需要继续处理: 对左子树进行递归,返回值记为 t1 对右子树进行递归,返回值记位 t2 t1 ,t2 存在以下几种情况:

【处理技巧!!】

【123按照异常情况处理】

①. 当t1, t2都为空时,说明root的左右子树中都不存在o1, o2, 返回空 ②. 当t1为空且t2不为空时,说明左子树找不到 o1, o2,所以返回 t2 ③. 当t2为空且t1不为空时,说明右子树找不到 o1, o2,所以返回 t1

【4按正常情况处理】

④. 当t1, t2都不为空时,说明o1, o2分别位于root的左右子树中,既root为答案,返回root

import java.util.*;

public class Solution {

    public int lowestCommonAncestor (TreeNode root, int o1, int o2) {
        if (root == null)
            return -1;
        if (root.val == o1 || root.val == o2)
            return root.val;
        //         1结果在左子树中
        int left = lowestCommonAncestor(root.left, o1, o2);
        //         2结果在右子树中
        int right = lowestCommonAncestor(root.right, o1, o2);
        if (left == -1)
            return right;
        if (right == -1)
            return left;
		//         3根节点是祖先
        return root.val;
    }
}

==BM39 序列化二叉树 【难!!】==

要求:序列化和反序列化都是空间复杂度 O(n)O(n),时间复杂度 O(n)O(n)

通常使用的前序、中序、后序、层序遍历记录的二叉树的信息不完整,即唯一的输出序列可能对应着多种二叉树可能性。题目要求的 序列化 和 反序列化 是 可逆操作 。因此,序列化的字符串应携带 完整的二叉树信息 。

序列化 使用==层序遍历==实现。反序列化 通过以上递推公式反推各节点在序列中的索引,进而实现。

反序列化 Deserialize : 基于本文开始推出的 node , node.left , node.right 在序列化列表中的位置关系,可实现反序列化。

利用==队列按层构建二叉树==,借助一个指针 i 指向节点 node 的左、右子节点,每==构建一个 node 的左、右子节点==,指针 i 就向右移动 11 位。

import java.util.*;
public class Solution {
    String Serialize(TreeNode root) {
        if (root == null) return "";
        Queue<TreeNode> queue = new LinkedList<>();
        StringBuilder res = new StringBuilder();
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
//             注意这块!!
            if (node != null) {
                res.append(node.val + ",");
                queue.offer(node.left);
                queue.offer(node.right);
            } else {
                res.append("null,");
            }
        }
        return res.toString();
    }

    TreeNode Deserialize(String str) {
        if (str.equals("")) return null;
        Queue<TreeNode> queue = new LinkedList<>();
        String[] res = str.split(",");
        TreeNode root = new TreeNode(Integer.parseInt(res[0]));
        queue.offer(root);
        int i = 1;
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
//             注意这块!!
            if (!res[i].equals("null")) {
                node.left = new TreeNode(Integer.parseInt(res[i]));
                queue.offer(node.left);
            }
            if (!res[i + 1].equals("null")) {
                node.right = new TreeNode(Integer.parseInt(res[i + 1]));
                queue.offer(node.right);
            }
            i = i + 2;
        }
        return root;
    }
}

==BM40 重建二叉树 【难!!】==

分治算法解析: 递推参数:

==根节点在前序遍历的索引 root 、子树在中序遍历的左边界 left 、子树在中序遍历的右边界 right ;==

终止条件: 当 left > right ,代表已经越过叶节点,此时返回 nullnull ;

递推工作:

  • 建立根节点 node : 节点值为 preorder[root] ;

  • 划分左右子树: 查找根节点在中序遍历 inorder 中的索引 i ;

  • 构建左右子树: 开启左右子树递归;

    根节点索引 中序遍历左边界 中序遍历右边界 左子树 root + 1 left i - 1 右子树 i - left + root + 1 i + 1 right TIPS: i - left + root + 1含义为 根节点索引 + 左子树长度 + 1

返回值: 回溯返回 node ,作为上一层递归中根节点的左 / 右子节点;

import java.util.*;
// 思想:根据前序找到根节点,然后在中序中划分出左右子树
public class Solution {
    HashMap<Integer, Integer> dic = new HashMap<>();
    int[] pre1;
    public TreeNode reConstructBinaryTree(int [] pre, int [] vin) {
        pre1 = pre;
        for (int i = 0; i < vin.length; i++) {
            dic.put(vin[i], i);
        }
        return recur(0, 0, vin.length - 1);
    }
    TreeNode recur(int root, int left, int right) {
        if (left > right)
            return null;
//         根节点
        TreeNode node = new TreeNode(pre1[root]);
//         划分左右子树
        int pos = dic.get(pre1[root]);
        node.left = recur(root+1,left,pos-1);
//         右子树的根节点=root+左子树的长度
        node.right = recur(root+pos-left+1,pos+1,right);
        
        return node;
    }
}

==BM41 输出二叉树的右视图==

import java.util.*;

public class Solution {
    HashMap<Integer, Integer> dic = new HashMap<>();
    int[] pre1;
    public int[] solve (int[] xianxu, int[] zhongxu) {
        pre1 = xianxu;
        for (int i = 0; i < zhongxu.length; i++) {
            dic.put(zhongxu[i], i);
        }
        TreeNode root=recur(0, 0, zhongxu.length - 1);
        
        int[] tmp = new int[xianxu.length];
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        int j=0;
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                if (i == size - 1) {
                    tmp[j++] = node.val;
                }
                if (node.left != null)
                    queue.offer(node.left);
                if (node.right != null)
                    queue.offer(node.right);
            }
        }
        int[] res = new int[j];
        for(int p=0;p<j;p++){
            res[p]=tmp[p];
        }
        return res;
    }
    TreeNode recur(int root, int left, int right) {
        if (left > right)
            return null;
//         根节点
        TreeNode node = new TreeNode(pre1[root]);
//         划分左右子树
        int pos = dic.get(pre1[root]);
        node.left = recur(root + 1, left, pos - 1);
//         右子树的根节点=root+左子树的长度
        node.right = recur(root + pos - left + 1, pos + 1, right);
        return node;
    }

}

04 堆/栈/队列

BM42 用两个栈实现队列

import java.util.Stack;
// 当插入时,直接插入 stack1
// 当弹出时,当 stack2 不为空,弹出 stack2 栈顶元素,
// 如果 stack2 为空,将 stack1 中的全部数逐个出栈入栈 stack2,
// 再弹出 stack2 栈顶元素
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.isEmpty()) {
            while (!stack1.isEmpty()) {
                stack2.push(stack1.pop());
            }
        }
         return stack2.pop();
    }
}

BM43 包含min函数的栈

import java.util.Stack;

public class Solution {

    Stack<Integer> stack = new Stack<Integer>();
    Stack<Integer> minStack = new Stack<Integer>();
    public void push(int node) {
        stack.push(node);
        if (minStack.empty())
            minStack.push(node);
        else {
            if (node < minStack.peek() ) {
                minStack.push(node);
            } else {
                minStack.push(minStack.peek());
            }
        }
    }

    public void pop() {
        stack.pop();
        minStack.pop();
    }

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

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

BM44 有效括号序列

import java.util.*;

public class Solution {
    public boolean isValid (String s) {

        Stack<Character> stack = new Stack<>();
        for (int i = 0; i < s.length(); i++) {
            switch (s.charAt(i)) {
                case '[':
                case '{':
                case '(':
                    stack.push(s.charAt(i));
                    break;
//                     此处||两边的表达式位置会对结果有影响!!
                case ']':
                    if ( stack.empty() || stack.peek() != '['  )
                        return false;
                    stack.pop();
                    break;
                case '}':
                    if ( stack.empty() || stack.peek() != '{'  )
                        return false;
                    stack.pop();
                    break;
                case ')':
                    if (stack.empty() || stack.peek() != '('  )
                        return false;
                    stack.pop();
                    break;
            }
        }
        return stack.empty() ? true : false;
    }
}

除了由Vector定义的所有方法,自己也定义了一些方法:

序号 方法描述
1 boolean ==empty()== 测试堆栈是否为空。
2 Object ==peek( )== 查看堆栈顶部的对象,但不从堆栈中移除它。
3 Object pop( ) 移除堆栈顶部的对象,并作为此函数的值返回该对象。
4 Object push(Object element) 把项压入堆栈顶部。
5 int search(Object element) 返回对象在堆栈中的位置,以 1 为基数。

==BM45 滑动窗口的最大值 【刷一次!!】==

【双端队列】

要求:空间复杂度 O(n)O(n),时间复杂度 O(n)O(n)

  1. 遍历数组的每一个元素,
  2. 如果容器为空,则直接将当前元素==加入==到容器中。
  3. 如果容器不为空,则让当前元素和容器的==最后一个元素比较==,如果大于,则将容器的最后一个元素删除,然后继续讲当前元素和容器的最后一个元素比较
  4. 如果当前元素小于容器的最后一个元素,则直接将当前元素加入到容器的末尾
  5. 如果容器头部的元素已经不属于当前窗口的边界,则应该将头部元素删除
import java.util.*;
public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size) {

        ArrayList<Integer> res = new ArrayList<>();
        if (size == 0 || size > num.length) return res;
        Deque<Integer> deque = new LinkedList<>();
        for (int i = 0; i < num.length; i++) {
            while (!deque.isEmpty() && num[deque.peekLast()] < num[i] ) {
                deque.pollLast();
            }
            deque.offerLast(i);
            // 如果容器头部的元素已经不属于当前窗口的边界,则应该将头部元素删除
            if (deque.peekFirst() <= i - size) {
                deque.pollFirst();
            }

            if (i + 1 >= size) {
                res.add(num[ deque.peekFirst()]);
            }
        }
        return res;

    }
}

双端队列的方法

由于Deque继承了Queue接口,因此它继承了Queue接口的所有方法。

除了Queue接口中可用的方法之外,Deque界面还包括以下方法:

  • addFirst() - 在双端队列的开头添加指定的元素。如果双端队列已满,则引发异常。
  • addLast() - 在双端队列的末尾添加指定的元素。如果双端队列已满,则引发异常。
  • offerFirst() - 在双端队列的开头添加指定的元素。如果双端队列已满,则返回false。
  • offerLast() - 在双端队列的末尾添加指定的元素。如果双端队列已满,则返回false。
  • getFirst() - 返回双端队列的第一个元素。如果双端队列为空,则引发异常。
  • getLast() - 返回双端队列的最后一个元素。如果双端队列为空,则引发异常。
  • peekFirst() - 返回双端队列的==第一个元素==。如果双端队列为空,则返回null。
  • peekLast() - 返回双端队列的==最后一个元素==。如果双端队列为空,则返回null。
  • removeFirst() - 返回并删除双端队列的第一个元素。如果双端队列为空,则引发异常。
  • removeLast() - 返回并删除双端队列的最后一个元素。如果双端队列为空,则引发异常。
  • pollFirst() - 返回并删除双端队列的第一个元素。如果双端队列为空,则返回null。
  • pollLast() - 返回并删除双端队列的最后一个元素。如果双端队列为空,则返回null。

==BM46 最小的K个数==

【快排】

要求:空间复杂度 O(n)O(n) ,时间复杂度 O(nlogn)O(nlogn)

==时间复杂度 O(Nlog N): 库函数、快排等排序算法的平均时间复杂度为 O(N log N)== 空间复杂度 O(N) : 快速排序的递归深度最好(平均)为 O(\log N)O(logN) ,最差情况(即输入数组完全倒序)为 O(N)O(N)。

import java.util.*;
// 思想:先进行排序(快排),然后直接取前k个元素
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> res = new ArrayList<>();
        if (k == 0)
            return res;
        if (k >= input.length) {
            for (int i : input)
                res.add(i);
            return res;
        }
        int[] resArr = quickSort(input, 0, input.length - 1, k);
        for (int i : resArr) {
            res.add(i);
        }
        return res;
    }
//     快速排序
    public int[] quickSort(int [] input, int l, int h, int k) {
        int pivot = input[l];
        int low = l, high = h;
        while (low < high) {
            while (low < high && input[high] >= pivot)
                --high;
            input[low] = input[high];
            while (low < high && input[low] <= pivot)
                ++low;
            input[high] = input[low];
        }
        input[low] = pivot;

        if (low + 1 > k)
            return quickSort(input, l, low - 1, k);
        if (low + 1 < k)
            return quickSort(input, low + 1, h, k);
//         数组的复制
        return Arrays.copyOf(input, k);
    }
}
  1. boolean equals(int[] a,int[] b) 判断两个数组是否相等。 2 String toString(int[] a) 输出数组信息。 3 void fill(int[] a,int val) 将指定值填充到数组之中。【全部元素被替换成val】 4 void sort(int[] a) 对数组进行排序。【原数组被改变了】

  2. int binarySearch(int[] a,int key) 对排序后的数组进行二分法检索指定的值。 【返回负数表示未找到指定值!!】

  3. ==Arrays.asList(num[k], num[i], num[j]))== 该方法是将数组转化为list

已经知道之所以asList 方法产生的 ArrayList 不能修改大小,==是因为这个 ArrayList 并不是“货真价实”的 ArrayList== ,那我们就自行创建一个真正的 ArrayList :

public class Test {
   public static void main(String[] args) {
      String[] myArray = { "Apple", "Banana", "Orange" };
      List<String> myList = new ArrayList<String>(Arrays.asList(myArray));
      myList.add("Guava");
   }
}
  1. copyOfRange(int []original,int from,int to),original为原始的int型数组,from为开始角标值,to为终止角标值。(其中包括from角标,不包括to角标。即处于[from,to)状态)

==BM47 寻找第K大 【字节】==

【力扣已通过!!】

【快排】

import java.util.*;

public class Solution {
    public int findKth(int[] a, int n, int K) {
        return quickSort(a, 0, n - 1, K);
    }
//     快排,从大到小排序
    public int quickSort(int[] a, int l, int h, int K) {
        int pivot = a[l];
        int low = l, high = h;
        while (low < high) {
            while (low < high && a[high] <= pivot)
                high--;
            a[low] = a[high];
            while (low < high && a[low] >= pivot)
                low++;
            a[high] = a[low];
        }
        a[low] = pivot;
        if(low==K-1)
            return a[low];
        else if(low<K-1){
            return quickSort(a,low+1,h, K);
        }else
            return quickSort(a,l,low-1, K);
    }
}

==BM48 数据流中的中位数 【难!!】==

【优先队列 / 堆】

进阶: 空间复杂度 O(n) *O*(n) , 时间复杂度 O(nlogn) *O*(nlogn)

平衡堆:大顶堆+小顶堆(参考k神)

`即“大顶堆的堆顶”与“小顶堆的堆顶”就是排序数据流的两个中位数。

大顶堆 n 小顶堆 m=n+1

【规定奇数时,小顶堆个数多!】

findMedian(): ​ 1.当m+n为偶数时,中位数=(A堆顶元素+B堆顶元素)/2.0 ​ 2.当m+n为奇数时,中位数=A堆顶元素 ​ **addNum(int num):**其目的是使得两个堆平衡(数目差0或1)

当两堆总大小为偶数时,即两堆大小相等,先将新元素插入maxHeap,重新排序后将新的最值拿出并插入到minHeap; 当两堆总大小为奇数时,即两堆大小不等,先将新元素插入minHeap,重新排序后将新的最值拿出并插入到maxHeap;

import java.util.*;
public class Solution {
    Queue<Integer> A=new PriorityQueue<>(); //小顶堆
    Queue<Integer> B=new PriorityQueue<>((x,y)->(y-x)); //大顶堆
    
    public void Insert(Integer num) {
        if(A.size()!=B.size()){
            A.offer(num);
            B.offer(A.poll());
        }else{
            B.offer(num);
            A.offer(B.poll());
        }
    }
    public Double GetMedian() {
        return A.size()!=B.size()?(double)A.peek():(double)(A.peek()+B.peek())/2;
    }
}
import java.util.*;
public class Solution {
    List<Integer> save = new ArrayList<>();
    public void Insert(Integer num) {
        save.add(num);
        Collections.sort(save);
    }

    public Double GetMedian() {
        int len = save.size();
        if(len == 0){
            return null;
        }
        if(len % 2 == 0){
            int first = save.get(len/2-1);
            int second = save.get(len/2);
            return (double)(first+second)/2;
        }else{
            return (double)save.get(len/2);
        }
    }
}

==BM49 表达式求值 【未完成×】==

要求:空间复杂度: O(n)O(n),时间复杂度 O(n)O(n)

https://zbsong.top/article/%E7%AE%97%E6%B3%95%E2%80%94%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B1%82%E5%80%BC%E7%9A%84%E9%80%9A%E7%94%A8%E8%A7%A3%E6%B3%95/

BM52 数组中只出现一次的两个数字

要求:空间复杂度 O(N),时间复杂度 O(n)

import java.util.*;
public class Solution {
    
    public int[] FindNumsAppearOnce (int[] array) {
        int len = array.length;
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < len; i++) {
            if (map.containsKey(array[i])) {
                map.put(array[i], map.get(array[i]) + 1);
            } else {
                map.put(array[i], 1);
            }
        }
        int[] res = new int[len];
        Set set = map.keySet();
        Iterator iterator = set.iterator();
        int i = 0;
        while (iterator.hasNext()) {
            int key = Integer.parseInt(iterator.next().toString());
            int value = map.get(key);
            if (value == 1) {
                res[i++] = key;
            }
        }
        int[] tmp = Arrays.copyOf(res, i);
        Arrays.sort(tmp);
        return tmp;
    }
}

==BM53 缺失的第一个正整数==

进阶: 空间复杂度 O(1),时间复杂度 O(n)

方法三:将数组视为哈希表

要找的数一定在==[1,N+1]==之间 **思想:**遍历一次数组把>1的和<length的值放到原数组对应位置;然后再遍历一次数组查当前下标是否和值对应,如果不对应那这个下标就是答案,否则遍历完都没出现那么答案就是数组长度加1。

import java.util.*;
public class Solution {
    public int minNumberDisappeared (int[] nums) {
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            while (nums[i] >= 0 && nums[i] <= n && nums[nums[i] - 1]!=nums[i] ) {
                int tmp = nums[nums[i] - 1];
                nums[nums[i] - 1] = nums[i];
                nums[i] = tmp;
            }
        }
        for (int i = 0; i < n; i++) {
            if (nums[i] != i + 1)
                return i + 1;
        }
        return n+1;
    }
}

==BM54 三数之和==

空间复杂度:O(n^2),时间复杂度 O(n^2)

解题思路: 双指针法铺垫: 先将给定 nums 排序,复杂度为 O(NlogN) 双指针法思路: 固定 3 个指针中最左(最小)数字的指针 k,双指针 i,j 分设在数组索引 (k, len(nums))两端,通过双指针交替向中间移动,记录对于每个固定指针 k 的所有满足 nums[k] + nums[i] + nums[j] == 0 的 i,j 组合:

  1. 当 nums[k] > 0 时直接break跳出:因为 nums[j] >= nums[i] >= nums[k] > 0,即 33 个数字都大于 00 ,在此固定指针 k 之后不可能再找到结果了。
  2. 当 k > 0且nums[k] == nums[k - 1]时即跳过此元素nums[k]:因为已经将 nums[k - 1] 的所有组合加入到结果中,本次双指针搜索只会得到重复组合。
  3. i,j 分设在数组索引 (k, len(nums)) 两端,当i < j时循环计算s = nums[k] + nums[i] + nums[j],并按照以下规则执行双指针移动:
    • 当s < 0时,i += 1并跳过所有重复的nums[i];
    • 当s > 0时,j -= 1并跳过所有重复的nums[j];
    • 当s == 0时,记录组合[k, i, j]至res,执行i += 1和j -= 1并跳过所有重复的nums[i]和nums[j],防止记录到重复组合。
import java.util.*;
public class Solution {
//     思想:双指针法
    public ArrayList<ArrayList<Integer>> threeSum(int[] num) {
        ArrayList<ArrayList<Integer>> res = new ArrayList<>();
//         此时原数组已经被修改
        Arrays.sort(num);
        for (int k = 0; k < num.length - 2; k++) {
            if (num[k] > 0)  break;
            if (k > 0 && num[k] == num[k - 1])  continue;
            int i = k + 1, j = num.length - 1;
            while (i < j) {
                int sum = num[k] + num[i] + num[j];
                if (sum < 0) {
//                     注意i++和++i的区别   
                    while (i < j && num[i] == num[++i]);
                    //为什么不对???这样写??
                    //while (i < j && num[i] == num[i+1]) i++;
                } else if (sum > 0) {
                    while (i < j && num[j] == num[--j]);
                } else {
//                     技巧:如何将一个数组添加到另一个数组中!!
                    res.add(new ArrayList(Arrays.asList(num[k], num[i], num[j])));
                    
//                     这一步也很重要,思想技巧
                    while (i < j && num[i] == num[++i]);
                    while (i < j && num[j] == num[--j]);
                }
            }
        }
        return res;
    }
}

06 递归/回溯

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

要求:空间复杂度 O(n!)O(n!) ,时间复杂度 O(n!)O(n!)

import java.util.*;
// 典型的回溯类题目
public class Solution {
    public ArrayList<ArrayList<Integer>> permute(int[] num) {
        int n = num.length;
        ArrayList<ArrayList<Integer>> res = new ArrayList<>();
//         ArrayList<Integer> tmp = new ArrayList<>();
        int[] visted = new int[n];
//         进行回溯
        backTrack(num, visted, new ArrayList<Integer>(), res);

        return res;
    }

    public void backTrack(int[] num, int[] visted, ArrayList<Integer> tmp,
                          ArrayList<ArrayList<Integer>> res) {
        if (tmp.size() == num.length) {
            //问题???注意此处的写法
            res.add(new ArrayList<>(tmp));
            return;
        }

        for (int i = 0; i < num.length; i++) {
            if (visted[i] == 1) continue;
            visted[i] = 1;
            tmp.add(num[i]);
            backTrack(num, visted, tmp, res);
            visted[i] = 0;
            tmp.remove(tmp.size() - 1);
        }
    }
}

BM56 有重复项数字的全排列

import java.util.*;

public class Solution {
    public ArrayList<ArrayList<Integer>> permuteUnique(int[] num) {
        Arrays.sort(num);
        int n = num.length;
        ArrayList<ArrayList<Integer>> res = new ArrayList<>();
        int[] visted = new int[n];
//         进行回溯
        backTrack(num, visted, new ArrayList<Integer>(), res);

        return res;
    }

    public void backTrack(int[] num, int[] visted, ArrayList<Integer> tmp,
                          ArrayList<ArrayList<Integer>> res) {
        if (tmp.size() == num.length && !res.contains(new ArrayList<>(tmp))) {
            res.add(new ArrayList<>(tmp));
            return;
        }

        for (int i = 0; i < num.length; i++) {
            if (visted[i] == 1) continue;
            visted[i] = 1;
            tmp.add(num[i]);
            backTrack(num, visted, tmp, res);
            visted[i] = 0;
            tmp.remove(tmp.size() - 1);
        }
    }
}

==BM57 岛屿数量==

【递归】

思路一:深度优先遍历DFS 目标是找到矩阵中 “岛屿的数量” ,上下左右相连的 1 都被认为是连续岛屿。 dfs方法:

设目前指针指向一个岛屿中的某一点 (i, j),寻找包括此点的岛屿边界。 从 (i, j) 向此点的上下左右 (i+1,j),(i-1,j),(i,j+1),(i,j-1) 做深度搜索。 终止条件: (i, j) 越过矩阵边界; grid[i][j] == 0,代表此分支已越过岛屿边界。 搜索岛屿的同时,执行 grid[i][j] = '0',即将岛屿所有节点删除,以免之后重复搜索相同岛屿。 主循环: 遍历整个矩阵,当遇到 grid[i][j] == '1' 时,从此点开始做深度优先搜索 dfs,岛屿数 count + 1 且在深度优先搜索中删除此岛屿。 最终返回岛屿数 count 即可。

class Solution {
    // 深度优先遍历
    public int numIslands(char[][] grid) {
        int count=0;
        for(int i=0;i<grid.length;i++){
            for(int j=0;j<grid[0].length;j++){
                if(grid[i][j]=='1'){
                    dfs(grid,i,j);
                    count++;
                }
            }
        }
        return count;
    }
    
    private void dfs(char[][] grid,int i,int j){
        if(i<0 || j<0 || i>=grid.length || j>=grid[0].length || grid[i][j]=='0')
            return ;
       
        grid[i][j]='0';
        dfs(grid,i+1,j);
        dfs(grid,i,j+1);
        dfs(grid,i-1,j);
        dfs(grid,i,j-1);
    }
   
}

==BM58 字符串的排列==

import java.util.*;
public class Solution {
    ArrayList<String> res = new ArrayList<>();
    StringBuilder tmp = new StringBuilder();
    public ArrayList<String> Permutation(String str) {
//         Arrays.sort(str);
        int n = str.length();
        int[] visted=new int[n];
        backTrack(str, visted);
        return res;
    }

    public void backTrack(String str, int[] visted) {
        if (tmp.length()==str.length() && !res.contains(tmp.toString()) ) {
            res.add(tmp.toString());
            return;
        }

        for (int i = 0; i < str.length(); i++) {
            if(visted[i]==1) continue;
            visted[i]=1;
            tmp.append(str.charAt(i));
            backTrack(str,visted);
            visted[i]=0;
            tmp.deleteCharAt(tmp.length()-1);

        }
    }
}

4.StringBuffer、StringBuilder中的常用方法 【掌握!】 增:append(xxx) ==删:delete(int start,int end)== 【凡是涉及开始和结束的索引,都是左闭右开!!】 改:setCharAt(int n ,char ch) 【修改一个字符】 / replace(int start, int end, String str) 查:charAt(int n ) 插:insert(int offset, xxx) 长度:length(); 遍历:for() + charAt() / toString()

反转:reverse()

  1. 以上是排列的解决方案,55,56,58,三道题都是一个模板;

==BM59 N皇后问题 【难 ×】==

BM60 括号生成

import java.util.*;
public class Solution {
//     思想:如果左括号数量不大于 n,我们可以放一个左括号。
//     如果右括号数量小于左括号的数量,我们可以放一个右括号。
    ArrayList<String> res = new ArrayList<>();
    StringBuilder tmp = new StringBuilder( );
    public ArrayList<String> generateParenthesis (int n) {

        backTrack(n, 0, 0);
        return res;
    }

    public void backTrack(int n, int left, int right) {
        //注意终止条件
        if (tmp.length()==2*n) {
            res.add(tmp.toString());
            return;
        }
        
        if(left<n){
            tmp.append('(');
            backTrack(n,left+1,right);
            //注意此处!!
            tmp.deleteCharAt(tmp.length()-1); 
        }
        if(right<left){
            tmp.append(')');
            backTrack(n,left,right+1);
            tmp.deleteCharAt(tmp.length()-1);
        }
    }
}

==BM61 矩阵最长递增路径 【难】==

进阶:空间复杂度 O(nm)O(n**m) ,时间复杂度 O(nm)O(n**m)

【递归】

import java.util.*;
//     所有数据为开始节点进行查找
//     思想:深度优先搜索,由于序列递增,因此无需回溯。
public class Solution {
    int m, n,res;
    int 
    public int solve (int[][] matrix) {
        m = matrix.length;
        n = matrix[0].length;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                dfs(matrix,  i, j, 0, -1);
            }
        }
        return res;
    }

    public void dfs(int[][] matrix,  int i, int j, int tmp, int pre) {
        if (i < 0 || j < 0 || i >= m || j >= n || matrix[i][j] <= pre) {
            res = Math.max(res, tmp);
            return;
        }
        dfs(matrix, i - 1, j, tmp + 1, matrix[i][j]);
        dfs(matrix, i, j + 1, tmp + 1, matrix[i][j]);
        dfs(matrix, i + 1, j, tmp + 1, matrix[i][j]);
        dfs(matrix, i, j - 1, tmp + 1, matrix[i][j]);
    }
}

07 动态规划

基础题目

BM63 跳台阶

public class Solution {
    public int jumpFloor(int target) {
//         递归方式
//         if (target <= 2) return target;
//         return jumpFloor(target-1)+jumpFloor(target-2);

//         迭代方式
        if (target <= 2) return target;
        int res = 0, m = 1, n = 2;
        for (int i = 3; i <= target; i++) {
            res = m + n;
            m = n;
            n = res;
        }
        return res;

    }
}

==BM64 最小花费爬楼梯==

请你计算并返回达到==楼梯顶部==的最低花费。

import java.util.*;
public class Solution {
    public int minCostClimbingStairs (int[] cost) {
			//方式一   代码随想录
//         if(cost.length==0)
//             return 0;
//         if(cost.length==1)
//             return cost[0];
//         int sum=0,m=cost[0],n=cost[1];
//         for(int i=2;i<cost.length;i++){
//             sum=Math.min(m,n)+cost[i];
//             m=n;
//             n=sum;
//         }
//         return Math.min(m,n);
//     }
        //方式二
    public int minCostClimbingStairs(int[] cost) {
        if (cost == null || cost.length == 0) {
            return 0;
        }
        if (cost.length == 1) {
            return cost[0];
        }
        int[] dp = new int[cost.length];
        dp[0] = cost[0];
        dp[1] = cost[1];
        for (int i = 2; i < cost.length; i++) {
            dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];
        }
        //最后一步,如果是由倒数第二步爬,则最后一步的体力花费可以不用算
        return Math.min(dp[cost.length - 1], dp[cost.length - 2]);
    }
        
        //方式三  官网方法
        int n=cost.length;
        int[] dp=new int[n+1];
        dp[0]=dp[1]=0;
        
        for(int i=2;i<=n;i++){
            dp[i]=Math.min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
        }
        return dp[n];
}
}

BM65 最长公共子序列(二)

要求:空间复杂度 O(n^2),时间复杂度 O(n^2)

最长公共子序列问题是典型的二维动态规划问题。

import java.util.*;
public class Solution {
  
    public String LCS (String s1, String s2) {
        int m = s1.length();
        int n = s2.length();
        if(m==0 || n==0) return "-1";
       
        int[][] dp = new int[m + 1][n + 1];
        StringBuilder res = new StringBuilder();
        
        for(int i=1;i<m+1;i++){
            for(int j=1;j<n+1;j++){
                if(s1.charAt(i-1)==s2.charAt(j-1)){
                    //注意此处
                    dp[i][j]=dp[i-1][j-1]+1;
                }else{
                    dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        int len1=m,len2=n;
        while(len1>0 && len2>0){
            if(s1.charAt(len1-1)==s2.charAt(len2-1)){
                res.append(s1.charAt(len1-1));
                len1--;
                len2--;
            }else{
                if(dp[len1-1][len2]>dp[len1][len2-1])
                    len1--;
                else
                    len2--;
            }
        }
        if(res.length()==0) return "-1";
        return res.reverse().toString();
    }
}

BM66 最长公共子串

要求: 空间复杂度 O(n^2)O(n2),时间复杂度 O(n^2)O(n2)

import java.util.*;
public class Solution {
// maxLastInx
    public String LCS (String s1, String s2) {
        int m = s1.length();
        int n = s2.length();
        int maxLen = 0, maxLastInx = 0;

        int[][] dp = new int[m + 1][n + 1];
        StringBuilder res = new StringBuilder();
        for (int i = 1; i <  m + 1; i++) {
            for (int j = 1; j <  n + 1; j++) {
                if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                    if (maxLen < dp[i][j]) {
                        maxLen = dp[i][j];
                        maxLastInx = i;
                    }
                } else {
                    dp[i][j] = 0;
                }
            }
        }
// 注意substring范围?
        return s1.substring(maxLastInx - maxLen, maxLastInx);
    }
}

BM67 不同路径的数目(一)

import java.util.*;


public class Solution {
    /**
     * 
     * @param m int整型 
     * @param n int整型 
     * @return int整型
     */
//     思想:dp[i][j]表示到[i,j]点共有多少条路径,第一行和第一列的位置,只有一条路径;
//     其余位置,dp[i][j]=dp[i-1][j]+dp[i][j-1];只考虑前一步的状态即可,不用考虑其他状态
    public int uniquePaths (int m, int n) {
        
        int[][] dp=new int[m+1][n+1];
        for(int i=0;i<m;i++){
            dp[i][0]=1;
        }
         for(int j=0;j<n;j++){
            dp[0][j]=1;
        }
        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
                dp[i][j]=dp[i-1][j]+dp[i][j-1];
            }
        }
        return dp[m-1][n-1];
    }
}

BM68 矩阵的最小路径和

import java.util.*;


public class Solution {
   
    public int minPathSum (int[][] matrix) {
        int m=matrix.length,n=matrix[0].length;
        int[][] dp=new int[m][n];
        dp[0][0]=matrix[0][0];
        for(int i=1;i<m;i++){
            dp[i][0]=dp[i-1][0]+matrix[i][0];
        }
         for(int j=1;j<n;j++){
            dp[0][j]=dp[0][j-1]+matrix[0][j];
        }
        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
                dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+matrix[i][j];
            }
        }
        return dp[m-1][n-1];
        
    }
}

BM69 把数字翻译成字符串

import java.util.*;


public class Solution {
   public int solve (String nums) {

        int length = nums.length();
        if(length==0||nums.charAt(0)=='0')
            return 0;
        int[] dp = new int[length];
        dp[0] = 1;
        for (int i = 1; i < length; i++) {
            if(nums.charAt(i)!='0'){
                dp[i] = dp[i-1];
            }
            int num = (nums.charAt(i-1)-'0')*10+(nums.charAt(i)-'0');
            if(num>=10&&num<=26){
                if(i==1){
                    dp[i] += 1;
                }else if(num>=10&&num<=26){
                    dp[i] += dp[i-2];
                }
            }
        }
        return dp[length-1];
    }

}

西瓜算法

  1. 原地 算法找未出现的第一个正整数

  2. 二维矩阵每个维度有序,查找目标值

  3. 大数相乘;

  4. 链表按指定规则重新排列。

  5. 二叉树的最大宽度

  6. image-20220406184157535

  7. L474零和一

  8. 二维01背包

  9. 找到第K小的数剑指 Offer 40. 最小的k个数,秒

  10. 无重复的最长字串3. 无重复字符的最长子串,秒

  11. 道滑动窗口题,注意回滚

  12. lc 76 hard 滑窗 难点在左边界的移动

  13. 输入几个区间数组,输出覆盖的最大长度,题目难度算是mid吧,参考lc上的各种数组区间问题,可以在找区间过程中就进行统计,举例:[[1.0, 2.3], [4, 6], [5.5, 7], [5.5, 9], [12, 22]],输出16.3 = (2.3 - 1.0) + (9 - 4) + (22 - 12)

  14. 给定一个长为m的字符串s1,s1中的所有字符都不重复,给定一个长为n的字符串s2,能否在s2中找到仅有s1字符组成的字串(顺序任意),返回起始位置,未找到返回-1,举例,s1 = "abcd",s2 = "tbcacbdata",res = 3

    一个m大小的窗口向右移动就完了。。我直接用了一面算法题的思路写复杂了,喜提加面

  15. lc 33 搜索旋转排序数组

    lc 143 重排链表

华为

HJ8 合并表记录

import java.util.*;
public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        // 不指定初始化的大小,有bug???
        HashMap<Integer, Integer> map = new HashMap<>(n);
        while (scanner.hasNext()) {
            int key = scanner.nextInt();
            int value = scanner.nextInt();
            if (!map.containsKey(key)) {
                map.put(key, value);
            } else {
                map.put(key, map.get(key) + value);
            }
        }

        for (int key : map.keySet()) {
            System.out.println(key + " " + map.get(key));
        }
    }

}