LeetCode 热题 HOT 100 Java 题解 -- Part 2

news2024/12/25 2:17:27

练习地址

Part 1 : https://blog.csdn.net/qq_41080854/article/details/128829494

LeetCode 热题 HOT 100 Java 题解 -- Part 2

    • 36. 二叉树的中序遍历 94
    • 37. 不同的二叉搜索树 96
    • 38. 验证二叉搜索树 98
    • 39. 对称二叉树 101
    • 40. 二叉树的层序遍历 102
    • 41. 二叉树的最大深度 104
    • 42. 从前序与中序遍历序列构造二叉树 105
    • 43. 二叉树展开为链表 114
    • 44. 买卖股票的最佳时机 121
    • 45. 二叉树中的最大路径和 124
    • 46. 最长连续序列 128
    • 47. 只出现一次的数字 136
    • 48. 单词拆分 139
    • 49. 环形链表 141
    • 50. 环形链表 II 142
    • 51. LRU 缓存 146
    • 52. 排序链表 148
    • 53. 乘积最大子数组 152
    • 54. 最小栈 155
    • 55. 相交链表 160
    • 56. 多数元素 169
    • 57. 打家劫舍 198
    • 58. 岛屿数量 200
    • 59. 反转链表 206
    • 60. 课程表 207
    • 61. 实现 Trie (前缀树) 208
    • 62. 数组中的第K个最大元素 215
    • 63. 最大正方形 221
    • 64. 翻转二叉树 226
    • 65. 回文链表 234
    • 66. 二叉树的最近公共祖先 236
    • 67. 除自身以外数组的乘积 238
    • 68. 滑动窗口最大值 239
    • 69. 搜索二维矩阵 II 240
    • 70. 完全平方数 279
    • 71. 移动零 283
    • 72. 寻找重复数 287
    • 73. 二叉树的序列化与反序列化 297
    • 74. 最长递增子序列 300
    • 75. 删除无效的括号 301

36. 二叉树的中序遍历 94

给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。

示例 1:
在这里插入图片描述

输入:root = [1,null,2,3]
输出:[1,3,2]

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    List<Integer> res = new ArrayList<>();
    public List<Integer> inorderTraversal(TreeNode root) {
        dfs(root);
        return res;
    }

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

37. 不同的二叉搜索树 96

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

示例 1:

输入:n = 3
输出:5
示例 2:

输入:n = 1
输出:1

解析

假设 i 个节点存在二叉排序树的个数是 G (i),令 f(j) 为以 j 为根的二叉搜索树的个数,则
G ( i ) = f ( 1 ) + f ( 2 ) + . . . + f ( j ) G(i) = f(1) + f(2) + ... + f(j) G(i)=f(1)+f(2)+...+f(j)
当 j为根节点时,其左子树节点个数为 j-1 个,右子树节点为 i-j,则
f ( j ) = G ( j − 1 ) ∗ G ( i − j ) f(j) = G(j-1)*G(i - j) f(j)=G(j1)G(ij)

状态转移方程 G ( n ) = G ( 0 ) ∗ G ( n − 1 ) + G ( 1 ) ∗ ( n − 2 ) + … + G ( n − 1 ) ∗ G ( 0 ) G(n)=G(0) * G(n-1)+G(1) *(n-2)+\ldots+G(n-1) * G(0) G(n)=G(0)G(n1)+G(1)(n2)++G(n1)G(0)

代码

class Solution {
    public int numTrees(int n) {
        int[] dp = new int[n + 1];
        dp[0] = 1; //因为要做乘法
        for(int i = 1; i < n + 1; i++){ //第几个节点
            for(int j = 1; j < i + 1; j++){ //选第几个为根
                dp[i] += dp[j -1] * dp[i - j];
            }
        }
        return dp[n];
    }
}

38. 验证二叉搜索树 98

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

节点的左子树只包含 小于 当前节点的数。
节点的右子树只包含 大于 当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。

示例 1:

在这里插入图片描述

输入:root = [2,1,3]
输出:true

解析

对二叉树进行中序遍历,每遍历到一个节点都和当前已遍历的最后一个节点值比较,只要能满足递增关系就继续遍历,直到遍历所有节点。

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    long pre = Long.MIN_VALUE; //前一个的值
    public boolean isValidBST(TreeNode root) {
        if(root == null) return true;
        if(!isValidBST(root.left)) return false;
        if(pre >= root.val) return false;
        pre = root.val;
        return isValidBST(root.right);
    }
}

39. 对称二叉树 101

给你一个二叉树的根节点 root , 检查它是否轴对称。

示例 1:
在这里插入图片描述

输入:root = [1,2,2,3,4,4,3]
输出:true
示例 2:

输入:root = [1,2,2,null,3,null,3]
输出:false

解析

判断轴对称,可以看作是判断根节点的左右子树是否镜像,dfs 判断即可。

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean isSymmetric(TreeNode root) {
        if(root == null) return true;
        return dfs(root.left, root.right);
    }
    private boolean dfs(TreeNode left, TreeNode right){
        if(left == null && right == null) return true;
        if(left == null || right == null || right.val != left.val) return false;
        return dfs(left.left, right.right) && dfs(left.right, right.left);
    }
}

40. 二叉树的层序遍历 102

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

示例 1:

输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    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()){
            int n = queue.size();
            List<Integer> temp = new ArrayList<>();
            while(n-- > 0){
                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(new ArrayList(temp));
        }
        return res;
    }
}

41. 二叉树的最大深度 104

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

示例:
给定二叉树 [3,9,20,null,null,15,7],

3

/
9 20
/
15 7
返回它的最大深度 3 。

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int maxDepth(TreeNode root) {
        if(root == null) return 0;
        return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
    }
}

