《编程能力基础》刷题笔记(41 题)

《编程能力基础》刷题笔记

刷题代码:https://gitee.com/szluyu99/my-leet-code

1. 单调数列

题目:896. 单调数列

题解:递归、模拟、API

因为题目看着让人很想递归,先送上个递归做法:(强行递归,必定超时)

/**
 * 递归(超时)
 */
function isMonotonic(nums: number[]): boolean {
    return nums[0] < nums[nums.length - 1] ?
        isMonotonicIncrease(nums) : isMonotonicDecrease(nums)
};

// 递归判断是否单调递
const isMonotonicIncrease = function (nums: number[]): boolean {
    if (nums.length == 1) return true
    if (nums[0] > nums[1]) return false
    return isMonotonicIncrease(nums.slice(1)) ? true : false
}

// 递归判断是否单调减
const isMonotonicDecrease = function (nums: number[]): boolean {
    if (nums.length == 1) return true
    if (nums[0] < nums[1]) return false
    return isMonotonicDecrease(nums.slice(1)) ? true : false
}

然后才是正文。

解法1

  • 通过 nums[0]nums[1] 可以判断这个序列是 递增 还是 递减
  • 明确了目标,然后只需要遍历时两两比较即可,遇到不满足条件的情况直接返回 false
/**
* 通过 nums[0] 和 nums[nums.length - 1] 判断出增减方向
* 遍历数组一次,两两比较
*/
public boolean isMonotonic1(int[] nums) {
    // 先判断 递增 还是 递减
    if (nums[0] <= nums[nums.length - 1]) { // 递增
        for (int i = 1; i < nums.length; i++) 
            if (nums[i - 1] > nums[i]) return false;
    } else { // 递减
        for (int i = 1; i < nums.length; i++) 
            if (nums[i - 1] < nums[i]) return false;
    }
    return true;
}

解法2

  • 如果没有先判断出序列是 递增 还是 递减,也没关系
  • 可以先假设又递增又递减,然后判断时如果遇到一个不满足条件的情况就可以排除这个假设了
  • 中间如果加上一步减枝(可以快 1s)
public boolean isMonotonic(int[] nums) {
    boolean inc = true, dec = true;

    for (int i = 0; i < nums.length - 1; i++) {
        if (nums[i] > nums[i + 1]) inc = false;
        if (nums[i] < nums[i + 1]) dec = false;
        // 剪枝操作, 已经明确既不递增也不递减, 直接返回 false
        if (!inc && !dec) return false;
    }
    return inc || dec;
}

解法3:然后就是利用语言特性和 API 的做法了,比如 JS

var isMonotonic = function (nums) {
    return isSorted(nums) || isSorted(nums.reverse())
}
// 判断是否单调增
function isSorted(nums) {
    return nums.slice(1).every((item, i) => nums[i] <= item)
}

2. 实现 strStr()

题目:28. 实现 strStr()

题解:API、暴力、滑动窗口

先来一个每个人必定会尝试一下的解法:库函数

public int strStr(String haystack, String needle) {
    return haystack.indexOf(needle);
}

再来个比较常规但是会超时的暴力解法:暴力

public int strStr1(String haystack, String needle) {
    if (haystack.length() < needle.length()) return -1;
    if (needle.isEmpty()) return 0;
    for (int i = 0; i < haystack.length(); i++) {
        int tmpIdx = 0;
        while (i + tmpIdx < haystack.length()
            && haystack.charAt(i + tmpIdx) == needle.charAt(tmpIdx++)) 
            if (tmpIdx == needle.length()) return i;
    }
    return -1;
}

然后来个可以正常通过的解法:滑动窗口

public int strStr2(String haystack, String needle) {
    if (haystack.length() < needle.length()) return -1;
    // 窗口长度
    int width = needle.length();
    // 当前索引
    int idx = 0;
    // 只访问不会越界的部分
    while (idx + width <= haystack.length()) {
        // 找到则返回索引
        if (needle.equals(haystack.substring(idx, idx + width)))
            return idx;
        idx++; 
    }
    return -1;
}

至于其他比较强力的算法如 KMP 等等,待我功力深厚以后再回来将其拿下!

3. 平衡二叉树

题目:110. 平衡二叉树

题解:迭代

解法1:常规思路,先写一个 获取二叉树节点高度 的函数,然后对二叉树进行 遍历 + 判断

const isBalanced = function (root: TreeNode | null): boolean {
    if (!root) return true
    if (Math.abs(getHeight(root.left) - getHeight(root.right)) > 1)
        return false
    return isBalanced(root.left) && isBalanced(root.right)
}
// 获取二叉树节点的高度
const getHeight = (root: TreeNode | null) => {
    if (!root) return 0
    return Math.max(getHeight(root.left), getHeight(root.right)) + 1
}

解法2:这个有点难理解,其实是解法1的升级版

  • 解法1一定会遍历完整个树,解法2不一定,遍历时发现不满足条件就会直接结束
const isBalanced = function (root: TreeNode | null): boolean {
    return recur(root) != -1
}

const recur = function (root: TreeNode | null): number {
    if (!root) return 0
    let left = recur(root.left)
    if (left == -1) return -1
    let right = recur(root.right)
    if (right == -1) return -1
    return Math.abs(left - right) < 2 ? Math.max(left, right) + 1 : -1
}

4. 重复的子字符串

题目:459. 重复的子字符串

题解:模拟、技巧

这道题比较好想的思路是模拟,但是模拟也是有讲究的,要尽量减少暴力的次数(俗称剪枝)

在我的理解中,剪枝属于一种优化,其实把剪枝部分去掉应该也可以正常运行,但是效率会变低

解法1:模拟 + 剪枝 image.png

const repeatedSubstringPattern = function (s: string): boolean {
    if (s.length < 2) return false
    // 只需要遍历一半即可, 一半以后必然不能重复
    for (let i = 1; i <= s.length / 2; i++) {
        let sub = s.substring(0, i)
        // 剪枝:字符串的长度不是串的整数倍, 必然不满足条件
        if (s.length % i != 0) continue
        // 剪枝:最后不是以该子串结尾, 必然不满足条件
        if (sub != s.substring(s.length - sub.length)) continue
        // 利用 repeat API, 查看是否满足满足要求
        if (sub.repeat(s.length / sub.length) == s)
            return true
    }
    return false
}

解法2:技巧

这个思路是参考其他大佬的,确实很奇妙,学习了,可以留个印象

简单明了!!关于java两行代码实现的思路来源

[1] s = acd
[2] ss = acdacd
[3] ss.substring(1, ss.length - 1) = cdac (去头去尾)
判断: [3] 中包含 [1] 则满足条件, s = 'acd' 不满足条件
---

