算法面试40讲

文章目录

  • 算法面试40讲
    • 数组和链表
    • 堆栈和队列
    • 优先级队列
    • 哈希表
    • 树和二叉树
    • 二叉树的遍历
    • 递归与分治
    • 贪心算法
    • BFS和DFS
    • 剪枝
    • 二分查找
    • 字典树
    • 位运算
    • 动态规划
    • 并查集
    • LRU

算法面试40讲

极客时间《算法面试40讲》笔记

数组和链表

反转链表和判断链表是否有环

LC24_两两交换链表中的结点

public class Solution1 {
    /**
     * LC24:两两交换链表中的结点
     * 示例:1,2,3,4 反转成 2,1,4,3
     * 迭代
     */
    public ListNode swapPairs(ListNode head) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode temp = dummy;
        while (temp.next != null && temp.next.next != null) {
            ListNode node1 = temp.next;
            ListNode node2 = temp.next.next;

            temp.next = node2;
            node1.next = node2.next;
            node2.next = node1;

            temp = node1;
        }
        return dummy.next;
    }
}

LC25_k个一组反转链表

public class Solution {

    public ListNode reverseKGroup(ListNode head, int k) {
        if (head == null || head.next == null || k == 1) {
            return head;
        }
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode pre = dummy, cur = head, next;
        int len = 0;
        while (head != null) {
            len++;
            head = head.next;
        }
        // 整个链表有len/k子块需要翻转
        for (int i = 0; i < len / k; i++) {
            // 单个子系列中,需要翻转k-1次
            for (int j = 0; j < k - 1; j++) {
                // 这里翻转的过程,需要自己画图模拟头插入
                // 初始化:pre指向cur前一个,cur从需要反转的头结点开始,next记录cur后继结点
                //(1)记录cur后继结点
                next = cur.next;

                //(2)中间节点cur指向后继的后继
                cur.next = next.next;
                //(3)next后继指向子序列的头结点,不是指向cur
                next.next = pre.next;
                //(4)哑结点后继执行next
                pre.next = next;
            }
            // 一个字块更新完,移动pre和cur
            pre = cur;
            cur = cur.next;
        }
        return dummy.next;
    }
}

LC141_环形链表

public class Solution {
    /**
     * 判断链表是否有环
     */
    public boolean hasCycle(ListNode head) {
        if (head == null || head.next == null || head.next.next == null) {
            return false;
        }
        ListNode fast = head.next.next;
        ListNode slow = head.next;
        while (fast != slow) {
            if (fast.next == null || fast.next.next == null) {
                return false;
            }
            fast = fast.next.next;
            slow = slow.next;
        }
        return true;
    }
}

LC142_环形链表II

public class Solution {
    /**
     * 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回null
     */
    public ListNode detectCycle(ListNode head) {
        if (head == null || head.next == null || head.next.next == null) {
            return null;
        }
        ListNode fast = head.next.next;
        ListNode slow = head.next;
        // 快指针走2步,慢指针走1步
        while (fast != slow) {
            if (fast.next == null || fast.next.next == null) {
                return null;
            }
            fast = fast.next.next;
            slow = slow.next;
        }
        // 以上代码=判断链表是否有环,快指针一旦遇到慢指针,说明肯定有环,让快指针重新指向头结点
        fast = head;
        // 快指针走1步,慢指针走1步
        while (fast != slow) {
            fast = fast.next;
            slow = slow.next;
        }
        // 快慢指针一定会在第一个入环结点相遇,证明找wolai笔记
        return fast;
    }
}

LC206_反转链表

public class Solution {
    /**
     * LC206:反转单链表
     * 迭代
     */
    public ListNode reverseList(ListNode head) {
        ListNode cur = head;
        ListNode pre = null;
        while (cur != null) {
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
}

堆栈和队列

判断字符串是否合法

LC20_有效的括号

public class Solution {
    /**
     * 有效的括号
     */
    public boolean isValid(String s) {
        if (s == null || s.length() == 0) {
            return false;
        }
        Map<Character, Character> map = new HashMap<>(3);
        map.put('(', ')');
        map.put('[', ']');
        map.put('{', '}');
        LinkedList<Character> stack = new LinkedList<>();
        for (char c : s.toCharArray()) {
            if (stack.isEmpty() || map.containsKey(c)) {
                stack.push(c);
            } else {
                if (map.get(stack.peek()) != c) {
                    return false;
                }
                stack.pop();
            }
        }
        return stack.isEmpty();
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        String s = "()";
        System.out.println(solution.isValid(s));
    }
}

栈和队列相互实现

LC225_队列实现栈

public class MyStack {
    /**
     * 队列实现栈:可以用一个队列,模拟一个栈
     */
    private Queue<Integer> queue;

    public MyStack() {
        queue = new LinkedList<>();
    }

    public void push(int x) {
        // 保证后进的元素,维持在队列头部,便于出去
        // 所以先记录之前队列的长度
        int size = queue.size();
        queue.add(x);
        // 之前长度内的元素重新进入队列,让x保持在队头
        for (int i = 0; i < size; i++) {
            queue.add(queue.poll());
        }

    }

    public int pop() {
        return queue.poll();
    }

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

    public boolean empty() {
        return queue.isEmpty();
    }
}

LC232_用栈实现队列

public class MyQueue {
    /**
     * 用栈实现队列,一定是使用两个栈:stack1正常压入,stack2维持stack1的栈底到stack2的栈顶
     */
    private LinkedList<Integer> stack1;
    private LinkedList<Integer> stack2;

    public MyQueue() {
        stack1 = new LinkedList<>();
        stack2 = new LinkedList<>();
    }

    public void push(int x) {
        stack1.push(x);
    }

    public int pop() {
        if (empty()) {
            return -1;
        }
        // 栈1非空,栈2为空,往栈2压入数据
        if (stack2.isEmpty()) {
            pushStack2();
        }
        return stack2.pop();
    }

    public int peek() {
        if (empty()) {
            return -1;
        }
        // 栈1非空,栈2为空,往栈2压入数据
        if (stack2.isEmpty()) {
            pushStack2();
        }
        return stack2.peek();
    }

    public boolean empty() {
        return stack1.isEmpty() && stack2.isEmpty();
    }

    /**
     * 摊还时间复杂度
     */
    private void pushStack2() {
        while (!stack1.isEmpty()) {
            stack2.push(stack1.pop());
        }
    }
}

优先级队列

LC703_数据流中第k大元素

public class KthLargest {
    /**
     * 优先级队列
     */
    private PriorityQueue<Integer> minHeap;
    private int k;

    public KthLargest(int k, int[] nums) {
        minHeap = new PriorityQueue<>();
        this.k = k;
        for (int num : nums) {
            // 调用add操作
            add(num);
        }
    }

    public int add(int val) {
        if (minHeap.size() < k) {
            minHeap.offer(val);
        } else if (minHeap.peek() < val) {
            // 数据流中第k大元素一定是排序后,最后k个元素中最小的
            minHeap.poll();
            minHeap.offer(val);
        }
        return minHeap.peek();
    }

    public static void main(String[] args) {
        int k = 3;
        int[] nums = {4, 5, 8, 2};
        KthLargest kthLargest = new KthLargest(k, nums);
        System.out.println(kthLargest.add(3));
    }
}

LC239_滑动窗口最大值

public class Solution {
    /**
     * 滑动窗口最大值
     * 输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
     * 输出:[3,3,5,5,6,7]
     * 解释:
     * 滑动窗口的位置                最大值
     * ---------------             -----
     * [1  3  -1] -3  5  3  6  7      3
     * 1 [3  -1  -3] 5  3  6  7       3
     * 1  3 [-1  -3  5] 3  6  7       5
     * 1  3  -1 [-3  5  3] 6  7       5
     * 1  3  -1  -3 [5  3  6] 7       6
     * 1  3  -1  -3  5 [3  6  7]      7
     */
    public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums == null || nums.length < k || k < 1) {
            return new int[]{};
        }
        // 双端队列队头存区间最大值下标
        Deque<Integer> maxIndexDeque = new LinkedList<>();
        int[] res = new int[nums.length - k + 1];
        int index = 0;

        for (int i = 0; i < nums.length; i++) {
            // 双端队列,队头保证存窗口内最大值下标
            while (!maxIndexDeque.isEmpty() && nums[maxIndexDeque.peekLast()] <= nums[i]) {
                maxIndexDeque.pollLast();
            }
            // 存下标
            maxIndexDeque.addLast(i);

            // 数字下标与队列头差值>=窗口长度,队头移出队列
            if (i - maxIndexDeque.peekFirst() >= k) {
                maxIndexDeque.pollFirst();
            }

            // 数字下标>=窗口长度下标,就要记录队头元素
            if (i >= k - 1) {
                res[index++] = nums[maxIndexDeque.peekFirst()];
            }
        }
        return res;
    }

