Leetcode热题100道(上下班就能刷)

给大家一个上下班能快速的刷LeetCode hot100的好去处😁

数据结构

1.数组

1.轮转数组_4

2. 缺失的第一个正数_5

3. 最大子数组和_52


/**
 * 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
 */

/**
 * todo // 定义dp:以i结尾的最大子数组和 dp[i] = max(dp[i-1] + nums[i],nums[i])
 */
public class _52最大子数组和 {
    public int maxSubArray(int[] nums) {
        int max = nums[0];

        // todo 1. 设置dp【i】:前i的最小值
        int[] dp = new int[nums.length];

        // todo 2. dp初始化
        dp[0] = nums[0];


        // todo 3. dp迭代
        for (int i = 1; i < dp.length; i++) {
            dp[i] = Math.max(nums[i], dp[i - 1] + nums[i]); // todo 4, 要么选自己,要不不选自己
            max = Math.max(max, dp[i]);
        }

        return max;
    }
}

4.合并区间_56

/**
 * 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间
 */

/**
 * todo 先排个序
 *  这里巧用链表,可以快速的获取到last,通过last数组的第二个元素和当前数组的第一个元素对比,
 *  如果当前数组的第一个元素<=last数组的第二个元素, 就需要合并
 *  [[1,3],[2,6],[8,10],[15,18]]
 */
public class _56合并区间 {
    public int[][] merge(int[][] intervals) {
        if (intervals == null || intervals.length == 0) return new int[][]{};

        // todo 1.不需要特殊处理
        // todo 2.排个序
        Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));

        // todo 3. 用个linkedList存放结果
        LinkedList<int[]> list = new LinkedList<>();
        list.add(intervals[0]);

        for (int i = 1; i < intervals.length; i++) {
            int[] last = list.getLast();
            if (intervals[i][0] <= last[1]) {
                // todo 1. // 如果存在[[1,8],[2,6]],所以要max一下
                int max = Math.max(last[1], intervals[i][1]);
                last[1] = max;
            } else {
                // todo 2. 如果合并不了
                list.add(intervals[i]);
            }
        }

        return list.toArray(new int[list.size()][]);

    }
}

5.除自身以外数组的乘机_238

/**
 * 给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。
 * <p>
 * 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
 * <p>
 * 请 **不要使用除法,**且在 O(n) 时间复杂度内完成此题。
 */

/**
 * todo 遍历2遍,第一遍 从前往后遍历,pre记录前i-1个乘机和,res记录前i-1的乘机和,res[i]=pre; pre*=nums[i]
 *              第二遍,从后往前遍历,suf记录后i+1个乘机和,res[i]乘以suf, res[i]*=suf; suf*=nums[i]
 */
public class _238除自身以外数组的乘机 {

    public int[] productExceptSelf(int[] nums) {
        if (nums == null || nums.length == 0) {
            return new int[]{};
        }

        int pre = 1;  // todo 前i-1乘机和
        int suf = 1;  // todo 后i+1乘机和
        int length = nums.length;
        int[] res = new int[length]; // todo 第一次遍历存放前i-1的乘机,然后 * 后i+1的乘机

        // todo 1. 正序
        for (int i = 0; i < length; i++) {
            res[i] = pre; // todo 2. 设置前i-1的pre
            pre *= nums[i]; // todo 3. 前i-1 * 第i个值
        }

        // todo 2. 倒序
        for (int i = length - 1; i >= 0; i--) {
            res[i] *= suf;
            suf *= nums[i];
        }
        return res;
    }
}

2.矩阵

1.旋转图像_48

/**
 * 给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
 * <p>
 * 你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
 */

/**
 * todo  1.按照对角线 翻转 2、按行翻转
 */
public class _48旋转图像 {

    public void rotate(int[][] matrix) {
        // 1. 不需要特殊考虑

        // todo 2. 按照对角线 翻转
        int length = matrix.length;
        for (int i = 0; i < length; i++) {
            for (int j = i; j < length; j++) {
                int temp = matrix[i][j];
                matrix[i][j] = matrix[j][i];
                matrix[j][i] = temp;

            }
        }
        // todo 3. 按行翻转
        for (int i = 0; i < length; i++) {
            // todo 4. 这里只要遍历一半就行了
            for (int j = 0; j < length / 2; j++) {
                int temp = matrix[i][j]; // todo 第j个
                matrix[i][j] = matrix[i][length - 1 - j]; // todo 倒数j个
                matrix[i][length - 1 - j] = temp;
            }
        }
    }
}

2. 搜索二维矩阵二_240

/**
 * todo 从左下角往右上角遍历
 */
public class _240搜索二维矩阵二 {

    public boolean searchMatrix(int[][] matrix, int target) {
        int row = matrix.length - 1;
        int col = 0;

        while (row >= 0 && col < matrix[0].length) {
            if (matrix[row][col] < target) {
                col++;
            } else if (matrix[row][col] > target) { // todo 如果当前这个数字 大于target,就row--
                row--;
            } else {
                return true;
            }
        }
        return false;
    }
}

3.链表

