题目一
策略:
每次将绳子右端点放在一个点上,看绳子往左可以覆盖多少个点。
如何知道左边覆盖几个点?
在绳子右端点cur左边有序区域找第一个大于等于arr[cur]-L的位置
滑动窗口
每次绳子左侧放在一个点上,然后有边界向右走,要求【绳子长度arr[R]-arr[L]+1】超过绳子长度L
每次R不能继续往右动,就移动左边界,然后再看有边界能否继续右动。。。周而复始【窗口边界都不回头的】 => O(N)
// 长度为L的绳子最多覆盖几个点,请保证arr有序
public static int maxPoint(int[] arr, int L) {
int res = 1;
for (int i = 0; i < arr.length; i++) {
int nearest = nearestIndex(arr, i, arr[i] - L);
res = Math.max(res, i - nearest + 1);
}
return res;
}
// 在arr[0..R]范围上,找满足>=value的最左位置
public static int nearestIndex(int[] arr, int R, int value) {
int L = 0;
int index = R;
while (L < R) {
int mid = L + ((R - L) >> 1);
if (arr[mid] >= value) {
index = mid;
R = mid - 1;
} else {
L = mid + 1;
}
}
return index;
}
public static void main(String[] args) {
int[] arr = { 0, 13, 24, 35, 46, 57, 60, 72, 87 };
int L = 6;
System.out.println(maxPoint(arr, L));
}
题目二:
普通的解法:
算出可以使用最多8类型袋子M=N/8,然后看剩余苹果是否可以用6类型袋子搞定。不行,尝试M-1个8类型袋子,然后看剩余苹果是否可以用6类型袋子搞定。。。周而复始
如果剩余苹果超过24就不需要进行尝试了,因为前面一定是已经试过的了。【6和8最小公倍数特点】
public static int minBags(int apple) {
if (apple < 0) {
return -1;
}
int bag6 = -1;
int bag8 = apple / 8;
int rest = apple - 8 * bag8;
while (bag8 >= 0 && rest < 24) {
int restUse6 = minBagBase6(rest);
if (restUse6 != -1) {
bag6 = restUse6;
break;
}
rest = apple - 8 * (--bag8);
}
return bag6 == -1 ? -1 : bag6 + bag8;
}
// 如果剩余苹果rest可以被6个苹果的袋子搞定,返回袋子数量
// 不能搞定返回-1
public static int minBagBase6(int rest) {
return rest % 6 == 0 ? (rest / 6) : -1;
}
题目三:
预处理之后,后续频繁查询可以很快 => 用空间换时间
// RGRGR -> RRRGG
public static int minPaint(String s) {
if (s == null || s.length() < 2) {
return 0;
}
char[] chs = s.toCharArray(); // 转换成字符串数组
int[] right = new int[chs.length]; // 开一个数组记录arr[i..N-1]有多少个R
right[chs.length - 1] = chs[chs.length - 1] == 'R' ? 1 : 0; // 处理最后一个位置R的情况
for (int i = chs.length - 2; i >= 0; i--) { // 遍历处理从0-N-2每个位置往后R的情况
right[i] = right[i + 1] + (chs[i] == 'R' ? 1 : 0);
}
int res = right[0]; // 一开始没有左边,填涂颜色数就是0-N-1中R需要填涂成G的个数
int left = 0; // 从0位置开始往后,统计到arr[0..left]有多少个G
for (int i = 0; i < chs.length - 1; i++) { //遍历数组
left += chs[i] == 'G' ? 1 : 0; // 统计G的个数
res = Math.min(res, left + right[i + 1]);
}
res = Math.min(res, left + (chs[chs.length - 1] == 'G' ? 1 : 0)); // 判断一下将arr[0-N-1]所有G填涂成R的情况
return res;
}
public static void main(String[] args) {
String test = "GGGGGR";
System.out.println(minPaint(test));
}
题目四:
// 设置数组 right 和 down
// right[i][j] 表示第i行第j列及其往后有多少个连续的1
// down[i][j] 表示第j列第i行及其往后有多少个连续的1
public static void setBorderMap(int[][] m, int[][] right, int[][] down) {
int r = m.length; // 数组行数
int c = m[0].length; // 数组列数
if (m[r - 1][c - 1] == 1) { // 特判一下最后一个位置
right[r - 1][c - 1] = 1; // right数组最后一个位置
down[r - 1][c - 1] = 1;
}
// 处理最后一列的情况
for (int i = r - 2; i != -1; i--) {
if (m[i][c - 1] == 1) {
right[i][c - 1] = 1; // right数组最后一列
down[i][c - 1] = down[i + 1][c - 1] + 1; // down数组最后一列
}
}
// 处理最后一行的情况
for (int i = c - 2; i != -1; i--) {
if (m[r - 1][i] == 1) {
right[r - 1][i] = right[r - 1][i + 1] + 1; //right数组最后一行
down[r - 1][i] = 1; // down数组最后一行
}
}
// 处理一般位置的情况
for (int i = r - 2; i != -1; i--) {
for (int j = c - 2; j != -1; j--) {
if (m[i][j] == 1) {
right[i][j] = right[i][j + 1] + 1;
down[i][j] = down[i + 1][j] + 1;
}
}
}
}
// 返回最大正方形边长
public static int getMaxSize(int[][] m) {
int[][] right = new int[m.length][m[0].length];
int[][] down = new int[m.length][m[0].length];
setBorderMap(m, right, down); // 预处理rigth和down辅助数组
for (int size = Math.min(m.length, m[0].length); size != 0; size--) { // 枚举正方行边长
// 如果存在这样大小的正方行,直接返回,因为是从大到小尝试,第一个满足条件的一定就是最大的边长
if (hasSizeOfBorder(size, right, down)) {
return size;
}
}
return 0;
}
public static boolean hasSizeOfBorder(int size, int[][] right, int[][] down) {
// 遍历每个点作为正方形的左上方的点
for (int i = 0; i != right.length - size + 1; i++) {
for (int j = 0; j != right[0].length - size + 1; j++) {
// 如果正方形四条边都有size长度连续为1的部分,返回true,说明找到存在这样的正方形
if (right[i][j] >= size && down[i][j] >= size
&& right[i + size - 1][j] >= size
&& down[i][j + size - 1] >= size) {
return true;
}
}
}
return false;
}
// 随机生成矩阵
public static int[][] generateRandom01Matrix(int rowSize, int colSize) {
int[][] res = new int[rowSize][colSize];
for (int i = 0; i != rowSize; i++) {
for (int j = 0; j != colSize; j++) {
res[i][j] = (int) (Math.random() * 2); //生成[0-1]的随机整数
}
}
return res;
}
// 打印随机生成的矩阵
public static void printMatrix(int[][] matrix) {
for (int i = 0; i != matrix.length; i++) {
for (int j = 0; j != matrix[0].length; j++) {
System.out.print(matrix[i][j] + " ");
}
System.out.println();
}
}
public static void main(String[] args) {
int[][] matrix = generateRandom01Matrix(7, 8); // 生成7*8的矩阵
printMatrix(matrix); // 打印矩阵
System.out.println(getMaxSize(matrix)); // 打印最大正方行边长
}
题目五:
技巧:先加工成01等概率发生器。然后利用二进制位移位进行数字拼凑。
// 等概率返回1-5的函数
public static int f() {
return (int)(Math.random()*5)+1;
}
// 等概率返回0和1的函数
public static int r01(){
int res=0;
do{
res=f();
}while(res==3);
return res<3?0:1;
}
// 等该率返回1-7的函数
public static int g() {
int res=0;
do { // 等概率生成0-7
res=(r01()<<2)+(r01()<<1)+r01();
}while(res==7); // 如果是7就重新来
return res+1; // (0-6)+1 => (1-7)
}
对于第三种情况:两次为一组,如果是0 0 或 1 1,重新来。如果是0 1 则返回 0,如果是 1 0,则返回1。这样返回0和返回1的概率就都是p(1-p),做到了等概率。