刷题-DAY5

题目一

每种工作有难度和报酬,规定如下

class Job{

public int money;/该工作的报酬

public int hard;/该工作的难度

}

给定一个Job类型的数组jobarr,表示所有岗位,每个岗位都可以提供任意份工作选工作的标准是在难度不超过自身能力值的情况下,选择报酬最高的岗位,给定一个int类型的数组arr,表示所有人的能力返回int类型的数组,

难度相同的只选报酬最大的 其他的筛掉

先按照难度排 然后再按照报酬排

然后如果在顺序上 如果有两件工作 工作A难度比工作B低 报酬比工作B高 直接筛选掉B

然后在这个基础上选择

返回int类型的数组,T表示每个人按照标准选工作后所能获得的最高报酬

逻辑是挺简单 有点考验coding能力

class Mycompare implements Comparator<Job>{
	@Override
	public int compare(Job job1,Job job2){
		return job1.hard==job2.hard?job2.money-job1.money:job1.hard-job2.hard;
	}
}

public static int [] MaxMoney(Job [] jobs,int [] ability) {
		Arrays.sort(jobs,new Mycompare());
		TreeMap<Integer, Integer> map = new TreeMap<>();
		map.put(jobs[0].hard, jobs[0].money);
		Job pre = jobs[0];
		for (int i = 1; i < jobs.length; i++) {
			if (jobs[i].hard != pre.hard && jobs[i].money > pre.money) {
				pre = jobs[i];
				map.put(pre.hard, pre.money);
			}
		}
		int[] ans = new int[ability.length];
		for (int i = 0; i < ability.length; i++) {
			Integer key = map.floorKey(ability[i]);
			ans[i] = key != null ? map.get(key) : 0;
		}
		return ans;
	}

这个code是在是太巧妙了

我刚开始的做法是 HashMap去重的同时PriorityQueue 排序 然后再把结果倒到数组中 然后再二分查找 

题解的话 先通过一个Array.sort 尤其是这个比较器 只一步就完成了"难度正序 在难度相同的情况 钱逆序" 然后再用TreeMap完成筛选(不仅完成了难度相同的只选报酬最大的 也完成了工作A难度比工作B低 报酬比工作B高的情况 上面的做法很难完成第二个筛选 因为已经按照难度排序了 如果难度相同 只需要取相同难度中的第一个(所以只要再出现难度相同的全不要) 难度不同 如果钱比之前的少了 那不要(一组中的第一个一定是钱最多的 如果它的钱比前面难度的钱最多的少 那这一组就不用看了))

 然后直接取最和ability相邻的 (有没有在担心 万一之前难度小的有钱多的那? 在完成筛选后 如果前面真的有一个难度小钱多的工作 班当前的ability早就被删掉了 所以完成筛选后每一个都是最优解)

(比较器返回1第二个参数在前面 返回-1第一个参数在前面)

Integer key = map.floorKey(ability[i]);
ans[i] = key != null ? map.get(key) : 0;

这两行要注意 通过map取距离最近的值取出来的是KEY 但是我们要的是Value 而且这个KEY是可以为空的 而空的就不能用int接收

题目二

长度为N的数组arr,一定可以组成N^2个数值对。例如arr =[3,1,2].数值对有(3,3)(3,1)(3,2)(1,3)(1,1)(1,2)(2,3)(2,1)(2,2).也就是任意两个数都有数值对,而且自己和自己也算数值对。数值对怎么排序?规定,第一维数据从小到大,第一维数据一样的,第二维数组也从小到大。所以上面的数值对排序的结果为:(1,1)(1,2(1,3)(2,1)()2,2)(2,3)(3,1)(3,2)(3,3)给定一个数组arr.和整数k,返回第k小的数值对。

暴力的直接把所有都排出来 确实可以 但是面对高数据量很难ac

假设数组有序

1 1 2 2 3 3 3 4 5 5 

我们在找第56小

那么就是 先(56-1)/10 = 5....6 所以数值对的第一个数肯定是3(第5个数)

为啥要减1呢 这个是边界处理条件 想象一下找第50小50/10 = 5..0这不行 我们需要找的是4开头的第10个 而不是五开头的第0个 当然 做一个if整除 那么结果减1也可以

先排除掉1开头 2开头的 剩下16个

剩下的这所有数值对我们来描述一下

31 31 32 32 33 33 33 34 35 35 

31 31 32 32 33 33 33 34 35 35

31 31 32 32 33 33 33 34 35 35

.......

排序出来的31 31 31 31 31 31 32 32 32 32 32 32 33 33 33 33 33 33.....

在这剩下的数值对中找 第16小

本质上和上面的逻辑是一样的 只是这次为3个一组了

每个数都固定有三个一组 还是有序的 那不就是在找16在第几组中吗?

(16-1)/3 = 5 ....0 也就是在第五组中 做过减一处理了 实际含义是 前一个数在第几组中 既然是余零 就说明 上一个数是第4组最后一个 当前数就是五组的第一个

总结一下公式

N为数组有多少个数 K为找第几小