[1] s = acaaca
[2] ss = acaacaacaaca
[3] ss.substring(1, ss.length - 1) = caacaacaac (去头去尾)
判断: [3] 中包含 [1] 则满足条件, s = 'acaaca' 满足条件
const repeatedSubstringPattern = function (s: string): boolean {
    let ss = s.repeat(2)
    return ss.substring(1, ss.length - 1).indexOf(s) != -1
}

5. 逆波兰表达式求值

题目:150. 逆波兰表达式求值

题解:用栈模拟、用数组模拟

解法1:用栈模拟,会使这道题万分简单

public int evalRPN(String[] tokens) {
    Stack<Integer> stack = new Stack<>();
    for (String token : tokens) {
        if ("+".equals(token)) {
            Integer num1 = stack.pop();
            Integer num2 = stack.pop();
            stack.push(num2 + num1);
        } else if ("-".equals(token)) {
            Integer num1 = stack.pop();
            Integer num2 = stack.pop();
            stack.push(num2 - num1);
        } else if ("*".equals(token)) {
            Integer num1 = stack.pop();
            Integer num2 = stack.pop();
            stack.push(num2 * num1);
        } else if ("/".equals(token)) {
            Integer num1 = stack.pop();
            Integer num2 = stack.pop();
            stack.push(num2 / num1);
        } else {
            stack.push(Integer.parseInt(token));
        }
    }
    return stack.pop();
}

代码丑陋,略微优化一下:(效率无影响)

public int evalRPN(String[] tokens) {
    Stack<Integer> stack = new Stack<>();
    for (String token : tokens) {
        if ("+".equals(token))
            stack.push(stack.pop() + stack.pop());
        else if ("-".equals(token))
            stack.push(-stack.pop() + stack.pop());
        else if ("*".equals(token))
            stack.push(stack.pop() * stack.pop());
        else if ("/".equals(token)) {
            Integer num1 = stack.pop(), num2 = stack.pop();
            stack.push(num2 / num1);
        } else
            stack.push(Integer.parseInt(token));
    }
    return stack.pop();
}

解法2:用数组代替栈模拟

class Solution {
    public int evalRPN(String[] tokens) {
        int[] res = new int[tokens.length];
        int cur = 1; // 索引从 - 1 开始, 因为必须放进元素才能开始计算
        for (String token : tokens) {
            if ("/*-+".contains(token)) {
                int b = res[cur--], a = res[cur--];
                res[++cur] = calc(a, b, token);
            } else
                res[++cur] = Integer.parseInt(token);
        }
        return res[cur];
    }

    public int calc(int a, int b, String op) {
        if (op.equals("+")) return a + b;
        else if (op.equals("-")) return a - b;
        else if (op.equals("*")) return a * b;
        else if (op.equals("/")) return a / b;
        else return -1;
    }
}

6. 加一

题目:66. 加一

题解:分情况模拟及其优化

解法1:最朴素的模拟做法,这个基本上是第一反映想到的,速度也可以击败 100%

public int[] plusOne(int[] digits) {
    int len = digits.length;
    // 1 不会进位的情况(最后一位不为 9)
    // 例如: 123 --> 124
    if (digits[len - 1] != 9) {
        digits[len - 1] += 1;
        return digits;
    }

    // 2 全是9的特殊情况: 
    // 例如: 999 --> 1000
    boolean flag = true; // 是否全是9
    for (int i = 0; i < len; i++) {
        if (digits[i] != 9) {
            flag = false;
            break;
        }
    }
    if (flag) {
        int[] res = new int[len + 1];
        res[0] = 1;
        return res;
    }

    // 3 最后一位是9, 但是不全为 9
    // 例如: 129 --> 130
    digits[len - 1] += 1;
    while (len > 1) {
        // 无需进位, 直接跳出返回
        if (digits[len - 1] != 10) break;
        // 计算进位
        digits[len - 1] = 0;
        digits[--len - 1] += 1;
    }
    return digits;
}

解法2:其实是对以上代码的优化

public int[] plusOne(int[] digits) {
    for (int i = digits.length - 1; i >= 0; i--) {
        // 最后一位不为 9, 直接 + 1 返回结果
        if (digits[i] != 9) {
            digits[i]++;
            return digits;
        }
        // 最后一位为 9 或 10, 需要进位
        digits[i] = 0;
    }
    // 走完 for 还没有 return, 说明数字全是 9, 直接构造出结果并返回
    int[] res = new int[digits.length + 1];
    res[0] = 1;
    return res;
}

7. 二叉树中的列表

题目:1367. 二叉树中的列表

题解:递归

解法1:一个递归(超时)

boolean isSubPath(ListNode head, TreeNode root) {
    if (root == null) return false;
    if (head == null) return true;
    // 找到了与链表首节点相同的树节点
    if (root.val == head.val) {
        // 走到链表尾部, 表示已经找到和链表对应的路径
        if (head.next == null) return true;
        // 沿着这条路径走下去, 能对应上的则返回true
        if (root.left != null && head.next.val == root.left.val 
            && isSubPath(head.next, root.left))
            return true;
        if (root.right != null && head.next.val == root.right.val 
            && isSubPath(head.next, root.right))
            return true;
    }
    return isSubPath(head, root.left) || isSubPath(head, root.right);
}

解法2:两个递归(可 AC)

class Solution {
    boolean isSubPath(ListNode head, TreeNode root) {
        if (root == null) return false;
        if (dfs(head, root)) return true;
        return isSubPath(head, root.left) || isSubPath(head, root.right);
    }
         
    boolean dfs(ListNode head, TreeNode root) {
        // 链表全部匹配完, 匹配成功
        if (head == null) return true;
        // 二叉树访问到空节点, 匹配失败
        if (root == null) return false;
        // 当前二叉树上的节点与链表节点值不相等, 匹配失败
        if (root.val != head.val) return false;
        return dfs(head.next, root.left) || dfs(head.next, root.right);
    }
}

8. 字符串相乘

题目:43. 字符串相乘

9. 二进制求和

题目:67. 二进制求和

题解 :模拟

这题可以根据题意直接模拟,最多也就是代码长点,if 条件多点,先做出来再说!

以下两个解法就是代码长短问题,效率是一样的。

解法1:模拟 + 各种 if + StringBuilder

public String addBinary(String a, String b) {
    StringBuilder sb = new StringBuilder();
    int carry = 0;
    int p1 = a.length(), p2 = b.length();
    while (p1 >= 0 || p2 >= 0) {
        char ca = (--p1 >= 0) ? a.charAt(p1) : '0';
        char cb = (--p2 >= 0) ? b.charAt(p2) : '0';

        if (ca == '1' && cb == '1') {
            if (carry == 1) sb.insert(0, "1");
            else sb.insert(0, "0");
            carry = 1;
            continue;
        }

        if (ca == '0' && cb == '0') {
            if (carry == 1) sb.insert(0, "1");
            else sb.insert(0, "0");
            carry = 0;
            continue;
        }

        if (carry == 1) {
            sb.insert(0, "0");
            carry = 1;
        } else {
            sb.insert(0, "1");
            carry = 0;
        }
    }
    return sb.charAt(0) == '0' ? sb.substring(1).toString() : sb.toString();
}