    public static void main(String[] args) {
        int[] nums = {1, 3, -1, -3, 5, 3, 6, 7};
        int k = 3;
        Solution solution = new Solution();
        int[] res = solution.maxSlidingWindow(nums, k);
        System.out.println(Arrays.toString(res));
    }

    @Test
    public void deQueTest() {
        Deque<Integer> maxIndexDeque = new LinkedList<>();
        maxIndexDeque.addLast(1);
        maxIndexDeque.addLast(2);
        maxIndexDeque.addLast(3);
        System.out.println(maxIndexDeque.peekLast());
    }
}

哈希表

有效的字母异位词

LC242_有效的字母异位词

public class Solution {

    /**
     * 有效的字母异位词
     * 输入: s = "anagram", t = "nagaram"
     * 输出: true
     */
    public boolean isAnagram(String s, String t) {
        if (s.length() != t.length()) {
            return false;
        }
        int[] map = new int[256];

        for (char c : s.toCharArray()) {
            if (map[c] != 0) {
                map[c]++;
            } else {
                map[c] = 1;
            }
        }

        for (char c : t.toCharArray()) {
            if (map[c] != 0) {
                map[c]--;
                if (map[c] < 0) {
                    return false;
                }
            } else {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        String s = "ab";
        String t = "a";
        System.out.println(solution.isAnagram(s, t));
    }
}

LC1_两数之和

public class Solution {
    /**
     * 两数之和
     */
    public int[] twoSum(int[] nums, int target) {
        if (nums == null || nums.length < 2) {
            return new int[]{-1, -1};
        }
        // 存数组值,对应下标
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            if (!map.containsKey(target - nums[i])) {
                map.put(nums[i], i);
            } else {
                return new int[]{map.get(target - nums[i]), i};
            }
        }
        return new int[]{-1, -1};
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        int[] nums = {2, 7, 11, 15};
        int target = 9;
        System.out.println(Arrays.toString(solution.twoSum(nums, target)));
    }
}

LC15_三数之和

public class Solution {

    /**
     * 三数之和
     * 输入:nums = [-1,0,1,2,-1,-4]
     * 输出:[[-1,-1,2],[-1,0,1]]
     */
    public List<List<Integer>> threeSum(int[] nums) {
        // sort + find:将数组排序后查找
        if (nums == null || nums.length < 3) {
            return new ArrayList<>();
        }
        List<List<Integer>> res = new ArrayList<>();

        Arrays.sort(nums);
        int n = nums.length;
        // 三数之和,需要两重循环
        // 第一重循环:固定数nums[i]
        for (int i = 0; i < n - 2; i++) {
            // 三数之和=0,如果排序后第一个数>0,必不存在
            if (nums[i] > 0) {
                return res;
            }
            // 排序后,i之前相同的数无需重复判断,需要去重
            // 注意:不能是i之后的相同的数去重,因为i之后的数可以相同判断三数之和
            // [-4,-1,-1,0,1,2]
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }

            // 优化1
            if ((long) nums[i] + nums[i + 1] + nums[i + 2] > 0) {
                break;
            }
            // 优化2
            if ((long) nums[i] + nums[n - 2] + nums[n - 1] < 0) {
                continue;
            }

            int left = i + 1;
            int right = n - 1;
            // 第二重循环:找两个数
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum < 0) {
                    left++;
                } else if (sum > 0) {
                    right--;
                } else {
                    res.add(Arrays.asList(nums[i], nums[left], nums[right]));

                    // 去重
                    while (left < right && nums[left] == nums[left + 1]) {
                        left++;
                    }
                    while (left < right && nums[right] == nums[right - 1]) {
                        right--;
                    }

                    left++;
                    right--;
                }
            }

        }
        return res;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        int[] nums = {-1, 0, 1, 2, -1, -4};
        System.out.println(solution.threeSum(nums));
    }
}

LC18_四数之和

public class Solution {
    /**
     * 四数之和
     * 输入:nums = [1,0,-1,0,-2,2], target = 0
     * 输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
     */
    public List<List<Integer>> fourSum(int[] nums, int target) {
        if (nums == null || nums.length < 4) {
            return new ArrayList<>();
        }
        List<List<Integer>> res = new ArrayList<>();

        Arrays.sort(nums);

        int n = nums.length;
        // 三重循环,第一重:固定数nums[i]
        for (int i = 0; i < n - 3; i++) {
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            // 剪枝:当前数+前3个数>target,退出本轮循环
            if ((long) nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) {
                break;
            }

            // 剪枝:当前数+后3个数
            if ((long) nums[i] + nums[n - 3] + nums[n - 2] + nums[n - 1] < target) {
                continue;
            }

            // 第二重:固定数nums[j]
            for (int j = i + 1; j < n - 2; j++) {
                if (j > i + 1 && nums[j] == nums[j - 1]) {
                    continue;
                }

                if ((long) nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) {
                    break;
                }

                if ((long) nums[i] + nums[j] + nums[n - 2] + nums[n - 1] < target) {
                    continue;
                }

                int left = j + 1;
                int right = n - 1;
                // 第三重循环:找两个数
                while (left < right) {
                    int sum = nums[i] + nums[j] + nums[left] + nums[right];
                    if (sum < target) {
                        left++;
                    } else if (sum > target) {
                        right--;
                    } else {
                        res.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));

                        // 去重
                        while (left < right && nums[left] == nums[left + 1]) {
                            left++;
                        }
                        while (left < right && nums[right] == nums[right - 1]) {
                            right--;
                        }

                        left++;
                        right--;
                    }
                }
            }
        }

        return res;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        int[] nums = {2, 2, 2, 2};
        int target = 8;
        System.out.println(solution.fourSum(nums, target));
    }
}

树和二叉树

验证二叉搜索树

public class Solution {
    /**
     * 验证二叉搜索树
     */
    public boolean isValidBST(TreeNode root) {
        if (root == null) {
            return false;
        }
        List<Integer> list = new ArrayList<>();
        inorder(root, list);
        // 判断list元素是否是严格递增的
        for (int i = 1; i < list.size(); i++) {
            int pre = list.get(i - 1);
            int next = list.get(i);
            // 严格递增,有等于也不是严格递增
            if (pre >= next) {
                return false;
            }
        }
        return true;
    }

    private void inorder(TreeNode root, List<Integer> list) {
        if (root == null) {
            return;
        }
        inorder(root.left, list);
        list.add(root.val);
        inorder(root.right, list);
    }


}

最近公共祖先问题

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

public class Solution {

    /**
     * 二叉搜索树的最近公共祖先
     */
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        // 不改变原树结构
        TreeNode cur = root;
        while (true) {
            if (cur.val < p.val && cur.val < q.val) {
                cur = cur.right;
            } else if (cur.val > p.val && cur.val > q.val) {
                cur = cur.left;
            } else {
                break;
            }
        }
        return cur;
    }
}

LC236_二叉树的最近公共祖先

public class Solution {
    /**
     * 二叉树的最近公共祖先
     */
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null || root == p || root == q) {
            return root;
        }
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        // 成功的base case:最近公共祖先,两者都不为空
        if (left != null && right != null) {
            return root;
        }
        return left != null ? left : right;
    }
}

二叉树的遍历