42. 从前序与中序遍历序列构造二叉树 105

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

示例 1:

输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]

解析

二叉树的根节点就是前序序列的第一个节点,这样就可以把中序序列拆分成两部分,根节点前面就是左子树,后面就是右子树,那么就知道了左右子树的节点数量,依此就可以对前序序列也进行拆分,这样左右子树的前序、中序序列都知道了,递归构建左右子树就可以了。

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    Map<Integer, Integer> map = new HashMap<>();
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        int n = inorder.length;
        for(int i = 0; i < n; i++){
            map.put(inorder[i], i); //存放先序
        }
        return build(preorder, inorder, 0, n - 1, 0, n - 1); 
    }
    private TreeNode build(int[] preorder, int[] inorder, int pl, int pr, int il, int ir){
        if(pl > pr || il > ir) return null;
        int k = map.get(preorder[pl]) - il;//中序遍历中根节点的位置
        TreeNode node = new TreeNode(preorder[pl]);
        //左子树
        node.left = build(preorder, inorder, pl + 1, pl + k, il, il + k - 1);
        //右子树
        node.right = build(preorder, inorder, pl + k + 1, pr, il + k + 1, ir);
        return node;
    }
}

43. 二叉树展开为链表 114

给你二叉树的根结点 root ,请你将它展开为一个单链表:

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

示例 1:

输入:root = [1,2,5,3,4,null,6]
输出:[1,null,2,null,3,null,4,null,5,null,6]
示例 2:

输入:root = []
输出:[]。

解析

先序遍历,使用一个pre来记录上一个位置进行修改指向

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    TreeNode pre = null; //上一个位置
    public void flatten(TreeNode root) {
        if(root == null) return;
        //先序遍历
        TreeNode l = root.left; //记录下当前节点的左右结点
        TreeNode r = root.right;
        if(pre == null) pre = root;
        else{
            pre.right = root; //先序遍历的前一个位置指向当前结点
            pre.left = null; 
            pre = root;// 更新位置
        }
        flatten(l);
        flatten(r);
    }
}

44. 买卖股票的最佳时机 121

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

示例 1:

输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例 2:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。

解析

枚举每个位置,在与历史最低价差值最大的价格卖出。贪心算法

代码

class Solution {
    public int maxProfit(int[] prices) {
        int res = 0, minPrice = prices[0];
        for(int i = 1; i < prices.length; i++){
            res = Math.max(res, prices[i] - minPrice);
            minPrice = Math.min(minPrice, prices[i]);
        }
        return res;
    }
}

45. 二叉树中的最大路径和 124

路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。

路径和 是路径中各节点值的总和。

给你一个二叉树的根节点 root ,返回其 最大路径和 。

示例 1:

输入:root = [1,2,3]
输出:6
解释:最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6

解析

通过后序遍历递归得到左右子树的最大路径和,依此来更新结果。

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    int res = Integer.MIN_VALUE;
    public int maxPathSum(TreeNode root) {
        dfs(root);
        return res;
    }
    private int dfs(TreeNode node){
        if(node == null) return 0;
        //贡献大于0才被选中
        int left = Math.max(0, dfs(node.left));
        int right = Math.max(0, dfs(node.right));
        //计算最大路径和 节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值
        res = Math.max(res, node.val + left + right);
        //返回路径的贡献(只能走一条)
        return node.val + Math.max(left, right);
    }
}

46. 最长连续序列 128

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例 2:

输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9

解析

使用一个哈希表来记录数组中的数字,然后来遍历哈希表,找到每个连续片段的起点,计算长度即可。

代码

class Solution {
    public int longestConsecutive(int[] nums) {
        HashSet<Integer> set = new HashSet<>();//连续序列不允许重复
        for(int i = 0; i < nums.length; i++){
            set.add(nums[i]);
        }
        int res = 0;
        for(int i = 0; i < nums.length; i++){
            if(!set.contains(nums[i] - 1)){//找到了起点
                int num = nums[i] + 1;
                while(set.contains(num)){
                    num++;
                }
                res = Math.max(res, num - nums[i]);
            }
        }
        return res;
    }
}

47. 只出现一次的数字 136

给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。

示例 1 :

输入:nums = [2,2,1]
输出:1

解析

将数组中所有数字做异或,两两消除,最终剩下的就是只出现一次的元素。

代码

class Solution {
    public int singleNumber(int[] nums) {
        int res = 0;
        for(int num : nums){
            res ^= num;
        }
        return res;
    }
}

48. 单词拆分 139

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。

注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

示例 1:

输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释: 返回 true 因为 “leetcode” 可以由 “leet” 和 “code” 拼接成。
示例 2:

输入: s = “applepenapple”, wordDict = [“apple”, “pen”]
输出: true
解释: 返回 true 因为 “applepenapple” 可以由 “apple” “pen” “apple” 拼接成。
注意,你可以重复使用字典中的单词。
示例 3:

输入: s = “catsandog”, wordDict = [“cats”, “dog”, “sand”, “and”, “cat”]
输出: false

解析

序列化DP,动态规划,自己找符合的单词
定义 f(i) 表示 s 的前 i 个字符能够由字典中单词拼接构成,则 f [ i ] = f [ j − 1 ] & & exists ⁡ ( s [ j … i ] ) f[i]=f[j-1] \& \& \operatorname{exists}(s[j \ldots i]) f[i]=f[j1]&&exists(s[ji])