题解2:善用 'a' - '0' 这种方式转数字,优化代码

public String addBinary(String a, String b) {
    StringBuilder sb = new StringBuilder();
    int i = a.length() - 1, j = b.length() - 1;
    int c = 0; // 进位
    while (i >= 0 || j >= 0) {
        if (i >= 0) c += a.charAt(i--) - '0';
        if (j >= 0) c += b.charAt(j--) - '0';
        sb.append(c & 1);
        c >>= 1;
    }
    String res = sb.reverse().toString();
    return c > 0 ? "1" + res : res;
}

10. 数组形式的整数加法

题目:989. 数组形式的整数加法

题解:模拟

这题常规思路是比较容易做出来的,就是从后往前遍历,然后相加,同时保存一个进位,每次注意更新进位的值就行了

基于以上思路,Java 中使用 ArrayList 的话,前插 res.Add(0, val) 效率不如直接后插然后反转。。。

解法1:模拟,用 ArrayList 往后插入数据,然后反转容器

public List<Integer> addToArrayForm(int[] num, int k) {
    List<Integer> res = new ArrayList<>();
    int carry = 0, len = num.length - 1;
    while (len >= 0 || k != 0) {
        int n1 = len >= 0 ? num[len--] : 0;
        int n2 = k % 10;
        k /= 10;
        res.add((n1 + n2 + carry) % 10);
        carry = n1 + n2 + carry >= 10 ? 1 : 0;
    }
    if (carry == 1) res.add(1);
    Collections.reverse(res);
    return res;
}

但是如果使用 LinkedList 的前插 res.addFirst(val) 效果会很快

解法2:模拟,用 LinkedList 直接往前插入数据

public List<Integer> addToArrayForm(int[] num, int k) {
    LinkedList<Integer> res = new LinkedList<>();
    int carry = 0, len = num.length - 1;
    while (len >= 0 || k != 0) {
        int n1 = len >= 0 ? num[len--] : 0;
        int n2 = k % 10;
        k /= 10;
        res.addFirst((n1 + n2 + carry) % 10);
        carry = n1 + n2 + carry >= 10 ? 1 : 0;
    }
    if (carry == 1) res.addFirst(1);
    return res;
}

解法3:还有很多其他做法,比如将数字转成 int 数组后操作…

public List<Integer> addToArrayForm(int[] num, int k) {
    String[] ss = String.valueOf(k).split("");
    // string[] => int[]
    int[] is = Arrays.stream(ss).mapToInt(Integer::valueOf).toArray();
    List<Integer> res = new ArrayList<>();
    int carry = 0;
    int p1 = num.length - 1, p2 = is.length - 1;
    while (p1 >= 0 || p2 >= 0) {
        int n1 = p1 >= 0 ? num[p1--] : 0;
        int n2 = p2 >= 0 ? is[p2--] : 0;
        int sum = (n1 + n2 + carry) % 10;
        carry = (n1 + n2 + carry) >= 10 ? 1 : 0;
        res.add(0, sum);
    }
    if (carry == 1) res.add(0, 1);
    return res;
}

11. 每日温度

题目:739. 每日温度

题解:暴力、单调 栈

解法1:暴力

public int[] dailyTemperatures(int[] T) {
    int[] res = new int[T.length];
    for (int i = 0; i < T.length; i++) {
        for (int j = i + 1; j < T.length; j++) {
            if (T[j] > T[i]) {
                res[i] = j - i;
                break;
            }
        }
    }
    return res;
}

解法2:单调递减栈

注意:Java 中建议使用 Deque 而不是用 Stack

public int[] dailyTemperatures(int[] T) {
    // 单调递减栈(栈中存储的是下标)
    Deque<Integer> stack = new ArrayDeque<>();
    int[] res = new int[T.length];
    for (int i = 0; i < T.length; i++) {
        // 当前元素 > 栈顶下标对应元素
        while (!stack.isEmpty() && T[i] > T[stack.peekLast()]) {
            int temp = stack.removeLast(); // 获取栈顶下标
            res[temp] = i - temp; // 计算下标距离
        }
        stack.addLast(i); // 当前位置入栈
    }
    return res;
}

12.最后一个单词长度

题目:58. 最后一个单词的长度

题解:API

解法1:暴力使用 API

public int lengthOfLastWord(String s) {
    String[] words = s.split("\\s+");
    return words[words.length - 1].length();
}

解法2:轻度使用 API

public int lengthOfLastWord(String s) {
    s = s.trim();
    for (int i = s.length() - 1; i >= 0; i--)
        if (s.charAt(i) == ' ')
            return s.length() - i - 1;
    return s.length();
}

解法3:不使用 API

public int lengthOfLastWord(String s) {
    int i = s.length() - 1;
    // 去除末尾的空字符
    while (s.charAt(i) == ' ') i--;
    int cnt = 0;
    while (i >= 0 && s.charAt(i) != ' ') {
        cnt++;
        i--;
    }
    return cnt;
}

13. 旋转矩阵

题目:48. 旋转图像

题解:模拟 + 技巧

解法1:暴力模拟

  • 找到每个要交换的点的位置,然后模拟进行交换操作
public void rotate(int[][] matrix) {
    int n = matrix.length;
    for (int i = 0; i < n / 2; i++) {
        for (int j = i; j < n - i - 1; j++) {
            // 00 -> 02
            // 01 -> 12
            int temp = matrix[j][n - i - 1];
            matrix[j][n - i - 1] = matrix[i][j];
            // 02 -> 22
            // 12 -> 21
            int temp2 = matrix[n - i - 1][n - j - 1];
            matrix[n - i - 1][n - j - 1] = temp;
            // 22 -> 20
            // 21 -> 10
            temp = matrix[n - j - 1][i];
            matrix[n - j - 1][i] = temp2;
            // 20 -> 00
            // 10 -> 01
            matrix[i][j] = temp;
        }
    }
}

优化以上代码:

public void rotate(int[][] matrix) {
    int n = matrix.length - 1;
    for (int i = 0; i <= matrix.length / 2; i++) {
        for (int j = i; j < n - i; j++) {
            // 获取各顶点的值
            int a = matrix[i][j]; // 左上角
            int b = matrix[j][n - i]; // 右上角
            int c = matrix[n - i][n - j]; // 右下角
            int d = matrix[n - j][i]; // 左下角
            // 交换各顶点的值
            matrix[i][j] = d;
            matrix[j][n - i] = a;
            matrix[n - i][n - j] = b;
            matrix[n - j][i] = c;
        }
    }
}