public class NotRecursiveTraverse {
    // 先序非递归遍历(力扣144)
    public static void preTraverse(TreeNode head) {
        if (head == null) {
            return;
        }
        Stack<TreeNode> stack = new Stack<>();
        stack.push(head);
        while (!stack.isEmpty()) {
            head = stack.pop();
            System.out.print(head.value + " ");
            // 先序:先压右孩子,再压左孩子
            if (head.right != null) {
                stack.push(head.right);
            }
            if (head.left != null) {
                stack.push(head.left);
            }
        }
        System.out.println();
    }

    // 中序非递归遍历(力扣94)
    public static void inTraverse(TreeNode head) {
        if (head == null) {
            return;
        }
        Stack<TreeNode> stack = new Stack<>();
        while (!stack.isEmpty() || head != null) {
            // 中序非递归遍历先把所有左子树入栈
            if (head != null) {
                stack.push(head);
                head = head.left;
            } else {
                // 左子树到null就出栈,操作+入右子树
                head = stack.pop();
                System.out.println(head.value + " ");
                head = head.right;
            }
        }
        System.out.println();
    }

    // 后序非递归遍历(力扣145)
    public static void posTraverse(TreeNode head) {
        if (head == null) {
            return;
        }
        Stack<TreeNode> stack = new Stack<>();// 收集栈:存头右左
        Stack<TreeNode> temp = new Stack<>();// 辅助栈:存收集栈每次的出栈
        stack.push(head);
        while (!stack.isEmpty()) {
            // 辅助栈每次收集收集栈的出栈元素
            head = stack.pop();
            temp.push(head);
            // 收集栈每次存左右,出的时候就变成右左了
            if (head.left != null) {
                stack.push(head.left);
            }
            if (head.right != null) {
                stack.push(head.right);
            }
        }
        // 辅助栈依次出栈就是后续遍历
        while (!temp.isEmpty()) {
            System.out.println(temp.pop().value + "");
        }
        System.out.println();
    }
}
public class WidthTraverse {
    // 二叉树的宽度遍历 = 层次遍历(力扣102)
    public List<List<Integer>> levelOrder(TreeNode root) {
        if (root == null) {
            return new ArrayList<>();
        }
        Queue<TreeNode> queue = new LinkedList<>();
        List<List<Integer>> res = new ArrayList<>();
        queue.add(root);
        while (!queue.isEmpty()) {
            // 辅助数组在每次循环内生成,保证每一层都是新的List
            List<Integer> temp = new ArrayList<>();
            // 从利用queue的个数来遍历,让每一层的queue都遍历完
            for (int i = queue.size(); i > 0; i--) {
                TreeNode node = queue.poll();
                temp.add(node.val);
                if (node.left != null) {
                    queue.add(node.left);
                }
                if (node.right != null) {
                    queue.add(node.right);
                }
            }
            res.add(temp);
        }
        return res;
    }
}

递归与分治

Pow

LC50_pow

public class Solution {
    /**
     * pow(x,n)
     * 输入:x = 2.00000, n = 10
     * 输出:1024.00000
     * 方法:快速幂法
     */
    public double myPow(double x, int n) {
        if (n == 0) {
            return 1.0;
        }
        if (n == 1) {
            return x;
        }
        long b = n;
        if (b < 0) {
            x = 1 / x;
            b = -b;
        }

        double res = 1.0;
        while (b > 0) {
            if ((b & 1) == 1) {
                res *= x;
            }

            x *= x;
            b >>= 1;
        }

        return res;

    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        double x = 2.0;
        int n = 10;
        System.out.println(solution.myPow(x, n));
    }
}

求众数

LC169_多数元素

public class Solution1 {
    /**
     * 多数元素
     * 输入:[2,2,1,1,1,2,2]
     * 输出:2
     * 多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
     * 条件给定的数组总是存在多数元素。
     */
    public int majorityElement(int[] nums) {
        // 摩尔投票法
        int x = 0, votes = 0;
        for (int num : nums) {
            if (votes == 0) {
                x = num;
            }

            votes += (x == num) ? 1 : -1;
        }

        int count = 0;
        for (int num : nums) {
            if (x == num) {
                count++;
            }
        }

        if (count > nums.length / 2) {
            return x;
        }

        throw new RuntimeException("不存在多数元素");
    }
}

贪心算法

买卖股票最佳时机

LC121_买卖股票最佳时机I

public class Solution {

    /**
     * LC122_买卖股票最佳时机I
     * 输入:[7,1,5,3,6,4]
     * 输出:5
     * 解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     * 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
     */
    public int maxProfit(int[] prices) {
        // 找出买卖一只股票获得最大利润
        int min = prices[0];
        int profit = 0;
        for (int i = 1; i < prices.length; i++) {
            min = Math.min(min, prices[i]);
            profit = Math.max(profit, prices[i] - min);
        }
        return profit;
    }
}

LC122_买卖股票最佳时机II

public class Solution {

    /**
     * LC122_买卖股票最佳时机II
     * 输入: prices = [7,1,5,3,6,4]
     * 输出: 7
     * 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
     * 在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
     */
    public int maxProfit(int[] prices) {
        if (prices.length < 2) {
            return 0;
        }
        int profit = 0;
        // 贪心:只要后一天价格>前一天价格,就前一天买入,后一天卖出
        for (int i = 0; i < prices.length - 1; i++) {
            if (prices[i] < prices[i + 1]) {
                profit += prices[i + 1] - prices[i];
            }
        }
        return profit;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        int[] nums = {7, 1, 5, 3, 6, 4};
        System.out.println(solution.maxProfit(nums));
    }
}
public class Solution1 {

    /**
     * LC122_买卖股票最佳时机II
     * 输入: prices = [7,1,5,3,6,4]
     * 输出: 7
     * 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
     * 在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
     */
    public int maxProfit(int[] prices) {
        if (prices.length < 2) {
            return 0;
        }
        // 动态规划:dp[i][j]表示第i天买还是不买股票
        int[][] dp = new int[prices.length][2];

        dp[0][0] = 0;
        dp[0][1] = -prices[0];

        for (int i = 1; i < prices.length; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
        }

        return dp[prices.length - 1][0];
    }

    public static void main(String[] args) {
        Solution1 solution = new Solution1();
        int[] nums = {7, 1, 5, 3, 6, 4};
        System.out.println(solution.maxProfit(nums));
    }
}

BFS和DFS

二叉树层次遍历

public class Solution {

    /**
     * 二叉树层次遍历
     * 输入:root = [3,9,20,null,null,15,7]
     * 输出:[[3],[9,20],[15,7]]
     */
    public List<List<Integer>> levelOrder(TreeNode root) {
        if (root == null) {
            return new ArrayList<>();
        }
        List<List<Integer>> res = new ArrayList<>();

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

        while (!queue.isEmpty()) {
            // 每一次都新开辟temp,temp就无须新清空
            List<Integer> temp = new ArrayList<>();
            for (int i = queue.size(); i > 0; i--) {
                TreeNode node = queue.poll();
                temp.add(node.val);

                if (node.left != null) {
                    queue.offer(node.left);
                }

                if (node.right != null) {
                    queue.offer(node.right);
                }

            }

            res.add(temp);

        }
        return res;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        TreeNode root = new TreeNode(3);
        TreeNode node1 = new TreeNode(9);
        TreeNode node2 = new TreeNode(20);
        TreeNode node3 = new TreeNode(15);
        TreeNode node4 = new TreeNode(7);
        root.left = node1;
        root.right = node2;
        node2.left = node3;
        node2.right = node4;
        System.out.println(solution.levelOrder(root));
    }
}

二叉树最大和最小深度

LC104_二叉树最大深度

public class Solution {
    /**
     * 二叉树最大深度
     * 给定二叉树 [3,9,20,null,null,15,7]
     * 返回最大深度3
     */
    public int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
    }
}

LC111_二叉树最小深度

