练习一
题目一
(来自谷歌)
给定一个数组arr,长度为n
表示n个服务员,每个人服务一个人的时间
给定一个正数m,表示有m个人等位
如果你是刚来的人,请问你需要等多久?
假设:m远远大于n,比如n<=1000, m <= 10的9次方,该怎么做?
/*
第一种做法,每一个节点存储可以开始服务的时间和服务一个人需要多长时间两个值,利用小根堆存储这些节点,按照可以开始服务的时间进行排序。
当有人需要服务时,从小根堆弹出一个元素即可,服务到第m+1个人时,弹出的节点的第一个值就是需要等待的时间。
*/
public static int minWaitingTime1(int[] arr, int m) {
if (arr == null || arr.length == 0) {
return -1;
}
PriorityQueue<int[]> heap = new PriorityQueue<>((a, b) -> (a[0] - b[0])); //构建一个堆,按照可以开始服务的时间升序排列
int n = arr.length;
for (int i = 0; i < n; i++) { //将数组中的数全部加入堆
heap.add(new int[]{0, arr[i]});
}
for (int i = 0; i < m; i++) { //对m个人进行时间处理
int[] cur = heap.poll(); //弹出堆顶元素
cur[0] += cur[1]; //将节点的开始服务时间延后
heap.add(cur); //再将修改后的节点加入堆
}
return heap.peek()[0];
}
/*
第二种做法,二分答案法。
思路是求m时间内最多可以服务几个人,包括在服务中的和刚开始服务的。
*/
public static int minWaitingTime2(int[] arr, int m) {
if (arr == null || arr.length == 0) {
return -1;
}
int best = Integer.MAX_VALUE; //用于找到效率最高的服务员
for (int i : arr) {
best = Math.min(best, i);
}
int left = 0;
int right = best * m; //用时最长为best*m,不可能超过这个时间
int mid = 0; //中值
int near = 0; //满足服务m个人的等待时间
while (left <= right) {
mid = (left + right) >> 1;
int cover = 0; //可以服务几个人
for (int i : arr) {
cover += ((mid / i) + 1); //在mid的时间内,每一个服务员服务的最多人数为mid/i+1
}
if (cover >= m + 1) { //如果满足条件,尝试往左找,看看有没有更短的时间
near = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return near;
}
// 为了测试
public static int[] randomArray(int n, int v) {
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = (int) (Math.random() * v) + 1;
}
return arr;
}
// 为了测试
public static void main(String[] args) {
int len = 50;
int value = 30;
int mMax = 3000;
int testTime = 20000;
System.out.println("测试开始");
for (int i = 0; i < testTime; i++) {
int n = (int) (Math.random() * len) + 1;
int[] arr = randomArray(n, value);
int m = (int) (Math.random() * mMax);
int ans1 = minWaitingTime1(arr, m);
int ans2 = minWaitingTime2(arr, m);
if (ans1 != ans2) {
System.out.println("出错了!");
for (int num : arr) {
System.out.print(num + " ");
}
System.out.println();
System.out.println("m : " + m);
System.out.println(ans1);
System.out.println(ans2);
break;
}
}
System.out.println("测试结束");
}
题目二
(来自华为OD)
1号店铺贿赂问题
店铺数量n,编号1~n
人的数量m,编号1~m
每个人有自己投票的店铺p,和改投1号店的报价x
返回想让1号店铺成为人气最高的店,至少花多少钱
1 <= p,n,m <= 3000
1 <= x <= 10^9
import java.util.Arrays;
import java.util.ArrayList;
// 来自华为OD
// 1号店铺贿赂问题
// 店铺数量n,编号1~n
// 人的数量m,编号1~m
// 每个人有自己投票的店铺p,和改投1号店的报价x
// 返回想让1号店铺成为人气最高的店,至少花多少钱
// 1 <= p,n,m <= 3000
// 1 <= x <= 10^9
public class Code03_BecomeMostPopularShop {
// 暴力方法
// 为了测试
// n : 店铺数量
// m : 人的数量
// arr : 长度m,每个人有支持的店铺以及改投1号店的钱数
// 如果有某个人已经支持了1号店铺,那么钱数认为无意义
// 返回至少要花多少钱,才能让1号店铺成为人气最高的点
public static long minCost1(int n, int m, int[][] arr) {
// 统计每个店铺的支持人数
int[] cnts = new int[n + 1];
for (int[] p : arr) {
cnts[p[0]]++;
}
boolean needChange = false;
for (int i = 2; i <= n; i++) {
if (cnts[i] >= cnts[1]) {
needChange = true;
break;
}
}
// 如果1号店铺已经是最大人气,直接返回0
if (!needChange) {
return 0;
}
return process(arr, 0, n, new boolean[m]);
}
// 暴力方法
// 为了测试
// 暴力走两个分支:每个人要么改,要么不改
// 如果i号人改投,那么change[i] = true;
// 如果i号人不改投,那么change[i] = false;
// n : 店铺数量,固定参数
public static long process(int[][] arr, int i, int n, boolean[] change) {
if (i == arr.length) {
// 统计投票结果
int[] cnts = new int[n + 1];
long sum = 0;
for (int j = 0; j < arr.length; j++) {
if (change[j]) {
// 改投的店,人气都给1号店
cnts[1]++;
// 并且统计改投的钱数
sum += arr[j][1];
} else {
// 没有改投的店,统计词频
cnts[arr[j][0]]++;
}
}
// 看看此时1号店是不是人气最高的
boolean ok = true;
for (int j = 2; j <= n; j++) {
if (cnts[j] >= cnts[1]) {
ok = false;
break;
}
}
if (!ok) {
// 如果1号店不是人气最高的
// 说明当前的方案无效
return Long.MAX_VALUE;
} else {
// 否则说明当前的方案有效
// 返回这种方案的改投钱数
return sum;
}
} else {
// 可能性1,改投
long p1 = Long.MAX_VALUE;
if (arr[i][0] != 1) {
// 如果当前用户不支持1号店,才存在改投的可能性
change[i] = true;
p1 = process(arr, i + 1, n, change);
change[i] = false;
}
// 可能性1,不改投
long p2 = process(arr, i + 1, n, change);
return Math.min(p1, p2);
}
}
public static long minCost2(int n, int m, int[][] arr) {
// 统计每个店铺的支持人数
int[] cnts = new int[n + 1];
for (int[] p : arr) {
cnts[p[0]]++;
}
boolean needChange = false;
for (int i = 2; i <= n; i++) {
if (cnts[i] >= cnts[1]) {
needChange = true;
break;
}
}
// 如果1号店铺已经是最大人气,直接返回0
if (!needChange) {
return 0;
}
// 把所有的人,根据改投的钱数排序
// 钱少的在前面
// 排序之后,人也重新编号了,因为每个人的下标变了
Arrays.sort(arr, (a, b) -> a[1] - b[1]);
// 每个店拥有哪些人做统计
// 比如,5号店有:13号人、16号人、23号人
// shops.get(5) = {13, 16, 23}
// 注意,这个编号是排序之后的编号
// 也就是说,如果经历了排序,然后此时分组
// 5号店有:13号人、16号人、23号人
// 那么13号人的转投钱数 <= 16号人的 <= 23号人的
// shops.get(5) = {13, 16, 23}
// 每一个下标,都是排序之后的数组中,人的下标
ArrayList<ArrayList<Integer>> shops = new ArrayList<>();
for (int i = 0; i <= n; i++) {
shops.add(new ArrayList<>());
}
for (int i = 0; i < m; i++) {
shops.get(arr[i][0]).add(i);
}
// 某个用户是否已经改投1号店了
boolean[] used = new boolean[m];
long ans = Long.MAX_VALUE;
for (int i = cnts[1] + 1; i <= m; i++) {
long money = f(arr, n, cnts[1], i, shops, used);
if (money != -1) {
ans = Math.min(ans, money);
}
}
return ans;
}
// arr : 所有人都在arr里,并且根据钱数排好序了
// n : 一共有多少店
// already : 一号店已经有的人数
// must : 一号店最后一定要达到的人数,并且一定要以这个人数成为人气最高的店
// shops : 每个店铺里都有哪些支持者,固定的
// used : 某个人是否已经改投1号店了,辅助数组
// 返回值 :
// 如果一号店人数最后一定要达到must,并且一定可以成为人气最高的店,那么返回至少花的钱数
// 如果上面说的做不到,返回-1
public static long f(int[][] arr, int n, int already, int must, ArrayList<ArrayList<Integer>> shops,
boolean[] used) {
// 最开始时,任何人都没有转投
Arrays.fill(used, false);
// 总钱数
long sum = 0;
for (int i = 2; i <= n; i++) {
// 从2号店开始,考察每个店铺
// 如果当前店铺人数>=must,那么一定要减到must以下
// needChange : 当前的店有多少人需要转投
int needChange = Math.max(0, shops.get(i).size() - must + 1);
for (int j = 0; j < needChange; j++) {
// 因为arr中已经根据钱数排序
// 所以当前店铺拥有的支持者里
// 也一定是根据钱数从小到大排列的
// 这些人都给1号店铺
int people = shops.get(i).get(j);
sum += arr[people][1];
// 当前的人已经算用过了,改投过了
used[people] = true;
}
// 改投的人,要归属给1号店铺
already += needChange;
if (already > must) {
// 如果超过了must
// 说明1号店铺就严格达到must的人数,是做不到成为人气最高的店铺的
// 因为别的店的人数要降到must以下,转移的人都会超过must
// 所以返回-1
return -1;
}
}
// 经过上面的过程
// 1号店已经接受一些人了
// 接受的人是:
// 别的店的人数要降到must以下,所以转移过来的人
// 但是有可能1号店此时的人数already,还是不够must个
// 那么要凑齐must个,必须再选花钱少的人,补齐
for (int i = 0, j = 0; already + j < must; i++) {
// 补齐的人员,不能原本就属于1号店铺
// 也不能是上面的过程中,转移过来的,因为已经用掉了
// 所以两个条件都具备,才能给1号店去补
if (arr[i][0] != 1 && !used[i]) {
sum += arr[i][1];
j++;
}
}
// 返回最终花的钱数
return sum;
}
// 为了测试
// 生成人数为len的数据
// 每个人支持的店铺在1~n之间随机
// 每个人改投的钱数在1~v之间随机
public static int[][] randomArray(int len, int n, int v) {
int[][] arr = new int[len][2];
for (int i = 0; i < len; i++) {
arr[i][0] = (int) (Math.random() * n) + 1;
arr[i][1] = (int) (Math.random() * v) + 1;
}
return arr;
}
// 为了测试
public static void main(String[] args) {
int N = 10;
int M = 16;
int V = 100;
int testTimes = 10000;
System.out.println("测试开始");
for (int i = 0; i < testTimes; i++) {
int n = (int) (Math.random() * N) + 1;
int m = (int) (Math.random() * M) + 1;
int[][] arr = randomArray(m, n, V);
long ans1 = minCost1(n, m, arr);
long ans2 = minCost2(n, m, arr);
if (ans1 != ans2) {
System.out.println("出错了!");
System.out.println("n : " + n);
System.out.println("m : " + m);
for (int[] p : arr) {
System.out.println(p[0] + " , " + p[1]);
}
System.out.println(ans1);
System.out.println(ans2);
break;
}
}
System.out.println("测试结束");
// 这已经是题目给的最大数据量了
// 完全可以运行通过
int n = 3000;
int m = 3000;
int v = 1000000000;
int[][] arr = randomArray(n, m, v);
long start = System.currentTimeMillis();
minCost2(n, m, arr);
long end = System.currentTimeMillis();
System.out.println("最大数据量时的运行时间 : " + (end - start) + " 毫秒");
}
}
题目三
村里面一共有 n 栋房子。我们希望通过建造水井和铺设管道来为所有房子供水。对于每个房子 i,我们有两种可选的供水方案:一种是直接在房子内建造水井成本为 wells[i - 1] (注意 -1 ,因为 索引从0开始 )另一种是从另一口井铺设管道引水数组 pipes 给出了在房子间铺设管道的成本其中每个 pipes[j] = [house1j, house2j, costj]代表用管道将 house1j 和 house2j连接在一起的成本。连接是双向的。请返回 为所有房子都供水的最低总成本 。这道题很高频,引起注意本身也不难,转化一下变成最小生成树的问题即可
public class Code04_OptimizeWaterDistributionInVillage {
public static int MAXN = 10010;
public static int[][] edges = new int[MAXN << 1][3];
public static int esize;
public static int[] father = new int[MAXN];
public static int[] size = new int[MAXN];
public static int[] help = new int[MAXN];
public static void build(int n) {
for (int i = 0; i <= n; i++) {
father[i] = i;
size[i] = 1;
}
}
public static int find(int i) {
int s = 0;
while (i != father[i]) {
help[s++] = i;
i = father[i];
}
while (s > 0) {
father[help[--s]] = i;
}
return i;
}
public static boolean union(int i, int j) {
int f1 = find(i);
int f2 = find(j);
if (f1 != f2) {
if (size[f1] >= size[f2]) {
father[f2] = f1;
size[f1] += size[f2];
} else {
father[f1] = f2;
size[f2] += size[f1];
}
return true;
} else {
return false;
}
}
public static int minCostToSupplyWater(int n, int[] wells, int[][] pipes) {
esize = 0;
for (int i = 0; i < n; i++, esize++) {
edges[esize][0] = 0;
edges[esize][1] = i + 1;
edges[esize][2] = wells[i];
}
for (int i = 0; i < pipes.length; i++, esize++) {
edges[esize][0] = pipes[i][0];
edges[esize][1] = pipes[i][1];
edges[esize][2] = pipes[i][2];
}
Arrays.sort(edges, 0, esize, (a, b) -> a[2] - b[2]);
build(n);
int ans = 0;
for (int i = 0; i < esize; i++) {
if (union(edges[i][0], edges[i][1])) {
ans += edges[i][2];
}
}
return ans;
}
}
题目四
(来自亚马逊)
我们定义了一个函数 countUniqueChars(s) 来统计字符串 s 中的唯一字符,并返回唯一字符的个数。例如:s = "LEETCODE" ,则其中 "L", "T","C","O","D" 都是唯一字符,因为它们只出现一次,所以 countUniqueChars(s) = 5 。本题将会给你一个字符串 s ,我们需要返回 countUniqueChars(t) 的总和,其中 t 是 s 的子字符串。输入用例保证返回值为 32 位整数。注意,某些子字符串可能是重复的,但你统计时也必须算上这些重复的子字符串也就是说,你必须统计 s 的所有子字符串中的唯一字符。(leetcode828题)
package leetcode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class leetcode828 {
/*
思路:找到某个字符在字符串中出现的位置,假设i,j,k是某个字符出现的位置,则该字符的贡献度计算如下:(i+1)*(j-i)+(j-i)*(k-j)+(k-j)*(s.length()-k)
*/
public static int uniqueLetterString(String s) {
HashMap<Character, ArrayList<Integer>> map = new HashMap<>(); //用于存储某个字符在s中出现的位置
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (!map.containsKey(c)) { //如果map中不存在该字符,则将其加入map,初始值加入-1是为了方便后面的计算
map.put(c, new ArrayList<>());
map.get(c).add(-1);
}
map.get(c).add(i);
}
int res = 0; //统计最后的贡献度
for (Map.Entry<Character, ArrayList<Integer>> entry : map.entrySet()) { //拿到每一个字符和其对应出现的索引
ArrayList<Integer> value = entry.getValue(); //拿到索引列表
value.add(s.length()); //在列表的末尾加入s的长度是为了便于计算
for (int i = 1; i < value.size() - 1; i++) {
res += (value.get(i) - value.get(i - 1)) * (value.get(i + 1) - value.get(i));
}
}
return res;
}
public static void main(String[] args) {
String s = "ABC";
int i = uniqueLetterString(s);
System.out.println(i);
}
}
题目五
现在你总共有 numCourses 门课需要选,记为 0 到 numCourses - 1
给你一个数组 prerequisites ,其中 prerequisites[i] = [ai, bi]
表示在选修课程 ai 前 必须 先选修 bi
例如,想要学习课程0 ,你需要先完成课程 1 ,我们用一个匹配来表示:[0,1]
返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序
你只要返回任意一种就可以了。如果不可能完成所有课程,返回一个空数组
(leetcode210题)
public static int[] findOrder(int numCourses, int[][] prerequisites) {
//将数据处理成图结构
ArrayList<ArrayList<Integer>> graph = new ArrayList<>();
for (int i = 0; i < numCourses; i++) {
graph.add(new ArrayList<>());
}
int[] indegree = new int[numCourses]; //记录每个节点的入度
for (int[] prerequisite : prerequisites) {
int from = prerequisite[1];
int to = prerequisite[0];
graph.get(from).add(to);
indegree[to]++;
}
int[] zeroInDeque = new int[numCourses];
int l = 0;
int r = 0;
for (int i = 0; i < numCourses; i++) {
if (indegree[i] == 0) { //如果入度为0,加入zeroInDeque
zeroInDeque[r++] = i;
}
}
int count = 0;
while (l < r) {
int cur = zeroInDeque[l++];
count++;
for (int next : graph.get(cur)) { //当前节点的后续节点入度减一
indegree[next]--;
if (indegree[next] == 0) {
zeroInDeque[r++] = next;
}
}
}
return count == numCourses ? zeroInDeque : new int[0];
}
题目六
来自亚马逊
有一组 n 个人作为实验对象,从 0 到 n - 1 编号,其中每个人都有不同数目的钱
以及不同程度的安静值,为了方便起见,我们将编号为 x 的人简称为 "person x "。
给你一个数组 richer ,其中 richer[i] = [ai, bi]
表示 person ai 比 person bi 更有钱。另给你一个整数数组 quiet
其中 quiet[i] 是 person i 的安静值
richer 中所给出的数据 逻辑自洽
也就是在 person x 比 person y 更有钱的同时,不会出现 person y 比 person x 更有钱的情况
现在,返回一个整数数组 answer 作为答案,其中 answer[x] = y 的前提是,
在所有拥有的钱肯定不少于 person x 的人中
person y 是最安静的人(也就是安静值 quiet[y] 最小的人)
(leetcode851题)
public static int[] loudAndRich(int[][] richer, int[] quiet) {
int n=quiet.length;
ArrayList<ArrayList<Integer>> graph=new ArrayList<>();
for (int i = 0; i < n; i++) {
graph.add(new ArrayList<>());
}
int[] degree=new int[n];
for (int[] r : richer) {
//将穷者加入富者列表
graph.get(r[0]).add(r[1]);
degree[r[1]]++;
}
int[] zeroQueue=new int[n];
int l=0;
int r=0;
for (int i = 0; i < n; i++) {
if(degree[i]==0){
zeroQueue[r++]=i;
}
}
int[] ans=new int[n];
for (int i = 0; i < n; i++) { //初始时每个节点的答案是自己
ans[i]=i;
}
while(l<r){
int cur=zeroQueue[l++];
for (int next : graph.get(cur)) {
if(quiet[ans[next]]>quiet[ans[cur]]){ //如果富者的安静值小于穷者的安静值,更新穷者的安静值
ans[next]=ans[cur];
}
if(--degree[next]==0){
zeroQueue[r++]=next;
}
}
}
return ans;
}
题目七
给定一个数组arr,长度为n,产品编号从0~n-1
arr[i]代表初始时i号产品有多少份
存在一系列的产品转化方案的数组convert,长度为k,代表k个方案
比如具体某一个方案,convert[j] = {a, b, c, d, ...}
表示当前的j号方案转化出来的产品是a,转化1份a需要:1份b、1份c、1份d...
其中a、b、c、d...一定都在0~n-1范围内
并且题目保证a > Math.max(b, c, d, ....)
而且题目保证所有方案转化出来的产品编号一定是不重复的
请返回最终能得到的第n-1号商品的最大值
1 <= n <= 100
0 <= arr[i] <= 10^4
k < n
public class ConversionOfFinancialProducts {
public static int MAXN = 101;
public static int[] indegree = new int[MAXN]; //记录每个节点的入度数组
public static int[] help = new int[MAXN];
public static int[] zeroQueue = new int[MAXN]; //入度为0的节点
public static int[] need = new int[MAXN]; //需要多少个i位置节点的数量,例如need[0]=5,即需要5个0节点
public static int n;
public static int maxValue(int[] arr, int[][] convert) {
n = arr.length;
ArrayList<ArrayList<Integer>> graph=new ArrayList<>();
for (int i = 0; i < n; i++) {
graph.add(new ArrayList<>());
}
Arrays.fill(indegree,0,n,0); //入度数组初始化为0
for (int[] relation : convert) {
for (int i = 1; i < relation.length; i++) {
graph.get(relation[0]).add(relation[i]);
indegree[relation[i]]++;
}
}
int l=arr[n-1]+1; //n-1号物品一开始就有arr[n-1]份,所以从arr[n-1]+1开始尝试
int r=0; //arr[n-1]最多可能有多少份的右边界
for (int num : arr) {
r+=num;
}
int ans=arr[n-1]; //记录最终答案
int m;
while(l<=r){ //利用二分法找到最大值
m=l+((r-l)>>1); //记得在右移运算的时候加括号,否则计算顺序会出现问题
if(ok(arr,graph,m)){
ans=m;
l=m+1;
}else{
r=m-1;
}
}
return ans;
}
// arr里,是每一种商品的初始份额
// graph就是图,3 -> 0 1 2
// aim目标,一定要转化出这么多份!n-1号商品
public static boolean ok(int[] arr, ArrayList<ArrayList<Integer>> graph, int aim) {
int l=0;
int r=0;
for (int i = 0; i < n; i++) {
help[i]=indegree[i];
if(help[i]==0){ //将入度为0的节点加入zeroQueue
zeroQueue[r++]=i;
}
}
Arrays.fill(need,0,n,0);
need[n-1]=aim;
while(l<r){
int cur=zeroQueue[l++];
int supplement=Math.max(need[cur]-arr[cur],0); //如果已经拥有的份额大于所需份额,则不需要其他节点来转换,记为0
if(graph.get(cur).isEmpty()&&supplement>0){ //如果当前节点没有其他节点可以转换,但是又需要当前节点,返回false
return false;
}
for (int next : graph.get(cur)) {
need[next]+=supplement;
if(--help[next]==0){
zeroQueue[r++]=next;
}
}
}
return true;
}
public static void main(String[] args) {
int[] arr1 = { 2, 0, 0, 1, 0 };
int[][] convert1 = { { 4, 2, 3 }, { 1, 0 }, { 2, 1 } };
System.out.println(maxValue(arr1, convert1));
// 我构造了一个非常好的例子
// 课上说明一下
int[] arr2 = { 100, 5, 5, 0 };
int[][] convert2 = { { 1, 0 }, { 2, 0, 1 }, { 3, 0, 1, 2 } };
System.out.println(maxValue(arr2, convert2));
}
}
题目八
来自亚马逊、Adobe
给你两个长度为 n 下标从 0 开始的整数数组 cost 和 time
分别表示给 n 堵不同的墙刷油漆需要的开销和时间。你有两名油漆匠
一位需要 付费 的油漆匠,刷第 i 堵墙需要花费 time[i] 单位的时间
开销为 cost[i] 单位的钱。
一位 免费 的油漆匠,刷 任意 一堵墙的时间为 1 单位,开销为 0
但是必须在付费油漆匠 工作 时,免费油漆匠才会工作
请你返回刷完 n 堵墙最少开销为多少
(leetcode2742题)
public class leetcode2742 {
/*思路:当前来到i位置的墙,剩余s面墙,有两种选择:
1.刷i位置的墙,则需要花费cost[i],刷i位置的墙需要time[i]时间,免费的员工可以刷time[i]面墙,故还剩下s-1-time[i]面墙
2.不刷i位置的墙,则去考虑i+1位置的墙,还剩余s面墙
*/
/* leetcode上超时
//暴力递归法
public static int paintWalls(int[] cost, int[] time) {
return process(cost, time, 0, cost.length);
}
//当前来到i位置的墙,还剩下s面墙没有刷
public static int process(int[] cost, int[] time, int i, int s) {
if (s <= 0) { //已经没有墙可以刷了,返回0
return 0;
}
if (i == cost.length) { //在还有墙要刷的情况下,i位置越界了,返回Integer.MAX_VALUE。返回Integer.MAX_VALUE的原因是最终要求最小值,答案永远不可能是Integer.MAX_VALUE,方便比较
return Integer.MAX_VALUE;
}
//不刷i位置的墙
int p1 = process(cost, time, i + 1, s);
//刷i位置的墙
int p2 = Integer.MAX_VALUE;
int process = process(cost, time, i + 1, s - 1 - time[i]);
if (process != Integer.MAX_VALUE) { //如果接下来的结果不是一个无效值
p2 = cost[i] + process;
}
return Math.min(p1, p2);
}*/
/*//记忆化搜索法
public static int paintWalls(int[] cost, int[] time) {
int[][] dp = new int[cost.length + 1][cost.length + 1];
for (int i = 0; i < cost.length + 1; i++) {
Arrays.fill(dp[i], -1);
}
return process(cost, time, 0, cost.length, dp);
}
public static int process(int[] cost, int[] time, int i, int s, int[][] dp) {
if (s <= 0) {
return 0;
}
if (dp[i][s] != -1) {
return dp[i][s];
}
int ans;
if (i == cost.length) {
ans = Integer.MAX_VALUE;
} else {
//不刷i位置的墙
int p1 = process(cost, time, i + 1, s, dp);
//刷i位置的墙
int p2 = Integer.MAX_VALUE;
int process = process(cost, time, i + 1, s - 1 - time[i], dp);
if (process != Integer.MAX_VALUE) { //如果接下来的结果不是一个无效值
p2 = cost[i] + process;
}
ans = Math.min(p1, p2);
}
dp[i][s] = ans;
return ans;
}*/
//动态递归法
public static int paintWalls(int[] cost, int[] time) {
int n = cost.length;
int[][] dp = new int[n + 1][n + 1];
for (int i = 0; i < n + 1; i++) {
Arrays.fill(dp[i], -1);
}
for (int i = 0; i < n; i++) { //第一列除了最后一行全是0
dp[i][0] = 0;
}
for(int i=0;i<=n;i++){
dp[n][i]=Integer.MAX_VALUE;
}
//从下往上,从左往右
for (int i = n - 1; i >= 0; i--) {
for (int s = 1; s <= n; s++) {
//dp[i][s]=Math.min(dp[i+1][s],cost[i]+dp[i + 1][s - 1 - time[i]])
if(s-1-time[i]<=0){ //如果s-1-time[i]<=0,则dp[i + 1][s - 1 - time[i]]=0,故dp[i][s]=Math.min(cost[i],dp[i+1][s])
dp[i][s]=Math.min(cost[i],dp[i+1][s]);
}else if(dp[i+1][s-1-time[i]]!=Integer.MAX_VALUE) {
dp[i][s] = Math.min(dp[i + 1][s], cost[i]+dp[i + 1][s - 1 - time[i]]);
}else if(dp[i+1][s-1-time[i]]==Integer.MAX_VALUE){
dp[i][s] = Math.min(dp[i + 1][s], dp[i + 1][s - 1 - time[i]]);
}
}
}
return dp[0][n];
}
public static void main(String[] args) {
int[] cost = {1, 2, 3, 2};
int[] time = {1, 2, 3, 2};
System.out.println(paintWalls(cost, time));
}
}
题目九
来自思爱普
给你一个长度为 n 下标从 0 开始的整数数组 nums
它包含 1 到 n 的所有数字,请你返回上升四元组的数目。
如果一个四元组 (i, j, k, l) 满足以下条件,我们称它是上升的:
0 <= i < j < k < l < n 且
nums[i] < nums[k] < nums[j] < nums[l] 。
(leetcode2552题)
public static long countQuadruplets(int[] nums) {
int n=nums.length;
long ans=0; //最终有多少个四元组
int[] dp =new int[n]; //遍历到i位置时,以i位置作为小、大、中、特大四个数的中的时候三元组有多少个
for(int l=1;l<n;l++){
for(int j=0;j<l;j++){
if(nums[j]<nums[l]){ //如果j位置作为大的这个数小于l位置,则以l位置为结尾的四元组数量要加上以j位置结尾的三元组数量
ans+=dp[j];
}
}
//更新dp数组
int cnt=0;
for(int j=0;j<l;j++){
if(nums[j]<nums[l]){ //当把l位置加入的时候,遍历前面有几个数小的
cnt++;
}else{ //nums[j]比nums[l]大,则将dp[j]更新
dp[j]+=cnt;
}
}
}
return ans;
}