解法2:技巧

  • 先沿对角线翻转,再沿垂直竖线翻转,即可实现旋转矩阵
public void rotate(int[][] matrix) {
    int n = matrix.length;
    // 先沿对角线翻转
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < i; j++) {
            int temp = matrix[i][j];
            matrix[i][j] = matrix[j][i];
            matrix[j][i] = temp;
        }
    }
    // 再沿垂直竖线翻转
    for (int i = 0; i < n; i++) {
        for (int j = 0, k = n - 1; j < k; j++, k--) {
            int temp = matrix[i][k];
            matrix[i][k] = matrix[i][j];
            matrix[i][j] = temp;
        }
    }
}

14. 判断矩阵经轮转后是否一致

题目:1886. 判断矩阵经轮转后是否一致

n x n 矩阵旋转总结:

  • 90 度:沿左上到右下翻转,再沿垂直中线翻转
  • 180 度:沿水平中线翻转,沿垂直中线翻转
  • 270 度:沿左上到右下翻转,再沿水平中线翻转
  • 360 度:本身

题解:模拟

n x n 矩阵旋转总结:

  • 90 度:沿左上到右下翻转,再沿垂直中线翻转
  • 180 度:沿水平中线翻转,沿垂直中线翻转
  • 270 度:沿左上到右下翻转,再沿水平中线翻转
  • 360 度:本身

但是如果不是要一下子求出结果,而是要每个轮流判断,只需要利用转 90 度就行。

本题解法:暴力模拟

  • 先判断不转(相当于转 360度)是否满足两矩阵相等
  • 再判断转 90度 是否满足两矩阵相等
  • 再判断转 180度 满足两矩阵相等(在上面基础上再转 90度)
  • 再判断转 270度 满足两矩阵相等(在上面基础上再转 90度)

转 90 度的两种思路:

  • 思路1:暴力模拟,计算好 4 个顶点,模拟移动到下一个位置
public void rotate(int[][] matrix) {
    int n = matrix.length - 1;
    for (int i = 0; i <= matrix.length / 2; i++) {
        for (int j = i; j < n - i; j++) {
            // 获取各顶点的值
            int a = matrix[i][j]; // 左上角
            int b = matrix[j][n - i]; // 右上角
            int c = matrix[n - i][n - j]; // 右下角
            int d = matrix[n - j][i]; // 左下角
            // 交换各顶点的值
            matrix[i][j] = d;
            matrix[j][n - i] = a;
            matrix[n - i][n - j] = b;
            matrix[n - j][i] = c;
        }
    }
}

思路2:技巧,也就是上面的矩阵旋转总结中,沿左上到右下翻转,再沿垂直中线翻转

public void rotate(int[][] matrix) {
    int n = matrix.length;
    // 先沿对角线翻转
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < i; j++) {
            int temp = matrix[i][j];
            matrix[i][j] = matrix[j][i];
            matrix[j][i] = temp;
        }
    }
    // 再沿垂直竖线翻转
    for (int i = 0; i < n; i++) {
        for (int j = 0, k = n - 1; j < k; j++, k--) {
            int temp = matrix[i][k];
            matrix[i][k] = matrix[i][j];
            matrix[i][j] = temp;
        }
    }
}

根据以上的基础,可以知道本题代码为下:

class Solution {
    public boolean findRotation(int[][] mat, int[][] target) {
        // 不转
        if (checkMatrix(mat, target)) return true;
        // 转 90 度
        rotate(mat);
        if (checkMatrix(mat, target)) return true;
        // 转 180 度(再转 90 度)
        rotate(mat);
        if (checkMatrix(mat, target)) return true;
        // 转 270 度(再转 90 度)
        rotate(mat);
        if (checkMatrix(mat, target)) return true;
        return false;
    }

    /**
     * 将矩阵旋转 90 度
     */
    void rotate(int [][] matrix) {
        int n = matrix.length;
        // 沿 左上->右下 翻转
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < i; j++) {
                int temp = matrix[i][j];
                matrix[i][j] = matrix[j][i];
                matrix[j][i] = temp;
            }
        }
        // 沿 垂直中线 翻转
        for (int i = 0; i < n; i++) {
            for (int j = 0, k = n -1; j < k; j++, k--) {
                int temp = matrix[i][k];
                matrix[i][k] = matrix[i][j];
                matrix[i][j] = temp;
            }
        }
    }

    /**
     * 检查两矩阵是否相等
     */
    boolean checkMatrix(int[][] m1, int[][] m2) {
        for (int i = 0; i < m1.length; i++) {
            for (int j = 0; j < m1[0].length; j++) {
                if (m1[i][j] != m2[i][j]) return false; 
            }
        }
        return true;
    }
}

15. 螺旋矩阵

题目:54. 螺旋矩阵

题解:模拟

最朴素易懂的做法:

public List<Integer> spiralOrder(int[][] matrix) {
    int m = matrix.length, n = matrix[0].length;
    List<Integer> res = new ArrayList<>();
    int direction = 1; // 1234 - 右下左上
    boolean[][] visited = new boolean[m][n]; // 记录访问过的坐标, 防止重复访问
    int x = 0, y = 0; // 当前访问坐标
    // 初始化第一个位置
    res.add(matrix[0][0]);
    visited[0][0] = true;
    while (res.size() < m * n) {
        // 一直往 右 走
        while (direction == 1 && y + 1 < n && !visited[x][y + 1]) {
            res.add(matrix[x][++y]);
            visited[x][y] = true;
        }
        direction = 2; // 更改方向为 下

        // 一直往 下 走
        while (direction == 2 && x + 1 < m && !visited[x + 1][y]) {
            res.add(matrix[++x][y]);
            visited[x][y] = true;
        }
        direction = 3; // 更改方向为 左

        // 一直往 左 走
        while (direction == 3 && y - 1 >= 0 && !visited[x][y - 1]) {
            res.add(matrix[x][--y]);
            visited[x][y] = true;
        }
        direction = 4; // 更改方向为 上

        // 一直往 上 走
        while (direction == 4 && x - 1 >= 0 && !visited[x - 1][y]) {
            res.add(matrix[--x][y]);
            visited[x][y] = true;
        }
        direction = 1; // 更改方向为 右
    }
    return res;
}

16. 最接近原点的K个点

题目:973. 最接近原点的 K 个点

题解:数组排序、优先级队列

解法1:自定义排序规则后,利用数组的排序函数

public int[][] kClosest(int[][] points, int k) {
    Queue<int[]> queue = new PriorityQueue<>(
            Comparator.comparingDouble(o -> o[0] * o[0] + o[1] * o[1]));
    queue.addAll(Arrays.asList(points));

    int[][] res = new int[k][2];
    for (int i = 0; i < k; i++) res[i] = queue.poll();
    return res;
}

