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 的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 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;
}
}
918

被折叠的 条评论
为什么被折叠?