1.两数相加_2

/**
 * 你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
 * 请你将两个数相加,并以相同形式返回一个表示和的链表。
 * 你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
 */

/**
 * todo dummyNode + 双指针往后遍历
 */
public class _2两数相加 {

    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        // todo 1.由于是非空,所以不需要特殊处理

        ListNode dummy = new ListNode(-1); // todo 2.设计伪指针
        ListNode cur = dummy;

        int forward = 0;
        while (l1 != null || l2 != null) {
            //
            int value1 = l1 == null ? 0 : l1.val;
            int value2 = l2 == null ? 0 : l2.val;

            int sum = value1 + value2 + forward;
            cur.next = new ListNode(sum % 10); // todo 3.获取小数部分
            forward = sum / 10; // todo 4.获取整数部分

            if (l1 != null) {
                l1 = l1.next;
            }

            if (l2 != null) {
                l2 = l2.next;
            }
            cur = cur.next;
        }

        // todo 5.如果最后进位不为0
        if (forward != 0) {
            cur.next = new ListNode(forward);
        }

        return dummy.next;
    }

}

2. 删除链表的倒数第N个节点_19

public class _19删除链表的倒数第N个节点 {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(-1, head); // todo 1.定义一个dummy节点

        // todo 2.定义快慢指针
        ListNode fast = head;
        ListNode slow = dummy; // todo 注意slow指向dummy  !!slow就是要删除的节点

        // todo 3.删倒数第一个,那就多跑一步 删倒数第二个,那就多跑2步
        for (int i = 0; i < n; i++) {
            fast = fast.next;
        }

        while (fast != null) {
            fast = fast.next;
            slow = slow.next;
        }

        slow.next = slow.next.next;

        return dummy.next;
    }
}

3.合并两个有序链表_21

public class _21合并两个有序链表 {

    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode res = new ListNode(0);
        ListNode cur = res;

        while (list1 != null && list2 != null) {
            if (list1.val <= list2.val) {
                cur.next = list1;
                list1 = list1.next;
            } else {
                cur.next = list2;
                list2 = list2.next;
            }
            cur = cur.next;
        }

        if (list1 != null) {
            cur.next = list1;
        }
        if (list2 != null) {
            cur.next = list2;
        }

        return res.next;

    }
}

4. 合并k个升序链表_23

/**
 * todo 使用优先级队列,把k个链表数据都丢到优先级队列中
 */
public class _23合并k个升序链表 {

    public ListNode mergeKLists(ListNode[] lists) {
        // todo 1、判断链表是否为null
        if (lists == null)
            return null;

        // todo 2、创建优先级队列
        PriorityQueue<ListNode> queue = new PriorityQueue<>(Comparator.comparingInt(x -> x.val));

        // todo 3. 遍历每一条链表, 把数据放到优先级队列中
        for (ListNode node : lists) {
            while (node != null) {
                queue.offer(node);
                node = node.next;
            }
        }

        ListNode dummy = new ListNode();
        ListNode cur = dummy;
        // todo 4、从队列中取数据,
        while (!queue.isEmpty()) {
            ListNode miniNode = queue.poll();
            cur.next = miniNode;
            cur = cur.next;

            // todo 5、如果是最后一个
            if (queue.isEmpty()) {
                cur.next = null;
            }
        }

        return dummy.next;

    }
}

5. 两两交换链表中的节点_24

/**
 * 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
 */

/**
 * todo 创建dummy节点,
 *  创建三个指针:
 *  第一个指针为交换节点的前指针:current
 *  第二个指针为交换的第一个节点:node1
 *  第三个指针为交换的第二个节点: node2
 *  交换过程:
 *  node1.next = node2.next;
 *  node2.next = node1
 *  current.next = node2;
 *  current = node1;
 */
public class _24两两交换链表中的节点 {
    public ListNode swapPairs(ListNode head) {
        // todo 1、创建一个dummy节点
        ListNode dummy = new ListNode(-1, head);
        // todo 2、创建一个current指向它,也就是交换的前置节点
        ListNode current = dummy;

        while (current.next != null && current.next.next != null) {

            ListNode node1 = current.next;
            ListNode node2 = current.next.next;

            // todo 3、进行交换
            node1.next = node2.next;
            node2.next = node1;
            current.next = node2;

            // todo 4、跳到交换后的第二个节点,也就是下一次交换的 前置节点
            current = node1;

        }

        return dummy.next;

    }
}

6. K个一组翻转链表_25

public class _25K个一组翻转链表aaa {
    public ListNode reverseKGroup(ListNode head, int k) {
        return null;
    }
}

7.复制带随机指针的链表_138

 public ListNode copyRandomList(ListNode head) {
        return null;
    }

8.环形链表_141

/**
 * 给你一个链表的头节点 head ,判断链表中是否有环。
 * <p>
 * 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
 * <p>
 * 如果链表中存在环 ,则返回 true 。 否则,返回 false 。
 */