解法2:利用优先级队列

public int[][] kClosest(int[][] points, int k) {
    Queue<int[]> queue = new PriorityQueue<>(
            Comparator.comparingDouble(o -> o[0] * o[0] + o[1] * o[1]));
    queue.addAll(Arrays.asList(points));

    int[][] res = new int[k][2];
    for (int i = 0; i < k; i++) res[i] = queue.poll();
    return res;
}

对优先级队列做法的优化:只维护 size = k 的优先队列

public int[][] kClosest(int[][] points, int k) {
    PriorityQueue<int[]> queue = new PriorityQueue<>((a, b) -> cmp(a, b));
    // 遍历的同时维护 size = k 的优先级队列
    for (int i = 0; i < points.length; i++) {
        // 堆中元素数量 < k, 直接放入
        if (queue.size() < k)
            queue.add(points[i]);
        // 堆中已经有 k 个元素, 比较堆顶元素和当前遍历元素
        else {
            int[] temp = queue.peek();
            if (cmp(points[i], temp) > 0) {
                queue.poll();
                queue.add(points[i]);
            }
        }
    }
    int[][] res = new int[k][2];
    for (int i = 0; i < k; i++)
        res[i] = queue.poll();
    return res;
}

int cmp(int[] a, int[] b) {
    return (b[0] * b[0] + b[1] * b[1]) - (a[0] * a[0] + a[1] * a[1]);
}

17. 等差子数组

题目:1630. 等差子数组

题解:暴力模拟

思路:暴力模拟

public List<Boolean> checkArithmeticSubarrays(int[] nums, int[] l, int[] r) {
    List<Boolean> res = new ArrayList<>();
    for (int i = 0; i < l.length; i++)
        res.add(isArithmetic(Arrays.copyOfRange(nums, l[i], r[i] + 1)));
    return res;
}

boolean isArithmetic(int[] arr) {
    if (arr.length <= 1) return true;
    Arrays.sort(arr);
    int diff = arr[1] - arr[0];
    for (int i = 1; i < arr.length; i++) {
        if (arr[i] - arr[i - 1] != diff)
            return false;
    }
    return true;
}

18. N 叉树的层次遍历

题目:429. N 叉树的层序遍历

题解:BFS

let levelOrder = function (root: Node | null): number[][] {
    if (!root) return []
    let queue = [root], res = []
    while (queue.length) {
        let temp = []
        for (let i = queue.length - 1; i >= 0; i--) {
            let node = queue.pop()
            temp.unshift(node.val)
            queue.unshift(...node.children)
        }
        res.push(temp)
    }
    return res
};

19. 下一个更大元素 II

题目:503. 下一个更大元素 II

题解:暴力、单调栈

暴力:

  • 读懂题目就能做,注意判断特殊情况
  • 复杂度为 O(n * n)
const nextGreaterElements = function (nums: number[]): number[] {
    let len = nums.length
    if (len <= 1) return [-1]
    let res = new Array(len).fill(null)
    for (let i = 0; i < len; i++) {
        for (let j = i + 1; j < len + i; j++) {
            if (nums[j % len] > nums[i]) {
                res[i] = nums[j % len]
                break
            }
        }
        if (res[i] == null) res[i] = -1
    }
    return res
};

单调栈:

  • 「单调栈」就是栈内元素满足单调性的栈结构。
  • 单调栈的根本作用在于求得「每一个数字在原始序列中左 / 右边第一个大于 / 小于它自身的数字」
  • 由于每一个数字只会入栈一次且最多出栈一次,因此总的时间复杂度为 O(n)
public int[] nextGreaterElements(int[] nums) {
    int[] res = new int[nums.length];
    Arrays.fill(res, -1);
    Deque<Integer> stack = new ArrayDeque<>();
    for (int i = 0; i < nums.length * 2; i++) {
        // 栈不为空, 且当前元素 > 栈顶下标对应的元素
        while (!stack.isEmpty() && nums[i % nums.length] > nums[stack.getLast()]) {
            // 更新栈顶下标对应的结果
            res[stack.removeLast()] = nums[i % nums.length];
        }
        stack.addLast(i % nums.length);
    }
    return res;
}

20. 下一个更大元素 III

题目:556. 下一个更大元素 III

题解:排序模拟

此题和 31.下一个排列 几乎一样,加了一个返回值判断而已

思路:

  • 对于 [2, 6, 3, 5, 4, 1]

  • 先从后往前找到一个逆序数 N, 即为 3

  • 然后再从 N 后面找到一个比它大的 “最小数”

  • 即在 [5, 4, 1] 中找到比 3 大的最小数, 为 4

    (这里可以直接再次从后往前找,因为这段数字倒着遍历必然是正序的)

  • 交换两者位置,则为 [2, 6, 4, 5, 3, 1]

  • 然后对一开始 N 后面位置进行反转

  • 即从 [2, 6, 4, 5, 3, 1] 到 [2, 6, 4, 1, 3, 5]

public int nextGreaterElement(int n) {
    char[] cs = String.valueOf(n).toCharArray();
    int i = cs.length - 2;
    // 从后往前找到第一个逆序的数字 N
    while (i >= 0 && cs[i] >= cs[i + 1]) i--;
    if (i < 0) return -1;
    // 找到 N 后面比 N 大的 "最小数字"
    int j = cs.length - 1;
    while (j >= 0 && cs[j] <= cs[i]) j--;
    
    // 交换 N 和 比N大的"最小数字"的位置
    swap(cs, i, j); 
    // 将原来的 N 位置后面的数字变为最小序(本就是倒序,反转一下即可)
    reverse(cs, i + 1);

    // 转换成 int, 越界则返回 -1
    long res = 0;
    for (char c : cs) res = res * 10 + (c - '0');
    return res > Integer.MAX_VALUE ? -1 : (int)res;
}

void reverse(char[] cs, int start) {
    int i = start, j = cs.length - 1;
    while (i < j) swap(cs, i++, j--);
}

void swap(char[] cs, int i, int j) {
    char temp = cs[i];
    cs[i] = cs[j];
    cs[j] = temp;
}

21. 通知所有员工所需的时间 TODO

题目:1376. 通知所有员工所需的时间

22. 字母异位词分组

题目:49. 字母异位词分组

题解:哈希

其实这题可以将问题转化一下,如何确定一个 “字母异位词” 的 “唯一值”:

  • 对于 “abc”, “acb”, “bca” 等都属于字母异位词,如何找到一个 “唯一值” 将他们进行分组
  • 最直接的做法就是排序,他们经过排序后都会变成:“abc”,也就找到了那个 “唯一值”
  • 所以如果可以将找到唯一值的过程优化的更快,整体效率也就更快(解法 2)

解法1:排序来找到唯一值