public class Solution {
    /**
     * 二叉树最小深度
     * 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
     * 输入:root = [3,9,20,null,null,15,7]
     * 输出:2
     */
    public int minDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int left = minDepth(root.left);
        int right = minDepth(root.right);
        // 左右子树有一个为0
        if (left == 0 || right == 0) {
            return left + right + 1;
        }
        // 左右子树都不为0
        return Math.min(left, right) + 1;
    }

    public static void main(String[] args) {
        TreeNode root = new TreeNode(3);
        TreeNode node1 = new TreeNode(9);
        TreeNode node2 = new TreeNode(20);
        TreeNode node3 = new TreeNode(15);
        TreeNode node4 = new TreeNode(7);
        root.left = node1;
        root.right = node2;
        node2.left = node3;
        node2.right = node4;

        Solution solution = new Solution();
        System.out.println(solution.minDepth(root));
    }
}

生成有效括号组合

LC22_括号生成

public class Solution {
    /**
     * 括号生成
     * 生成所有可能且有效的括号组合
     * 输入:n = 3
     * 输出:["((()))","(()())","(())()","()(())","()()()"]
     */
    public List<String> generateParenthesis(int n) {
        List<String> res = new ArrayList<>();
        // n对括号,会生成2n长的字符串,使用dfs
        recursion(n, n, "", res);
        return res;
    }

    private void recursion(int leftNeed, int rightNeed, String subList, List<String> res) {
        if (leftNeed == 0 && rightNeed == 0) {
            res.add(subList);
            return;
        }
        // 还有需要的左括号,就添加左括号
        if (leftNeed > 0) {
            recursion(leftNeed - 1, rightNeed, subList + "(", res);
        }
        // 还需要左括号<还需要的右括号 = 已经生成的左括号>已经生成的右括号,还需要补充右括号
        if (leftNeed < rightNeed) {
            recursion(leftNeed, rightNeed - 1, subList + ")", res);
        }
    }
}

剪枝

N皇后问题

LC51_N皇后

public class Solution {

    List<List<String>> res = new ArrayList<>();

    /**
     * N皇后
     * 输入:n = 4
     * 输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
     * 解释:如上图所示,4 皇后问题存在两个不同的解法。
     */
    public List<List<String>> solveNQueens(int n) {
        // 初始化cs二维数组,根据题目条件初始化放入.
        char[][] cs = new char[n][n];
        for (char[] c : cs) {
            Arrays.fill(c, '.');
        }

        backTrack(cs, 0, n);

        return res;
    }


    public void backTrack(char[][] cs, int i, int n) {
        // 终止条件:row越过n-1,说明这是一种正确的放法
        if (i == n) {
            List<String> list = new ArrayList<>();
            for (char[] c : cs) {
                // char[]直接转成String,加进list中
                list.add(String.copyValueOf(c));
            }

            res.add(list);
            return;
        }
        for (int j = 0; j < n; ++j) {
            // 到达一行,遍历该行的每一列是否与之前的皇后是否产生攻击
            if (isValid(cs, i, j, n)) {
                // 将有效当期位设为Q
                cs[i][j] = 'Q';
                // 递归下一行
                backTrack(cs, i + 1, n);
                // 回溯,当前有效位还原,循环开启下一行
                cs[i][j] = '.';
            }
        }
    }

    public boolean isValid(char[][] cs, int x, int y, int n) {
        // 行不用检查,因为是逐行放入cs中
        // 检查列
        for (int i = 0; i < n; ++i) {
            if (cs[i][y] == 'Q') {
                return false;
            }
        }
        // 检查45°斜线
        for (int i = x - 1, j = y - 1; i >= 0 && j >= 0; i--, j--) {
            if (cs[i][j] == 'Q') {
                return false;
            }
        }
        // 检查135°反斜线
        for (int i = x - 1, j = y + 1; i >= 0 && j <= n - 1; i--, j++) {
            if (cs[i][j] == 'Q') {
                return false;
            }
        }
        return true;
    }
}

LC52_N皇后II

public class Solution {

    private int count = 0;

    /**
     * N皇后II
     * 输入:n = 4
     * 输出:2
     */
    public int totalNQueens(int n) {
        // 初始化cs二维数组,根据题目条件初始化放入.
        char[][] cs = new char[n][n];
        for (char[] c : cs) {
            Arrays.fill(c, '.');
        }

        backTrack(cs, 0, n);

        return count;
    }


    public void backTrack(char[][] cs, int i, int n) {
        // 终止条件:row越过n-1,说明这是一种正确的放法
        if (i == n) {
            count++;
            return;
        }
        for (int j = 0; j < n; ++j) {
            // 到达一行,遍历该行的每一列是否与之前的皇后是否产生攻击
            if (isValid(cs, i, j, n)) {
                // 将有效当期位设为Q
                cs[i][j] = 'Q';
                // 递归下一行
                backTrack(cs, i + 1, n);
                // 回溯,当前有效位还原,循环开启下一行
                cs[i][j] = '.';
            }
        }
    }

    public boolean isValid(char[][] cs, int row, int col, int n) {
        // 行不用检查,因为是逐行放入cs中
        // 检查列
        for (int i = 0; i < n; ++i) {
            if (cs[i][col] == 'Q') {
                return false;
            }
        }
        // 检查45°斜线
        for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
            if (cs[i][j] == 'Q') {
                return false;
            }
        }
        // 检查135°反斜线
        for (int i = row - 1, j = col + 1; i >= 0 && j <= n - 1; i--, j++) {
            if (cs[i][j] == 'Q') {
                return false;
            }
        }
        return true;
    }
}

数独问题

LC36_有效的数独

public class Solution {
    /**
     * 有效的数独
     * 输入:board =
     * [["5","3",".",".","7",".",".",".","."]
     * ,["6",".",".","1","9","5",".",".","."]
     * ,[".","9","8",".",".",".",".","6","."]
     * ,["8",".",".",".","6",".",".",".","3"]
     * ,["4",".",".","8",".","3",".",".","1"]
     * ,["7",".",".",".","2",".",".",".","6"]
     * ,[".","6",".",".",".",".","2","8","."]
     * ,[".",".",".","4","1","9",".",".","5"]
     * ,[".",".",".",".","8",".",".","7","9"]]
     * 输出:true
     * 注意:空白格用'.'表示
     */
    public boolean isValidSudoku(char[][] board) {
        if (board == null || board.length == 0) {
            return false;
        }
        return dfs(board);
    }


