LeetCode 100题(10题)

1:312. 戳气球

题意:

有 n 个气球,编号为0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。

现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。 这里的 i - 1 和 i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1或 i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。

求所能获得硬币的最大数量。

思路:

第一眼看到这个问题肯定感觉是dp,但是想了很久,也没有想出来如何定义状态和状态转移,后面看了眼题解才恍然大悟!!!原来我们只要反过来想就会简单很多,也就是把”戳气球“给变成”加气球“,我们首先确定一个区间【L,R】,然后用 res[L][R] 来表示这个区间所能产生的最大值,然后枚举这个区间的每个位置,状态转移方程就很简单了,也就是:

res[i][j] = Math.max(res[i][j], sum + res[i][k] + res[k][j]);

这个sum就是我们在【L,R】中添加的一个气球,也就是 val[k] * val[i] * val[j];

然后注意题目说了边界当成1处理,所有为了方便处理我们就在原数组的首尾都添加1就好;

所以我们这题只要反过来想会发现他其实就是一道常规的区间DP!!!

class Solution {
    public int maxCoins(int[] nums) {
        int n = nums.length;
        int[] val = new int[n + 2];
        for(int i = 1; i <= n; i ++){
            val[i] = nums[i - 1];
        }
        val[n + 1] = 1;
        val[0] = 1;

        int[][] res = new int[n + 2][n + 2];
        for(int i = n - 1; i >= 0; i --){
            for(int j = i + 2; j <= n + 1; j ++){
                for(int k = i + 1; k < j; k ++){
                    int sum = val[k] * val[i] * val[j];
                    res[i][j] = Math.max(res[i][j], sum + res[i][k] + res[k][j]);
                }
            }
        }
        return res[0][n + 1];
    }
}

2:309. 买卖股票的最佳时机含冷冻期 - 力扣(LeetCode)

题意:

给定一个整数数组prices,其中第  prices[i] 表示第 i 天的股票价格 。​

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

  • 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

思路:见代码,我个人觉得写的很详细了,花了不少时间

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int dp[][] = new int[n][2];
        // dp[i][0] 表示对于第i个元素此时未持有股票(可能此时卖掉,也可能就是单纯没有)所获的最大利益
        // dp[i][1] 表示对于第i个元素此时持有股票(可能是持有的之前的股票,也可能是刚买的第i个股票)所获的最大利益

        for(int i = 0; i < n; i ++){  // 这一步赋值很重要啊,仔细想一想对于 "买入" 的操作,相较于 "卖出" 来说 是随时可以进行的,也就是说我可以从任意一个元素开始买 
            dp[i][1] = -prices[i];
        }

        dp[0][0] = 0;

        for(int i = 0; i < n; i ++){
            if(i >= 1){
                dp[i][1] = Math.max(dp[i - 1][1], dp[i][1]); //"买入"操作是随时可以进行的,没有必要建立在前一个状态上,当然也可以建立在前一个状态上,也就是说,我此刻手里如果有股票了,我就可以不买了
                dp[i][0] = dp[i - 1][0]; // "卖出"操作是必须建立在我手里有股票的基础上的,所以必须要继承前一个状态
            }
            for(int j = 0; j < i; j ++){
                dp[i][0] = Math.max(dp[i][0], dp[j][1] + prices[i]);
                if(i - j > 1) dp[i][1] = Math.max(dp[i][1], dp[j][0] - prices[i]); // 此刻如果买入必须要满足冷却的条件
            }
        }
        return dp[n - 1][0];
    }
}

3:301. 删除无效的括号 - 力扣(LeetCode)

题意:

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

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

思路:

很暴力的一道题,我们先用两个变量,lremove, rremove 来分别表示左括号和右括号所需要删除的数量,具体实现就是:当我们遍历的 “(” 时,就让lremove加1,遍历到“)”时,先判断lremove是否为0,如果lremove为0,我们就让 rremove加1,这点很好想;

然后就是具体的暴力过程了,当我们的lremove或者rremove大于0时,我们就尝试删除每个位置的左括号或者右括号,最后当lremove和rremove都为0时,我们就判断这个字符串是否合法即可,这个判断方法也很好想,具体见代码中的isValid方法

class Solution {

    public List<String> ans = new ArrayList<>();

    public List<String> removeInvalidParentheses(String s) {
        int lremove = 0;
        int rremove = 0;
        for(int i = 0; i < s.length(); i ++){
            if(s.charAt(i) == '(') lremove ++;
            if(s.charAt(i) == ')'){
                if(lremove == 0) rremove ++;
                else lremove --;
            }
        }
        dfs(s, 0, lremove, rremove);
        return ans;
    }

    public void dfs(String s, int start, int lremove, int rremove){
        if(lremove == 0 && rremove == 0){
            if(isValid(s)) ans.add(s);
            return;
        }
        if(lremove + rremove > s.length()) return;

        for(int i = start; i < s.length(); i ++){
            if(i != start && s.charAt(i) == s.charAt(i - 1)) continue;
            if(lremove > 0 && s.charAt(i) == '('){ // 尝试去除左括号
                dfs(s.substring(0, i) + s.substring(i + 1), i, lremove - 1, rremove);
            }
            if(rremove > 0 && s.charAt(i) == ')'){ // 尝试去除右括号
                dfs(s.substring(0, i) + s.substring(i + 1), i, lremove, rremove - 1);
            }
        }
    }

    public boolean isValid(String s){
        int cnt = 0;
        for(int i = 0; i < s.length(); i ++){
            if(s.charAt(i) == '(') cnt ++;
            else if(s.charAt(i) == ')') cnt --;
            if(cnt < 0) return false;
        }
        return cnt == 0;
    }
}