public List<List<String>> groupAnagrams(String[] strs) {
    Map<String, List<String>> map = new HashMap<>();
    for (int i = 0; i < strs.length; i++) {
        String temp = getFeature(strs[i]);
        if (!map.containsKey(temp)) map.put(temp, new ArrayList<>());
        map.get(temp).add(strs[i]);
    }
    return new ArrayList<>(map.values());
}

String getFeature(String s) {
    char[] cs = s.toCharArray();
    Arrays.sort(cs);
    return String.valueOf(cs);
}

解法2:编码出唯一值

public List<List<String>> groupAnagrams(String[] strs) {
    Map<String, List<String>> map = new HashMap<>();
    for (String s : strs) {
        String encode = encode(s);
        if (!map.containsKey(encode)) map.put(encode, new ArrayList<>());
        map.get(encode).add(s);
    }
    return new ArrayList<>(map.values());
}

// 根据出现的次数进行编码,26位
public String encode(String s) {
    char[] code = new char[26];
    for (char c : s.toCharArray()) {
        int delta = c - 'a';
        code[delta]++;
    }
    return new String(code);
}

23. 找到字符串中所有字母异位词 todo

题目:438. 找到字符串中所有字母异位词

题解

暴力 + 排序:

public List<Integer> findAnagrams(String s, String p) {
    int len = p.length();
    char[] pcs = p.toCharArray();
    Arrays.sort(pcs);
    List<Integer> res = new ArrayList<>();
    for (int i = 0; i <= s.length() - len; i++) {
        char[] cs = s.substring(i, i + len).toCharArray();
        Arrays.sort(cs);
        if (Arrays.equals(cs, pcs)) res.add(i);
    }
    return res;
}

24. 乘积小于K的子数组 to do

题目:713. 乘积小于K的子数组

题解

暴力:

public int numSubarrayProductLessThanK(int[] nums, int k) {
    int count = 0;
    for (int i = 0; i < nums.length; i++) {
        int sum = nums[i];
        if (sum < k) count++;
        for (int j = i + 1; j < nums.length; j++) {
            sum *= nums[j];
            if (sum < k) count++;
            else break;
        }
    }
    return count;
}

25. 二维区域和检索 - 矩阵不可变

题目:304. 二维区域和检索 - 矩阵不可变

题解:前缀和

暴力可以过:

class NumMatrix {
    int[][] m;

    public NumMatrix(int[][] matrix) {
        m = matrix;
    }

    public int sumRegion(int row1, int col1, int row2, int col2) {
        int sum = 0;
        for (int i = row1; i <= row2; i++)
            for (int j = col1; j <= col2; j++)
                sum += m[i][j];
        return sum;
    }
}

前缀和一:一维前缀和的角度,计算每一行的前缀和

class NumMatrix {
    int[][] pre;

    public NumMatrix(int[][] matrix) {
        int n = matrix.length, m = (n == 0) ? 0 : matrix[0].length;
        pre = new int[n + 1][m + 1];
        for (int i = 0; i < n; i++) {
            int tempSum = 0; // 每一行的前缀和数组
            for (int j = 0; j < m; j++) {
                tempSum += matrix[i][j];
                pre[i + 1][j + 1] = tempSum;
            }
        }
    }
    
    public int sumRegion(int row1, int col1, int row2, int col2) {
        int sum = 0;
        for (int i = row1; i <= row2; i++) 
            sum += pre[i + 1][col2 + 1] - pre[i + 1][col1];
        return sum;
    }
}

前缀和二:二维前缀和的角度

image.png

class NumMatrix {
    int[][] sum;

    public NumMatrix(int[][] matrix) {
        int n = matrix.length, m = (n == 0) ? 0 : matrix[0].length;
        sum = new int[n + 1][m + 1];
        // 预处理前缀和数组(模板)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + matrix[i - 1][j - 1];
    }

    public int sumRegion(int x1, int y1, int x2, int y2) {
        x1++;y1++;x2++;y2++;
        return sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] + sum[x1 - 1][y1 - 1];
    }

}

26. 最小差值 II

题目:910. 最小差值 II

题解

public boolean reachingPoints(int sx, int sy, int tx, int ty) {
  while (tx > 0 && ty > 0){ // 因为sx, sy, tx, ty 是范围在 [1, 10^9] 的整数,逆推不能出界
    if (sx == tx && sy == ty) return true; // 判断是否到达了起始值
    // 每次逆推只能有tx、ty中较大值减去若干个较小值
    if (tx > ty) tx -= ty;
    else ty -= tx;
  }
  return false;
}
public boolean reachingPoints1(int sx, int sy, int tx, int ty) {
  while (tx > 0 && ty > 0){ // 因为sx, sy, tx, ty 是范围在 [1, 10^9] 的整数,逆推不能出界
    if (sx == tx && sy == ty) return true; // 判断是否到达了起始值
    // 此时只能有tx减去ty
    // tx - sx是目标与起始值在x的差距,我们需要一次减去n * ty达到快速逼近sx的目的
    if (tx > ty) tx -= Math.max((tx - sx) / ty, 1) * ty;
    // 此时只能有ty减去tx
    // ty - sy是目标与起始值在y的差距,我们需要一次减去n * tx达到快速逼近sy的目的
    else ty -= Math.max((ty - sy) / tx, 1) * tx;
  }
  return false;
}

27. 重排链表

题目:143. 重排链表

题解:模拟、拆分 + 拼接

暴力模拟:实现一个方法:将最链表的最后一个节点移动到首位,然后不停的调用这个方法就行

public void reorderList(ListNode head) {
    ListNode cur = head;
    while (cur != null && cur.next != null && cur.next.next != null) {
        moveList(cur);
        cur = cur.next.next;
    }
}

/**
    * 将最后一个节点移到首位
    * @param head
    */
void moveList(ListNode head) {
    ListNode last = head, pre = null;
    while (last.next != null) {
        pre = last;
        last = last.next;
    }
    ListNode temp = head.next;
    head.next = last;
    last.next = temp;
    pre.next = null;
}

拆分 + 拼接:

  1. 快慢指针将链表拆分成 两段
  2. 反转后段链表
  3. 将反转后的后段链表插入到前段的的节点之间
public void reorderList(ListNode head) {
    // 快慢指针分出两段链表
    ListNode slow = head, fast = head.next;
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
    }
    // 后半部分的链表头节点
    ListNode later = slow.next;
    slow.next = null; // 断开两部分链表的连接

    later = reverseList(later); // 反转后半部分链表

    // 拼接两段链表
    ListNode front = head;
    while (front != null && later != null) {
        ListNode laterNext = later.next;
        ListNode frontNext = front.next;

        front.next = later;
        later.next = frontNext;

        front = frontNext;
        later = laterNext;
    }

}

ListNode reverseList(ListNode head) {
    if (head == null || head.next == null) return head;
    ListNode node = reverseList(head.next);
    head.next.next = head;
    head.next = null;
    return node;
}