代码

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        HashSet<String> set = new HashSet<>();
        for(String str : wordDict){
            set.add(str);
        }
        int n = s.length();
        boolean[] dp = new boolean[n + 1];//定义 f(i) 表示 s 的前 i 个字符能够由字典中单词拼接构成
        dp[0] = true;
        for(int i = 1; i < n + 1; i++){
            for(int j = 1; j < n + 1; j++){
                if(dp[j - 1] && set.contains(s.substring(j - 1, i))){
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[n];
    }
}

49. 环形链表 141

给你一个链表的头节点 head ,判断链表中是否有环。

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

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

解析

我们使用两个指针,fast 与 slow。它们起始都位于链表的头部。随后,slow 指针每次向后移动一个位置,而 fast 指针向后移动两个位置。如果链表中存在环,则 fast 指针最终将再次与 slow 指针在环中相遇。

代码

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
            if(slow == fast) return true;
        }
        return false;
    }
}

50. 环形链表 II 142

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

解析

我们使用两个指针,fast 与 slow。它们起始都位于链表的头部。随后,slow 指针每次向后移动一个位置,而 fast 指针向后移动两个位置。如果链表中存在环,则 fast 指针重新移动到头部,fast和slow每次往后移动一个位置最终将再次与 slow 指针在环中相遇。

代码

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
            if(slow == fast){
                fast = head;
                while(fast != slow){
                    fast = fast.next;
                    slow = slow.next;
                }
                return fast;
            }
        }
        return null;
    }
}

51. LRU 缓存 146

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache 类:
LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

示例:

输入
[“LRUCache”, “put”, “put”, “get”, “put”, “get”, “put”, “get”, “get”, “get”]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]

解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1); // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2); // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1); // 返回 -1 (未找到)
lRUCache.get(3); // 返回 3
lRUCache.get(4); // 返回 4

解析

哈希表 + 双向链表,哈希表记录 key 和链表节点的映射关系,当需要淘汰时,从链表尾部删除节点;当需要更新时间戳时,通过哈希表获取节点,将其删除并插入到链表头。

代码

//hash表和双向链表
class DNode{ //get 
    int key;
    int value;
    DNode pre;
    DNode next;
    public DNode(){
        this.key = 0;
        this.value = 0;
        this.pre = null;
        this.next = null;
    }
    public DNode(int key, int value){
        this.key = key;
        this.value = value;
        this.pre = null;
        this.next = null;
    }
}

class LRUCache {
    HashMap<Integer, DNode> map; // put
    int capacity;
    int size;
    DNode head;
    DNode tail;

    public LRUCache(int capacity) {
        map = new HashMap<>(capacity);
        head = new DNode();
        tail = new DNode();
        this.capacity = capacity;
        this.size = 0;
        head.next = tail;
        tail.pre = head;
    }
    
    public int get(int key) {
        if(map.containsKey(key)){
            DNode node = map.get(key);
            moveToHead(node);
            return node.value; 
        }else{
            return -1;
        }
    }
    
    public void put(int key, int value) {
        if(map.containsKey(key)){
            DNode node = map.get(key);
            moveToHead(node);
            node.value = value;
        }else{
            //判断是否超过容量
            DNode node = new DNode(key, value);
            map.put(key, node); //map放入对应键
            size++;
            addToHead(node);//双向链表插入node
            //超过容量
            if (this.size > this.capacity) {
                removeLast();//删除最后一个不用的
                size--;
            }
        }
    }

    private void moveToHead(DNode node){
        //删除结点
        node.pre.next = node.next;
        node.next.pre = node.pre;
        //移动到头部
        node.next = head.next;
        node.pre = head;
        head.next = node;
        node.next.pre = node;
    }

    private void addToHead(DNode node){
        node.next = head.next;
        node.pre = head;
        head.next = node;
        node.next.pre = node;
    }

