文章目录
1583.统计不开心的朋友
比较暴力的方法,主要考察的还是数据结构。
class Solution {
public int unhappyFriends(int n, int[][] preferences, int[][] pairs) {
int ans = 0;
int[] index = new int[n];
// 维护一个匹配数组
for (int i = 0 ;i<pairs.length;i++){
int[] cur = pairs[i];
index[cur[0]] = cur[1];
index[cur[1]] = cur[0];
}
// 维护每个人心中的排位
int[][] count = new int[n][n]; // 表示i心中在j的顺位
for (int i = 0;i<n;i++){
for (int j = 0; j<n-1;j++){
count[i][preferences[i][j]] = j+1; // 越大好感越低
}
}
// 开始检查,外层循环每个人,内存枚举如果匹配其他的人。
for (int i = 0; i<n; i++){// 当前人
int with = index[i]; // 当前人匹配的人
for (int j = 0; j<n-1; j++){
int cur = preferences[i][j]; 当前人好感的人
if (cur == with) break;
int with_with = index[cur]; // 好感人匹配的人
// 好感人对匹配人的好感低于对当前人
if (count[cur][with_with]>count[cur][i]) {
ans++;
break;
}
}
}
return ans;
}
}
1584.连接所有点的最小费用(最小连通树)
经典最小连通树的问题,注意一下这里的Java版本的写法和优化。这里的图是一个稠密图,也就是说点少边多。比较适合prim算法。
- Prim算法
class Solution {
public int minCostConnectPoints(int[][] points) {
return Prim(points);
}
public int Prim(int[][] points) {
int n=points.length;
// 需要一个距离表,一个标记表
int[] dist = new int[n];
Arrays.fill(dist, Integer.MAX_VALUE);
boolean[] added=new boolean[n];。
int ans=0;
for(int i=0; i<n; i++){
int min=0;
// 循环1: 寻找不在联通图中,且距离最近的点
for(int j=0; j<n; j++){
if(!added[j]&&dist[j]<dist[min]){
min=j;
}
}
added[min]=true;
if(dist[min]!=Integer.MAX_VALUE) ans+=dist[min];
// 循环2:更新距离表
for(int j=0; j<n; j++){
if(!added[j]){
// 更新在新的点进入之后,连通图到各个点的距离。
dist[j]=Math.min(dist[j], mdist(points[min], points[j]));
}
}
}
return ans;
}
int mdist(int[] x, int[] y){
return Math.abs(x[0]-y[0])+Math.abs(x[1]-y[1]);
}
}
- Kruskal算法:利用并查集,每次查找最近的两个点,判断是否已经相连,若没则连接。
class Solution {
class UF{
int[] fa;
int[] sz;
public UF(int N){
fa = new int[N+1];
sz = new int[N+1];
for (int i = 0;i<=N;i++)fa[i] = i;
for (int i = 0; i<N;i++)sz[i] = 1;
}
public int findfa(int x){
if (fa[x] == x)return x;
fa[x] = findfa(fa[x]);
return fa[x];
}
public boolean isUnion(int x, int y){
return findfa(x) == findfa(y);
}
public void unino(int x, int y){
int xfa = findfa(x);
int yfa = findfa(y);
//if (xfa == yfa) return;
if (sz[xfa] > sz[yfa]){
fa[yfa] = xfa;
sz[xfa] += sz[yfa];
}
else{
fa[xfa] = yfa;
sz[yfa] += sz[xfa];
}
}
}
public int minCostConnectPoints(int[][] points) {
int n = points.length;
// 优先队列进行了优化,并且需要重写比较方法,这里用了很实用的lambda表达式。
PriorityQueue<int[]> pq=new PriorityQueue<>((o1,o2)->o1[2]-o2[2]);
for (int i = 0; i<n;i++){
for (int j = i+1; j<n;j++){
pq.add(new int[]{i,j, dis(points[i], points[j])});
}
}
return Kruskal(pq, n);
}
public int Kruskal(PriorityQueue<int[]> pq, int N){
int res = 0;
int cnt = 0;
UF uf = new UF(N);
while(cnt != N-1){
int[] cur = pq.poll();
int x = cur[0], y = cur[1];
if (uf.isUnion(x,y)) continue;
else{
uf.unino(x,y);
res += cur[2];
cnt++;
}
}
return res;
}
public int dis(int[] A, int[] B){
return Math.abs(A[0]-B[0])+Math.abs(A[1]-B[1]);
}
}
1585. 检查字符串是否可以通过排序子字符串得到另一个字符串(思路)
是一道思路题算法题,我觉着还是挺妙的。但是我很难找到通用的思路。坑神有一个思路,是说每次只是在两个元素之间进行调换。但是这里就有一个问题了,如果该数a
的后面还有一个比他大的数字a+n
,此时的调换是无法实现的。
因此我们可以从后往前数,如果当前需要a
,但是比它大的数字b
是存在的且在原数组中是在其后方,这时无论如何调换,都是无法实现的。
class Solution {
public boolean isTransformable(String ss, String tt) {
char[] s = ss.toCharArray();
char[] t = tt.toCharArray();
int n = ss.length();
// 这里生成了一个数组,生成方法是类名+[]
// 用deque实现stack。
Deque<Integer>[] stack = new LinkedList[10];
for (int i = 0;i<10;i++){
stack[i] = new LinkedList<>();
}
for (int i = 0; i <n;i++){
int a = s[i]-'0';
stack[a].push(i);
}
for (int i = n-1; i>=0; i--){
int a = t[i]-'0';
if (stack[a].isEmpty()) return false;
for (int j = a+1; j<10;j++){
if (!stack[j].isEmpty() && stack[j].peek()>stack[a].peek()){
return false;
}
}
stack[a].pop();
}
return true;
}
}
1588.所有奇数长度的子串和(低复杂度)
虽然是一道简单题目,但是可以用更低的复杂度算法。这个方法也是有点前缀和的意思。
class Solution {
public int sumOddLengthSubarrays(int[] arr) {
int n = arr.length;
int ans = 0;
/* 暴力
for (int i = 1; i<=n;i += 2){ // 长度
for (int j = 0; j+i-1<n; j++){
for (int k = 0; k<i; k++){
ans += arr[j+k];
}
}
}
*/
// 枚举了起点
for (int i = 0; i<n; i++){
int sum = 0, cnt = 0;
// 在该起点下,依次进行累加。
for (int j = i ; j<n;j++){
sum += arr[j];
cnt++;
if ((cnt&1) == 1) ans += sum;
}
}
return ans;
}
}
1589.所有排列中的最大和(差分数组)
是一类比较有特点的题目,主要是利用差分统计区间的覆盖频次问题。维护了一个差分数组,对于每次的覆盖区间,区间头位置+1,区间结尾+1的位置-1。最后在进行累加,这样数组每个位置就对应了相应位置的频次。真的很妙了。
class Solution {
public int maxSumRangeQuery(int[] nums, int[][] requests) {
// 差分数组
Arrays.sort(nums);
int mod = 1000000007;
int n = nums.length;
int[] f = new int[n];
int ans = 0;
// 差分数组
for (int[] e:requests){
f[e[0]] += 1;
if (e[1]+1<n) f[e[1]+1] -= 1;
}
for (int i = 1; i<n ;i++){
f[i] += f[i-1];
}
//
Arrays.sort(f);
for (int i = n-1; i>=0; i--){
if (f[i] == 0) return ans;
//ans = (int)((long)(ans + f[i]*nums[i])%mod);
ans = (int)(ans + 1L*f[i]*nums[i])%mod;
}
return ans;
}
}
1590.使数组和能被P整除
也是一道好题,利用了取模,和前缀和。由于我们要求得到前缀和除P之后的余数,因此我们可以利用hashmap存储一下。然后每次查找最近的符合要求的位置,计算得到长度。尤其注意大数的处理。一定小心一切可能爆整数的存在。
class Solution {
public int minSubarray(int[] nums, int p) {
int n = nums.length;
Map<Integer, Integer> hashmap = new HashMap<>();
hashmap.put(0,0);
int[] presum = new int[n+1];
int sum = 0;
int ans = Integer.MAX_VALUE;
for (int i = 0; i < n ; i++){
int now = nums[i];
sum = (int)((long)(sum+now)%p);
presum[i+1] = (int)((long)(presum[i]+now)%p);
}
int res = sum % p;
for (int i = 1; i<=n ;i++){
int cur = presum[i];
hashmap.put(cur,i);
// 思路:寻找res = cur-target;
int target = (int)((long)(cur-res+p)%p);
if (hashmap.containsKey(target)){
ans = Math.min(ans, i-hashmap.get(target));
}
}
if (ans == Integer.MAX_VALUE || ans == n)return -1;
return ans;
}
}
1591.奇怪的打印机II(拓扑排序)
又是一道很好的题,这周的周赛真是好题啊。一道转换为拓扑排序的题目。看似是涂色,其实可以构建起颜色转换之间的有向连接。当颜色1需要覆盖在颜色2上,可以得到一个从2指向1的标志,并且1的限制条件+1。如果某个颜色的限制条件为0,就可以揭开。和选课问题一样/
class Solution {
public boolean isPrintable(int[][] targetGrid) {
int n = targetGrid.length;
int m = targetGrid[0].length;
int[] up = new int[61];
Arrays.fill(up, Integer.MAX_VALUE);
int[] button = new int[61];
int[] left = new int[61];
Arrays.fill(left, Integer.MAX_VALUE);
int[] right = new int[61];
//Set<Integer> set = new HashSet<>();
for (int i = 0; i<n; i++){
for (int j = 0; j<m; j++){
int color = targetGrid[i][j];
//set.add(color);
up[color] = Math.min(up[color], i);
left[color] = Math.min(left[color], j);
right[color] = Math.max(right[color], j);
button[color] = Math.max(button[color], i);
}
}
List<Integer>[] egde = new ArrayList[61]; // 构建图
for (int i = 0; i<61;i++){
egde[i] = new ArrayList<>();
}
int[][] have = new int[61][61]; // 防止重复
int[] ins = new int[61]; // 入读
for (int i = 0; i<n;i++){
for (int j = 0;j <m; j++){
int color = targetGrid[i][j];
for (int k = 1; k<61;k++){
if (k == color) continue;
if (i>=up[k] && i<= button[k] && j>=left[k] && j<=right[k] && have[color][k] == 0) {
have[color][k] = 1;
egde[color].add(k);
ins[k]++;
}
}
}
}
// 拓扑排序
Queue<Integer> queue = new LinkedList<>();
for (int i = 1; i<61; i++){
if (ins[i] == 0) queue.offer(i);
}
int num = 0;
while (!queue.isEmpty()){
int cur = queue.poll();
num++;
for (int next:egde[cur]){
ins[next]--;
if (ins[next] == 0) {
queue.offer(next);
}
}
}
System.out.println(num);
return num == 60;
}
}