28. 复制带随机指针的链表

题目:138. 复制带随机指针的链表

题解:哈希

public Node copyRandomList(Node head) {
    // 使用 map 存储 旧结点 和 新结点 的映射
    Map<Node, Node> map = new HashMap<>();
    Node node = head;
    while (node != null) {
        map.put(node, new Node(node.val));
        node = node.next;
    }
    node = head;
    while (node != null) {
        map.get(node).next = map.get(node.next);
        map.get(node).random = map.get(node.random);
        node = node.next;
    }
    return map.get(head);
}

29. 两数相加

题目:2. 两数相加

难度中等7863收藏分享切换为英文接收动态反馈

题解:模拟

思路:模拟遍历,同时维护一个进位变量

public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
    ListNode root = new ListNode(0), cursor = root;
    int carry = 0;
    while (l1 != null || l2 != null || carry > 0) {
        int n1 = (l1 == null) ? 0 : l1.val;
        int n2 = (l2 == null) ? 0 : l2.val;
        int sumVal = n1 + n2 + carry; // 计算数字 + 进位和
        carry = sumVal / 10; // 计算下一个进位

        cursor.next = new ListNode(sumVal % 10);
        cursor = cursor.next;
        
        if (l1 != null) l1 = l1.next;
        if (l2 != null) l2 = l2.next;
    }
    return root.next;
}

30. 两数相加 II

题目:445. 两数相加 II

题解:反转链表3次、栈

思路1:

  • 反转 l1、反转 l2
  • 计算 l1 + l2
  • 反转 l1 + l2 的结果
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        l1 = reverList(l1);
        l2 = reverList(l2);

        ListNode root = new ListNode(0), cursor = root;
        int carry = 0;
        while (l1 != null || l2 != null || carry > 0) {
            int n1 = (l1 == null) ? 0 : l1.val;
            int n2 = (l2 == null) ? 0 : l2.val;
            int sumVal = n1 + n2 + carry; // 计算数字 + 进位和
            carry = sumVal / 10; // 计算下一个进位

            cursor.next = new ListNode(sumVal % 10);
            cursor = cursor.next;
            
            if (l1 != null) l1 = l1.next;
            if (l2 != null) l2 = l2.next;
        }
        return reverList(root.next);
    }

    ListNode reverList(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode node = reverList(head.next);
        head.next.next = head;
        head.next = null;
        return node;
    }
}

思路2:栈

public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
    LinkedList<Integer> s1 = new LinkedList<>(), s2 = new LinkedList<>();
    while(l1 != null) {
        s1.addFirst(l1.val);
        l1 = l1.next;
    }
    while(l2 != null) {
        s2.addFirst(l2.val);
        l2 = l2.next;
    }
    int carry = 0;
    ListNode lastNode = null;
    while(!s1.isEmpty() || !s2.isEmpty() || carry > 0) {
        int n1 = s1.isEmpty() ? 0 : s1.removeFirst();
        int n2 = s2.isEmpty() ? 0 : s2.removeFirst();
        int sum = n1 + n2 + +carry;
        carry = sum / 10;
        ListNode curNode = new ListNode(sum % 10);
        curNode.next = lastNode;
        lastNode = curNode;
    }
    return lastNode;
}

31. 旋转链表

题目:61. 旋转链表

题解:往后拼接

public ListNode rotateRight(ListNode head, int k) {
    if (head == null || k == 0) return head;
    // 将某个节点指向链表最后, 此时顺便拿到链表长度
    ListNode node = head;
    int size = 1;
    while (node.next != null) {
        size++;
        node = node.next;
    }
    // 计算头节点后移的次数
    int i = (size - k % size);
    // 将头节点的值往后拼接并后移
    while (i > 0) {
        node.next = new ListNode(head.val);
        head = head.next;
        node = node.next;
        i--;
    }
    return head;
}

32. 二叉搜索树迭代器

题目:173. 二叉搜索树迭代器

class BSTIterator {
    List<Integer> list;

    public BSTIterator(TreeNode root) {
        list = new LinkedList<>();
        dfs(root);
    }

    void dfs(TreeNode root) {
        if (root == null)
            return;
        dfs(root.left);
        list.add(root.val);
        dfs(root.right);
    }

    public int next() {
        return list.remove(0);
    }

    public boolean hasNext() {
        return !list.isEmpty();
    }
}

33. 座位预约管理系统

题目:1845. 座位预约管理系统

题解:优先级队列

class SeatManager {
    Queue<Integer> q;

    public SeatManager(int n) {
        q = new PriorityQueue<>();
        for (int i = 1; i <= n; i++) q.add(i);
    }
    
    public int reserve() {
        return q.poll();
    }
    
    public void unreserve(int seatNumber) {
        q.add(seatNumber);
    }
}

34. 柠檬水找零

题目:860. 柠檬水找零

题解:贪心 + 模拟

这道题是很好的 贪心 + 模拟:

public boolean lemonadeChange(int[] bills) {
    // 20 根本不用关心
    int five = 0, ten = 0;
    for (int bill: bills) {
        // 5 不用找
        if (bill == 5) five++;
        // 10 必须找 5
        else if (bill == 10) {
            if (five == 0) return false;
            five--;
            ten++;
        // 20 从 10, 5 依次找, 凑不到 15 则无法找
        } else if (bill == 20) {
            int temp = 15;
            while (temp != 0) {
                if (ten > 0 && temp >= 10) {
                    temp -= 10;
                    ten--;
                } else if (five > 0) {
                    temp -= 5;
                    five--;
                } else return false;
            }
        }
    }
    return true;
}

35. 最小栈

题目:155. 最小栈

题解:优先级队列、双栈、链表

思路1:优先级队列

class MinStack {
    Deque<Integer> stack;
    Queue<Integer> queue;

    public MinStack() {
        stack = new ArrayDeque<>();
        queue = new PriorityQueue<>();
    }
    
    public void push(int val) {
        stack.addLast(val);
        queue.add(val);
    }
    
    public void pop() {
        queue.remove(stack.removeLast());
    }
    
    public int top() {
        return stack.getLast();
    }
    
    public int getMin() {
        return queue.peek();
    }
}

思路2:维护两个栈

class MinStack {
    Deque<Integer> stack, minStack;
    public MinStack() {
        stack = new ArrayDeque<>();
        minStack = new ArrayDeque<>();
    }
    
    public void push(int val) {
        if (minStack.isEmpty() || val <= minStack.getLast())
            minStack.addLast(val);
        stack.addLast(val);
    }

    public void pop() {
        if (minStack.getLast().equals(stack.removeLast()))
            minStack.removeLast();
    }
    
    public int top() {
        return stack.getLast();
    }
    
    public int getMin() {
        return minStack.getLast();
    }
}