4:300. 最长递增子序列 - 力扣(LeetCode)

题意:

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

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

思路:很简单的一道dp,就不多说了。

class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];
        Arrays.fill(dp, 1);
        int ans = 1;

        for(int i = 0; i < n; i ++){
            for(int j = 0; j < i; j ++){
                if(nums[i] > nums[j]){
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                    ans = Math.max(ans, dp[i]);
                }
            }
        }
        return ans;
    }
}

5:297. 二叉树的序列化与反序列化 - 力扣(LeetCode)

题意:

让你实现两个方法,分别为二叉树的序列化和反序列化;

思路:

一开始以为是什么高端的做法,看了题解才知道就是一个简单的dfs

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Codec {

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        return dfs_serialize(root, "");
    }

    public String dfs_serialize(TreeNode root, String str){
        if(root == null){
            str += "None,";
            return str;
        }
        str += String.valueOf(root.val) + ",";
        str = dfs_serialize(root.left, str);
        str = dfs_serialize(root.right, str);
        return str;
    }


    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        String[] dataArrary = data.split(",");
        List<String> dataList = new LinkedList<String>(Arrays.asList(dataArrary));
        return dfs_deserialize(dataList);      
    }

    public TreeNode dfs_deserialize(List<String> dataList){
        if (dataList.get(0).equals("None")) {
            dataList.remove(0);
            return null;
        }

        TreeNode root = new TreeNode(Integer.valueOf(dataList.get(0)));
        dataList.remove(0);
        root.left = dfs_deserialize(dataList);
        root.right = dfs_deserialize(dataList);
        return root;
    }
}

// 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));

6:287. 寻找重复数 - 力扣(LeetCode)

题意:

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

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

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

思路:

说实话这题我感觉挺难的,我这边选的是 【Floyd判圈算法】,也就是图论的做法,也就是让 

i -> nums[i]建一条边,因为存在重复的数字 target,因此这个位置一定有起码两条指向它的边,因此整张图一定存在环,而且这个target就是环的入口,后面的做法就和那个环形链表一模一样

142. 环形链表 II - 力扣(LeetCode)https://leetcode.cn/problems/linked-list-cycle-ii/description/

class Solution {
    public int findDuplicate(int[] nums) {
        int n = nums.length;
        int slow = 0;
        int fast = 0;
        do{
            slow = nums[slow];
            fast = nums[nums[fast]];
        }while(slow != fast); // 找到相遇点再停下

        slow = 0;
        while(slow != fast){ // 从出发点到入环点的距离和从相遇点到入环点的距离是相等的
            slow = nums[slow];
            fast = nums[fast];
        }
        return slow;
    }
}

7:283. 移动零 - 力扣(LeetCode)

题意:

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

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

思路:

用双指针进行操作

class Solution {
    public void moveZeroes(int[] nums) {
        int n = nums.length;
        int l = 0, r = 0; // 左指针左边均为非零数,右指针左边直到左指针处均为0;
        while(r < n){
            if(nums[r] != 0){
                swap(l, r, nums);
                l ++;
            }
            r ++;
        }
    }

    public void swap(int i, int j, int[] nums){
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
}

8:279. 完全平方数 - 力扣(LeetCode)

题意:

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

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

思路:先把完全平方数给处理出来,然后它本质上还是个完全背包问题

class Solution {

    public final int N = 10005;
    List<Integer> perfect_Squares = new ArrayList<>();

    public int numSquares(int n) {
        init();
        int m = perfect_Squares.size();

        // for(int i = 0; i < m; i ++){
        //     System.out.println(perfect_Squares.get(i));
        // }

        int[] dp = new int[n + 1];
        Arrays.fill(dp, 10000);
        dp[0] = 0;
        for(int i = 0; i <= n; i ++){
            for(int j = 0; j < m; j ++){
                if(i >= perfect_Squares.get(j)){
                    int w = perfect_Squares.get(j);
                    dp[i] = Math.min(dp[i], dp[i - w] + 1);
                }
            }
        }

        return dp[n];
    }

    public void init(){
        for(int i = 1; i < N; i ++){
            int tmp = (int)Math.sqrt(i);
            if(tmp * tmp == i) perfect_Squares.add(i); 
        }
    }
}

9:240. 搜索二维矩阵 II - 力扣(LeetCode)

题意:

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

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

思路:

遍历每一行然后二分就行,这个二分我们一定要记一个模板,不然这个边界问题太蛋疼了,很容易出错或者超时

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int n = matrix.length;
        int m = matrix[0].length;   
        int[] nums = new int[n * m];
        int idx = 0;

        for(int[] row: matrix){
            if(binary_search(row, target)) return true;
        }
        return false;
    }

    public boolean binary_search(int[] nums, int target){
        int l = 0, r = nums.length - 1;
        while(l < r){
            int mid = (l + r + 1) / 2;
            if(nums[mid] <= target) l = mid;
            else r = mid - 1;
        }
        return nums[l] == target;
    }
}

10:239. 滑动窗口最大值 - 力扣(LeetCode)

题意:

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

返回 滑动窗口中的最大值 

思路:

滑动窗口的板子题,不多说了

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        List<Integer> ans = new ArrayList<>();
        int[] q = new int[n + 1];

        int head = 1, tail = 0;
        for(int i = 0; i < n; i ++){
            while(head <= tail && nums[q[tail]] <= nums[i]) tail --;
            q[++ tail] = i;
            if(q[head] < i - k + 1) head ++;
            if(i >= k - 1) ans.add(nums[q[head]]);
        }
        int[] arr = ans.stream().mapToInt(Integer::intValue).toArray();
        return arr;
    }
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值