(k-1)/N = f   f为数值对中第一个数为数组第几个数

a 为 数组中小于f的个数

b为 数组中等于f的个数

f1 = k - a*N  就是 利用第一位小于f排除后 剩下的数中找第f1小

(f1 - 1)/b  肯定是以f开头了 那么f有b个 所以相同的数有b组 那就找f1在第几组中即可

那么我们需要找到小于f的数 和==f的个数 这就需要快排思想了

public static void swap(int [] arr,int x,int y) {
		int tmp = arr[x];
		arr[x] = arr[y];
		arr[y] = tmp;
	}

	public static int [] partition(int [] arr,int L,int R,int p) {
	    if(R<L) {
	    	return new int []{-1,-1};
	    }
	    if(L==R) {
            return new int[] {L,R};
        }
		swap(arr, p, R);
		int Lsize = L-1;
		int Rsize = R;
		int num = arr[R];
		int i = L;
		while(i<Rsize) {
			if(arr[i]<num) {
				swap(arr,i++,++Lsize);
			}
			else if(arr[i]==num) {
				i++;
			}
			else if(arr[i]>num) {
				swap(arr,i,--Rsize);
			}
		}
		swap(arr,Rsize,R);
		return new int [] {Lsize+1,Rsize};
		
	}
	
	public static int[] findcouple(int [] arr,int K) {
		int N = arr.length;
		if (K > N * N) {
			return null;
		}
		int one = findMin(arr, (K-1)/N);
		int a = 0;
		int b = 0;
		for (int i : arr) {
			if(i<one) {
				a++;
			}
			if(i==one) {
				b++;
			}
		}
		int two = ((K-a*N)-1)/b;
		return new int [] {one,findMin(arr,two)};
	}
	public static int findMin(int[] arr, int N) {
		int L = 0;
		int R = arr.length - 1;
		int pivot = 0;
		int[] range = null;
		while (L < R) {
			pivot = arr[L + (int) (Math.random() * (R - L + 1))];
			range = partition(arr, L, R, pivot);
			if (N < range[0]) {
				R = range[0] - 1;
			} else if (N > range[1]) {
				L = range[1] + 1;
			} else {
				return pivot;
			}
		}
		return arr[L];//假如说 最后只有3个元素要排 那么敲定中间之后 剩下的左和右都不会排了 且此时N并不等于 range[0]或range[1]
	}

题目三

背包容量为w一共有n袋零食,第i袋零食体积为v[i]总体积不超过背包容量的情况下一共有多少种零食放法?(总体积为0也算一种放法)。

public static int process(int pre,int rest,int [] v) {
   /*	if(pre==v.length) {
			return 1;
		}
		if(v[pre]==rest) {
			return 1;
		}
		if(v[pre]<rest) {
			return process(pre+1, rest, v);
		}
		*/
		
		if(rest<0) {
			return -1;
		}
		if(pre == v.length) {
			return 1;
		}
		int p1 = process(pre+1, rest-v[pre], v);
		int p2 = process(pre+1, rest, v);
		return process(pre+1, rest-v[pre], v)+process(pre+1, rest, v);
	}
	public static int MaxMethod(int [] v,int w) {
		return process(0,w,v);
	}

本质上是遍历每一个零食要/不要 如果只要第一个的话实质上就是 第一个要 第二个不要......

注释部分是我之前的basecase 无零食可选就代表着所有零食都遍历完了(不关系之前到底是什么情况 我只关心所有都遍历了无论要还是不要) 就是一种选法

改成动态规划喵

纵轴pre从大到小

横轴rest 从小到大

dp[i][j] 依赖于 dp[i-1][j]+dp[i]-1[j-arr[i]]   前提是不越界 每个这种减 都得注意一下越不越界

不过在这里 我又有了点疑问i-arr[i]的话 那整个dp数组岂不是很大? 如果直接j-1表示呢

横轴的含义为...剩余的空间 如果剩余空间不存在那就置0? 如果改成j-1就变成了已经遍历过的包裹数  是不是可以节省很多空间?不行啊 那怎么判断还能不能放下 这含义都变了啊

public static int ways2(int[] arr, int w) {
		int N = arr.length;
		int[][] dp = new int[N + 1][w + 1];
		for (int j = 0; j <= w; j++) {
			dp[N][j] = 1;
		}
		for (int i = N - 1; i >= 0; i--) {
			for (int j = 0; j <= w; j++) {
				dp[i][j] = dp[i + 1][j] + ((j - arr[i] >= 0) ? dp[i + 1][j - arr[i]] : 0);
			}
		}
		return dp[0][w];
	}

既然是依赖i+1行的话 那肯定是要把最底下一行推出来的 

所有零食遍历完了 不管剩多少空间 都是一种结果 作为basecase把最后一行填上

最后一行的意思是 没有零食 空间0~w 的结果都是1

那么我们求的dp[0][w] 就是有arr里面全部零食 空间w的结果

所有点都依赖左下方或者下方的点 所以要右上的点?

猜你喜欢

转载自blog.csdn.net/chara9885/article/details/131734822
今日推荐