思路3:维护一个链表,在链表的属性上维护 min

class MinStack {
    private Node head;

    public void push(int x) {
        if (head == null)
            head = new Node(x, x);
        else
            head = new Node(x, Math.min(x, head.min), head);
    }

    public void pop() {
        head = head.next;
    }

    public int top() {
        return head.val;
    }

    public int getMin() {
        return head.min;
    }

    private class Node {
        int val;
        int min;
        Node next;

        private Node(int val, int min) {
            this(val, min, null);
        }

        private Node(int val, int min, Node next) {
            this.val = val;
            this.min = min;
            this.next = next;
        }
    }
}

36. 扁平化嵌套列表迭代器

题目:341. 扁平化嵌套列表迭代器

题解:递归扁平化

public class NestedIterator implements Iterator<Integer> {
    int idx = 0;
    List<Integer> list; // 存储扁平化以后的列表
	
  	// 递归扁凭化列表
    void add(List<NestedInteger> nestedList) {
        for (NestedInteger integer : nestedList) {
            if (!integer.isInteger()) { // 不是数字, 递归
                add(integer.getList());
            } else { // 是数字, 直接加到 list
                list.add(integer.getInteger());
            }
        }
    }

    public NestedIterator(List<NestedInteger> nestedList) {
        list = new ArrayList<>();
        add(nestedList);
    }

    @Override
    public Integer next() {
        return list.get(idx++);
    }

    @Override
    public boolean hasNext() {
        return idx < list.size();
    }
}

37. 设计一个验证系统

题目:1797. 设计一个验证系统

题解:读题模拟

class AuthenticationManager {
    int timeToLive; // 过期时间
    Map<String, Integer> map; // 验证码与其过期时间

    public AuthenticationManager(int timeToLive) {
        this.map = new HashMap<>();
        this.timeToLive = timeToLive;
    }
    
    public void generate(String tokenId, int currentTime) {
        map.put(tokenId, currentTime + timeToLive);
    }
    
    public void renew(String tokenId, int currentTime) {
        // 没有这个验证码、验证码已经过期, 或者正好过期
        if (!map.containsKey(tokenId) || map.get(tokenId) <= currentTime) return;
        map.put(tokenId, currentTime + timeToLive);
    }
    
    public int countUnexpiredTokens(int currentTime) {
        int count = 0;
        for (Map.Entry<String, Integer> entry: map.entrySet()) {
            if (entry.getValue() > currentTime) count++;
        }
        return count;
    }
}

38. 设计链表

题目:设计链表

题解:基础功

class MyLinkedList {

    class Node {
        int val;
        Node next;

        Node() {
            this.val = 0;
            this.next = null;
        }

        Node(int val, Node next) {
            this.val = val;
            this.next = next;
        }
    }

    Node head; // 虚拟头结点
    Node tail; // 尾节点, 方便从最后插入

    public MyLinkedList() {
        this.head = new Node(); // 虚拟头节点
        this.tail = this.head; // 一开始没有尾节点
    }

    public int get(int index) {
        Node node = head.next;
        while (index > 0) {
            node = node.next;
            index--;
            if (node == null) return -1;
        }
        return node.val;
    }

    public void addAtHead(int val) {
        Node newNode = new Node(val, head.next);
        head.next = newNode;
        maintainTail();
    }

    public void addAtTail(int val) {
        Node newNode = new Node(val, null);
        tail.next = newNode;
        maintainTail();
    }

    public void addAtIndex(int index, int val) {
        if (index <= 0) {
            addAtHead(val); // index <= 0, 直接往前面插入
            return;
        }
        Node node = head;
        while (index > 0) {
            node = node.next;
            index--;
            // index 大于链表长度, 不会插入节点
            if (node == null) return;
        }
        // index 等于链表长度, 在后面插入节点
        if (node.next == null) {
            addAtTail(val);
            return;
        }
        Node newNode = new Node(val, node.next);
        node.next = newNode;
        maintainTail();
    }

    public void deleteAtIndex(int index) {
        if (index < 0) return;
        Node node = head;
        while (index > 0) {
            node = node.next;
            index--;
            if (node == null) return;
        }
        tail = node; // 需要维护一下尾节点, 直接删除有可能让 tail 乱掉
        if (node.next != null) {
            node.next = node.next.next;
        } else {
            node.next = null;
        }
        maintainTail();
    }

    // 维护尾节点
    void maintainTail() {
        while (tail.next != null) {
            tail = tail.next;
        }
    }
}

39. O(1) 时间插入、删除和获取随机元素

题目:O(1) 时间插入、删除和获取随机元素

题解:List 与 Map

class RandomizedSet {
    Map<Integer, Integer> map;
    List<Integer> list;
    Random random;

    public RandomizedSet() {
        map = new HashMap<>();
        list = new ArrayList<>();
        random = new Random();
    }

    public boolean insert(int val) {
        if (map.containsKey(val))
            return false;
        list.add(val);
        map.put(val, list.size() - 1);
        return true;
    }

    public boolean remove(int val) {
        if (!map.containsKey(val))
            return false;
        int index = map.get(val);
        int last = list.get(list.size() - 1); // 最后一个元素的值
        // 用最后一个元素覆盖要删除的元素(同时维护 map 和 list)
        list.set(index, last);
        map.put(last, index);
        list.remove(list.size() - 1);
        map.remove(val);
        return true;
    }

    public int getRandom() {
        int randomIdx = random.nextInt(list.size());
        return list.get(randomIdx);
    }
}

40. 设计循环链表

题目:622. 设计循环队列

题解:基础功

class MyCircularQueue {

    int[] nums; // 元素, nums.length - 队列容量
    int head; // 当前指针
    int size; // 当前元素数量

    public MyCircularQueue(int k) {
        nums = new int[k];
    }

    public boolean enQueue(int value) {
        if (isFull()) return false;
        nums[(head + size) % nums.length] = value;
        size++;
        return true;
    }

    public boolean deQueue() {
        if (isEmpty()) return false;
        head = (head + 1) % nums.length;
        size--;
        return true;
    }

    public int Front() {
        return (isEmpty()) ? -1 : nums[head];
    }

    public int Rear() {
        return (isEmpty()) ? -1 : nums[(head + size - 1) % nums.length];
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public boolean isFull() {
        return nums.length == size;
    }
}

41. 我的日程安排表 I

题目:729. 我的日程安排表 I

题解:暴力模拟

public class MyCalendar {
    List<int[]> calendar;

    MyCalendar() {
        calendar = new ArrayList();
    }

    public boolean book(int start, int end) {
        for (int[] iv : calendar) {
            if (iv[0] < end && start < iv[1]) return false;
        }
        calendar.add(new int[] { start, end });
        return true;
    }
}
  • 15
    点赞
  • 118
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

萌宅鹿同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值