    private void removeLast(){
        DNode node = tail.pre;
        map.remove(node.key);
        node.pre.next = node.next;
        node.next.pre = node.pre;
        node.next = null;
        node.pre = null;
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

52. 排序链表 148

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。

示例 1:
在这里插入图片描述
输入:head = [4,2,1,3]
输出:[1,2,3,4]

解析

归并排序

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode sortList(ListNode head) {
        if(head == null) return null;
        return sort(head);
    }

    public ListNode sort(ListNode node){
        if(node.next == null) return node;//单个结点
        ListNode fast = node;
        ListNode slow = fast; //中点位置
        ListNode pre = null;
        //找到中点
        while(fast != null && fast.next != null){
            pre = slow;
            fast = fast.next.next;
            slow = slow.next;
        }
        pre.next = null;
        ListNode left = sort(node);
        ListNode right = sort(slow);
        return merge(left, right);
    }

    public ListNode merge(ListNode left, ListNode right){
        if(left == null) return right;
        if(right == null) return left;
        ListNode dummy = new ListNode();
        ListNode help = dummy;
        while(left != null && right != null){
            if(left.val > right.val){
                help.next = right;
                right = right.next;
            }else{
                help.next = left;
                left = left.next;
            }
            help = help.next;
        }
        if(left != null) help.next = left;
        if(right != null) help.next = right;
        return dummy.next;
    }
}

53. 乘积最大子数组 152

给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

测试用例的答案是一个 32-位 整数。

子数组 是数组的连续子序列。

示例 1:

输入: nums = [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例 2:

输入: nums = [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

解析

本题与 0053. 最大子序和 的思路相似,但由于是乘法运算,负数相乘可能得到最大值,因此在遍历的同时需要维护最大值和最小值两个变量。

在这里插入图片描述

代码

class Solution {
    public int maxProduct(int[] nums) {
        int res = nums[0]; 
        int f = nums[0];// 最大值
        int g = nums[0];// 最小值
        for(int i = 1; i < nums.length; i++){
            int t = nums[i];
            int fa = f * t;
            int ga = g * t;
            f = Math.max(t, Math.max(fa, ga));
            g = Math.min(t, Math.min(fa, ga));
            res = Math.max(res, f);
        }
        return res;
    }
}

54. 最小栈 155

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

MinStack() 初始化堆栈对象。
void push(int val) 将元素val推入堆栈。
void pop() 删除堆栈顶部的元素。
int top() 获取堆栈顶部的元素。
int getMin() 获取堆栈中的最小元素。

示例 1:

输入:
[“MinStack”,“push”,“push”,“push”,“getMin”,“pop”,“top”,“getMin”]
[[],[-2],[0],[-3],[],[],[],[]]

输出:
[null,null,null,null,-3,null,0,-2]

解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.

解析

使用一个辅助栈,push 的同时在辅助栈中加入当前最小值。
在这里插入图片描述

代码

class MinStack {
    Deque<Integer> stack;
    Deque<Integer> minstack;

    public MinStack() {
        stack = new ArrayDeque<>();
        minstack = new ArrayDeque<>();
    }
    
    public void push(int val) {
        stack.push(val);
        if(minstack.isEmpty() || minstack.peek() > val){
            minstack.push(val);
        }else{
            minstack.push(minstack.peek());
        }
    }
    
    public void pop() {
        stack.pop();
        minstack.pop();
    }
    
    public int top() {
        return stack.peek();
    }
    
    public int getMin() {
        return minstack.peek();
    }
}

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(val);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

55. 相交链表 160

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

图示两个链表在节点 c1 开始相交:

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

解析

统计长度,并且长的先走length,最后一起走就是答案

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        int al = 0, bl = 0;
        ListNode cur1 = headA;
        ListNode cur2 = headB;
        while(cur1 != null){
            cur1 = cur1.next;
            al++;
        }
        while(cur2 != null){
            cur2 = cur2.next;
            bl++;
        }
        if(cur1 != cur2) return null;
        cur1 = al - bl < 0 ? headA : headB; // 短的那一节
        cur2 = cur1 == headA ? headB : headA;// 长的那一节
        // 长的走
        for(int i = 0; i < Math.abs(al - bl); i++){
            cur2 = cur2.next;
        }
        // 一起走
        while(cur1 != cur2){
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return cur2;
    }
}

56. 多数元素 169

给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:

输入:nums = [3,2,3]
输出:3
示例 2:

输入:nums = [2,2,1,1,1,2,2]
输出:2

解析

Boyer-Moore 投票算法:每次都找出一对不同的元素,从数组中删除,直到数组为空或只有一种元素。

不难证明,如果存在元素 e 出现频率超过半数,那么数组中最后剩下的就只有 e。

代码实现:使用 ans 记录已遍历元素中出现次数最多的元素,count 记录还未被抵消的数量,当遍历结束 ans 即为数组的众数。

代码

class Solution {
    public int majorityElement(int[] nums) {
        int res = 0, count = 0;
        for(int i = 0; i < nums.length; i++){
            if(count == 0) res = nums[i];
            if(nums[i] != res) count--;
            else count++;
        }
        return res;
    }
}

57. 打家劫舍 198

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 1:

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

解析

动态规划,dp[i] 表示偷第 i 家 获得的最高金额
状态方程 dp[i] = max(dp[i - 1], nums[i -1] + dp[i -2]),不偷这家(累计上次金额)和偷这家(这家金额和累计上上次金额)

代码

class Solution {
    public int rob(int[] nums) {
        int[] dp = new int[nums.length + 1];
        dp[0] = 0; //表示偷第 i 家 获得的最高金额
        dp[1] = nums[0];
        for(int i = 2; i < nums.length + 1; i++){
            dp[i] = Math.max(dp[i - 1], nums[i - 1] + dp[i - 2]);
        }
        return dp[nums.length];
    }
}

58. 岛屿数量 200

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

输入:grid = [
[“1”,“1”,“1”,“1”,“0”],
[“1”,“1”,“0”,“1”,“0”],
[“1”,“1”,“0”,“0”,“0”],
[“0”,“0”,“0”,“0”,“0”]
]
输出:1

解析

  • step 1:优先判断空矩阵等情况。
  • step 2:从上到下从左到右遍历矩阵每一个位置的元素,如果该元素值为1,统计岛屿数量。
  • step 3:接着将该位置的1改为0,然后使用dfs判断四个方向是否为1,分别进入4个分支继续修改。

代码

class Solution {
    public int numIslands(char[][] grid) {
        int res = 0;
        for(int i = 0; i < grid.length; i++){
            for(int j = 0; j < grid[0].length; j++){
                if(grid[i][j] == '1'){
                    dfs(grid, i, j);
                    res++;
                }
            }
        }

        return res;
    }
    private void dfs(char[][] grid, int i, int j){
        if(i< 0 || i >= grid.length || j < 0 || j >= grid[0].length //边界条件
                || grid[i][j] == '0') return;
        grid[i][j] = '0';
        dfs(grid, i + 1, j);
        dfs(grid, i - 1, j);
        dfs(grid, i, j + 1);
        dfs(grid, i, j - 1); 
    }
}

59. 反转链表 206

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例 1:

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode pre = null;
        ListNode next = null;
        while(head != null){
            next = head.next;
            head.next = pre;
            pre = head;
            head = next;
        }
        return pre;
    }
}

60. 课程表 207

你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。

在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。

例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。

示例 1:

输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
示例 2:

输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。

解析

通过dfs,判断是否有环

代码

