1130. 叶值的最小代价生成树
【代码一】通过—记忆化搜索
关于改成动态规划,略。
class Solution {
// 中序遍历得到的叶子节点顺序就是所有叶子节点从左到右列出的顺序
// 暴力递归->记忆化搜索。f(arr, i, j)表示[i, j]最小和,而[i, j]可
// 分类讨论以[i+1,j-1]中的任意一个数k为分界点分为左树和右树,则可得
// f(arr,i,j) = max(f(arr,i,k)+f(arr,k+1,j)+max(arr,i,k)*max(arr,k+1,j))
// 递归超时,改成记忆化搜索
public int mctFromLeafValues(int[] arr) {
int n = arr.length;
int[][] dp = new int[n][n];
for(int i = 0; i < n; i++){
Arrays.fill(dp[i], -1);
}
return f(arr, 0, arr.length-1, dp);
}
private int f(int[] arr, int i, int j, int[][] dp){
if(i >= j){
return dp[i][j] = 0;
}
if(i + 1 == j){
return dp[i][j] = arr[i] * arr[j];
}
if(dp[i][j] != -1){
return dp[i][j];
}
int res = Integer.MAX_VALUE;
for(int k = i; k < j; k++){
// 关键递归方程
res = Math.min(res, f(arr, i, k, dp) + f(arr, k+1, j, dp) + max(arr, i, k) * max(arr, k+1, j));
}
return dp[i][j] = res;
}
private int max(int[] arr, int i, int j){
int max = Integer.MIN_VALUE;
for(int k = i; k <= j; k++){
max = Math.max(max, arr[k]);
}
return max;
}
}
【代码二】通过—单调栈
class Solution {
// 中序遍历得到的叶子节点顺序就是所有叶子节点从左到右列出的顺序
// 参考讨论区题解,单调栈比较巧妙,但不易想到。确实如此。。题目第三个条件很关键。
// 维持一个单调递减栈,若当前元素c小于栈顶元素就入栈,否则弹出一个元素x,此时栈顶
// 是y,c>=x且y>x那么取min(c,y)*x贡献给最终的最小和,消耗掉x,留下min(c,y),这样
// 就保证了题目中的第三个条件并且保证了最终的最小和。贪心。。。局部最优---整体最优
// 每个非叶子节点是由左右子树最大叶节点乘积形成,而最终却要最小和,那我们就贪心地尽力
// 把每个非叶节点所用的那两个叶节点搞小就OK了。
public int mctFromLeafValues(int[] arr) {
Stack<Integer> stack = new Stack<>();
stack.push(Integer.MAX_VALUE);
int res = 0;
for(int i = 0; i < arr.length; i++){
int cur = arr[i];
while(cur >= stack.peek()){
int pop = stack.pop();
int min = Math.min(arr[i], stack.peek());
res += min * pop;
cur = min;
}
stack.push(cur);
}
while(stack.size() > 2){
res += stack.pop() * stack.peek();
}
return res;
}
}
1140. 石子游戏 II
【代码一】超时—暴力递归
参考877. 石子游戏
class Solution {
public int stoneGameII(int[] piles) {
return f(piles, 1, 0);
}
private int f(int[] piles, int M, int index){
if(index + 2*M >= piles.length){
return sum(piles, index, piles.length-index);
}
int res = 0;
for(int i = 1; i <= 2*M; i++){
res = Math.max(res, sum(piles, index, i) + s(piles, Math.max(M, i), index+i));
}
return res;
}
private int s(int[] piles, int M, int index){
if(index + 2*M >= piles.length){
return 0;
}
int res = Integer.MAX_VALUE;
for(int i = 1; i <= 2*M; i++){
res = Math.min(res, f(piles, Math.max(M, i), index+i));
}
return res;
}
private int sum(int[] a, int st, int n){
int res = 0;
for(int i = st; i < st+n; i++){
res += a[i];
}
return res;
}
}
【代码二】通过—记忆化搜索
关于改成动态规划,略。
class Solution {
public int stoneGameII(int[] piles) {
int n = piles.length;
int[][] dpf = new int[n+1][n+1];
int[][] dps = new int[n+1][n+1];
return f(piles, 1, 0, dpf, dps);
}
private int f(int[] piles, int M, int index, int[][] dpf, int[][] dps){
if(index + 2*M >= piles.length){
return sum(piles, index, piles.length-index);
}
if(dpf[M][index] != 0){
return dpf[M][index];
}
int res = 0;
for(int i = 1; i <= 2*M; i++){
res = Math.max(res, sum(piles, index, i) + s(piles, Math.max(M, i), index+i, dpf, dps));
}
return dpf[M][index] = res;
}
private int s(int[] piles, int M, int index, int[][] dpf, int[][] dps){
if(index + 2*M >= piles.length){
return 0;
}
if(dps[M][index] != 0){
return dps[M][index];
}
int res = Integer.MAX_VALUE;
for(int i = 1; i <= 2*M; i++){
res = Math.min(res, f(piles, Math.max(M, i), index+i, dpf, dps));
}
return dps[M][index] = res;
}
private int sum(int[] a, int st, int n){
int res = 0;
for(int i = st; i < st+n; i++){
res += a[i];
}
return res;
}
}
983. 最低票价
【代码一】超时—暴力递归
class Solution {
// 暴力递归:对每个旅行日可以买1日票,7日票,30日票。
public int mincostTickets(int[] days, int[] costs) {
return f(days, costs, 1);
}
// 从st天开始,到最后一个旅行日,所需最小费用
private int f(int[] days, int[] costs, int st){
// 二分找到大于等于st的最小旅行日,从该旅行日开始
int loc = bi(days, st);
if(loc == -1){
return 0;
}
// 1. 买1日票
int cost1 = costs[0] + f(days, costs, days[loc]+1);
// 2. 买7日票
int cost2 = costs[1] + f(days, costs, days[loc]+7);
// 3. 买30日票
int cost3 = costs[2] + f(days, costs, days[loc]+30);
return Math.min(cost1, Math.min(cost2, cost3));
}
private int bi(int[] a, int v){
int l = 0;
int r = a.length - 1;
int res = -1;
while(l <= r){
int mid = l + ((r-l)>>1);
if(a[mid] >= v){
res = mid;
r = mid - 1;
}else{
l = mid + 1;
}
}
return res;
}
}
【代码二】通过—记忆化搜索
class Solution {
// 暴力递归:对每个旅行日可以买1日票,7日票,30日票。再改成记忆化搜索
public int mincostTickets(int[] days, int[] costs) {
int[] dp = new int[366];
return f(days, costs, 1, dp);
}
// 从st天开始,到最后一个旅行日,所需最小费用
private int f(int[] days, int[] costs, int st, int[] dp){
// 二分找到大于等于st的最小旅行日,从该旅行日开始
int loc = bi(days, st);
if(loc == -1){
return 0;
}
if(dp[st] != 0){
return dp[st];
}
// 1. 买1日票
int cost1 = costs[0] + f(days, costs, days[loc]+1, dp);
// 2. 买7日票
int cost2 = costs[1] + f(days, costs, days[loc]+7, dp);
// 3. 买30日票
int cost3 = costs[2] + f(days, costs, days[loc]+30, dp);
return dp[st] = Math.min(cost1, Math.min(cost2, cost3));
}
// 二分查找大于等于v的最小值的位置
private int bi(int[] a, int v){
int l = 0;
int r = a.length - 1;
int res = -1;
while(l <= r){
int mid = l + ((r-l)>>1);
if(a[mid] >= v){
res = mid;
r = mid - 1;
}else{
l = mid + 1;
}
}
return res;
}
}
343. 整数拆分
【代码一】超时—暴力递归
class Solution {
public int integerBreak(int n) {
return f(n);
}
// f(n) = Math.max(Math.max(1*(n-1), 1*f(n-1)),
// Math.max(2*(n-2), 2*f(n-2)), …… )
// 注意至少分成2个。。。
private int f(int n){
if(n <= 2){
return 1;
}
int res = 0;
for(int i = 1; i < n; i++){
res = Math.max(res, Math.max(i*(n-i), i * f(n-i)));
}
return res;
}
}
【代码二】通过—记忆化搜索
class Solution {
public int integerBreak(int n) {
int[] dp = new int[n+1];
return f(n, dp);
}
// f(n) = Math.max(Math.max(1*(n-1), 1*f(n-1)),
// Math.max(2*(n-2), 2*f(n-2)), …… )
// 注意至少分成2个。。。
private int f(int n, int[] dp){
if(n <= 2){
return 1;
}
if(dp[n] != 0){
return dp[n];
}
int res = 0;
for(int i = 1; i < n; i++){
res = Math.max(res, Math.max(i*(n-i), i * f(n-i, dp)));
}
return dp[n] = res;
}
}
【代码三】通过—打表法
class Solution {
// 打表观察规律
public int integerBreak(int n) {
int[] dp = new int[59];
dp[2] = 1;
dp[3] = 2;
dp[4] = 4;
dp[5] = 6;
dp[6] = 9;
for(int i = 7; i <= 58; i++){
dp[i] = dp[i-3] * 3;
}
return dp[n];
}
}
740. 删除与获得点数
【代码一】通过—记忆化搜索
class Solution {
// 首先可以用暴力回溯法,但分析时间复杂度肯定不行
// 参考题解,根据数据特征,统计1-10000每个数出现的次数
// 然后整个数组不久成了198.打家劫舍了嘛。。。
public int deleteAndEarn(int[] nums) {
int[] a = new int[10001];
for(int i = 0; i < nums.length; i++){
a[nums[i]]++;
}
return rob(a);
}
private int rob(int[] nums) {
int[] help = new int[nums.length];
Arrays.fill(help, -1);
return f(nums, 0, help);
}
private int f(int[] nums, int i, int[] help){
if(i >= nums.length){
return 0;
}
if(help[i] == -1){
// 注意这里稍作更改nums[i]*i
help[i] = Math.max(f(nums, i+1, help), nums[i]*i + f(nums, i+2, help));
}
return help[i];
}
}
1105. 填充书架
【代码一】通过—记忆化搜索
class Solution {
public int minHeightShelves(int[][] books, int shelf_width) {
int[] dp = new int[books.length];
return f(books, shelf_width, 0, dp);
}
// 从i到books.length-1所得最小高度
// 每一层中每个k到books.length-1所得最小高度中的最小值就是该层搞定的最小值,逐层搞定
private int f(int[][] books, int shelf_width, int i, int[] dp){
if(i >= books.length){
return 0;
}
if(dp[i] != 0){
return dp[i];
}
int highest = 0;
int curWidth = shelf_width;
int res = 1000*1000;
for(int k = i; k < books.length; k++){
curWidth -= books[k][0]; // 用来处理每一层
if(curWidth < 0){
break;
}
highest = Math.max(highest, books[k][1]);
res = Math.min(res, f(books, shelf_width, k+1, dp) + highest);
}
return dp[i] = res;
}
}
813. 最大平均值和的分组
【代码一】通过—记忆化搜索(可改成区间DP)
class Solution {
public double largestSumOfAverages(int[] A, int K) {
double[] sum = new double[A.length];
sum[0] = A[0]*1.0;
for(int i = 1; i < A.length; i++){
sum[i] = sum[i-1] + A[i];
}
double[][][] dp = new double[A.length][A.length][K+1];
for(int i = 0; i < A.length; i++){
for(int j = 0; j < A.length; j++){
Arrays.fill(dp[i][j], -1.0);
}
}
return f(A, 0, A.length-1, K, sum, dp);
}
// 从i到j分成K组,得到的最大分数
// f(i, j, K) = max(f(i,p,1) + f(p+1,j,K-1) + avg(A,i,p)), p>=i && p<j && j-p >= K-1
private double f(int[] A, int i, int j, int K, double[] sum, double[][][] dp){
if(j-i+1 < K || K <= 0){
return 0.0;
}
if(dp[i][j][K] != -1.0){
return dp[i][j][K];
}
if(j-i+1 == K){
return dp[i][j][K] = sum[j]-sum[i]+A[i];
}
if(K == 1){
return dp[i][j][K] = (sum[j]-sum[i]+A[i])/(j-i+1);
}
double ans = 0.0;
for(int p = i; p < j; p++){
ans = Math.max(ans, f(A, p+1, j, K-1, sum, dp)+(sum[p]-sum[i]+A[i])/(p-i+1));
}
return dp[i][j][K] = ans;
}
}
139. 单词拆分
【代码一】通过—记忆化搜索
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Boolean[] dp = new Boolean[s.length()];
return f(s, new HashSet<String>(wordDict), 0, dp);
}
// 从start开始,字符串是否可以用字典中的单词拼成
private boolean f(String s, Set<String> wordDict, int start, Boolean[] dp){
if(start == s.length()){
return true;
}
if(dp[start] != null){
return dp[start];
}
// 分类讨论,暴力枚举。以start开头,以end结束的所有情况
for(int end = start + 1; end <= s.length(); end++){
if(wordDict.contains(s.substring(start, end)) && f(s, wordDict, end, dp)){
return dp[start] = true;
}
}
return dp[start] = false;
}
}
【代码二】通过—BFS
class Solution {
// BFS,阅读代码就能懂。。。本质还是暴力枚举的分类讨论。
// 父节点到该节点的两个数字就可以理解为s的左右两个下标
// 本题明显搜索的是一张图,只要找到一条路就可以了,走到某个节点没能找到
// 一条可行路的话,定是不能再走这个节点了。。由此做优化
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> set = new HashSet<>(wordDict);
Queue<Integer> que = new LinkedList<>();
que.add(0);
boolean[] vis = new boolean[s.length()+1];
while(que.size() > 0){
int start = que.poll();
for(int end = start+1; end <= s.length(); end++){
if(!vis[end] && set.contains(s.substring(start, end))){
que.add(end);
vis[end] = true;
if(end == s.length()){
return true;
}
}
}
}
return false;
}
}
1039. 多边形三角剖分的最低得分
【代码一】通过—记忆化搜索
class Solution {
public int minScoreTriangulation(int[] A) {
int[][] dp = new int[A.length][A.length];
for(int i = 0; i < A.length; i++){
Arrays.fill(dp[i], -1);
}
return f(A, 0, A.length-1, dp);
}
// i~j形成的最优结果
// 以某条边ij为分界线,i左侧递归A[0]~A[i],i右侧递归A[i]~A[N-1]
private int f(int[] A, int i, int j, int[][] dp){
if(i + 1 == j){
return dp[i][j] = 0;
}
if(dp[i][j] != -1){
return dp[i][j];
}
int ans = Integer.MAX_VALUE;
for(int k = i+1; k < j; k++){
ans = Math.min(ans, f(A, i, k, dp) + f(A, k, j, dp) + A[i]*A[k]*A[j]);
}
return dp[i][j] = ans;
}
}
1277. 统计全为 1 的正方形子矩阵
【代码一】通过—暴力枚举
class Solution {
public int countSquares(int[][] matrix) {
int ans = 0;
for(int a = 0; a < matrix.length; a++){
for(int b = a; b < matrix.length; b++){
for(int c = 0; c < matrix[0].length; c++){
if(c+b-a < matrix[0].length && check(matrix, a, b, c, c+b-a)){
ans++;
}
}
}
}
return ans;
}
private boolean check(int[][] matrix, int a, int b, int c, int d){
for(int i = a; i <= b; i++){
for(int j = c; j <= d; j++){
if(matrix[i][j] != 1){
return false;
}
}
}
return true;
}
}
【代码二】通过—动态规划(递推)
class Solution {
// 递推(技巧。。。)
// f(i,j)表示以m[i][j]为右下角的最大边长,那么,
// if(m[i][j]==1):
// f(i,j) = min(f(i-1,j), f(i,j-1), f(i,j)) + 1
public int countSquares(int[][] matrix) {
int[][] dp = new int[matrix.length][matrix[0].length];
int ans = 0;
for(int a = 0; a < matrix.length; a++){
for(int b = 0; b < matrix[0].length; b++){
if(a == 0 || b == 0){
dp[a][b] = matrix[a][b];
}else if(matrix[a][b] == 0){
dp[a][b] = 0;
}else{
dp[a][b] = Math.min(dp[a-1][b-1], Math.min(dp[a-1][b], dp[a][b-1])) + 1;
}
ans += dp[a][b];
}
}
return ans;
}
}
特别说明
本文参考leetcode官方网站题库及相关讨论区解答。链接地址