    private boolean dfs(char[][] board) {
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                if (board[i][j] != '.') {
                    if (!isValid(board, i, j, board[i][j])) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
     * board[row][col]能否放入c
     */
    private boolean isValid(char[][] board, int row, int col, char c) {
        for (int i = 0; i < 9; i++) {

            // 检查行是否有c
            if (board[i][col] != '.' && board[i][col] == c) {
                // 原本的位置不用检查
                if (i == row) {
                    continue;
                }
                return false;
            }
            // 检查列是否有c
            if (board[row][i] != '.' && board[row][i] == c) {
                if (i == col) {
                    continue;
                }
                return false;
            }
            // 检查3*3的方格里是否有c
            // i=0,1,2都属于第0个方格,所以用/3
            int x = 3 * (row / 3) + i / 3;
            // i=3,4,5都要回到0,1,2的位置,所以用%3
            int y = 3 * (col / 3) + i % 3;
            if (board[x][y] != '.' && board[x][y] == c) {
                if (x == row && y == col) {
                    continue;
                }
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        char[][] cs = {
                {'5', '3', '.', '.', '7', '.', '.', '.', '.'},
                {'6', '.', '.', '1', '9', '5', '.', '.', '.'},
                {'.', '9', '8', '.', '.', '.', '.', '6', '.'},
                {'8', '.', '.', '.', '6', '.', '.', '.', '3'},
                {'4', '.', '.', '8', '.', '3', '.', '.', '1'},
                {'7', '.', '.', '.', '2', '.', '.', '.', '6'},
                {'.', '6', '.', '.', '.', '.', '2', '8', '.'},
                {'.', '.', '.', '4', '1', '9', '.', '.', '5'},
                {'.', '.', '.', '.', '8', '.', '.', '7', '9'}};
        System.out.println(solution.isValidSudoku(cs));
    }


}

LC37_解数独

public class Solution {

    /**
     * 解数独
     */
    public void solveSudoku(char[][] board) {
        if (board == null || board.length == 0) {
            return;
        }
        dfs(board);
    }


    private boolean dfs(char[][] board) {
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                // 空白位置用'.'代替
                if (board[i][j] == '.') {
                    // 每一个空白位置用字符1-9去尝试
                    for (char c = '1'; c <= '9'; c++) {
                        // i,j位置能否放入字符c
                        if (isValid(board, i, j, c)) {
                            board[i][j] = c;
                            // 递归下一个空白位置
                            if (dfs(board)) {
                                return true;
                            } else {
                                board[i][j] = '.';
                            }
                        }
                    }
                    return false;
                }
            }
        }
        return true;
    }


    /**
     * board[row][col]能否放入c
     */
    private boolean isValid(char[][] board, int row, int col, char c) {
        for (int i = 0; i < 9; i++) {
            // 检查行是否有c
            if (board[i][col] != '.' && board[i][col] == c) {
                return false;
            }
            // 检查列是否有c
            if (board[row][i] != '.' && board[row][i] == c) {
                return false;
            }
            // 检查3*3的方格里是否有c
            int x = 3 * (row / 3) + i / 3;
            int y = 3 * (col / 3) + i % 3;
            if (board[x][y] != '.' && board[x][y] == c) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        char[][] cs = {
                {'5', '3', '.', '.', '7', '.', '.', '.', '.'},
                {'6', '.', '.', '1', '9', '5', '.', '.', '.'},
                {'.', '9', '8', '.', '.', '.', '.', '6', '.'},
                {'8', '.', '.', '.', '6', '.', '.', '.', '3'},
                {'4', '.', '.', '8', '.', '3', '.', '.', '1'},
                {'7', '.', '.', '.', '2', '.', '.', '.', '6'},
                {'.', '6', '.', '.', '.', '.', '2', '8', '.'},
                {'.', '.', '.', '4', '1', '9', '.', '.', '5'},
                {'.', '.', '.', '.', '8', '.', '.', '7', '9'}};
        solution.solveSudoku(cs);
    }
}

二分查找

求平方根

LC69_平方根

public class Solution {
    /**
     * 求x平方根
     */
    public int mySqrt(int x) {
        if (x == 0 || x == 1) {
            return x;
        }
        int left = 1, right = x;
        int res = 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (mid < x / mid) {
                left = mid + 1;
                // 左边界移动,res等于mid
                res = mid;
            } else if (mid > x / mid) {
                right = mid - 1;
            } else {
                return mid;
            }
        }

        return res;
    }
}

补充,平方根精确到小数点n位

public class CalSqrt {
    /**
     * 精度:精确到小数点后6位
     */
    private final double ACCURACY = 0.000001;

    /**
     * 附加:求一个数x的平方根,精确到小数点后6位
     */
    public float calSqrt(int x) {
        // 所有类型都为浮点型
        float left = 0;
        float right = x;
        while (Math.abs(right - left) >= ACCURACY) {
            float mid = left + (right - left) / 2;
            float mid2 = mid * mid;
            if (mid2 - x > ACCURACY) {
                right = mid;
            } else if (x - mid2 > ACCURACY) {
                left = mid;
            } else {
                return mid;
            }
        }
        return -1;
    }
}

字典树

实现字典树

LC208_实现Trie

public class Trie {

    static class TrieNode {
        public boolean isWordEnd;
        public TrieNode[] children;

        public TrieNode() {
            // 26个字母
            children = new TrieNode[26];
        }
    }

    private TrieNode root;

    /**
     * 实现一个前缀树
     */
    public Trie() {
        root = new TrieNode();
    }

    public void insert(String word) {
        TrieNode cur = root;
        for (char c : word.toCharArray()) {
            if (cur.children[c - 'a'] == null) {
                cur.children[c - 'a'] = new TrieNode();
            }
            cur = cur.children[c - 'a'];
        }
        cur.isWordEnd = true;
    }

    public boolean search(String word) {
        TrieNode cur = root;
        for (char c : word.toCharArray()) {
            if (cur.children[c - 'a'] != null) {
                cur = cur.children[c - 'a'];
            } else {
                return false;
            }
        }
        return cur.isWordEnd;
    }

    public boolean startsWith(String prefix) {
        TrieNode cur = root;
        for (char c : prefix.toCharArray()) {
            if (cur.children[c - 'a'] != null) {
                cur = cur.children[c - 'a'];
            } else {
                return false;
            }
        }
        return true;
    }
}

二维数组中单词的搜索

LC79_单词搜索

public class Solution {
    /**
     * 单词搜索
     * 输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
     * 输出:true
     */
    public boolean exist(char[][] board, String word) {
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                if (dfs(board, i, j, word, 0)) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean dfs(char[][] board, int i, int j, String word, int index) {
        if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || word.charAt(index) != board[i][j]) {
            return false;
        }
        if (index == word.length() - 1) {
            return true;
        }
        // 剪枝:防止单词搜索回退已检查过的位置
        board[i][j] = '*';

        boolean res = dfs(board, i + 1, j, word, index + 1)
                || dfs(board, i - 1, j, word, index + 1)
                || dfs(board, i, j + 1, word, index + 1)
                || dfs(board, i, j - 1, word, index + 1);
        // 还原
        board[i][j] = word.charAt(index);
        return res;
    }
}

LC212_单词搜索II

  • 方法1
public class Solution {
    /**
     * 单词搜索II
     * 输入:board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]], words = ["oath","pea","eat","rain"]
     * 输出:["eat","oath"]
     */
    public List<String> findWords(char[][] board, String[] words) {
        // 去重:add时会有重复
        Set<String> set = new HashSet<>();
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                for (int k = 0; k < words.length; k++) {
                    if (dfs(board, i, j, words[k], 0)) {
                        set.add(words[k]);
                    }
                }
            }
        }
        // 结果集转成list
        return new ArrayList<>(set);
    }

    /**
     * 复用79_单词搜索的dfs
     */
    private boolean dfs(char[][] board, int i, int j, String word, int index) {
        if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || word.charAt(index) != board[i][j]) {
            return false;
        }
        if (index == word.length() - 1) {
            return true;
        }
        // 剪枝:防止单词搜索回退已检查过的位置
        board[i][j] = '*';

        boolean res = dfs(board, i + 1, j, word, index + 1)
                || dfs(board, i - 1, j, word, index + 1)
                || dfs(board, i, j + 1, word, index + 1)
                || dfs(board, i, j - 1, word, index + 1);
        // 还原
        board[i][j] = word.charAt(index);
        return res;
    }
}

  • 方法2
public class Solution1 {

    private Set<String> set = new HashSet<>();

    /**
     * 单词搜索II
     * 输入:board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]], words = ["oath","pea","eat","rain"]
     * 输出:["eat","oath"]
     * 方法:使用Trie树
     */
    public List<String> findWords(char[][] board, String[] words) {
        // 将所有单词放入前缀树
        Trie trie = new Trie();
        for (String word : words) {
            trie.insert(word);
        }
        int m = board.length;
        int n = board[0].length;
        boolean[][] visited = new boolean[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                dfs(board, i, j, visited, "", trie);
            }
        }

        return new ArrayList<>(set);
    }

    private void dfs(char[][] board, int i, int j, boolean[][] visited, String str, Trie trie) {
        if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || visited[i][j]) {
            return;
        }
        str += board[i][j];
        if (!trie.startsWith(str)) {
            return;
        }

        if (trie.search(str)) {
            // 放入set中
            set.add(str);
        }

        visited[i][j] = true;
        dfs(board, i + 1, j, visited, str, trie);
        dfs(board, i - 1, j, visited, str, trie);
        dfs(board, i, j + 1, visited, str, trie);
        dfs(board, i, j - 1, visited, str, trie);
        visited[i][j] = false;
    }


    /**
     * LC208_实现Trie数
     */
    class Trie {

        class TrieNode {
            public boolean isWordEnd;
            public TrieNode[] children;

            public TrieNode() {
                // 26个字母
                children = new TrieNode[26];
            }
        }

        private TrieNode root;

        /**
         * 实现一个前缀树
         */
        public Trie() {
            root = new TrieNode();
        }

        public void insert(String word) {
            TrieNode cur = root;
            for (char c : word.toCharArray()) {
                if (cur.children[c - 'a'] == null) {
                    cur.children[c - 'a'] = new TrieNode();
                }
                cur = cur.children[c - 'a'];
            }
            cur.isWordEnd = true;
        }

        public boolean search(String word) {
            TrieNode cur = root;
            for (char c : word.toCharArray()) {
                if (cur.children[c - 'a'] != null) {
                    cur = cur.children[c - 'a'];
                } else {
                    return false;
                }
            }
            return cur.isWordEnd;
        }

        public boolean startsWith(String prefix) {
            TrieNode cur = root;
            for (char c : prefix.toCharArray()) {
                if (cur.children[c - 'a'] != null) {
                    cur = cur.children[c - 'a'];
                } else {
                    return false;
                }
            }
            return true;
        }
    }

    public static void main(String[] args) {
        Solution1 solution = new Solution1();
        char[][] board = {
                {'o', 'a', 'a', 'n'},
                {'e', 't', 'a', 'e'},
                {'i', 'h', 'k', 'r'},
                {'i', 'f', 'l', 'v'}};
        String[] words = {"oath", "pea", "eat", "rain"};
        System.out.println(solution.findWords(board, words));

    }

}