class Solution {
    List<List<Integer>> graph = new ArrayList<>();
    int[] isvisited;
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        //创建图
        for(int i = 0; i < numCourses; i++){
            graph.add(new ArrayList<>());
        }
        isvisited = new int[numCourses];
        //添加节点
        for(int[] cp : prerequisites){
            graph.get(cp[1]).add(cp[0]);
        }
        //判断每个节点是否有环
        for(int i = 0; i < numCourses; i++){
            if(hasCircle(prerequisites, i)) return false;
        }
        return true;
    }

    private boolean hasCircle(int[][] prerequisites, int index){
        if(isvisited[index] == 1) return true;//走到自身,有环
        if(isvisited[index] == 2) return false;//走过了,没有环,跳过
        isvisited[index] = 1;
        for(Integer i : graph.get(index)){ //出度
            if(hasCircle(prerequisites, i)) return true;
        }
        isvisited[index] = 2; //走过无环标记
        return false;
    }
}

61. 实现 Trie (前缀树) 208

Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。

请你实现 Trie 类:

Trie() 初始化前缀树对象。
void insert(String word) 向前缀树中插入字符串 word 。
boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。

Trie 的应用场景,8 个字:一次建树,多次查询。

代码

class Trie {
    private TreeNode root;

    public Trie() {
        root = new TreeNode();
    }
    
    public void insert(String word) {
        TreeNode node = root;
        for(char c : word.toCharArray()){
            if(node.next[c - 'a'] == null){
                node.next[c - 'a'] = new TreeNode();
            }
            node = node.next[c - 'a']; //移动下一位
        }
        node.isEnd = true;
    }
    
    public boolean search(String word) {
        TreeNode node = root;
        for(char c : word.toCharArray()){
            node = node.next[c - 'a'];
            if(node == null) return false;
        }
        return node.isEnd;
    }
    
    public boolean startsWith(String prefix) {
        TreeNode node = root;
        for(char c : prefix.toCharArray()){
            node = node.next[c - 'a'];
            if(node == null) return false;
        }
        return true;
    }
}
class TreeNode{
    boolean isEnd;
    TreeNode[] next;
    public TreeNode(){
        isEnd = false; //是否到头
        next = new TreeNode[26]; //26个字母,默认为null
    }
}
/**
 * Your Trie object will be instantiated and called as such:
 * Trie obj = new Trie();
 * obj.insert(word);
 * boolean param_2 = obj.search(word);
 * boolean param_3 = obj.startsWith(prefix);
 */

62. 数组中的第K个最大元素 215

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

解析

快排,转换为求前N最小

代码

class Solution {
    public int findKthLargest(int[] nums, int k) {
        return quickSort(nums, 0, nums.length - 1, nums.length - k);
    }
    public int quickSort(int[] nums, int l, int r, int idx){
        if(l == r) return nums[l];
        int k = l + (int)(Math.random() * (r - l + 1));
        swap(nums, r, k);
        int q = partion(nums, l, r);//小于等于区域的右边界
        if(q == idx) return nums[q];
        else if(q < idx) return quickSort(nums, q + 1, r, idx);
        else return quickSort(nums, l, q - 1, idx);
    }

    private void swap(int[] nums, int a, int b){
        int t = nums[a];
        nums[a] = nums[b];
        nums[b] = t;
    }

    private int partion(int[] nums, int l, int r){
        int cmp = nums[r];
        int left = l - 1;
        for(int i = l; i < r; i++){
            if(nums[i] <= cmp){
                swap(nums, i, ++left);
            }
        }
        swap(nums, r, left + 1);
        return left + 1;
    }
}

63. 最大正方形 221

在一个由 ‘0’ 和 ‘1’ 组成的二维矩阵内,找到只包含 ‘1’ 的最大正方形,并返回其面积。

示例 1:
在这里插入图片描述

输入:matrix = [[“1”,“0”,“1”,“0”,“0”],[“1”,“0”,“1”,“1”,“1”],[“1”,“1”,“1”,“1”,“1”],[“1”,“0”,“0”,“1”,“0”]]
输出:4

解析

动态规划, d p [ i ] [ j ] dp[i][j] dp[i][j] 表示以 i, j 为右下角的正方形的最大边长
转移方程 d p ( i , j ) = m i n ( d p ( i − 1 , j ) , d p ( i − 1 , j − 1 ) , d p ( i , j − 1 ) ) + 1 dp(i,j)=min(dp(i−1,j),dp(i−1,j−1),dp(i,j−1))+1 dp(i,j)=min(dp(i1,j),dp(i1,j1),dp(i,j1))+1

代码

class Solution {
    public int maximalSquare(char[][] matrix) {
        int r = matrix.length;
        int c = matrix[0].length;
        int[][] dp = new int[r + 1][c + 1];//dp[i][j] 表示以 i, j 为右下角的正方形的最大边长
        int maxSide = 0;
        for(int i = 1; i < r + 1; i++){
            for(int j = 1; j < c + 1; j++){
                if(matrix[i - 1][j - 1] == '1'){
                    //成为正方形应该是斜上,上 和 左 都 1 , 并且短板效应
                    dp[i][j] = Math.min(dp[i - 1][j - 1],Math.min(dp[i - 1][j], dp[i][j - 1])) + 1;
                    maxSide = Math.max(maxSide, dp[i][j]);
                }
            }
        }
        return maxSide * maxSide;
    }
}

64. 翻转二叉树 226

给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

示例 1:
在这里插入图片描述

输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]

解析

先序遍历从顶向下交换即可

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public TreeNode invertTree(TreeNode root) {
        if(root == null) return null;
        TreeNode tmp = root.left;
        root.left = root.right;
        root.right = tmp;
        invertTree(root.left);
        invertTree(root.right);
        return root;
    }
}

65. 回文链表 234

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。

示例 1:
在这里插入图片描述