/**
 * todo pre: 环形链表,说明不管快慢指针怎么走,最终都会相遇!!!!!
 *  1、快慢指针,快指针指向head.next,慢指针指向head
 *  2、while(fast != slow) 如果快指针走到了null,说明没有环 , slow = slow.next, fast = fast.next.next
 */
public class _141环形链表 {

    public boolean hasCycle(ListNode head) {
        if (head == null || head.next == null) return false;
        ListNode fast = head.next;
        ListNode slow = head;

        while (slow != fast) {
            if (fast == null || fast.next == null) {
                return false;
            }
            slow = slow.next;
            fast = fast.next.next;
        }
        return true;
    }

}

9. 环形链表2_142

/**
 * todo:
 *  快指针跑2步,慢指针跑1步,
 *  1. 第一次相遇的时候,快跑了2b, 慢跑了b
 *  2. 这时候让快在跑a步,也就是第二次相遇的时候,快跑了2b+a, 慢跑了b+a
 *  3. 本质上利用快慢指针和2次相遇 屏蔽了快比慢多跑b,并且第二次相遇
 */
public class _142环形链表二aaa {

    public ListNode detectCycle(ListNode head) {
        // 1. 特殊情况,为空或者只有一个
        if (head == null || head.next == null)
            return null;

        // 2. 设置快慢指针,快每次跑2步,慢每次跑1步
        ListNode fast = head;
        ListNode slow = head;
        while (true) {
            // 3. 如果fast跑到结尾了,说明不存在环
            if (fast == null || fast.next == null)
                return null;
            // 4. fast跑2步,slow跑1步
            fast = fast.next.next;
            slow = slow.next;

            // 5. 如果第一次相遇,就退出
            if (fast == slow)
                break;

        }

        fast = head;
        // 6. 第二次相遇,快跑了2b+a,慢跑了b+a
        while (fast != slow) {
            fast = fast.next;
            slow = slow.next;

        }
        return fast;
    }
}

10.LRU缓存_146

public class _146LRU缓存 extends LinkedHashMap<Integer, Integer> {// 1.继承linkedHashMap

    private int capacity;

    public _146LRU缓存(int capacity) {
        super(capacity, 0.75f, true);// 2.super构造方法(容量、 hash因子、 按顺序排列)
        this.capacity = capacity;
    }

    public int get(int key) {
        return super.getOrDefault(key, -1);
    }

    public void put(int key, int value) {
        super.put(key, value);
    }

    @Override
    public boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
        return size() > capacity;
    }// 3. 重写removeEldestEntry
}

11.排序链表_148

public class _148排序链表aaa {
    public ListNode sortList(ListNode head) {
        return null;
    }
}

12. 相交链表_160


/**
 * todo
 *  1、使用双指针
 *  2、当a走完,走b,当b走完,走a,如果有交点,最终会相遇,如果没有交点,就会同时走到null
 */
public class _160相交链表 {

    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode A = headA;
        ListNode B = headB;

        // todo 当a走完,走b;当b走完 走a;
        while (A != B) {
            A = A == null ? headB : A.next;
            B = B == null ? headA : B.next;
        }

        return A;
    }
}

13.反转链表_206

/**
 * todo:使用迭代方法
 *  1、准备2个指针 prev = null、cur = head
 *  2、准备第三个指针,next = cur.next(因为反转之后cur的next会丢失,所以提前保存)
 *  2、cur.next = prev;prev = cur、cur = next(完成交换、2个指针向前进一步)
 */
public class _206反转链表 {
    /**
     * 使用迭代
     *
     * @param head
     * @return
     */
    public ListNode reverseList2(ListNode head) {
        ListNode cur = head;
        ListNode pre = null;
        while (cur != null) {
            // 先保存cur.next, 因为反转之后,就不知道当前节点的next是谁
            ListNode next = cur.next;

            // 指针反转
            cur.next = pre;
            pre = cur;
            cur = next;

        }
        return pre;
    }
}

14. 回文链表_234

/**
 * todo:将链表的数据放到list中,通过list下标来判断是否是回文链表
 */
public class _234回文链表 {

    public boolean isPalindrome(ListNode head) {
        if (head.next == null) {
            return true;
        }

        List<ListNode> list = new ArrayList();
        // todo 1、将head中的数据放到数组中
        while (head != null) {
            list.add(head);
            head = head.next;
        }

        // todo 2、从第一个和最后一个分别开始判断是否相等
        int i = 0;
        int j = list.size() - 1;
        while (i < j) {
            if (list.get(i).val != list.get(j).val) {
                return false;
            } else {
                i++;
                j--;
            }
        }
        return true;
    }
}

4.栈

1.有效的括号_20

/**
 * 给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
 * <p>
 * 有效字符串需满足:
 * <p>
 * 左括号必须用相同类型的右括号闭合。
 * 左括号必须以正确的顺序闭合。
 * 每个右括号都有一个对应的相同类型的左括号。
 */

/**
 * todo 使用栈来做,因为要做字符的匹配
 *  1、如果进一个‘(’,就push一个')',对其他[]和{}都是同理的
 *  2、如果是右边括号,就开始要判断了,1、如果stack为空(之前没有放进去一个左括号) 2、如果pop出来和之前放进去的不一样 都是false
 *  3、做完一整个操作,如果栈还是为空,返回true,说明匹配完毕了
 */
