题目1:给定一个数组,求如果排序之后,相邻两数的最大差值,要求时间复杂度O(N),且要不能用非基于比较的排序
[ ]假设是长度为9的数组
首先遍历数组 找出min 和 max
等分成10份
举例:
分为 桶内相邻和桶外相邻
中间必定存在空桶
每个桶只留该桶的最小值和最大值
public static int maxGap(int[] nums){
if(nums == null || nums.length < 2){
return 0;
}
int len = nums.length;
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for(int i = 0; i < len; i++){
min = Math.min(min, nums[i]);
max = Math.max(max, nums[i]);
}
if(min == max){
return 0;
}
boolean[] hasNum = new boolean[len + 1];//hasNum[i] i号桶是否进来过数字
int[] maxs = new int[len + 1]; //maxs[i] i号桶收集的所有数字的最大值
int[] mins = new int[len + 1]; //mins[i] i号桶收集的所有数字的最小值
int bid = 0;
for(int i = 0; i < len; i++){
bid = bucket(nums[i], len, min, max);
mins[bid] = hasNum[bid] ? Math.min(mins[bid], nums[i]) : nums[i];
maxs[bid] = hasNum[bid] ? Math.max(maxs[bid], nums[i]) : nums[i];
hasNum[bid] = true;
}
int res = 0;
int lastMax = maxs[0]; //上一个非空桶的最大值
int i = 1;
for (; i <= len; i++){
if(hasNum[i]){
res = Math.max(res, mins[i] - lastMax);
lastMax = maxs[i];
}
}
return res;
}
public static int bucket(long num, long len, long min, long max){
return (int)((num - min) * len / (max - min));
}
最后的结果不一定是空桶两侧的结果
设置空桶的意义在于设置一个平凡解,杀死一个类型的可能性(一个桶内部),进而达到优化
题目二:给定n个数字a1,...,an,问最多有多少不重复的非空区间,使得每个区间内数字的异或和都为0,返回子数组的数量
以每个数字结尾的情况下答案是什么?
最优划分下
1)[i]所在的部分异或和不是0 dp[i] = dp[i - 1]
2)[i]所在的部分异或和是0
找到离i最近的k 异或和为0
【3,2,1,4,0,1,2,3】
dp【0,0,1,1,2,2,2,3】
public static int mostEOR(int[] arr) {
int xor = 0;
//dp[i] -> arr[0..i]在最优化分的情况下,异或和为0最多的部分是多少个
int[] dp = new int[arr.length];
//key : 从0出发的某个前缀异或和
//value : 这个前缀异或和出现额最晚位置(index)
HashMap<Integer, Integer> map = new HashMap<>();
map.put(0, -1);
for(int i = 0; i < arr.length; i++){
xor ^= arr[i]; //xor -> 0..i所有数的异或和
if (map.containsKey(xor)){//上一次这个异或和出现的位置
//pre -> pre + 1 -> i ,最优化分,最后一个部分的开始位置
//(pre + 1, i) 最后一个位置
int pre = map.get(xor); //a 0..a (a+1..i)
dp[i] = pre == -1? 1 : (dp[pre] + 1);
}
//dp[i] = Max { dp[i - 1], dp[k-1] + 1}
if(i > 0) {
dp[i] = Math.max(dp[i-1],dp[i]);
}
map.put(xor, i);
}
return dp[dp.length - 1];
}
题目三:现有n1+n2种面值的硬币,其中前n1为普通币,可以取任意枚,后几种为纪念币,每种最多只能取一枚,每种硬币有一个面值,问有多少找方法拼出m的面值
举例:【3,2,5】任意枚 【1,2,4】1枚 m=10
1) 任意枚0 一枚 10 方法数 a
2) 任意枚1 一枚9 方法数b
。。。。
任意枚10 一枚0 方法数c
方法数累加
dp[i][j] = dp[i-1][j-arr[i]*k] k大于等于0且 j-arr[i]*k大于等于0
题目四:给定两个一维int数组A和B
其中,A是长度为m、元素从小到大排好序的有序数组。B是长度为n从小到大排好序的有序数组。希望从A和B数组中,找到最大的k个数字,要求:使用尽量少的比较次数
做法1:双指针外排 时间复杂度O(k)
做法2:先假设第K小的数字在A,对A做二分求出M,看M在B中有多少数在M前面,不满足然后继续二分 时间复杂度O(logN*logM)
最优解O(logN):
算法原型:如果两个有序数组长度一样,怎么找到上中位数
1)两个数组长度都为偶数
A[a,b,c,d] b[
如果b==b‘ 则是上中位数
b> b' 则c不是上中位数,b'也不是上中位数,可能的是a,b,c',d',递归求上中位数
子问题的上中位数就是整个问题的上中位数
2)两个数组长度都是奇数
A[1,2,3,4,5] B[1',2',3',4',5']
如果3 = 3',则是第五小的数
3 > 3’,则3,4,5不可能,1',2'也不可能
则可能的有1,2和3',4',5',则手动比较2和3',如果3' >=2,则是第五小的数,否则1,2和4',5'递归
假设A数组长度为10,B数组长度为17,第K小
1)1=<K<=10,A拿出1~K,B拿出1~K',求出上中位数
2)10<K=<17 排除掉B数组的K-10-1和最后17-K个数
手动比较B数组的K-10数和A中的10,如果大于等于,则是,否则多淘汰一个数
接着递归求出上中位数
3)17<K<=27 排除掉A数组的 K-17-1的数
排除掉B数组的K-10-1的数
然后在剩下的数中,先判断A数组的K-17和17',如果A数组的K-17>=17',则是第K小,否则抛弃
比较B数组的K-10和A数组的10比,不是就多淘汰一个
接着剩下的数递归求上中位数
public static int findKthNum(int[] arr1, int[] arr2, int kth){
if(arr1 == null || arr2 == null){
throw new RuntimeException("Your arr is invalid!");
}
if(kth < 1 || kth > arr1.length + arr2.length) {
throw new RuntimeException("Your arr is invalid!");
}
int[] longs = arr1.length >= arr2.length ? arr1 : arr2;
int[] shorts = arr1.length < arr2.length ? arr1 : arr2;
int l = longs.length;
int s = shorts.length;
if(kth <= s){
return getUpMedian(shorts, 0, kth - 1, longs, 0, kth - 1);
}
if(kth > l){
if(shorts[kth - l - 1] >= longs[l - 1]){
return shorts[kth - l - 1];
}
if(longs[kth - s - 1] >= shorts[s - 1]){
return longs[kth - s - 1];
}
return getUpMedian(shorts, kth - l, s - 1, longs, kth - s, l - 1);
}
//2)
if(longs[kth - s - 1] >= shorts[s - 1]){
return longs[kth - s - 1];
}
return getUpMedian(shorts, 0, s - 1, longs, kth - s, kth - 1);
}
//两个数组一定要等长
public static int getUpMedian(int[] a1, int s1, int e1, int[] a2, int s2, int e2){
int mid1 = 0;
int mid2 = 0;
int offset = 0;
while(s1 < e1){
mid1 = (s1 + e1) / 2;
mid2 = (s2 + e2) / 2;
offset = ((e1 - s1 + 1) & 1) ^ 1;
if (a1[mid] > a2[mid2]){
e1 = mid1;
s2 = mid2 + offset;
} else if(a1[mid1] < a2[mid2]){
s1 = mid1 + offset;
e2 = mid2;
} else{
return a1[mid1];
}
}
return Math.min(a1[s1], a2[s2]);
}
题目五:(约瑟夫环)
构建一个公式f,求活着的节点在上一段的编号。O(1)
Y= X % i
建立关系
编号 = (报数 - 1)%i + 1
老=((新 - 1)+ S)% i + 1
S:被杀节点编号
S=(m-1)%i + 1
老=(新+m-1)%i+1
public static intgetLive(int i, int m){
if(i == 1){
return 1;
}
return (getLive(i - 1, m) + m - 1) % i + 1;
}
public static Node josehusKill2(Node head, int m){
if(head == null || head.next == head || m < 1){
return head;
}
Node cur = head.next;
int tmp = 1; // tmp -> list size
while(cur != head){
tmp++;
cur = cur.next;
}
tmp = getLive(tmp, m); //tmp -> service node position
while(--tmp != 0){
head = head.next;
}
head.next = head;
return head;
}
老=(新+m-1)%i+1 此题的m每轮都变
//0...n-1个人围成一圈,依次循环取用arr中的数字
//杀n-1轮,返回活的人的原始编号
public static int live(int n, int[] arr){
return no(n ,arr, 0);
}
//当剩i个人,当前取用数字是arr[index],并且下面的过程,从index出发,循环取用
//返回哪个人会活(在i个人中的编号)
public stati int no(int i, int[] arr, int index){
if(i == 1){
return 1;
}
//老 = (新 + m - 1) % i + 1
return (no(i - 1, arr, nextIndex(arr.length, index)) + arr[index] - 1) % i + 1;
}
//r如果数组长度为size,当前下标为index,返回循环的模型下,下一个index是多少
public static int nextIndex(int size, int index){
return index == size - 1? 0 : index + 1;
}