输入:head = [1,2,2,1]
输出:true
示例 2:

输入:head = [1,2]
输出:false

解析

利用快慢指针找到中点,将后半截反序,两个指针从两边遍历到中点,逐个比较

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        ListNode f = head;
        ListNode s = head;
        while(f != null && f.next != null){
            f = f.next.next;
            s = s.next;
        }
        s = reverse(s);
        f = head;
        while(s != null){
            if(s.val != f.val) return false;
            s = s.next;
            f = f.next;
        }
        return true;
    }

    public ListNode reverse(ListNode node){
        ListNode pre = null;
        ListNode next = null;
        while(node != null){
            next = node.next;
            node.next = pre;
            pre = node;
            node = next;
        }
        return pre;
    }
}

66. 二叉树的最近公共祖先 236

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

示例 1:
在这里插入图片描述

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null) return null;
        if(root.val == p.val || root.val == q.val) return root;//节点为自己的祖先
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        if(left != null && right != null) return root;//从左右子树找到了
        return left == null ? right : left;
    }
}

67. 除自身以外数组的乘积 238

给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。

题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。

请不要使用除法,且在 O(n) 时间复杂度内完成此题。

示例 1:

输入: nums = [1,2,3,4]
输出: [24,12,8,6]
示例 2:

输入: nums = [-1,1,0,-3,3]
输出: [0,0,9,0,0]

解析

左右累乘,记录每个元素的左右乘积

代码

class Solution {
    public int[] productExceptSelf(int[] nums) {
        int[] res = new int[nums.length];
        int left = 1;
        int right = 1;
        for(int i = 0; i < nums.length; i++){
            res[i] = left;
            left *= nums[i];
        }
        for(int j = nums.length - 1; j >= 0; j--){
            res[j] *= right;
            right *= nums[j];
        }
        return res;
    }
}

68. 滑动窗口最大值 239

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值 。

示例 1:

输入: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

解析

单调队列维护窗口的最大值

代码

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        Deque<Integer> deque = new LinkedList<>();
        int[] res = new int[nums.length - k + 1];
        for(int i = 0; i < nums.length; i++){
            while(!deque.isEmpty() && nums[i] > nums[deque.peekLast()]){
                deque.pollLast();
            }
            deque.offerLast(i);
            while(!deque.isEmpty() && deque.peekFirst() < (i - k + 1)){
                //已经过了窗口
                deque.pollFirst();
            }
            if(i - k + 1 >= 0){
                res[i - k + 1] = nums[deque.peekFirst()];
            }
        }
        return res;
    }
}

69. 搜索二维矩阵 II 240

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:

每行的元素从左到右升序排列。
每列的元素从上到下升序排列。

示例 1:

输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
输出:true

解析

首先以左下角为起点,若是它小于目标元素,则往右移动去找大的,若是他大于目标元素,则往上移动去找小的。

代码

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int r = matrix.length;
        int c = matrix[0].length;
        int i = r - 1;
        int j = 0;
        while(i >= 0 && j < c){
            if(matrix[i][j] > target){
                i--;
            }else if(matrix[i][j] < target){
                j++;
            }else{
                return true;
            }
        }
        return false;
    }
}

70. 完全平方数 279

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。

示例 1:

输入:n = 12
输出:3
解释:12 = 4 + 4 + 4
示例 2:

输入:n = 13
输出:2
解释:13 = 4 + 9

解析

完全背包,dp[i] 表示最小需要完全平方数的数量

代码

class Solution {
    public int numSquares(int n) {
        //完全背包
        int[] dp = new int[n + 1]; //dp[i] 表示最小需要完全平方数的数量 
        Arrays.fill(dp, Integer.MAX_VALUE);
        dp[0] = 0;
        //物品 和 容量 i * i 价值 为 1
        for(int i = 1; i * i < n + 1; i++){  //每一个平方数
            int weight = i * i; 
            for(int j = weight; j < n + 1; j++){ // 整数 n
                dp[j] = Math.min(dp[j], dp[j - weight] + 1);
            }
        }
        return dp[n];
    }
}

71. 移动零 283

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

示例 1:

输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:

输入: nums = [0]
输出: [0]

使用一个 idx 指针,指向非零元素的最后一个位置,遍历数组,当遇到非零数字时,将其移动到 idx 上,并把 idx 后移。

当遍历结束后,把 idx 后面的位置全部置为 0 即可。

代码

class Solution {
    public void moveZeroes(int[] nums) {
        int idx = 0;
        for(int i = 0; i < nums.length; i++){
            if(nums[i] != 0){
                nums[idx++] = nums[i];
            }
        }

        while(idx < nums.length) nums[idx++] = 0;
    }
}

72. 寻找重复数 287

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。

假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。

你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。

示例 1:

输入:nums = [1,3,4,2,2]
输出:2
示例 2:

输入:nums = [3,1,3,4,2]
输出:3

解析

本题可以转换为已知一个有环链表,求出环的入口

代码

class Solution {
    public int findDuplicate(int[] nums) {
        int s = nums[0], f = nums[nums[0]];// nums[0] 看做是指针,0 也是指针
        // 有环链表
        while(s != f){
            s = nums[s];
            f = nums[nums[f]];
        }
        f = 0;
        while(s != f){
            s = nums[s];
            f = nums[f];
        }
        return s;
    }
}

73. 二叉树的序列化与反序列化 297

序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。

请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。

提示: 输入输出格式与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。

解析