public class _20有效的括号 {
    public boolean isValid(String s) {
        // todo 1.不需要特殊处理,因为length > =1

        // todo 2.创建一个栈,利用栈的特性,先进后出,做匹配
        Stack<Character> stack = new Stack<Character>();

        // 3.
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '(') {
                stack.push(')');
            } else if (c == '{') {
                stack.push('}');
            } else if (c == '[') {
                stack.push(']');
            }
            // todo 如果不是这几个
            else {
                if (stack.isEmpty()) { // todo 比如直接就是}]这种
                    return false;
                }
                if (c != stack.pop()) { // todo 比如直接就是{[}]这种
                    return false;
                }
            }
        }

        if (stack.isEmpty()) {
            return true;
        }

        return false;
    }
}

2.柱状图中最大的矩形_84

3.最小栈_155

/**
 * todo 使用2个栈来做
 *  1、使用第一个栈main来保存所有的数据
 *  2、使用第二个栈helper来完成min 函数,栈底->栈顶 是降序的
 *  push函数:main.push, 如果helper为空,或者数字比helper.peek小,helper.push
 *  pop函数: main.pop, 如果helper不为空,并且helper.peek和main.peek的值是一样的,helper.pop
 *  top函数; main.peek
 *  min函数: helper.peek
 */
public class _155最小栈 {

    // 主栈
    private Stack<Integer> main = new Stack();

    // 栈底->栈顶 降序辅助栈,取的时候取栈顶就行
    private Stack<Integer> helper = new Stack();

    /**
     * initialize your data structure here.
     */
    public _155最小栈() {

    }

    // main正常push,辅助栈push的时候,看一下helper是否为空,或者push的数据是不是比helper栈顶数据小
    public void push(int x) {
        main.push(x);
        if (helper.isEmpty() || x <= helper.peek()) {
            helper.push(x);
        }
    }

    public void pop() {
        // 在main出栈的时候,保证helper栈元素和main一致,如果说helper和main一样的话,也要出栈
        if (!helper.isEmpty() && main.peek().equals(helper.peek())) {
            helper.pop();
        }
        main.pop();
    }

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

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

4.字符串解码_394

/**
 * 给定一个经过编码的字符串,返回它解码后的字符串。
 * <p>
 * 编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
 * <p>
 * 你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
 * <p>
 * 此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
 */

import java.util.Stack;

/**
 * 3[a2[c]]
 * 逻辑都是统一的: (2 * c + a)* 3
 */
public class _394字符串解码aaa {
    public String decodeString(String s) {

        // 1. 不需要特殊处理

        // 2. 准备容器
        StringBuilder stringBuilder = new StringBuilder(); // 1.存放遍历过程中的字符串
        int mul = 0; // 2.倍数
        Stack<String> strStack = new Stack<String>(); // 3. 存放上一个[]里面的字符串
        Stack<Integer> mulStack = new Stack<Integer>(); // 4. 存放上一个[]里面的倍数

        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '[') { // 5.将【左边的字符串和倍数都存到stack中
                mulStack.push(mul);
                strStack.push(stringBuilder.toString());

                // 6.重置倍数 和字符串容器
                mul = 0;
                stringBuilder = new StringBuilder();

            } else if (c == ']') {
                // 7. 将【之前的倍数和字符串取出来, 最终等于 之前的字符串 + 倍数 * 【】中间的字符串
                String pop = strStack.pop();

                Integer count = mulStack.pop();
                StringBuilder temp = new StringBuilder();
                for (Integer integer = 0; integer < count; integer++) {
                    temp.append(stringBuilder);
                }

                stringBuilder = new StringBuilder();
                stringBuilder.append(pop).append(temp);

            } else if (c >= '0' && c <= '9') {
                mul = 10 * mul + Integer.parseInt(c + ""); // 3. 记录倍数,防止 10、11这种
            } else {
                stringBuilder.append(c); // 4. 记录【之前的字符串
            }
        }

        return stringBuilder.toString();
    }
}

5.每日温度_739

5. 队列

6. 二叉树

1.二叉树的中序遍历_94

/**
 * todo 中序遍历:递归(左子树)+ 操作+ 递归(右子数)
 */
public class _94二叉树的中序遍历 {
    public List<Integer> inorderTraversal(TreeNode root) {
        ArrayList<Integer> res = new ArrayList<>();
        inorder(root, res);
        return res;
    }

    public void inorder(TreeNode root, ArrayList<Integer> res) {
        if (root == null) {
            return;
        }

        inorder(root.left, res);
        res.add(root.val);
        inorder(root.right, res);
    }
}

2.不同的二叉搜索树_96

/**
 * 给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
 */
public class _96不同的二叉搜索树again {
    public int numTrees(int n) {
        return 0;
    }
}

3.验证二叉搜索树_98

