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

刷题代码: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;
}

猜你喜欢

转载自blog.csdn.net/weixin_43734095/article/details/123987591