先序遍历

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Codec {
    StringBuilder sb = new StringBuilder();
    Queue<String> queue = new LinkedList<>();
    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        //先序遍历
        serializeHelp(root);
        return sb.toString();
    }

    private void serializeHelp(TreeNode node){
        if(node == null){
            sb.append("#_");
            return;
        }
        sb.append(node.val);
        sb.append("_");
        serialize(node.left);
        serialize(node.right);
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        String[] res = data.split("_", -1);
        for(String str : res){
            queue.offer(str);
        }
        return deserializeHelp();
    }

    private TreeNode deserializeHelp(){
        //先序遍历
        String str = queue.poll();
        if(str.equals("#")) return null;
        TreeNode node = new TreeNode(Integer.valueOf(str));
        node.left = deserializeHelp();
        node.right = deserializeHelp();
        return node;
    }
}

// Your Codec object will be instantiated and called as such:
// Codec ser = new Codec();
// Codec deser = new Codec();
// TreeNode ans = deser.deserialize(ser.serialize(root));

74. 最长递增子序列 300

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

解析

动态规划, p[i] 为考虑前 i 个元素,以第 i 个数字结尾的最长上升子序列的长度

代码

class Solution {
    public int lengthOfLIS(int[] nums) {
        int[] dp = new int[nums.length];
        Arrays.fill(dp, 1);
        int res = 1;
        for(int i = 0; i < nums.length; i++){
            for(int j = 0; j < i; j++){
                if(nums[i] > nums[j]){
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                    res = Math.max(res, dp[i]);
                }
            }
        }
        return res;
    }
}

75. 删除无效的括号 301

给你一个由若干括号和字母组成的字符串 s ,删除最小数量的无效括号,使得输入的字符串有效。

返回所有可能的结果。答案可以按 任意顺序 返回。

示例 1:

输入:s = “()())()”
输出:[“(())()”,“()()()”]
示例 2:

输入:s = “(a)())()”
输出:[“(a())()”,“(a)()()”]
示例 3:

