拼接最大数(HARD)
- 划分解空间:第一个数组取x个数,第二个数组取y个数,其中 x + y = k x+y = k x+y=k。显然取的数在满足顺序条件的情况下是最大数,利用单调栈求解
- 取数后,转化为如何将两个序列拼成最大数。双指针(归并)即可。
- 使用双指针贪心归并。考察两个序列的第一个数。若不等则取较大的,指针后移。若相等,考察后面第一个不等的数,较大的那个序列指针加1。
class Solution {
public int[] deal(int[] num,int k)
{
List<Integer> s = new ArrayList<>();
int pop = 0;
for(int i= 0;i<num.length;i++)
{
if(s.isEmpty())
s.add(num[i]);
else
{
while(!s.isEmpty() && num[i] > s.get(s.size() - 1) && pop < k)
{
s.remove(s.size() - 1);
pop++;
}
s.add(num[i]);
}
}
int[] ret = new int[num.length - k];
for(int i = 0;i<ret.length;i++)
ret[i] = s.get(i);
return ret;
}
public boolean greater(int[] a,int[] b)
{
for(int i = 0;i<a.length;i++)
{
if(a[i] > b[i])
return true;
if(a[i] < b[i])
return false;
}
return false;
}
public int[] maxNumber(int[] nums1, int[] nums2, int k) {
int[] now = new int[k];
int[] max = new int[k];
boolean first =true;
for(int x = Math.max(0,k - nums2.length);x<=Math.min(k ,nums1.length);x++)
{
int[] a = deal(nums1,nums1.length - x);
int[] b = deal(nums2,nums2.length - (k - x));
int i = 0,j = 0,count = 0;
while(i<a.length && j < b.length)
{
if(a[i] > b[j]) {
now[count++] = a[i];
i++;
}
else if(a[i] < b[j])
{
now[count++] = b[j];
j++;
}
else
{
int q = 0;
while(i+q<a.length && j+q < b.length && a[i+q] == b[j+q])
{
q++;
}
if(i+q >= a.length && j+q>=b.length)
{
now[count++] = a[i];
i++;
}
else if(j+q>=b.length)
{
now[count++] = a[i];
i++;
}
else if(i + q >= a.length)
{
now[count++] = b[j];
j++;
}
else if(a[i+q] > b[j+q]) {
now[count++] = a[i];
i++;
}
else if(b[j + q] > a[i + q])
{
now[count++] = b[j];
j++;
}
}
}
while(i < a.length)
{
now[count++] = a[i];
i++;
}
while(j < b.length)
{
now[count++] = b[j];
j++;
}
if(greater(now,max))
System.arraycopy(now,0,max,0,now.length);
}
return max;
}
public static void main(String[] args)
{
new Solution().maxNumber(new int[]{7,6,1,9,3,2,3,1,1},new int[]{4,0,9,9,0,5,5,4,7},9);
}
}
区间和的个数(HARD)
分治+归并排序
- 回顾:逆序对问题的分治求法。数组一分为二 + 求一分为二之间逆序对数目
- 递归:一分为二,假设左侧满足条件的个数已经求出,右侧的满足条件个数已经求出,且左右两侧均有序。
- 考察前缀和数组。设左侧部分为数组 n 1 n_1 n1,右侧部分为数组 n 2 n_2 n2,均为升序。即求 n 2 [ j ] − n 1 [ i ] ∈ ( l o w e r , u p p e r ) n_2[j] - n_1[i] \in (lower,upper) n2[j]−n1[i]∈(lower,upper)的(i,j)个数。对 n 2 n_2 n2利用滑动窗口(或称为双指针),只会向右侧移动而不会回溯。对 n 1 n_1 n1中的每个值顺序求解。
- 算法分析:至多遍历一次 n 1 , n 2 n_1,n_2 n1,n2,复杂度 O ( n ) O(n) O(n),再加上merge的复杂度 O ( n ) O(n) O(n)。所以复杂度等于merge复杂度 O ( n ) O(n) O(n)。最终复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
class Solution {
int ans = 0;
public long[] mergesort(int low,int high,long[] nums,int lower,int upper)
{
if(low == high)
{
if(nums[low] >= lower && nums[low]<=upper)
ans++;
return new long[]{nums[low]};
}
int mid = (low+high) / 2;
long[] a = mergesort(low,mid,nums,lower,upper);
long[] b = mergesort(mid + 1,high,nums,lower,upper);
long[] c = new long[high - low + 1];
int i=0,l=0,r=0;
while(i<a.length)
{
if(r < 0)
r++;
while(l<b.length && (long)b[l] - (long)a[i] < (long)lower)
l++;
while(r<b.length && (long)b[r] - (long)a[i] <= (long)upper)
r++;
r--;
if(l<=r)
ans += (r - l + 1);
i++;
}
l = r = i = 0;
while(l < a.length && r < b.length)
{
if(a[l] <= b[r])
{
c[i++] = a[l];
l++;
}
else
{
c[i++] = b[r];
r++;
}
}
while(l < a.length)
{
c[i++] = a[l];
l++;
}
while(r < b.length)
{
c[i++] = b[r];
r++;
}
return c;
}
public int countRangeSum(int[] nums, int lower, int upper) {
long[] pre = new long[nums.length];
pre[0] = nums[0];
for(int i =1;i<nums.length;i++)
pre[i] = pre[i - 1] + nums[i];
mergesort(0,pre.length - 1,pre,lower,upper);
return ans;
}
public static void main(String[] args)
{
new Solution().countRangeSum(new int[]{2147483647,-2147483648,-1,0},-1,0);
}
}
总结
- 在归并排序和快速排序过程中,在递归的阶段除了做该做的事情外,还可以做其他事情而使得复杂度不提升。例如在本题中就做了统计(i,j)对,在逆序对中统计了逆序对。
按要求补齐数组(HARD)
最优子结构(显然)
贪心选择
- 为了包括1,如果num中不包括1一定要补充1
- MOTIVATION:每次选择使区间扩充尽可能大,又填补了空缺。
- 然后,一定存在某个最优解,第一步填写原始num中所有可能和在[1,n]空缺的最小值(设为x)。
- 若不然,任取某个最优解,一定会填写 < x <x的某个值进以补全x。将此操作提到第一步再重复原始最优解的所有操作构造了另一个最优解。
算法分析
- 即使num只有1,按上述算法,每次区间规模扩大2倍。因此至多添加logn步即可最优解。
Implementation(巧妙)
- 设遍历到 n u m [ k ] num[k] num[k]时,未将num[k]纳入有区间[1,r],将num[k]纳入后区间变为 [ 1 , r ] ∪ [ n u m [ k ] , n u m [ k ] + r ] [1,r] \cup [num[k],num[k] + r] [1,r]∪[num[k],num[k]+r]
- 由于num是递增的,对原始num的所有和构成的区间,区间之间的空白部分第一次生成(r与num[k]间的空白)就不可能由后续的数字填充。 只能当即填充。
- 维护r的定义:当前从1开始所能连续到达的最远数字。
- 若 [ 1 , r ] ∪ [ n u m [ k ] , n u m [ k ] + r ] [1,r] \cup [num[k],num[k] + r] [1,r]∪[num[k],num[k]+r]中间有空隙,则按上述贪心思路填充,直到将空隙消除。消除后,对新的 r , r^, r,,更新 r ← r , + n u m s [ k ] r \leftarrow r^, + nums[k] r←r,+nums[k]。
public int minPatches(int[] nums, int n) {
int ans = nums[0] == 1?0:1;
long r = 1;
int i = nums[0] == 1?1:0;
while(r < (long)n && i < nums.length)
{
if(r < (long)nums[i] - 1)
{
ans++;
r = 2*r + 1;
}
else
{
r = r + (long)nums[i];
i++;
}
}
while(r < (long)n) {
r = 2 * r + 1;
ans++;
}
return ans;
}
路径交叉(HARD)
class Solution {
public boolean isSelfCrossing(int[] distance) {
if(distance.length < 4)
return false;
if(distance[2] > distance[0] || distance[3] < distance[1])
{
if(distance.length == 4)
return false;
int upper = distance[1] == distance[3] ? 0 : distance[0];
int left = -distance[1];
int lower = distance[0] - distance[2];
int right = 0;
int x = -distance[1] + distance[3],y = distance[0] - distance[2];
int j = 4;
if(distance[3] > distance[1])
{
int prex = 0;
int prey = 0;
int tempx;
int tempy;
while(true)
{
if(j>=distance.length)
return false;
tempx = x;
tempy = y;
y += distance[j];
if(y < prey)
left = prex - distance[j-3];
else if(y<=prey + distance[j-4])
left = prex;
else left = Integer.MIN_VALUE;
if(j+1 >= distance.length)
return false;
x -= distance[j + 1];
if(x <= left)
return true;
if(j+2 >= distance.length)
return false;
if(x > prex)
lower = prey+distance[j-4] - distance[j-2];
else if(x >= prex - distance[j-3])
lower = prey + distance[j-4];
else lower = Integer.MIN_VALUE;
y -= distance[j+2];
if(y <= lower)
return true;
if(j+3 >= distance.length)
return false;
right = tempx;
if(x < prex - distance[j-3] && (y<=prey+distance[j-4] && y>=prey+distance[j-4]-distance[j-2]))
right = prex - distance[j-3];
else if(y < tempy)
right = Integer.MAX_VALUE;
x+=distance[j+3];
if(x>=right)
return true;
if(distance[j+1] >= distance[j+3])
{
upper = (y<tempy && x>=prex - distance[j-3])?tempy:tempy + distance[j];
left = tempx - distance[j+1];
lower = tempy + distance[j] - distance[j+2];
right = x;
j+=4;
break;
}
prex = tempx;
prey = tempy;
j+=4;
}
}
else
right = x;
for(;j<distance.length;j+=4)
{
y = y + distance[j];
if(y >= upper)
return true;
upper = y;
if(j+1 >= distance.length)
return false;
x -= distance[j+1];
if(x <= left)
return true;
left = x;
if(j+2 >= distance.length)
return false;
y -= distance[j+2];
if(y <= lower)
return true;
lower = y;
if(j+3 >= distance.length)
return false;
x += distance[j+3];
if(x >= right)
return true;
right = x;
}
return false;
}
else return true;
}
}
回文对(HARD)
- 讨论每一种拼接的情况。相同长度,需要互为逆序;不同长度,长度较小的需要是长度较长的前缀或后缀,长度较长的剩余部分需要是回文串
- 获取每个字符串的逆序,保存到key为该逆序,value为Index的哈希表。当长度不同时,对每个原始字符串的前缀和后缀在hash中查找index,另外判断剩余部分是否是回文串。
超时代码
class Solution {
public List<List<Integer>> palindromePairs(String[] words) {
Map<String,Integer> q = new HashMap<>();
int emptyindex = -1;
for(int i = 0;i<words.length;i++)
{
if(words[i].equals(""))
emptyindex = i;
StringBuilder a = new StringBuilder(words[i]);
q.put(a.reverse().toString(),i);
}
List<List<Integer>> ans = new ArrayList<>();
for(int i = 0;i<words.length;i++)
{
if(q.containsKey(words[i]) && q.get(words[i]) != i) {
List<Integer> temp = new ArrayList<>();
temp.add(i); temp.add(q.get(words[i]));
ans.add(temp);
}
}
for(int p = 0;p<words.length;p++)
{
String a = words[p];
boolean[][] dp = new boolean[a.length()][a.length()];
if(words[p].equals(""))
continue;
for(int i = a.length() - 1;i>=0;i--)
for(int j = a.length() - 1;j>=0;j--)
{
if(i >= j)
dp[i][j] = true;
else
dp[i][j] = (i + 1 >= j - 1 || (dp[i + 1][j - 1] )) && a.charAt(j) == a.charAt(i);
}
if(dp[0][a.length() - 1] && emptyindex!=-1)
{
List<Integer> temp = new ArrayList<>();
temp.add(p); temp.add(emptyindex);
ans.add(temp);
temp = new ArrayList<>();
temp.add(emptyindex);temp.add(p);
ans.add(temp);
}
for(int i = 0;i<a.length() - 1;i++)
{
if(dp[i+1][a.length() - 1] && q.containsKey(a.substring(0,i+1)))
{
List<Integer> temp = new ArrayList<>();
temp.add(p); temp.add(q.get(a.substring(0,i+1)));
ans.add(temp);
}
if(dp[0][a.length() - 2 - i] && q.containsKey(a.substring(a.length() - 1 - i)))
{
List<Integer> temp = new ArrayList<>();
temp.add(q.get(a.substring(a.length() - 1 - i)));
temp.add(p);
ans.add(temp);
}
}
}
return ans;
}
public static void main(String[] args)
{
new Solution().palindromePairs(new String[]{"abcd","dcba","lls","s","sssll"
});
}
}
改进
- 原因:卡常。不需要将dp[i][j]全部计算完成。
class Solution {
public List<List<Integer>> palindromePairs(String[] words) {
Map<String,Integer> q = new HashMap<>();
int emptyindex = -1;
for(int i = 0;i<words.length;i++)
{
if(words[i].equals(""))
emptyindex = i;
StringBuilder a = new StringBuilder(words[i]);
q.put(a.reverse().toString(),i);
}
List<List<Integer>> ans = new ArrayList<>();
for(int i = 0;i<words.length;i++)
{
if(q.containsKey(words[i]) && q.get(words[i]) != i) {
List<Integer> temp = new ArrayList<>();
temp.add(i); temp.add(q.get(words[i]));
ans.add(temp);
}
}
for(int p = 0;p<words.length;p++)
{
String a = words[p];
if(words[p].equals(""))
continue;
if(ispar(a,0,a.length() - 1)&& emptyindex!=-1)
{
List<Integer> temp = new ArrayList<>();
temp.add(p); temp.add(emptyindex);
ans.add(temp);
temp = new ArrayList<>();
temp.add(emptyindex);temp.add(p);
ans.add(temp);
}
for(int i = 0;i<a.length() - 1;i++)
{
if(ispar(a,i+1,a.length() - 1) && q.containsKey(a.substring(0,i+1)))
{
List<Integer> temp = new ArrayList<>();
temp.add(p); temp.add(q.get(a.substring(0,i+1)));
ans.add(temp);
}
if(ispar(a,0,a.length() - 2 - i)&& q.containsKey(a.substring(a.length() - 1 - i)))
{
List<Integer> temp = new ArrayList<>();
temp.add(q.get(a.substring(a.length() - 1 - i)));
temp.add(p);
ans.add(temp);
}
}
}
return ans;
}
public boolean ispar(String a,int low,int high)
{
while(low <= high)
{
if(a.charAt(low) == a.charAt(high))
{
low++;
high--;
}
else
return false;
}
return true;
}
public static void main(String[] args)
{
new Solution().palindromePairs(new String[]{"abcd","dcba","lls","s","sssll"
});
}
}
将数据流变为多个不相交区间(HARD)
朴素的二分查找和区间维护
- 算法分析:合并区间时删除后一个区间可能造成 O ( n ) O(n) O(n)的复杂度
class SummaryRanges {
List<int[]> ita = new ArrayList<>();
public SummaryRanges() {
}
public void addNum(int val) {
if(ita.size() == 0) {
ita.add(new int[]{val, val});
return;
}
int low = 0;
int high = ita.size();
while(low < high)
{
int mid = (low + high) / 2;
if(!(ita.get(mid)[0] >= val))
low = mid + 1;
else high = mid;
}
if(low < ita.size() && low - 1 >= 0)
{
if(ita.get(low-1)[1] >= val || ita.get(low)[0] == val)
return;
if(val == ita.get(low-1)[1] + 1 && val == ita.get(low)[0] - 1)
{
ita.get(low - 1)[1] = ita.get(low)[1];
ita.remove(low);
return;
}
if(val == ita.get(low-1)[1] + 1)
ita.get(low - 1)[1]++;
else if(val == ita.get(low)[0] - 1)
ita.get(low)[0]--;
else
ita.add(low,new int[]{val,val});
}
else if(low < ita.size())
{
if(ita.get(low)[0] == val )
if(val == ita.get(low)[0] - 1)
ita.get(low)[0]--;
else ita.add(low,new int[]{val,val});
}
else
{
if(ita.get(low-1)[1] >= val )
return;
if(val == ita.get(low-1)[1] + 1)
ita.get(low - 1)[1]++;
else ita.add(low,new int[]{val,val});
}
}
public int[][] getIntervals() {
int[][] ans = new int[ita.size()][2];
for(int i = 0;i<ita.size();i++)
ans[i] = ita.get(i);
return ans;
}
}
加入哨兵的代码优化
- 前后方分别加入[MIN,MIN],[MAX,MAX]区间避免分类讨论
class SummaryRanges {
List<int[]> ita = new ArrayList<>();
public SummaryRanges() {
ita.add(new int[]{Integer.MIN_VALUE,Integer.MIN_VALUE});
ita.add(new int[]{Integer.MAX_VALUE,Integer.MAX_VALUE});
}
public void addNum(int val) {
int low = 1;
int high = ita.size() - 1;
while(low < high)
{
int mid = (low + high) / 2;
if(!(ita.get(mid)[0] >= val))
low = mid + 1;
else high = mid;
}
if(ita.get(low-1)[1] >= val || ita.get(low)[0] == val)
return;
if(val == ita.get(low-1)[1] + 1 && val == ita.get(low)[0] - 1)
{
ita.get(low - 1)[1] = ita.get(low)[1];
ita.remove(low);
return;
}
if(val == ita.get(low-1)[1] + 1)
ita.get(low - 1)[1]++;
else if(val == ita.get(low)[0] - 1)
ita.get(low)[0]--;
else
ita.add(low,new int[]{val,val});
}
public int[][] getIntervals() {
int[][] ans = new int[ita.size() - 2][2];
for(int i = 1;i<ita.size()-1;i++)
ans[i-1] = ita.get(i);
return ans;
}
}
使用TreeSet有序映射维护区间 + 哨兵优化
- 红黑树都数据结构可以区间值为键值。查找最后一个小于和第一个大于等于的区间左端点,删除,插入等都在 O ( l o g n ) O(logn) O(logn)时间完成。
- 无论何种情况顺序都不会变化,因此改变数组元素并不影响原始红黑树的结构
class SummaryRanges {
TreeSet<int[]> ita = new TreeSet<>(new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return Integer.compare(o1[0],o2[0]);
}
});
public SummaryRanges() {
ita.add(new int[]{Integer.MIN_VALUE,Integer.MIN_VALUE});
ita.add(new int[]{Integer.MAX_VALUE,Integer.MAX_VALUE});
}
public void addNum(int val) {
int[] cur = new int[]{val,val};
int[] prev = ita.floor(cur);
int[] next = ita.ceiling(cur);
if(prev[1] >= val || next[0] == val)
return;
if(val == prev[1] + 1 && val == next[0] - 1)
{
prev[1] = next[1];
ita.remove(next);
return;
}
if(val == prev[1] + 1)
prev[1]++;
else if(val == next[0] - 1)
next[0]--;
else
ita.add(new int[]{val,val});
}
public int[][] getIntervals() {
Iterator<int[]> iter = ita.iterator();
int n = ita.size();
int[][] ans = new int[n-2][2];
iter.next();
for(int i = 0;i<n-2;i++)
{
ans[i] = iter.next();
}
return ans;
}
}
俄罗斯套娃信封问题(HARD,很好的一道题)
- 信封间显然偏序关系。将信封看作结点,即为求有向无环图中最长路径
- 但是建图 O ( n 2 ) O(n^2) O(n2)
Motivation(考察最优解的形式)
- 注意解的特殊性:任何上升序列的宽w都是不同的
- 根据宽对所有的信封分组,宽相同的为一组
- 问题转换为从每个分组中抽取至多一封信(当前分组也可以不抽取),使得上升序列最大。
- 上升首先要保证宽上升。故先考虑分组顺序为:宽较小的分组在前,较大的在后。这样分组抽取的顺序也就保证了。
- 问题转换为:按顺序,每个分组至多选择一封信,且选择后的信封高要上升。如何能选择最多的信封?
- 如能对每个分组中的高从大到小排列,则上升序列不会出现在分组的内部,只会出现在分组之间,此时求最长上升子序列即可(二分求法)。
public int maxEnvelopes(int[][] envelopes) {
Arrays.sort(envelopes, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
int a = Integer.compare(o1[0],o2[0]);
if(a != 0)
return a;
return -Integer.compare(o1[1],o2[1]);
}
});
List<Integer> a = new ArrayList<>();
a.add(envelopes[0][1]);
for(int i = 1;i<envelopes.length;i++)
{
int low = 0;
int high = a.size();
int b = a.get(a.size() - 1);
if(b < envelopes[i][1])
{
a.add(envelopes[i][1]);
}
else if(b > envelopes[i][1])
{
while(low < high)
{
int mid = (low + high) >> 1;
if(a.get(mid) < envelopes[i][1])
low = mid + 1;
else high = mid;
}
a.set(low,envelopes[i][1]);
}
}
return a.size();
}
矩形区域不超过K的最大数值和
二维前缀和(暴力),复杂度 O ( m 2 n 2 ) O(m^2n^2) O(m2n2)
- d p [ i ] [ j ] dp[i][j] dp[i][j],i,j格点及其左上角矩阵元素之和
class Solution {
public int maxSumSubmatrix(int[][] matrix, int k) {
int m = matrix.length;
int n = matrix[0].length;
int[][] dp = new int[m][n];
int ans = Integer.MIN_VALUE;
for(int i = 0;i<m;i++)
for(int j = 0;j<n;j++)
{
int a = j-1 >= 0 ? dp[i][j-1]: 0;
int b = i-1>=0? dp[i-1][j] : 0;
int c = (i-1)>=0 && (j-1)>=0?dp[i-1][j-1]:0;
dp[i][j] = a + b -c +matrix[i][j];
if(dp[i][j]<=k)
ans = Math.max(ans,dp[i][j]);
}
for(int i = 0;i<m;i++)
for(int j = 0;j<n;j++)
{
for(int p = 0;p<=i;p++)
for(int q = 0;q<=j;q++)
{
int c = (p-1)>=0 && (q-1)>=0 ? dp[p-1][q-1]:0;
int a = (p-1)>=0 ? dp[p-1][j] : 0;
int b = (q-1)>=0? dp[i][q-1]:0;
int d = (dp[i][j] - a - b + c);
if(d<=k)
ans = Math.max(ans,d);
}
}
return ans;
}
}
数组最大区间和(有序集合log的魅力)
- 一维数组,如何求最大区间和?求前缀和,利用平衡树维护前缀和的有序性,在树上二分求解,时间复杂度 O ( l o g n ) O(logn) O(logn)
- 有序集合:
- Ceiling方法:求解大于等于指定数的最小数
- floor方法:求解小于等于指定数的最大数
class Solution {
public int maxSumSubmatrix(int[][] matrix, int k) {
int m = matrix.length;
int n = matrix[0].length;
int[][] dp = new int[matrix.length][matrix[0].length];
int ans = Integer.MIN_VALUE;
for(int i = 0;i<matrix.length;i++)
{
for(int j = 0;j<dp[0].length;j++)
{
int a;
if(i == 0)
a = 0;
else a = dp[i-1][j];
dp[i][j] = a + matrix[i][j];
}
}
for(int i = 0;i<matrix.length;i++)
for(int j = i;j<matrix.length;j++)
{
TreeSet<Integer> s = new TreeSet<>();
s.add(0);
int pre = 0;
for(int q= 0;q<matrix[0].length;q++)
{
int a;
if(i - 1 < 0)
a = 0;
else a = dp[i-1][q];
int cur = dp[j][q] - a;
pre += cur;
Integer p = s.ceiling(pre - k);
if(p != null)
ans = Math.max(ans,pre - p);
s.add(pre);
}
}
return ans;
}
public static void main(String[] args)
{
new Solution().maxSumSubmatrix(new int[][]{{1,0,1},{0,-2,3}},2);
}
}
O(1)时间插入,删除和获取随机元素-允许重复
- 没有想到的点:固化的想存储每个元素个数,利用0-1间随机数的线性映射。可以利用一个list存储所有元素,根据random生成到size的整数即可
- 如果顺序无关紧要,删除list中一个元素可以与最后一个元素交换达到 O ( 1 ) O(1) O(1)复杂度
不使用迭代器
- 当元素不重复时,可使用map:数据到数据list下标的映射,以及数据list实现getrandom。
- 对同一元素在b数组中的下标不重复,使用上述方法getrandom
- b数组存储所有元素
- m_1:val到b中下标列表的映射
- m_2: val到一个map的映射,这个map为b中下标到m_1下标列表下标的映射
class RandomizedCollection {
List<Integer> b = new ArrayList<>();
Map<Integer,List<Integer>> m_1 = new HashMap<>();
Map<Integer,Map<Integer,Integer>> m_2 = new HashMap<>();
Random rand = new Random();
public RandomizedCollection() {
}
public boolean insert(int val) {
boolean ans = !m_1.containsKey(val);
b.add(val);
List<Integer> temp = m_1.getOrDefault(val,new ArrayList<>());
temp.add(b.size() - 1);
m_1.put(val,temp);
Map<Integer,Integer> m = m_2.getOrDefault(val,new HashMap<>());
m.put(b.size() - 1,temp.size() - 1);
m_2.put(val,m);
return ans;
}
public boolean remove(int val) {
if(m_1.containsKey(val))
{
List<Integer> temp = m_1.get(val);
int index = temp.remove(temp.size() - 1);
Map<Integer,Integer> tmp = m_2.get(val);
tmp.remove(index);
if(index == b.size() - 1)
b.remove(b.size() - 1);
else
{
int q = b.get(b.size() - 1);
b.set(index,q);
Map<Integer,Integer> p = m_2.get(q);
List<Integer> r = m_1.get(q);
int index_r = p.get(b.size() - 1);
p.remove(b.size() - 1);
r.set(index_r,index);
p.put(index,index_r);
b.remove(b.size() - 1);
}
if(temp.size() == 0)
{
m_1.remove(val);
m_2.remove(val);
}
return true;
}
return false;
}
public int getRandom() {
return b.get(rand.nextInt(b.size()));
}
使用迭代器
- 上述方法复杂的原因在于不能随机获取set中某个元素导致(即获取一个下标)
- 使用迭代器.next方法获取元素
class RandomizedCollection {
Map<Integer, Set<Integer>> idx;
List<Integer> nums;
public RandomizedCollection() {
idx = new HashMap<Integer, Set<Integer>>();
nums = new ArrayList<Integer>();
}
public boolean insert(int val) {
nums.add(val);
Set<Integer> set = idx.getOrDefault(val, new HashSet<Integer>());
set.add(nums.size() - 1);
idx.put(val, set);
return set.size() == 1;
}
public boolean remove(int val) {
if (!idx.containsKey(val)) {
return false;
}
Iterator<Integer> it = idx.get(val).iterator();
int i = it.next();
int lastNum = nums.get(nums.size() - 1);
nums.set(i, lastNum);
idx.get(val).remove(i);
idx.get(lastNum).remove(nums.size() - 1);
if (i < nums.size() - 1) {
idx.get(lastNum).add(i);
}
if (idx.get(val).size() == 0) {
idx.remove(val);
}
nums.remove(nums.size() - 1);
return true;
}
public int getRandom() {
return nums.get((int) (Math.random() * nums.size()));
}
}
完美矩形(HARD)
找规律(没有想到判断重叠的方法)
- 多画一些图找出充要条件
- 找出矩形区域的四个顶点,则首先需要满足这四个顶点出现且仅出现一次,所有矩形面积之和等于该区域面积再加上矩形间两两不重合即可。
- 有限区域不重叠的充要条件的必要条件:矩形区域四个顶点仅出现一次且其余顶点出现2或4次。
- 考察任意一种矩形重合的情形(注意区域的有限性),产生矛盾。
- 于是上述条件为充要条件。
class Solution {
class point
{
int x;
int y;
public point(int x,int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
point point = (point) o;
return x == point.x &&
y == point.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}
public boolean isRectangleCover(int[][] rectangles) {
Map<point,Integer> m = new HashMap<>();
long s = 0;
int lx = Integer.MAX_VALUE;
int ly = Integer.MAX_VALUE;
int ux = Integer.MIN_VALUE;
int uy = Integer.MIN_VALUE;
point point1;
point point2;
point point3;
point point4;
for(int i = 0;i<rectangles.length;i++)
{
int x1 = rectangles[i][0];
int y1 = rectangles[i][1];
int x2 = rectangles[i][2];
int y2 = rectangles[i][3];
s += (long)(x2 - x1) * (y2 - y1);
lx = Math.min(lx,x1);
ly = Math.min(ly,y1);
ux = Math.max(ux,x2);
uy = Math.max(uy,y2);
point1 = new point(x1,y1);
point2 = new point(x1,y2);
point3 = new point(x2,y1);
point4 = new point(x2,y2);
m.put(point1,m.getOrDefault(point1,0)+1);
m.put(point2,m.getOrDefault(point2,0)+1);
m.put(point3,m.getOrDefault(point3,0)+1);
m.put(point4,m.getOrDefault(point4,0)+1);
}
point1 = new point(lx,ly);
point2 = new point(lx,uy);
point3 = new point(ux,ly);
point4 = new point(ux,uy);
if(s!= (long)(uy - ly) * (ux - lx)|| m.getOrDefault(point1,0)!= 1 || m.getOrDefault(point2,0)!= 1 || m.getOrDefault(point3,0)!= 1 || m.getOrDefault(point4,0)!= 1)
return false;
m.remove(point1); m.remove(point2); m.remove(point3); m.remove(point4);
for(point a : m.keySet())
{
if(m.get(a) != 2 && m.get(a) != 4)
return false;
}
return true;
}
public static void main(String[] args)
{
new Solution().isRectangleCover(new int[][]{{-100000,-100000,100000,100000}});
}
}
扫描线(另一种充分必要条件)
- 边缘竖线单独出现且连成整体(红色)
- 其他竖线若直接相连需要成对出现(绿色)
- 如图所示
周赛T4-统计可以被K整除的下标数(HARD)
- 对于固定点nums[j]若想乘一个数使得结果能被k整除。分解质因数的结果表明nums[i]必须有因子k/gcd(nums[j],k)
- 没有想到:如何维护nums的前缀中每个因子的个数。
- 不对每个数分解质因数再组合(造成重复),而是考察每个可能的因子所能达到的数(注意体会)
- 注意static的应用:只运行一次,降低时间复杂度
- 算法分析:预处理每个数的因子所用时间最多
m + m 2 + ⋯ + = l o g m m + \frac{m}{2} + \cdots + = logm m+2m+⋯+=logm
class Solution {
public int gcd(int m,int n)
{
if(m < n)
return gcd(n,m);
int r = 0;
while(n>0)
{
r = m % n;
m = n;
n = r;
}
return m;
}
static List<Integer>[] a = new List[100001];
static{
for(int i= 1;i<a.length;i++)
a[i] = new ArrayList();
for(int i = 1;i<=100000;i++)
for(int j = i;j<=100000;j+=i)
a[j].add(i);
}
public long countPairs(int[] nums, int k) {
Map<Integer,Integer> b = new HashMap<>();
for(int x : a[nums[0]])
b.put(x,b.getOrDefault(x,0)+1);
long ans =0;
for(int i = 1;i<nums.length;i++)
{
int x =k/ gcd(nums[i],k);
ans += (long)b.getOrDefault(x,0);
for(int o : a[nums[i]])
b.put(o,b.getOrDefault(o,0) + 1);
}
return ans;
}
}
双周赛T4-统计数组中好三元组数目
MergeSort的思路
- 没有想到的点:问题的形式化:设nums1中i f [ i ] , f [ j ] , f [ k ] f[i],f[j],f[k] f[i],f[j],f[k]
- 只需要f[i]
class Solution {
public int[] mergesort(int low,int high,int[] nums,Map<Integer,Integer> b,boolean asend)
{
if(low == high)
return new int[]{nums[low]};
int mid = (low + high) >> 1;
int[] l = mergesort(low,mid,nums,b,asend);
int[] r = mergesort(mid + 1,high ,nums,b,asend);
int[] ret = new int[l.length + r.length];
int i = 0,j = 0,k = 0;
while(i < l.length && j < r.length)
{
if(l[i] < r[j])
{
if(asend)
{
b.put(l[i],b.getOrDefault(l[i],0)+(r.length - j));
ret[k++] = l[i++];
}
else
{
b.put(r[j],b.getOrDefault(r[j],0) + (l.length - i));
ret[k++] = r[j++];
}
}
else
{
if(asend)
ret[k++] = r[j++];
else ret[k++] = l[i++];
}
}
while(i < l.length)
{
ret[k++] = l[i++];;
}
while(j < r.length)
ret[k++] = r[j++];
return ret;
}
public long goodTriplets(int[] nums1, int[] nums2) {
int[] f = new int[nums1.length];
Map<Integer, Integer> a = new HashMap<>();
for (int i = 0; i < nums2.length; i++)
{
a.put(nums2[i],i);
}
for(int i = 0;i<nums1.length;i++)
{
f[i] = a.get(nums1[i]);
}
int[] g = new int[f.length];
System.arraycopy(f,0,g,0,f.length);
long ans = 0;
a.clear();
Map<Integer,Integer> b = new HashMap<>();
mergesort(0,f.length - 1,f,a,true);
mergesort(0,f.length - 1,g,b,false);
for(int c : a.keySet())
if(b.containsKey(c))
ans += (long) a.get(c) * b.get(c);
return ans;
}
public static void main(String[] args)
{
new Solution().goodTriplets(new int[]{2,0,1,3},new int[]{0,1,2,3});
}
}
数据结构-树状数组