/**
 * 给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
 * <p>
 * 有效 二叉搜索树定义如下:
 * <p>
 * 节点的左
 * 子树
 * 只包含 小于 当前节点的数。
 * 节点的右子树只包含 大于 当前节点的数。
 * 所有左子树和右子树自身必须也是二叉搜索树。
 */

/**
 * todo:对于每一层在递归什么,比如root,或者root.left 都要保证一个什么东西:
 *  当前节点在一个合法的范围,对于root就是 >minValue 和< maxValue,对于下一层的left >minValue 和小于root,对于下一层的right > root和小于maxValue
 */
public class _98验证二叉搜索树 {
    public boolean isValidBST(TreeNode root) {
        if (root == null) return true;

        return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
    }

    private boolean isValidBST(TreeNode node, long minValue, long maxValue) {
        if (node == null) return true; // 递归终止条件

        if (node.val <= minValue || node.val >= maxValue) return false;

        return isValidBST(node.left, minValue, node.val) && isValidBST(node.right, node.val, maxValue);
    }
}

4. 对称二叉树_101

/**
 * todo 怎么判定一个二叉树是对称的,
 *  1、root为空
 *  2、root不为空,并且左子树和右子树是对称的
 *  3、左右子树又是如何对称:1、left和right为null 2、left和right相等 3、left的left和right.right对称、left的right和right的left对称
 */
public class _101对称二叉树 {
    public boolean isSymmetric(TreeNode root) {
        // 一个树是对称的,意味着它的左子树和右子树是镜像对称的
        return root == null || isMirror(root.left, root.right);
    }

    private boolean isMirror(TreeNode left, TreeNode right) {
        // 如果两个节点都为空,那么它们是对称的
        if (left == null && right == null) {
            return true;
        }
        // 如果其中一个节点为空,另一个不为空,那么它们不是对称的
        if (left == null || right == null) {
            return false;
        }
        // 如果两个节点的值不同,那么它们不是对称的
        if (left.val != right.val) {
            return false;
        }
        // 递归检查当前节点的左子节点的左子树和右子节点的右子树,以及当前节点的右子节点的左子树和左子节点的右子树是否对称
        return isMirror(left.left, right.right) && isMirror(left.right, right.left);
    }
}

5. 二叉树的层序遍历_102


/**
 * todo 用queue来解决
 *  1、把root节点放到queue中
 *  2、遍历queue,每一个层遍历,都创建一个List,然后把节点的left、right放到queue里面去
 */
public class _102二叉树的层序遍历 {

    public List<List<Integer>> res = new ArrayList<>();
    public Queue<TreeNode> queue = new ArrayDeque<TreeNode>();

    public List<List<Integer>> levelOrder(TreeNode root) {
        if (root == null) {
            return res;
        }
        queue.add(root);
        helper();
        return res;
    }

    public void helper() {
        while (!queue.isEmpty()) {
            // 给每一次queue里面的数据新建一个list
            List<Integer> level = new ArrayList<Integer>();

            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();

                level.add(node.val);

                if (node.left != null) {
                    queue.add(node.left);
                }
                if (node.right != null) {
                    queue.add(node.right);
                }
            }
            res.add(level);
        }
    }
}

6. 二叉树的最大深度_104

/**
 * 深度:从root往下有几个节点,就是多少深度
 */

/**
 * todo 相当于后序遍历,左子树、右子数,最后操作(计算深度
 */
public class _104二叉树的最大深度 {

    public int maxDepth(TreeNode root) {
        // 如果节点为空,则该树的深度为0
        if (root == null) {
            return 0;
        }

        // 递归地计算左子树和右子树的最大深度
        int leftDepth = maxDepth(root.left);
        int rightDepth = maxDepth(root.right);

        // 树的最大深度是左、右子树深度的最大值 + 1(当前节点自身)
        return Math.max(leftDepth, rightDepth) + 1;
    }
}

7. 从前序与中序遍历序列构造二叉树_105

/**
 * 给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
 */

/**
 * todo
 *  1、前序给我们带来什么,一下子能知道根是谁,
 *  2、中序给我们什么,通过根 知道左子树的结束点在哪里,右子树开始在哪里
 *  <p>     3
 *      9      20
 *          15    7
 *  <p> 输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
 *  所以算法就是不断的执行递归函数 recur(int root, int left, int right):
 *  1、从前序 + root位置创建跟节点
 *  2、从根节点 + 中序遍历来获取根节点的位置i
 *  3、root的左子树的范围【left, i-1】,root的右子树的范围【i+1, right】
 *  4、继续递归调用root.left = recur(root + 1, left, i - 1), root.right = recur(root + (i-left) +1 ,i + 1,right)
 */
public class _105从前序与中序遍历序列构造二叉树 {
    int[] preorder;
    HashMap<Integer, Integer> dic = new HashMap<>();

    public TreeNode _105从前序与中序遍历序列构造二叉树(int[] preorder, int[] inorder) {
        this.preorder = preorder;
        for (int i = 0; i < inorder.length; i++)
            dic.put(inorder[i], i);
        return recur(0, 0, inorder.length - 1);

    }