位运算

统计位的个数

LC191_位1的个数

public class Solution {
    /**
     * 位1的个数
     * 输入:00000000000000000000000000001011
     * 输出:3
     */
    public int hammingWeight(int n) {
        int count = 0;
        while (n != 0) {
            n &= n - 1;
            count++;
        }
        return count;
    }
}

2的幂次和比特位计数问题

LC231_2的幂

public class Solution {
    /**
     * 2的幂
     * 输入:n = 1
     * 输出:true
     * 解释:2^0 = 1
     */
    public boolean isPowerOfTwo(int n) {
        // 若是2的幂,n & n-1必为0 且 n>0
        return n > 0 && (n & n - 1) == 0;
    }
}

LC338_比特位计数

public class Solution {
    /**
     * LC338_比特位计数
     * 输入:n = 2
     * 输出:[0,1,1]
     * 解释:返回0到n中每个数中1的个数
     * 0 --> 0
     * 1 --> 1
     * 2 --> 10
     */
    public int[] countBits(int n) {
        int[] res = new int[n + 1];
        for (int i = 0; i <= n; i++) {
            res[i] = hammingWeight(i);
        }
        return res;
    }

    /**
     * 汉明码计数=位1的个数
     */
    private int hammingWeight(int n) {
        int count = 0;
        while (n != 0) {
            n &= n - 1;
            count++;
        }
        return count;
    }
}

动态规划

爬楼梯

LC70_爬楼梯

public class Solution {

    /**
     * 爬楼梯
     * 输入:n = 2
     * 输出:2
     * 输入:n = 3
     * 输出:3
     */
    public int climbStairs(int n) {
        if (n < 0) {
            return -1;
        }
        if (n <= 3) {
            return n;
        }
        int a = 1;
        int b = 2;
        int sum;
        for (int i = 0; i < n - 2; i++) {
            sum = a + b;
            a = b;
            b = sum;
        }
        return b;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        int n = 4;
        System.out.println(solution.climbStairs(n));
    }
}

三角形中的最小路径和

LC120_三角形最小路径和

public class Solution {

    /**
     * 三角形最小路径和
     * 输入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
     * 输出:11
     * 解释:如下面简图所示:
     * 2
     * 3 4
     * 6 5 7
     * 4 1 8 3
     * 自顶向下的最小路径和为11(2+3+5+1=11)
     */
    public int minimumTotal(List<List<Integer>> triangle) {
        if (triangle == null || triangle.size() == 0) {
            return -1;
        }
        // 思考自顶向下,贪心是失败的,原因的最底部可能是大数导致结果失败
        // 自底向上,从三角形最后一行往上找最小路径
        int[] dp = new int[triangle.get(triangle.size() - 1).size()];
        // dp初始化为三角形最底层
        for (int i = 0; i < dp.length; i++) {
            dp[i] = triangle.get(triangle.size() - 1).get(i);
        }
        // 从三角形倒数第二层开始动态规划
        for (int i = triangle.size() - 2; i >= 0; i--) {
            for (int j = 0; j < triangle.get(i).size(); j++) {
                // dp=Math.min(下一层相邻)+当前节点值
                dp[j] = Math.min(dp[j + 1], dp[j]) + triangle.get(i).get(j);
            }
        }
        return dp[0];
    }

    public static void main(String[] args) {
        List<List<Integer>> triangle = new ArrayList<>();
        List<Integer> one = new ArrayList<>();
        one.add(2);
        List<Integer> two = new ArrayList<>();
        two.add(3);
        two.add(4);
        List<Integer> three = new ArrayList<>();
        three.add(6);
        three.add(5);
        three.add(7);

        List<Integer> four = new ArrayList<>();
        four.add(4);
        four.add(1);
        four.add(8);
        four.add(3);

        triangle.add(one);
        triangle.add(two);
        triangle.add(three);
        triangle.add(four);

        Solution solution = new Solution();
        int res = solution.minimumTotal(triangle);

        System.out.println(res);
    }
}

乘积最大子序列

LC152_乘积最大子数组

public class Solution {
    /**
     * 乘积最大子数组
     * 注意:子数组指数组的连续子序列
     * 输入: nums = [2,3,-2,4]
     * 输出: 6
     */
    public int maxProduct(int[] nums) {
        int max = Integer.MIN_VALUE;
        // 由于存在负数,导致子数组乘积从最大变成最小
        // 所以每一位数组元素都需要存当前位置的最大乘积、最小乘积
        // iMax:表示0到i的最大乘积
        int iMax = 1;
        // iMin:表示0到i的最小乘积
        int iMin = 1;
        for (int i = 0; i < nums.length; i++) {
            // 遇到负数,交换iMax、iMin
            if (nums[i] < 0) {
                int temp = iMax;
                iMax = iMin;
                iMin = temp;
            }

            iMax = Math.max(iMax * nums[i], nums[i]);
            iMin = Math.min(iMin * nums[i], nums[i]);

            max = Math.max(max, iMax);
        }
        return max;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        int[] nums = {2, 3, -1, 4};
        System.out.println(solution.maxProduct(nums));
    }
}

买卖股票系列

LC188_买卖股票最佳时机IV

public class Solution {

    /**
     * 买卖股票的最佳时机IV
     * 注意:'最多'可以完成k笔交易
     * 输入:k = 2, prices = [2,4,1]
     * 输出:2
     * 解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。
     */
    public int maxProfit(int k, int[] prices) {
        if (prices == null || prices.length == 0) {
            return 0;
        }
        int n = prices.length;
        // n天最多进行n/2笔交易
        // k取交易数最小值即可
        k = Math.min(k, n / 2);
        // buy[i][j]:持有股票下,到第i天交易j次的最大利润
        int[][] buy = new int[n][k + 1];
        // sell[i][j]:不持有股票下,到第i天交易j次的最大利润
        int[][] sell = new int[n][k + 1];

        // 初始化
        buy[0][0] = -prices[0];
        sell[0][0] = 0;
        for (int i = 1; i <= k; i++) {
            // /2防止Integer.Min减数时越界
            buy[0][i] = sell[0][i] = Integer.MIN_VALUE / 2;
        }

        // 动态转移
        for (int i = 1; i < n; i++) {
            // 更新第i天,进行0次交易的最大利润
            buy[i][0] = Math.max(buy[i - 1][0], sell[i - 1][0] - prices[i]);
            for (int j = 1; j <= k; j++) {
                buy[i][j] = Math.max(buy[i - 1][j], sell[i - 1][j] - prices[i]);
                // buy[i - 1][j - 1]
                sell[i][j] = Math.max(sell[i - 1][j], buy[i - 1][j - 1] + prices[i]);
            }
        }
        // 最大利润一定是sell中的最大值
        return Arrays.stream(sell[n - 1]).max().getAsInt();
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        int k = 2;
        int[] nums = {3, 2, 6, 5, 0, 3};
        System.out.println(solution.maxProfit(k, nums));
    }
}

LC309_最佳买卖股票时机含冷冻期