输入:s = “)(”
输出:[“”]

解析

回溯算法,确定最后的结果长度并决定要不要删除左右括号,边界判断,左括号大于等于右括号

代码

class Solution {
    HashSet<String> set = new HashSet<>();
    int len = 0;
    public List<String> removeInvalidParentheses(String s) {
        int lc = 0, rc = 0;//左右括号的数量
        for(char c : s.toCharArray()){
            if(c == '(') lc++;
            else if(c == ')'){
                if(lc == 0) rc++;
                else lc--;
            }
        }
        //输出字符串的长度
        len = s.length() - lc - rc;
        dfs(s, 0, 0, 0, "");
        return new ArrayList<String>(set);
    }

    private void dfs(String s, int l, int r, int idx, String p){
        if(l < r) return; //右括号 大于 左括号
        if(s.length() == idx){
            // 结果是否满足长度要求
            if(p.length() == len && l == r) set.add(p);
            return;
        }

        char c = s.charAt(idx);
        if(c == '('){
            dfs(s, l + 1, r, idx + 1, p + c);// 不删
            dfs(s, l, r, idx + 1, p);// 删
        }else if(c == ')'){
            dfs(s, l, r + 1, idx + 1, p + c);
            dfs(s, l, r, idx + 1, p);
        }else{ // 非括号,不删
            dfs(s, l, r, idx + 1, p + c);
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/380605.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

cycleGAN算法解读

本文参考&#xff1a;https://blog.csdn.net/Mr_health/article/details/112545671 1 CycleGAN概述 CycleGAN&#xff1a;循环生成对抗神经网络&#xff0c;是一种非监督学习模型。 Pix2pix方法适用于成对数据的风格迁移&#xff0c;而大多数情况下对于A风格的图像&#xf…

vue样式绑定(v-if)

文章目录一.第一次用vue框架二.要求:1.定义两种样式&#xff0c;一种描述正确的状态&#xff0c;一种描述错误的状态。2.在结构代码中定义一个块&#xff0c;实现绑定正确的样式状态。3.定义一个按钮&#xff0c;实现正确和错误两种状态的class切换。三.源代码四.效果一.第一次…

极客之选:用Rollup打包工具优化前端代码,让你成为前端领域的高手

前端开发面临着不断变化的技术和越来越复杂的项目需求&#xff0c;如何优化前端代码成为了前端开发人员必须要面对的挑战。本论文介绍了一个名为Rollup的打包工具&#xff0c;该工具可以帮助开发人员优化前端代码&#xff0c;减小代码体积&#xff0c;提高网站性能。本论文将介…

那些年用过的IDEA插件

今天和大家分享一下经常使用的IDEA的插件&#xff0c;希望有所帮助。一、IDEA插件CodeGlance2显示代码缩略图插件&#xff0c;方便查看代码。Lombok用于编译期间自动生成getter、setter、构造、toString等方法&#xff0c;简化代码。Mybatis Builder或MybatisXMapper接口和xml双…

2023雅虎邮箱不能注册?别急,这份教程教你成功注册雅虎邮箱

这几年&#xff0c;跨境电商的迅猛发展&#xff0c;越来越多人加入这片蓝海&#xff0c;跨境人拥有一个专业的邮箱账户显得尤为重要&#xff0c;它是商业交流和日常工作的必备工具。因此&#xff0c;雅虎邮箱成为了许多人的首选&#xff0c;全球范围内使用雅虎邮箱的人数是非常…

问题三十二:离散二维傅立叶变换(Discrete Fourier Transformation)

为了将灰度图像表示为频谱图&#xff0c;我们需要进行以下步骤&#xff1a; 加载图像并将其转换为灰度图像。对图像进行二维离散傅里叶变换。将变换结果表示为幅度谱和相位谱。可以对幅度谱和相位谱进行可视化&#xff0c;以查看频率分布。对幅度谱和相位谱进行逆变换&#xf…

做毕业设计,前端部分你需要掌握的6个核心技能

其实前端新手如果想要自己实现一套毕业设计项目并非简单的事&#xff0c;因为之前很多人一直还停留在知识点的阶段&#xff0c;而且管理系统和C端网站都需要开发&#xff0c;但现在需要点连成线了。所以在启动项目开发之前呢&#xff0c;针对前端部分&#xff0c;我列举一些非常…

react lazyLoad学习记录

react lazyLoad学习记录1.lazyLoad用处2.使用2.1 react-router-dom5版本写法2.2 react-router-dom6版本写法1.lazyLoad用处 默认例如首页&#xff0c;如果有好十几个甚至百个路由&#xff0c;react是会默认一下全部把路由组件一下全部加载的&#xff0c;极可能造成页面卡顿。r…

mysql数据库之sql语句性能分析工具

一、sql执行频率。 mysql客户端连接成功后&#xff0c;通过show [session | global] status 命令可以提供服务器状态信息。通过如下指令&#xff0c;可以查看当前数据库的INSERT/UPDATE/DELETE的访问频次。 #一个下划线代表一个字符 show global status like com_; 二、慢查…

冲鸭!33% 程序员月薪达到 5 万元以上~

2023年&#xff0c;随着互联网产业的蓬勃发展&#xff0c;程序员作为一个自带“高薪多金”标签的热门群体&#xff0c;被越来越多的人所关注。在过去充满未知的一年中&#xff0c;他们的职场现状发生了一定的改变。那么&#xff0c;程序员岗位的整体薪资水平、婚恋现状、职业方…

注意,摸鱼程序员常用的9个小技巧,早点下班不秃头

9个养生小技巧&#xff0c;祝大家不秃头嗨害大家好鸭&#xff01; 我是小熊猫~毕竟摸鱼一时爽&#xff0c;一直摸一直爽嘛~一、整理字符串输入二、迭代器切片&#xff08;Slice&#xff09;三、跳过可迭代对象的开头四、只包含关键字参数的函数 (kwargs)五、创建支持「with」语…

【C/C++】getchar()在处理字符输入时的一个细节

1、当我们进行“输入”后&#xff0c;无论输入一个字符或者是一个数字、一个字符串。都会自动产生一个换行符&#xff0c;而这个不起眼的回车符(‘\n’)也是一个‘字符’。如果我们需要连续多次输入‘字符’&#xff0c;则需要在每次输入字符后&#xff0c;及时处理这个换行符。…

前端二面vue面试题总结

什么是 mixin &#xff1f; Mixin 使我们能够为 Vue 组件编写可插拔和可重用的功能。如果希望在多个组件之间重用一组组件选项&#xff0c;例如生命周期 hook、 方法等&#xff0c;则可以将其编写为 mixin&#xff0c;并在组件中简单的引用它。然后将 mixin 的内容合并到组件中…

如何使用BackupOperatorToDA将Backup Operators用户权限提升为域管理员

关于BackupOperatorToDA BackupOperatorToDA是一款功能强大的红队提权工具&#xff0c;该工具能够在不使用域控制器RDP或WinRM的情况下&#xff0c;帮助广大研究人员将Backup Operators组的成员账号提升为域管理员权限。 如果红队研究人员在渗透测试的过程中&#xff0c;拿到…

代码随想录之数组(力扣题号)

69 x的平方根 这题需要注意的点在于数据的数据范围导致计算x的平方可能会超过Integer范围 超出范围之后的大小判断就会出错 if(2147488281>2147395600) System.out.println("yes"); //没有输出 注意不能这样&#xff0c;会超出Integer范围解决&#xff1a;前面…

百度文心大模型开发者斩获CCF BDCI大赛唯一『最佳算法能力奖』

‍2023年2月24日至25日&#xff0c;中国计算机学会&#xff08;CCF&#xff09;主办、苏州市吴江区人民政府支持&#xff0c;苏州市吴江区工信局、吴江区东太湖度假区管理办公室、苏州市吴江区科技局、CCF大数据专家委员会及其他专业委员会等多家组织单位共同承办的大数据与AI领…

在C#中使用互斥量解决多线程访问共享资源的冲突问题

在阿里云上对互斥量的概述&#xff1a;互斥量的获取是完全互斥的&#xff0c;即同一时刻&#xff0c;互斥量只能被一个任务获取。而信号量按照起始的计数值的配置&#xff0c;可以存在多个任务获取同一信号量的情况&#xff0c;直到计数值减为0&#xff0c;则后续任务无法再获取…

王道操作系统课代表 - 考研计算机 第四章 文件管理 究极精华总结笔记

本篇博客是考研期间学习王道课程 传送门 的笔记&#xff0c;以及一整年里对 操作系统 知识点的理解的总结。希望对新一届的计算机考研人提供帮助&#xff01;&#xff01;&#xff01; 关于对 “文件管理” 章节知识点总结的十分全面&#xff0c;涵括了《操作系统》课程里的全部…

Python使用异步线程池实现异步TCP服务器交互

背景&#xff1a; 实现客户端与服务端交互&#xff0c;由于效率原因&#xff0c;要发送与接收异步&#xff0c;提高效率。 需要多线程&#xff0c;本文用线程池管理。 common代码&#xff1a; import pickle import struct import timedef send_msg(conn, data):time.sleep(…

centos7配置静态网络常见问题归纳

系列相似配置与安装软件问题整理与归纳文章目录 安装pymysql库_pymysql库安装_张小鱼༒的博客-CSDN博客 解决pip更新的代码_pip更新代码_张小鱼༒的博客-CSDN博客 python当中的第三方wxPython库的安装解答_pip install wx_张小鱼༒的博客-CSDN博客 spark里面配置jdk后的编程…