    TreeNode recur(int root, int left, int right) {
        if (left > right)
            return null; // 递归终止
        TreeNode node = new TreeNode(preorder[root]); // 建立根节点
        int i = dic.get(preorder[root]); // 划分根节点、左子树、右子树
        node.left = recur(root + 1, left, i - 1); // 开启左子树递归
        node.right = recur(root + (i - left) + 1, i + 1, right); // 开启右子树递归 // todo root+(index -left)+ 1
        return node; // 回溯返回根节点
    }

}

8. 将有序数组转换为二叉搜索树_108

/**
 * todo:前序模版: 先创建root元素,然后左子树递归,右子树递归
 */
public class _108将有序数组转换为二叉搜索树 {

    public TreeNode sortedArrayToBST(int[] nums) {
        // 数组,左,右
        return conversion(nums, 0, nums.length - 1);
    }

    TreeNode conversion(int[] nums, int left, int right) {
        if (left > right)
            return null;
        int mid = (left + right) / 2;
        TreeNode root = new TreeNode(nums[mid]);
        root.left = conversion(nums, left, mid - 1);
        root.right = conversion(nums, mid + 1, right);
        return root;
    }
}

9. 二叉树展开为链表_114

/**
 * 给你二叉树的根结点 root ,请你将它展开为一个单链表:
 *
 * 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
 * 展开后的单链表应该与二叉树 先序遍历 顺序相同。
 */

/**
 * todo:先序遍历将TreeNode放到集合中,遍历集合将TreeNode的left置空,right=集合的下一个treeNode
 */
public class _114二叉树展开为链表 {

    public void flatten(TreeNode root) {

        List<TreeNode> list = new ArrayList<TreeNode>();

        // 前序遍历把节点放到list中
        preOrder(root, list);

        // 将list的结构重新改成想要的树
        for (int i = 0; i < list.size(); i++) {
            TreeNode treeNode = list.get(i);
            treeNode.left = null;
            treeNode.right = i == list.size() - 1 ? null : list.get(i + 1);
        }
    }

    private void preOrder(TreeNode root, List<TreeNode> list) {
        if (root == null) return;
        list.add(root);

        preOrder(root.left, list);
        preOrder(root.right, list);
    }
}

10. 二叉树的最大路径和_124

在这里插入代码片

11. 二叉树的右视图_199

在这里插入代码片

12. 翻转二叉树_226

/**
 * todo 也是个后序操作的模版: 先递归左子树,后递归右子数,最后reverse
 */
public class _226翻转二叉树 {

    public TreeNode invertTree(TreeNode root) {
        // 如果节点为空,则直接返回null
        if (root == null) {
            return null;
        }

        // 递归翻转左子树和右子树
        TreeNode left = invertTree(root.left);
        TreeNode right = invertTree(root.right);

        // 交换当前节点的左右子树
        root.left = right;
        root.right = left;

        // 返回当前节点,作为其父节点的子节点
        return root;
    }

}

13.二叉搜索树中第k个小的元素_230

/**
 * 给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 小的元素(从 1 开始计数)。
 */

/**
 * todo: 用中序遍历的模版,一直左子树递归,左子树出栈的时候,计数器+1,当计数器=k的时候,就是返回值,然后右子树递归
 */
public class _230二叉搜索树中第k小的元素 {

    // todo 1、结果值
    int res = 0;

    // todo 2、从左子树开始出栈次序
    int rank = 0;


    public int kthSmallest(TreeNode root, int k) {
        traverse(root, k);
        return res;
    }

    void traverse(TreeNode root, int k) {
        if (root == null)
            return;
        // todo 3、 左子树递归
        traverse(root.left, k);

        // todo 4、当运行到这里,说明左子树出栈,已经是最小的
        rank++;
        // todo 5.找到第k个,获取它的值
        if (k == rank) {
            res = root.val;
            return;
        }
        traverse(root.right, k);
    }

}

14. 二叉树的最近公共祖先_236

/**
 * todo 1、将二叉树当前的节点和父亲节点放到 map中
 *      2、开始遍历p节点,将p节点放到一个set中,并且不断从map中获取它的父节点,就是从子节点到父节点都放到这个集合中
 *      3、开始遍历q节点到它的父节点,如果set中存在这些节点,就返回,相当于找到它们共有的父亲节点
 */
public class _236二叉树的最近公共祖先 {
    public Map<Integer, TreeNode> nodeValue2parent = new HashMap<>();
    Set<Integer> visited = new HashSet<>();

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        // 1、通过遍历二叉树将节点的值和它的父节点存到nodeValue2parent
        dfs(root, null);

        // 2.遍历p的父节点
        while (p != null) {
            visited.add(p.val);
            p = nodeValue2parent.get(p.val);
        }

        // 3.遍历q的父节点,如果节点同时存在于visited
        while (q != null) {
            if (visited.contains(q.val)) {
                return q;
            }
            q = nodeValue2parent.get(q.val);
        }

        return null;
    }


    public void dfs(TreeNode node, TreeNode parent) {
        if (node != null) {
            nodeValue2parent.put(node.val, parent);
            dfs(node.left, node);
            dfs(node.right, node);
        }
    }

}