public class Solution {
    /**
     * 最佳买卖股票时机含冷冻期
     * 输入: prices = [1,2,3,0,2]
     * 输出: 3
     * 解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
     */
    public int maxProfit(int[] prices) {
        if (prices == null || prices.length == 0) {
            return 0;
        }
        int n = prices.length;
        // dp[i][j]:第i天最大利润
        // 当来到新的一天,会有以下3种状态,所以是二维数组
        // j=0说明第i天持有股票
        // j=1说明第i天不持有股票,处于冷冻期
        // j=2说明第i天不持有股票,不处于冷冻期
        int[][] dp = new int[n][3];
        // 初始化
        // 第0天持有股票:属于买股票
        dp[0][0] = -prices[0];
        // 第0天不持有股票,处于冷冻期,不存在,假设为0
        dp[0][1] = 0;
        // 第0天不持有股票,不处于冷冻期,不存在,假设为0
        dp[0][2] = 0;

        for (int i = 1; i < n; i++) {
            // 第i天持有股票:前一天不处于冷冻期+今天买股票;前一天持有股票不操作
            dp[i][0] = Math.max(dp[i - 1][2] - prices[i], dp[i - 1][0]);
            // 第i天不持有股票,处于冷冻期:只能是前一天持有股票,今天卖出获得收益
            dp[i][1] = dp[i - 1][0] + prices[i];
            // 第i天不持有股票,不处于冷冻期:今天没有任何操作,取前一天不持有股票两种状态最大值
            dp[i][2] = Math.max(dp[i - 1][1], dp[i - 1][2]);
        }
        return Math.max(dp[n - 1][1], dp[n - 1][2]);
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        int[] nums = {2, 1};
        System.out.println(solution.maxProfit(nums));
    }
}

最长递增子序列

LC300_最长递增子序列

public class Solution {
    /**
     * 最长递增子序列
     * 输入:nums = [10,9,2,5,3,7,101,18]
     * 输出:4
     * 解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
     */
    public int lengthOfLIS(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int[] dp = new int[nums.length];
        Arrays.fill(dp, 1);

        int maxLen = 1;
        for (int i = 0; i < nums.length; i++) {
            // 从[j,i]计算到i时的最长递增子序列长度
            for (int j = 0; j <= i; j++) {
                if (nums[j] < nums[i]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                    maxLen = Math.max(maxLen, dp[i]);
                }
            }
        }
        return maxLen;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        int[] nums = {10, 9, 2, 5, 3, 7, 101, 18};
        System.out.println(solution.lengthOfLIS(nums));
    }

}

零钱兑换

LC322_零钱兑换

public class Solution {
    /**
     * 零钱兑换
     * 输入:coins = [1, 2, 5], amount = 11
     * 输出:3
     * 解释:11 = 5 + 5 + 1
     */
    public int coinChange(int[] coins, int amount) {
        if (coins == null || coins.length == 0) {
            return -1;
        }
        // dp[i]=选出总额为i的最少硬币数
        int[] dp = new int[amount + 1];
        // 遍历[1,amount],因为金额从1到amount
        for (int i = 1; i <= amount; i++) {
            // 预设失败值
            dp[i] = amount + 1;
            for (int coin : coins) {
                if (coin <= i) {
                    dp[i] = Math.min(dp[i], dp[i - coin] + 1);
                }
            }
        }

        return dp[amount] == amount + 1 ? -1 : dp[amount];
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        int[] coins = {1, 2, 5};
        int target = 11;
        System.out.println(solution.coinChange(coins, target));
    }
}

LC72_编辑距离

public class Solution {

    /**
     * 编辑距离
     * 输入:word1 = "horse", word2 = "ros"
     * 输出:3
     * 解释:
     * horse -> rorse (将 'h' 替换为 'r')
     * rorse -> rose (删除 'r')
     * rose -> ros (删除 'e')
     */
    public int minDistance(String word1, String word2) {
        int m = word1.length();
        int n = word2.length();
        // dp[i][j] = word1到i位置转换成word2到j位置的最少步数
        int[][] dp = new int[m + 1][n + 1];
        // 第一列:word2=null,word1每个位置需要删除的步数=最少步数
        for (int i = 1; i <= m; i++) {
            dp[i][0] = i;
        }
        // 第一行:word1=null,word2每个位置需要增加的步数=最少步数
        for (int i = 1; i <= n; i++) {
            dp[0][i] = i;
        }

        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                // 该位置元素相同,最少步数由上一次dp决定
                if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1];
                } else {
                    // “dp[i-1][j-1]表示替换操作,dp[i-1][j] 表示删除操作,dp[i][j-1] 表示插入操作。”的补充理解:
                    // word1="horse",word2="ros",且 dp[5][3] 为例:
                    // (1) dp[i-1][j-1]=dp[4][2]=替换,即先将 word1 的前 4 个字符 hors 转换为 word2 的前 2 个字符 ro
                    // (2) dp[i][j-1]=dp[5][2]=插入,即先将 word1 的前 5 个字符 horse 转换为 word2 的前 2 个字符 ro
                    // (3) dp[i-1][j]=dp[4][3]=删除,即先将 word1 的前 4 个字符 hors 转换为 word2 的前 3 个字符 ros

                    // 有三种操作状态,取三者最小+1
                    dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
                }
            }
        }

        return dp[m][n];
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        String w1 = "horse";
        String w2 = "ros";
        System.out.println(solution.minDistance(w1, w2));
    }
}

并查集

实现一个简单并查集

public class QuickUnionFind {

    private int[] roots;

    public QuickUnionFind(int n) {
        roots = new int[n];
        for (int i = 0; i < n; i++) {
            roots[i] = i;
        }
    }

    private int findRoot(int i) {
        // 找出最大的老大
        int root = i;
        while (root != roots[root]) {
            root = roots[root];
        }
        // 路径压缩,i经过的所有结点都指向老大
        while (i != roots[i]) {
            int temp = roots[i];
            roots[i] = root;
            i = temp;
        }
        // 返回老大
        return root;
    }

    private boolean connected(int p, int q) {
        return findRoot(p) == findRoot(q);
    }

    private void union(int p, int q) {
        int root1 = findRoot(p);
        int root2 = findRoot(q);
        roots[root2] = root1;
    }


}

岛屿数量

LC200_岛屿数量

  • 方法1
public class Solution {
    /**
     * 岛屿数量
     * 输入:grid = [
     * ["1","1","0","0","0"],
     * ["1","1","0","0","0"],
     * ["0","0","1","0","0"],
     * ["0","0","0","1","1"]
     * ]
     * 输出:3
     */
    public int numIslands(char[][] grid) {
        if (grid == null || grid.length == 0) {
            return 0;
        }
        int count = 0;
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
                // 遇到1,就将四周的岛屿染色为0;计数+1
                if (grid[i][j] == '1') {
                    dfs(grid, i, j);
                    count++;
                }
            }
        }
        return count;
    }


    private void dfs(char[][] grid, int i, int j) {
        if (i < 0 || i > grid.length - 1 || j < 0 || j > grid[0].length - 1 || grid[i][j] != '1') {
            return;
        }
        // 染色,成0
        grid[i][j] = '0';
        dfs(grid, i + 1, j);
        dfs(grid, i - 1, j);
        dfs(grid, i, j + 1);
        dfs(grid, i, j - 1);
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        char[][] grid = {{'1', '1', '0', '0', '0'}, {'1', '1', '0', '0', '0'}, {'0', '0', '1', '0', '0'}, {'0', '0', '0', '1', '1'},};
        System.out.println(solution.numIslands(grid));
    }


}

  • 方法2
