阅读量:0
public class Code01_Stock1 { public static int maxProfit(int[] prices) { int ans = 0; for (int i = 1, min = prices[0]; i < prices.length; i++) { // min : 0...i范围上的最小值 min = Math.min(min, prices[i]); ans = Math.max(ans, prices[i] - min); } return ans; } }
从0 - i上的最大利润:在i天的时候卖,需要0-i上的最小值;不在i上卖,则需要0 - i-1天上的最大利润,比较即可
122. 买卖股票的最佳时机 II - 力扣(LeetCode)
public class Code02_Stock2 { public static int maxProfit(int[] prices) { int ans = 0; for (int i = 1; i < prices.length; i++) { ans += Math.max(prices[i] - prices[i - 1], 0); } return ans; } }
只要两天之间的差值为正数,即有利润,就购买;否则不买
123. 买卖股票的最佳时机 III - 力扣(LeetCode)
public class Code03_Stock3 { // 完全不优化枚举的方法 // 通过不了,会超时 public static int maxProfit1(int[] prices) { int n = prices.length; // dp1[i] : 0...i范围上发生一次交易,不要求在i的时刻卖出,最大利润是多少 int[] dp1 = new int[n]; for (int i = 1, min = prices[0]; i < n; i++) { min = Math.min(min, prices[i]); dp1[i] = Math.max(dp1[i - 1], prices[i] - min); } // dp2[i] : 0...i范围上发生两次交易,并且第二次交易在i时刻卖出,最大利润是多少 int[] dp2 = new int[n]; int ans = 0; for (int i = 1; i < n; i++) { // 第二次交易一定要在i时刻卖出 for (int j = 0; j <= i; j++) { // 枚举第二次交易的买入时机j <= i dp2[i] = Math.max(dp2[i], dp1[j] + prices[i] - prices[j]); } ans = Math.max(ans, dp2[i]); } return ans; } // 观察出优化枚举的方法 // 引入best数组,需要分析能力 public static int maxProfit2(int[] prices) { int n = prices.length; // dp1[i] : 0...i范围上发生一次交易,不要求在i的时刻卖出,最大利润是多少 int[] dp1 = new int[n]; for (int i = 1, min = prices[0]; i < n; i++) { min = Math.min(min, prices[i]); dp1[i] = Math.max(dp1[i - 1], prices[i] - min); } // best[i] : 0...i范围上,所有的dp1[i]-prices[i],最大值是多少 // 这是数组的设置完全是为了替代最后for循环的枚举行为 int[] best = new int[n]; best[0] = dp1[0] - prices[0]; for (int i = 1; i < n; i++) { best[i] = Math.max(best[i - 1], dp1[i] - prices[i]); } // dp2[i] : 0...i范围上发生两次交易,并且第二次交易在i时刻卖出,最大利润是多少 int[] dp2 = new int[n]; int ans = 0; for (int i = 1; i < n; i++) { // 不需要枚举了 // 因为,best[i]已经揭示了,0...i范围上,所有的dp1[i]-prices[i],最大值是多少 dp2[i] = best[i] + prices[i]; ans = Math.max(ans, dp2[i]); } return ans; } // 发现所有更新行为都可以放在一起 // 并不需要写多个并列的for循环 // 就是等义改写,不需要分析能力 public static int maxProfit3(int[] prices) { int n = prices.length; int[] dp1 = new int[n]; int[] best = new int[n]; best[0] = -prices[0]; int[] dp2 = new int[n]; int ans = 0; for (int i = 1, min = prices[0]; i < n; i++) { min = Math.min(min, prices[i]); dp1[i] = Math.max(dp1[i - 1], prices[i] - min); best[i] = Math.max(best[i - 1], dp1[i] - prices[i]); dp2[i] = best[i] + prices[i]; ans = Math.max(ans, dp2[i]); } return ans; } // 发现只需要有限几个变量滚动更新下去就可以了 // 空间压缩的版本 // 就是等义改写,不需要分析能力 public static int maxProfit4(int[] prices) { int dp1 = 0; int best = -prices[0]; int ans = 0; for (int i = 1, min = prices[0]; i < prices.length; i++) { min = Math.min(min, prices[i]); dp1 = Math.max(dp1, prices[i] - min); best = Math.max(best, dp1 - prices[i]); ans = Math.max(ans, best + prices[i]); // ans = Math.max(ans, dp2); } return ans; } }
188. 买卖股票的最佳时机 IV - 力扣(LeetCode)
public class Code04_Stock4 { // 就是股票问题2 public static int free(int[] prices) { int ans = 0; for (int i = 1; i < prices.length; i++) { ans += Math.max(prices[i] - prices[i - 1], 0); } return ans; } public static int maxProfit1(int k, int[] prices) { int n = prices.length; if (k >= n / 2) { // 这是一个剪枝 // 如果k >= n / 2,那么代表所有上坡都可以抓到 // 所有上坡都可以抓到 == 交易次数无限,所以回到股票问题2 return free(prices); } int[][] dp = new int[k + 1][n]; for (int i = 1; i <= k; i++) { for (int j = 1; j < n; j++) { dp[i][j] = dp[i][j - 1]; for (int p = 0; p < j; p++) { dp[i][j] = Math.max(dp[i][j], dp[i - 1][p] + prices[j] - prices[p]); } } } return dp[k][n - 1]; } public static int maxProfit2(int k, int[] prices) { int n = prices.length; if (k >= n / 2) { // 这是一个剪枝 // 如果k >= n / 2,那么代表所有上坡都可以抓到 // 所有上坡都可以抓到 == 交易次数无限,所以回到股票问题2 return free(prices); } int[][] dp = new int[k + 1][n]; for (int i = 1, best; i <= k; i++) { best = dp[i - 1][0] - prices[0]; for (int j = 1; j < n; j++) { // 用best变量替代了枚举行为 dp[i][j] = Math.max(dp[i][j - 1], best + prices[j]); best = Math.max(best, dp[i - 1][j] - prices[j]); } } return dp[k][n - 1]; } // 对方法2进行空间压缩的版本 public static int maxProfit3(int k, int[] prices) { int n = prices.length; if (k >= n / 2) { // 这是一个剪枝 // 如果k >= n / 2,那么代表所有上坡都可以抓到 // 所有上坡都可以抓到 == 交易次数无限,所以回到股票问题2 return free(prices); } int[] dp = new int[n]; for (int i = 1, best, tmp; i <= k; i++) { best = dp[0] - prices[0]; for (int j = 1; j < n; j++) { tmp = dp[j]; dp[j] = Math.max(dp[j - 1], best + prices[j]); best = Math.max(best, tmp - prices[j]); } } return dp[n - 1]; } }
dp[i][j]表示从0-j天最多买卖i次所能获得的最大收益
714. 买卖股票的最佳时机含手续费 - 力扣(LeetCode)
public class Code05_Stack5 { public static int maxProfit(int[] prices, int fee) { // prepare : 交易次数无限制情况下,获得收益的同时扣掉了一次购买和手续费之后,最好的情况 int prepare = -prices[0] - fee; // done : 交易次数无限制情况下,能获得的最大收益 int done = 0; for (int i = 1; i < prices.length; i++) { done = Math.max(done, prepare + prices[i]); prepare = Math.max(prepare, done - prices[i] - fee); } return done; } }
手续费无非是在卖出的时候多出一笔钱,所以和无限次购买股票是一样的
309. 买卖股票的最佳时机含冷冻期 - 力扣(LeetCode)
public class Code06_Stack6 { public static int maxProfit1(int[] prices) { int n = prices.length; if (n < 2) { return 0; } // prepare[i] : 0...i范围上,可以做无限次交易,获得收益的同时一定要扣掉一次购买,所有情况中的最好情况 int[] prepare = new int[n]; // done[i] : 0...i范围上,可以做无限次交易,能获得的最大收益 int[] done = new int[n]; prepare[1] = Math.max(-prices[0], -prices[1]); done[1] = Math.max(0, prices[1] - prices[0]); for (int i = 2; i < n; i++) { done[i] = Math.max(done[i - 1], prepare[i - 1] + prices[i]); prepare[i] = Math.max(prepare[i - 1], done[i - 2] - prices[i]); } return done[n - 1]; } // 只是把方法1做了变量滚动更新(空间压缩) // 并没有新的东西 public static int maxProfit2(int[] prices) { int n = prices.length; if (n < 2) { return 0; } // prepare : prepare[i-1] int prepare = Math.max(-prices[0], -prices[1]); // done2 : done[i-2] int done2 = 0; // done1 : done[i-1] int done1 = Math.max(0, prices[1] - prices[0]); for (int i = 2, curDone; i < n; i++) { // curDone : done[i] curDone = Math.max(done1, prepare + prices[i]); // prepare : prepare[i-1] -> prepare[i] prepare = Math.max(prepare, done2 - prices[i]); done2 = done1; done1 = curDone; } return done1; } }
903. DI 序列的有效排列 - 力扣(LeetCode)
public class Solution { public static int numPermsDISequence1(String s) { return f(s.toCharArray(), 0, s.length() + 1, s.length() + 1); } // 猜法很妙! // 一共有n个数字,位置范围0~n-1 // 当前来到i位置,i-1位置的数字已经确定,i位置的数字还没确定 // i-1位置和i位置的关系,是s[i-1] : D、I // 0~i-1范围上是已经使用过的数字,i个 // 还没有使用过的数字中,比i-1位置的数字小的,有less个 // 还没有使用过的数字中,比i-1位置的数字大的,有n - i - less个 // 返回后续还有多少种有效的排列 public static int f(char[] s, int i, int less, int n) { int ans = 0; if (i == n) { ans = 1; } else if (i == 0 || s[i - 1] == 'D') { for (int nextLess = 0; nextLess < less; nextLess++) { ans += f(s, i + 1, nextLess, n); } } else { for (int nextLess = less, k = 1; k <= n - i - less; k++, nextLess++) { ans += f(s, i + 1, nextLess, n); } } return ans; } public static int numPermsDISequence2(String str) { int mod = 1000000007; char[] s = str.toCharArray(); int n = s.length + 1; int[][] dp = new int[n + 1][n + 1]; for (int less = 0; less <= n; less++) { dp[n][less] = 1; } for (int i = n - 1; i >= 0; i--) { for (int less = 0; less <= n; less++) { if (i == 0 || s[i - 1] == 'D') { for (int nextLess = 0; nextLess < less; nextLess++) { dp[i][less] = (dp[i][less] + dp[i + 1][nextLess]) % mod; } } else { for (int nextLess = less, k = 1; k <= n - i - less; k++, nextLess++) { dp[i][less] = (dp[i][less] + dp[i + 1][nextLess]) % mod; } } } } return dp[0][n]; } // 通过观察方法2,得到优化枚举的方法 public static int numPermsDISequence(String str) { int mod = 1000000007; char[] s = str.toCharArray(); int n = s.length + 1; int[][] dp = new int[n + 1][n + 1]; for (int less = 0; less <= n; less++) { dp[n][less] = 1; } for (int i = n - 1; i >= 0; i--) { if (i == 0 || s[i - 1] == 'D') { dp[i][1] = dp[i + 1][0]; for (int less = 2; less <= n; less++) { dp[i][less] = (dp[i][less - 1] + dp[i + 1][less - 1]) % mod; } } else { dp[i][n - i - 1] = dp[i + 1][n - i - 1]; for (int less = n - i - 2; less >= 0; less--) { dp[i][less] = (dp[i][less + 1] + dp[i + 1][less]) % mod; } } } return dp[0][n]; } }
public class Code01_MaximumProfitInJobScheduling { public static int MAXN = 50001; public static int[][] jobs = new int[MAXN][3]; public static int[] dp = new int[MAXN]; public static int jobScheduling(int[] startTime, int[] endTime, int[] profit) { int n = startTime.length; for (int i = 0; i < n; i++) { jobs[i][0] = startTime[i]; jobs[i][1] = endTime[i]; jobs[i][2] = profit[i]; } // 工作按照结束时间从小到大排序 Arrays.sort(jobs, 0, n, (a, b) -> a[1] - b[1]); dp[0] = jobs[0][2]; for (int i = 1, start; i < n; i++) { start = jobs[i][0]; dp[i] = jobs[i][2]; if (jobs[0][1] <= start) { dp[i] += dp[find(i - 1, start)]; } dp[i] = Math.max(dp[i], dp[i - 1]); } return dp[n - 1]; } // job[0...i]范围上,找到结束时间 <= start,最右的下标 public static int find(int i, int start) { int ans = 0; int l = 0; int r = i; int m; while (l <= r) { m = (l + r) / 2; if (jobs[m][1] <= start) { ans = m; l = m + 1; } else { r = m - 1; } } return ans; } }
按照结束时间从小到大排序,才不会错过最大的利益
public class Solution { // 最普通的动态规划 // 不优化枚举 public static int kInversePairs1(int n, int k) { int mod = 1000000007; // dp[i][j] : 1、2、3...i这些数字,形成的排列一定要有j个逆序对,请问这样的排列有几种 int[][] dp = new int[n + 1][k + 1]; dp[0][0] = 1; for (int i = 1; i <= n; i++) { dp[i][0] = 1; for (int j = 1; j <= k; j++) { if (i > j) { for (int p = 0; p <= j; p++) { dp[i][j] = (dp[i][j] + dp[i - 1][p]) % mod; } } else { // i <= j for (int p = j - i + 1; p <= j; p++) { dp[i][j] = (dp[i][j] + dp[i - 1][p]) % mod; } } } } return dp[n][k]; } public static int kInversePairs(int n, int k) { int mod = 1000000007; int[][] dp = new int[n + 1][k + 1]; dp[0][0] = 1; // window : 窗口的累加和 for (int i = 1, window; i <= n; i++) { dp[i][0] = 1; window = 1; for (int j = 1; j <= k; j++) { if (i > j) { window = (window + dp[i - 1][j]) % mod; } else { // i <= j window = ((window + dp[i - 1][j]) % mod - dp[i - 1][j - i] + mod) % mod; } dp[i][j] = window; } } return dp[n][k]; } }
public class Solution { // 为了让所有语言的同学都可以理解 // 不会使用任何java语言自带的数据结构 // 只使用最简单的数组结构 public static int MAXN = 101; public static int MAXC = 26; public static int[] ring = new int[MAXN]; public static int[] key = new int[MAXN]; public static int[] size = new int[MAXC]; public static int[][] where = new int[MAXC][MAXN]; public static int[][] dp = new int[MAXN][MAXN]; public static int n, m; public static void build(String r, String k) { for (int i = 0; i < MAXC; i++) { size[i] = 0; } n = r.length(); m = k.length(); for (int i = 0, v; i < n; i++) { v = r.charAt(i) - 'a'; where[v][size[v]++] = i; ring[i] = v; } for (int i = 0; i < m; i++) { key[i] = k.charAt(i) - 'a'; } for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { dp[i][j] = -1; } } } public static int findRotateSteps(String r, String k) { build(r, k); return f(0, 0); } // 指针当前指着轮盘i位置的字符,要搞定key[j....]所有字符,最小代价返回 public static int f(int i, int j) { if (j == m) { // key长度是m // 都搞定 return 0; } if (dp[i][j] != -1) { return dp[i][j]; } int ans; if (ring[i] == key[j]) { // ring b // i // key b // j ans = 1 + f(i, j + 1); } else { // 轮盘处在i位置,ring[i] != key[j] // jump1 : 顺时针找到最近的key[j]字符在轮盘的什么位置 // distance1 : 从i顺时针走向jump1有多远 int jump1 = clock(i, key[j]); int distance1 = (jump1 > i ? (jump1 - i) : (n - i + jump1)); // jump2 : 逆时针找到最近的key[j]字符在轮盘的什么位置 // distance2 : 从i逆时针走向jump2有多远 int jump2 = counterClock(i, key[j]); int distance2 = (i > jump2 ? (i - jump2) : (i + n - jump2)); ans = Math.min(distance1 + f(jump1, j), distance2 + f(jump2, j)); } dp[i][j] = ans; return ans; } // 从i开始,顺时针找到最近的v在轮盘的什么位置 public static int clock(int i, int v) { int l = 0; // size[v] : 属于v这个字符的下标有几个 int r = size[v] - 1, m; // sorted[0...size[v]-1]收集了所有的下标,并且有序 int[] sorted = where[v]; int find = -1; // 有序数组中,找>i尽量靠左的下标 while (l <= r) { m = (l + r) / 2; if (sorted[m] > i) { find = m; r = m - 1; } else { l = m + 1; } } // 找到了就返回 // 没找到,那i顺指针一定先走到最小的下标 return find != -1 ? sorted[find] : sorted[0]; } public static int counterClock(int i, int v) { int l = 0; int r = size[v] - 1, m; int[] sorted = where[v]; int find = -1; // 有序数组中,找<i尽量靠右的下标 while (l <= r) { m = (l + r) / 2; if (sorted[m] < i) { find = m; l = m + 1; } else { r = m - 1; } } // 找到了就返回 // 没找到,那i逆指针一定先走到最大的下标 return find != -1 ? sorted[find] : sorted[size[v] - 1]; } }
未排序数组中累加和小于或等于给定值的最长子数组长度_牛客题霸_牛客网 (nowcoder.com)
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.StreamTokenizer; // 至今的最优解,全网题解几乎都是我几年前讲的方法 public class Main { public static int MAXN = 100001; public static int[] nums = new int[MAXN]; public static int[] minSums = new int[MAXN]; public static int[] minSumEnds = new int[MAXN]; public static int n, k; public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StreamTokenizer in = new StreamTokenizer(br); PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); while (in.nextToken() != StreamTokenizer.TT_EOF) { n = (int) in.nval; in.nextToken(); k = (int) in.nval; for (int i = 0; i < n; i++) { in.nextToken(); nums[i] = (int) in.nval; } out.println(compute1()); } out.flush(); out.close(); br.close(); } public static int compute1() { int[] sums = new int[n + 1]; for (int i = 0, sum = 0; i < n; i++) { // sum : 0...i范围上,这前i+1个数字的累加和 sum += nums[i]; // sums[i + 1] : 前i+1个,包括一个数字也没有的时候,所有前缀和中的最大值 sums[i + 1] = Math.max(sum, sums[i]); } int ans = 0; for (int i = 0, sum = 0, pre, len; i < n; i++) { sum += nums[i]; pre = find(sums, sum - k); len = pre == -1 ? 0 : i - pre + 1; ans = Math.max(ans, len); } return ans; } public static int find(int[] sums, int num) { int l = 0; int r = n; int m = 0; int ans = -1; while (l <= r) { m = (l + r) / 2; if (sums[m] >= num) { ans = m; r = m - 1; } else { l = m + 1; } } return ans; } public static int compute2() { minSums[n - 1] = nums[n - 1]; minSumEnds[n - 1] = n - 1; for (int i = n - 2; i >= 0; i--) { if (minSums[i + 1] < 0) { minSums[i] = nums[i] + minSums[i + 1]; minSumEnds[i] = minSumEnds[i + 1]; } else { minSums[i] = nums[i]; minSumEnds[i] = i; } } int ans = 0; for (int i = 0, sum = 0, end = 0; i < n; i++) { while (end < n && sum + minSums[end] <= k) { sum += minSums[end]; end = minSumEnds[end] + 1; } if (end > i) { // 如果end > i, // 窗口范围:i...end-1,那么窗口有效 ans = Math.max(ans, end - i); sum -= nums[i]; } else { // 如果end == i,那么说明窗口根本没扩出来,代表窗口无效 // end来到i+1位置,然后i++了 // 继续以新的i位置做开头去扩窗口 end = i + 1; } } return ans; } }