15. 路径总和_437

public class _437路径总和 {

    public int pathSum(TreeNode root, long targetSum) {
        if (root == null) return 0;
        return pathsFromNode(root, targetSum) + pathSum(root.left, targetSum) + pathSum(root.right, targetSum);
    }

    /**
     * todo 到指定节点满足target的个数,连续
     */
    public int pathsFromNode(TreeNode root, long targetSum) {
        int count = 0;
        if (root == null) return 0; // 递归终止条件
        if (root.val == targetSum) count++;

        // 继续往左右子树下探
        count += pathsFromNode(root.left, targetSum - root.val);
        count += pathsFromNode(root.right, targetSum - root.val);

        return count;
    }
}

16. 二叉树的直径_543


/**
 * todo:和最大深度类似,不同的是,每次在 后序遍历完之后都要更新 一下最大直径
 */
public class _543二叉树的直径 {
    static int ans = 0;

    public static int diameterOfBinaryTree(TreeNode root) {
        if (root == null) return 0;

        depth(root);
        return ans;
    }

    private static int depth(TreeNode node) {
        if (node == null) return 0; //递归终止条件

        int leftDepth = depth(node.left);
        int rightDepth = depth(node.right);

        ans = Math.max(ans, leftDepth + rightDepth);

        return Math.max(leftDepth, rightDepth) + 1;

    }
}

7. 图

8. hash

算法

1. 排序

1.归并排序

public class MergeSort {

    public static void merge(int[] a, int low, int mid, int high) {
        int[] temp = new int[high - low + 1];
        int i = low;// 左指针
        int j = mid + 1;// 右指针
        int k = 0;
        // 把较小的数先移到新数组中
        while (i <= mid && j <= high) {
            if (a[i] < a[j]) {
                temp[k++] = a[i++];
            } else {
                temp[k++] = a[j++];
            }
        }
        // 把左边剩余的数移入数组
        while (i <= mid) {
            temp[k++] = a[i++];
        }
        // 把右边边剩余的数移入数组
        while (j <= high) {
            temp[k++] = a[j++];
        }
        // 把新数组中的数覆盖a数组
        for (int k2 = 0; k2 < temp.length; k2++) {
            a[k2 + low] = temp[k2];
        }
    }

    public static void mergeSort(int[] a, int low, int high) {
        int mid = (low + high) / 2;
        if (low < high) {
            // 左边
            mergeSort(a, low, mid);
            // 右边
            mergeSort(a, mid + 1, high);
            // 左右归并
            merge(a, low, mid, high);
            System.out.println(Arrays.toString(a));
        }

    }

    public static void main(String[] args) {
        int a[] = {51, 46, 20, 18, 65, 97, 82, 30, 77, 50};
        mergeSort(a, 0, a.length - 1);
        System.out.println("排序结果:" + Arrays.toString(a));
    }

}

2. 快速排序

/**
 * [6, 72, 113, 11, 23]   6 比23小,array[point=0] = array【i=0】,array【i=0】= array[point=0] , point = 1
 * [6, 72, 113, 11, 23]
 * [6, 72, 113, 11, 23]
 * [6, 11, 113, 72, 23]  11比23小,array【point=1】= array[i = 3], array[i=3] = array[point = 1], point = 2
 * [6, 11, 23, 72, 113]  等遍历完成后,array[point = 2] = array[high], array[high] = array[point = 2]
 * [6, 11, 23, 72, 113]
 * 排序后的结果
 * [6, 11, 23, 72, 113]
 */
public class QuickSort {

    public static int partition(int[] array, int low, int high) {
        System.out.println("这是一次");
        // 取最后一个元素作为中心元素
        int pivot = array[high];
        // todo 如果array[i]小于中心元素,那就pointer跟这个array[i]替换,然后pointer++,也就是左边都是小于中心元素的
        int pointer = low;
        // 遍历数组中的所有元素,将比中心元素大的放在右边,比中心元素小的放在左边
        for (int i = low; i < high; i++) {
            if (array[i] <= pivot) {
                // 将比中心元素小的元素和指针指向的元素交换位置
                // 如果第一个元素比中心元素小,这里就是自己和自己交换位置,指针和索引都向下一位移动
                // 如果元素比中心元素大,索引向下移动,指针指向这个较大的元素,直到找到比中心元素小的元素,并交换位置,指针向下移动
                int temp = array[i];
                array[i] = array[pointer];
                array[pointer] = temp;
                pointer++;
            }
            System.out.println(Arrays.toString(array));
            System.out.println("pointer:" + pointer);
            System.out.println("i:" + i);
        }
        // 将中心元素和指针指向的元素交换位置
        int temp = array[pointer];
        array[pointer] = array[high];
        array[high] = temp;
        System.out.println(Arrays.toString(array));

        return pointer;
    }

    public static void quickSort(int[] array, int low, int high) {

        if (low < high) {
            // 获取划分子数组的位置
            int position = partition(array, low, high);

            // 左子数组递归调用
            quickSort(array, low, position - 1);
            // 右子数组递归调用
            quickSort(array, position + 1, high);
        }
    }