public class Solution1 {
    /**
     * 岛屿数量
     * 输入:grid = [
     * ["1","1","0","0","0"],
     * ["1","1","0","0","0"],
     * ["0","0","1","0","0"],
     * ["0","0","0","1","1"]
     * ]
     * 输出:3
     */
    public int numIslands(char[][] grid) {
        if (grid == null || grid.length == 0) {
            return 0;
        }

        int m = grid.length;
        int n = grid[0].length;
        UnionFind uf = new UnionFind(grid);

        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                // 遇见'1'=岛屿,就放入并查集中
                if (grid[i][j] == '1') {
                    if (i - 1 >= 0 && grid[i - 1][j] == '1') {
                        uf.union(i * n + j, (i - 1) * n + j);
                    }
                    if (i + 1 < m && grid[i + 1][j] == '1') {
                        uf.union(i * n + j, (i + 1) * n + j);
                    }
                    if (j - 1 >= 0 && grid[i][j - 1] == '1') {
                        uf.union(i * n + j, i * n + j - 1);
                    }
                    if (j + 1 < n && grid[i][j + 1] == '1') {
                        uf.union(i * n + j, i * n + j + 1);
                    }
                }
            }
        }

        return uf.getCount();
    }

    /**
     * 定义并查集数据结构
     */
    class UnionFind {
        int count;
        int[] parent;

        public UnionFind(char[][] grid) {
            count = 0;
            int m = grid.length;
            int n = grid[0].length;
            // 铺平成一维
            parent = new int[m * n];
            for (int i = 0; i < m; ++i) {
                for (int j = 0; j < n; ++j) {
                    if (grid[i][j] == '1') {
                        // 有几行,一维数组下标=行数*n+j
                        parent[i * n + j] = i * n + j;
                        // 初始化时,一个'1'表示一个单独岛屿,有几个'1'就有多少个岛屿
                        ++count;
                    }
                }
            }
        }

        public int find(int i) {
            int root = i;
            while (root != parent[root]) {
                root = parent[root];
            }
            // 路径压缩,i经过的所有结点都指向老大
            while (i != parent[i]) {
                int temp = parent[i];
                parent[i] = root;
                i = temp;
            }
            // 返回老大
            return root;
        }

        public void union(int x, int y) {
            int root1 = find(x);
            int root2 = find(y);
            if (root1 != root2) {
                parent[root2] = root1;
                // 不相等,连通数-1
                count--;
            }
        }

        public int getCount() {
            return count;
        }
    }


    public static void main(String[] args) {
        Solution1 solution = new Solution1();
        char[][] grid = {{'1', '1', '0', '0', '0'}, {'1', '1', '0', '0', '0'}, {'0', '0', '1', '0', '0'}, {'0', '0', '0', '1', '1'},};
        System.out.println(solution.numIslands(grid));
    }


}

LC547_省份数量

public class Solution {
    /**
     * 省份数量
     * 输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
     * 输出:2
     */
    public int findCircleNum(int[][] isConnected) {
        // 初始化并查集
        int[] roots = new int[isConnected.length];
        for (int i = 0; i < roots.length; i++) {
            roots[i] = i;
        }
        // isConnected是N*N的,有几行就有几个城市
        // 初始化省份数量
        int count = isConnected.length;
        for (int i = 0; i < isConnected.length; i++) {
            for (int j = i + 1; j < isConnected.length; j++) {
                // 省份间相互连通,连通的省份数量减1
                if (isConnected[i][j] == 1 && union(roots, i, j)) {
                    count--;
                }
            }
        }
        return count;
    }


    private int find(int[] roots, int i) {
        if (roots[i] != i) {
            roots[i] = find(roots, roots[i]);
        }
        return roots[i];
    }

    private boolean union(int[] roots, int i, int j) {
        int root1 = find(roots, i);
        int root2 = find(roots, j);
        if (root1 != root2) {
            roots[root2] = root1;
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        int[][] nums = {
                {1, 1, 0},
                {1, 1, 0},
                {0, 0, 1}
        };
        System.out.println(solution.findCircleNum(nums));
    }
}

LRU

LRU

LC146_LRU缓存

public class LRUCache {

    private int size;
    private int capacity;
    private Map<Integer, DLinkedNode> cache;
    private DLinkedNode head;
    private DLinkedNode tail;

    public LRUCache(int capacity) {
        this.size = 0;
        this.capacity = capacity;
        this.cache = new HashMap<>(capacity);
        // 题目规定key、value>=0,这里可以传-1表示头尾结点
        this.head = new DLinkedNode(-1, -1);
        this.tail = new DLinkedNode(-1, -1);
        this.head.next = tail;
        this.tail.pre = head;
    }

    public int get(int key) {
        if (size == 0) {
            return -1;
        }
        DLinkedNode node = cache.get(key);
        if (node == null) {
            return -1;
        }

        deleteNode(node);
        removeToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        DLinkedNode node = cache.get(key);
        if (node != null) {
            node.value = value;
            deleteNode(node);
            removeToHead(node);
            return;
        }

        if (size == capacity) {
            // 细节:容量满了,先清理缓存,再删除末尾结点
            cache.remove(tail.pre.key);
            deleteNode(tail.pre);
            size--;
        }

        node = new DLinkedNode(key, value);
        cache.put(key, node);
        // 更新缓存
        removeToHead(node);
        size++;
    }

    private void deleteNode(DLinkedNode node) {
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }

    private void removeToHead(DLinkedNode node) {
        node.next = head.next;
        head.next.pre = node;
        head.next = node;
        node.pre = head;
    }


    /**
     * 定义双向链表结点数据结构
     */
    private class DLinkedNode {
        int key;
        int value;
        DLinkedNode pre;
        DLinkedNode next;

        public DLinkedNode(int key, int value) {
            this.key = key;
            this.value = value;
        }

        public DLinkedNode() {

        }

    }
}

LC460_LFU缓存

public class LFUCache {

    private int size;
    private int capacity;
    private Map<Integer, Node> cache;
    /**
     * 频次对应的双向链表
     */
    private Map<Integer, DoubleLinkedList> freqMap;
    /**
     * 当前最小值
     */
    private int min;

    public LFUCache(int capacity) {
        this.cache = new HashMap<>(capacity);
        this.freqMap = new HashMap<>();
        this.capacity = capacity;
    }

    public int get(int key) {
        Node node = cache.get(key);
        if (node == null) {
            return -1;
        }
        // node增加频次
        addFreq(node);
        return node.value;
    }

    public void put(int key, int value) {
        if (capacity == 0) {
            return;
        }
        Node node = cache.get(key);
        // node存在就更新频次
        if (node != null) {
            node.value = value;
            addFreq(node);
        } else {
            // node不存在
            // 链表满啦,移除最不经常使用的=移除min对应的链表
            if (size == capacity) {
                DoubleLinkedList minList = freqMap.get(min);
                cache.remove(minList.tail.pre.key);
                minList.removeNode(minList.tail.pre);
                size--;
            }

            node = new Node(key, value);
            cache.put(key, node);
            // 获取频次为1的链表
            DoubleLinkedList linkedList = freqMap.get(1);
            if (linkedList == null) {
                linkedList = new DoubleLinkedList();
                freqMap.put(1, linkedList);
            }
            linkedList.addNode(node);
            size++;
            // node不存在,更新最不长使用频次=1
            min = 1;
        }
    }

    private void addFreq(Node node) {
        // 从原freq对应的链表里移除, 并更新min
        int freq = node.freq;
        DoubleLinkedList linkedList = freqMap.get(freq);
        linkedList.removeNode(node);
        // freq = min 且 原freq对应的链表为空
        if (freq == min && linkedList.head.next == linkedList.tail) {
            min = freq + 1;
        }

        // 更新freq
        node.freq++;

        linkedList = freqMap.get(freq + 1);
        if (linkedList == null) {
            linkedList = new DoubleLinkedList();
            freqMap.put(freq + 1, linkedList);
        }

        linkedList.addNode(node);
    }

    class DoubleLinkedList {
        Node head;
        Node tail;

        public DoubleLinkedList() {
            head = new Node();
            tail = new Node();
            head.next = tail;
            tail.pre = head;
        }

        void removeNode(Node node) {
            node.pre.next = node.next;
            node.next.pre = node.pre;
        }

        void addNode(Node node) {
            node.next = head.next;
            head.next.pre = node;
            head.next = node;
            node.pre = head;
        }
    }

    /**
     * 结点
     */
    class Node {
        int key;
        int value;
        int freq = 1;
        Node pre;
        Node next;

        public Node() {
        }

        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }
}

你可能感兴趣的:(数据结构与算法,java,算法,数据结构)