    public static void main(String[] args) {
        //int[] array = {6, 15, 10, 11, 8};
        int[] array = {6, 72, 113, 11, 23};
        quickSort(array, 0, array.length - 1);
        System.out.println("排序后的结果");
        System.out.println(Arrays.toString(array));
    }

}

2. 二分

1.二分查找_704


public class _704二分查找 {

    public static int search(int[] nums, int target) {
        if (null == nums || nums.length == 0) {
            return -1;
        }

        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] > target) {
                right = mid - 1;
            } else if (nums[mid] < target) {
                left = mid + 1;
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        int[] nums = {1, 1, 7, 10, 30};
        int search = search(nums, 7);
        System.out.println(search);
    }
}

2. 二分查找(左边界)_704


public class _704二分查找_左边界 {

    public static int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;

        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (target == nums[mid]) {
                // todo 与常规二分区别, 继续往左边找
                right = mid - 1;
            } else if (target > nums[mid]) {
                left = mid + 1;
            } else if (target < nums[mid]) {
                right = mid - 1;
            }
        }

        // todo 与常规二分区别,最终left=right 是退出循环的,看left就行,left只会变大
        if (right < 0 || left >= nums.length || target != nums[left]) {
            return -1;
        }
        return left;
    }

    public static void main(String[] args) {
        int[] nums = {1, 7, 7, 10, 30};
        int search = search(nums, 7);
        System.out.println(search);
    }
}

3. 二分查找(右边界)_704

public class _704二分查找_右边界 {

    public static int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;

        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (target == nums[mid]) {
                left = mid + 1; // todo 与常规不同的,往右边找
            } else if (target > nums[mid]) {
                left = mid + 1;
            } else if (target < nums[mid]) {
                right = mid - 1;
            }
        }

        if (right < 0 || left >= nums.length || nums[right] != target) {
            return -1;
        }

        return right;
    }

    public static void main(String[] args) {
        int[] nums = {1, 1, 7, 7, 30};
        int search = search(nums, 7);
        System.out.println(search);
    }
}

4.

3. 双指针

1. 盛最多水的容器_11

/**
 * 给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
 * 找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
 * 返回容器可以储存的最大水量。
 */

/**
 * todo:使用双指针,left和right,判断left和right哪个高度大,如果height(left)>height(right) ?
 *  max(max,(right-left) * height[left++] :max(max,(right-left) * height[right--]))
 */
public class _11盛最多水的容器_again {

    public int maxArea(int[] height) {
        // todo 1. 不需要特殊处理


        int max = 0;
        int left = 0;
        int right = height.length - 1;
        // todo 2. 当left < right
        while (left < right) {
            // todo 3. 看左边和右边哪个高度大,如果左边,那就是从当前的容量和右边往左格的容量的最大值(这里height[left ++]表示先算面积,然后left++),
            //  如果是右边大,那就是当前容量和左边往右一个的容量的最大值
            max = height[left] < height[right] ?
                    Math.max(max, (right - left) * height[left++]) : Math.max(max, (right - left) * height[right--]);
        }

        return max;
    }
}

2.三数之和_15

在这里插入代码片

3. 接雨水_42

4. 移动零_283

/**
 * 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
 * <p>
 * 请注意 ,必须在不复制数组的情况下原地对数组进行操作。
 */

/**
 * todo:用j指向不为0的数字,刚开始指向第一个,遍历nums数组,如果nums[i]不为0, nums[j]= nums[i]
 *  总结:如果nums[i]不为0,就不断往nums[j]放,nums【j】依次放到不是零的数
 */
public class _283移动零 {
    public void moveZeroes(int[] nums) {
        if (nums == null) {
            return;
        }
        int j = 0;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != 0) {
                int tmp = nums[i];
                nums[i] = nums[j];
                nums[j++] = tmp;
            }
        }

    }
}

4. 滑动窗口

5. 贪心

1.跳跃游戏_55

/**
 * 给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。
 * <p>
 * 判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false
 */

/**
 * [2,3,1,1,4]:true,[3,2,1,0,4]:false
 */
public class _55跳跃游戏 {
    public boolean canJump(int[] nums) {
        if (nums == null || nums.length == 0) return false;

        int maxReach = 0;

        for (int i = 0; i < nums.length; i++) {
            // todo 如果当前位置超过之前能到达的最大位置
            if (i > maxReach) return false;

            // todo 对于当前位置,取一个能跑的最远位置(之前的maxReach,还是当前跑的最远的位置)
            maxReach = Math.max(maxReach, i + nums[i]);

            // todo 如果最远位置已经能跑到最后了,就成功了
            if (maxReach >= nums.length - 1) return true;

        }
        return false;
    }
}

6. 回溯

1.电话号码的字母组合_17

在这里插入代码片

2.括号生成_22

3.组合总和_39

4. 全排列_46

5. 子集_78

6.单词搜索_79

7.目标和_494

7. 动态规划

1.

2.

3.

4.

5.

8. 高级动态规划

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值