【问题】
有三顶红帽子和两顶白帽子.将其中的三顶帽子分别戴在 A、B、C三人头上.这三人每人都只能看见其他两人头上的帽子,但看不见自己头上戴的帽子,并且也不知道剩余的两顶帽子的颜色。
问A: “你戴的是什么颜色的帽子?” A回答说:“不知道。” 接着,又以同样的问题问B。 B想了想之后,也回答说:“不知道。” 最后问C。 C也想了一会回答说:“我知道我戴的帽子是什么颜色了。” 当然,C是在听了A、B的回答之后而作出回答的。试问:C戴的是什么颜色的帽子?
【回答】
A回答不知道,说明A看到肯定不是两顶白帽,要么是两顶红帽,要么是一白一红;
B回答不知道,在他知道自己和C是要么两顶红帽,要么一白一红的前提下,说明C肯定不是白帽子,否则他就知道自己是红色的了。说明C是红帽子! C根据这一点推断出自己是红帽子!
100枚硬币平放在桌面上,10枚正面朝上,90枚反面朝上,不能看,不能仔细摸,可以移动翻转硬币。请问如何江它们分为正面朝上硬币数目相等的两堆?
方案:将其中10枚硬币移出来,翻个个。
解释 :加入移出的硬币里有9枚反,1枚正,翻转后就是9正1反,剩下的硬币里是81枚反,9枚正。
——如何避免有的人抢不到,有的人抢的很多,有的人抢的很少?
容易想到的是:随机区间 ( 0, 剩余金额 ),这样会导致先抢的人拿得多。
思路:
假设一共有 N 元,一共有 K 个人,则可以每个人拿到的钱为 random(N - (K - 1) * 0.01),即保证后面K-1个人至少还有0.01元,然后更新N为剩余的钱,直到最后一个人就直接拿N。
生成随机数的方法:
Math.random()*100+1 //输出[0,100]的数
或者:
Random r = new Random();
double res = 0;
res = r.nextInt(100);
System.out.println(res);
——用 0-4 随机数生成器 rand4() 来生成0-6随机数 rand6()
初步印象:乘以四分之六不就行了?——不行!
思路:生成一个比6大的数据范围,并且能够确定每个数字出现的概率都相同。
rand4() * rand4() 和 rand4() + rand4() 得到的各个数的概率是不同的!
【n进制计算】
如果对0-4进行排列,得到16个数字,每个数字都是唯一的,说明每个数字出现的概率都是一致的!
故而rand4() * 5得到也是随机的 ,rand4() * 5 + rand4()也是随机的。所以可以对其中的0-20进行三等分。
结果:rand6() = (rand4() * 5 + rand4()) <= 20 ? x/3 : loop
代码:
class Main{
public static void main(String[] args) {
int[] result = new int[7];
for (int i = 0; i < 50000; i++) {
int r = rand6();
result[r]++;
}
for (int i = 0; i < result.length; i++) {
System.out.println("num:" + i + " times: " + result[i]);
}
}
public static int rand4() {
//大于等于 0.0 且小于 1.0
double rand = Math.random() * 5;
return (int) rand;
}
// 0 -6 实际 7个数字
public static int rand6() {
int result = rand4() * 5 + rand4();
do {
result = rand4() * 5 + rand4();
} while (result > 20);
return result / 3;
}
}
两根香,一根烧完1小时,如何测量15分钟
思路:
先将一根香的一端点燃,另一根香的两端全部点燃。当第二根香全部烧完时,此时已经过了半个小时。再将第一根香的另一端也点燃,那么此时第一根香剩下部分烧完的时间就是 15 min。
——如何在很大的数量级的数据中,找出前1000大的数据?
来源于知乎
(1)数据还不够大的话:分治法
答:如果全部排序一遍的话,或者部分排序的话,时间复杂度就会很高。可以用分治法,比如快排操作。
随便找一个数t,用快排,使得左边的数大于它,右边的数小于它,如果前面一部分总数大于1000个,那么继续在前一部分进行快排查找,否则在后一部分进行查找。如果左边的数量等于1000,那么久找到了。
时间复杂度:O(N)
计算过程:快排的过程,时间是O(n),第二次减半,O(n/2),第三次O(n/4),显然是小于O(2n)的,所以就算作O(n)
——但是这样如果数据量很大,空间复杂度就会很高!
(2)数据超大,2G,怎么办?
可以把快排分出来的两部分数据放在txt文件里,第二次就从文件中读出来进行快排partition,但是这样的磁盘读写效率很低。
——用堆!
在内存中维护一个1000数的小顶堆(每个结点都比它的左右子节点小),小顶堆的根节点是最小的一个数。
即:取前1000个数(设为m),构成小顶堆(O(mlogm)),然后从文件中读取数据(共n个),和堆顶大小相比,如果比堆顶还小,就直接丢弃,如果比堆顶大,就替换堆顶,并且调整小顶堆。所有数据处理完毕后,小顶堆内就是top1000大的数了。(需要所有数据遍历一遍)
复杂度:时间复杂度为O(nmlogm),空间复杂度是10000(常数)。
这样的话,数据就只需要读取一次,不存在多次读写的问题了。
eg:从1000个数中找出前50个
public class TopN {
// 父节点
private int parent(int n) {
return (n - 1) / 2;
}
// 左孩子
private int left(int n) {
return 2 * n + 1;
}
// 右孩子
private int right(int n) {
return 2 * n + 2;
}
// 构建堆
private void buildHeap(int n, int[] data) {
for(int i = 1; i < n; i++) {
int t = i;
// 调整堆
while(t != 0 && data[parent(t)] > data[t]) {
int temp = data[t];
data[t] = data[parent(t)];
data[parent(t)] = temp;
t = parent(t);
}
}
}
// 调整data[i]
private void adjust(int i, int n, int[] data) {
if(data[i] <= data[0]) {
return;
}
// 置换堆顶
int temp = data[i];
data[i] = data[0];
data[0] = temp;
// 调整堆顶
int t = 0;
while( (left(t) < n && data[t] > data[left(t)]) || (right(t) < n && data[t] > data[right(t)]) ) {
if(right(t) < n && data[right(t)] < data[left(t)]) {
// 右孩子更小,置换右孩子
temp = data[t];
data[t] = data[right(t)];
data[right(t)] = temp;
t = right(t);
} else {
// 否则置换左孩子
temp = data[t];
data[t] = data[left(t)];
data[left(t)] = temp;
t = left(t);
}
}
}
// 寻找topN,该方法改变data,将topN排到最前面
public void findTopN(int n, int[] data) {
// 先构建n个数的小顶堆
buildHeap(n, data);
// n往后的数进行调整
for(int i = n; i < data.length; i++) {
adjust(i, n, data);
}
}
// 打印数组
public void print(int[] data) {
for(int i = 0; i < data.length; i++) {
System.out.print(data[i] + " ");
}
System.out.println();
}
}
https://blog.csdn.net/u012485480/article/details/78060752
如果是找到最小的前K个数,则用大顶堆!
——先取k个数构建大顶堆,根节点最大,每次从数据文件中取一个数,如果比堆定小,则用其替换根。然后重建大顶堆。读取完毕后,该堆中的数据为最小的k个数!
在一个文件中有10G个整数,乱序排列,要求找出中位数。内存限制为2G。
解法:首先假设是32位无符号整数。
场景题:需求:谁关注了我,我关注了谁,谁与我互相关注。表该如何设计,索引怎么建。查询语句怎么写。
粉丝关注表使用四列,主键id,userId,fansId,是否互相关注。用两行数据来保存互相的关注关系,这样查询起来更方便,用空间换时间。
主键有主键索引,剩下的字段不适合建索引,因为字段重复太多。
——经典博弈论问题
https://www.jianshu.com/p/ec769d3a1e89
有5个海盗,获得了100枚金币,于是他们要商量一个方法来分配金币。商议方式如下:
(1) 由5个海盗轮流提出分配方案。
(2) 如果超过半数海盗(包括提出者)同意该方案,则按照该方案分配。
(3) 如果同意该方案的人数(包括提出者)小于等于半数,则提出者要被扔到海里喂鱼,剩下的海盗继续商议分配。
(4) 海盗们都是绝对理性的,以自己尽可能多获得金币为目的。但是在收益相等的情况下,会倾向把提出者扔到海里。
问:第一个海盗应该提出怎样的分配方案,才能保证自己既不被扔到海里,又能使自己利益最大化?
直接分析第一个海盗的最优选择,是困难的,可以利用递归思想。
老一:如果我被扔海里,剩下4个,老二的最优方案是啥?我只要给老二多一点,就能赢得支持。
老二:如果我被扔海里,剩下3个,老三的最优方案是啥?我只要给老三多一点,就能赢得支持。
老三:如果我被扔海里,剩下2个,老四的最优方案是啥?我只要给老四多一点,就能赢得支持。
整个递归过程:
递归到最后两人为止:
老四:没有任何选择的余地,哪怕支持把所有钱都给老五,老五仍然反对(因为同意该方案的人小于等于半数了,提出者要被扔海里),导致老四被扔海里,金币归老五所有。
因此,
【题目】:高考成绩2000万数据,分数0-750,如何快速知道你的排名,如何知道任一分数排名?
【思路】:利用桶排序。 将分数分成 0 - 150, 151 - 300, 301 - 450, 451 - 600, 601 - 750 共五个区间(每个区间内还可以再分),将 2000 万分数据按照成绩分到对应的成绩区间中。这样就可以快速查到对应分数的排名了。
——从十亿数据中找到次数是2的数字 + 判断某个数是否在十亿数据中
【问题的提出】:遍历一遍这些数据或者二分查找,时间复杂度是没办法优化到哪里去了,但是10亿数据处理起来,占用的内存可以达到4G——内存不够!
如何用更小的内存来标记一个数字?——bit位
【位图】
如图是一个int占4个字节,32位,本来4个字节只能存一个int,现在利用位图可以存(映射)32个数字。
https://blog.csdn.net/lucky52529/article/details/90172264
位图法思想:
对于40亿个 unsigned int 的整数,每个数字用1个二进制数(一个二进制数占用1Bit,1Byte = 8Bit)来表示该数字是否存在,0为不存在,1为存在。从低位开始数:
第1个二进制数表示整数0是否存在,
第2个二进制数表示整数1是否存在,
第3个二进制数表示整数2是否存在,
依次类推 … …
第4294967296个二进制数用于表示整数4294967295是否存在。
unsigned int 在32&64位编译器的范围为 0~4294967295,4294967296个二进制数大约占用512M内存,是一个可以接受的范围。
思路:先遍历找出最大最小值,从而减小位图中数组的长度。再用位图做。
import java.util.*;
public class Main1{
public static void main(String[] args){
int[] a = {
1,2,3,4,5,6,7,8,9,10,11,12,13,14,12,15,16};
findDuplicated(a);
}
//找出最大最小
public static void findDuplicated(int[] a) {
int max = 0;
int min = 0;
for (int i = 0; i < a.length; i++) {
max = Math.max(max, a[i]);
min = Math.min(min, a[i]);
}
// 这边使用了 byte[],而不是使用了 int[] 和 boolean[],其实从原理上都可以的
int length = max / 8 - min / 8 + 1;
byte[] arry = new byte[length];
for (int i = 0; i < a.length; i++) {
int num = a[i];
int index = num / 8 - min / 8;
int k = (num - min) % 8;
// 这边使用这样的判断是因为使用的是 byte[],使用 int[] 或者 boolean[] 则使用另一种判断方法
if((arry[index] & 1 << k) > 0) {
System.out.println(num);
}else {
arry[index] |= (1<<k);
}
}
}
}
https://blog.csdn.net/v123411739/article/details/86652806
public class BitMap {
/**
* 位图提供的最大长度,
* 比如unsigned int的最大值为4294967295, 则需要的length为4294967296
*/
private long length;
/**
* 位图桶
*/
private static int[] bitmapBucket;
/**
* int用来表示32位二进制数,
* BIT_VALUE[0]表示第1个二进制数存在、
* BIT_VALUE[1]表示第2个二进制数存在,以此类推
* BIT_VALUE[0] = 00000000 00000000 00000000 00000001
* BIT_VALUE[1] = 00000000 00000000 00000000 00000010
* BIT_VALUE[2] = 00000000 00000000 00000000 00000100
* ...
* BIT_VALUE[31] = 10000000 00000000 00000000 00000000
*/
private static final int[] BIT_VALUE = {
0x00000001, 0x00000002, 0x00000004, 0x00000008,
0x00000010, 0x00000020, 0x00000040, 0x00000080,
0x00000100, 0x00000200, 0x00000400, 0x00000800,
0x00001000, 0x00002000, 0x00004000, 0x00008000,
0x00010000, 0x00020000, 0x00040000, 0x00080000,
0x00100000, 0x00200000, 0x00400000, 0x00800000,
0x01000000, 0x02000000, 0x04000000, 0x08000000,
0x10000000, 0x20000000, 0x40000000, 0x80000000};
/**
* length为1 - 32: 需要1个桶
* length为33 - 64: 需要2个桶
* ...
* 以此类推
*
* @param length
*/
public BitMap(long length) {
this.length = length;
// 根据长度算出,所需位图桶个数
bitmapBucket = new int[(int) (length >> 5) + ((length & 31) > 0 ? 1 : 0)];
}
/**
* 查找number是否存在于位图桶中
*
* @param number 要查询的数字
* @return true: number在位图桶中, false: number不在位图桶中
*/
public boolean getBit(long number) {
if (number < 0 || number > length) {
throw new IllegalArgumentException("非法参数");
}
// 计算该number在哪个桶
int belowIndex = (int) (number >> 5);
// 求出该number在桶里的下标,(求出该值在32位中的哪一位, 下标0 - 31)
int offset = (int) (number & 31);
// 拿到该桶的值
int currentValue = bitmapBucket[belowIndex];
// 计算该number是否存在
return ((currentValue & BIT_VALUE[offset])) == 0 ? false : true;
}
/**
* 将number在位图桶中标记为存在
*
* @param number 要标记的数字
*/
public void setBit(long number) {
// 合法性校验
if (number < 0 || number >= length) {
throw new IllegalArgumentException("非法参数");
}
// 计算该number在哪个桶
int belowIndex = (int) (number >> 5);
// 求出该number在桶里的下标,(求出该值在32位中的哪一位, 下标0 - 31)
int offset = (int) (number & 31);
// 拿到该桶的当前值
int currentValue = bitmapBucket[belowIndex];
// 将number在桶里标记
bitmapBucket[belowIndex] = currentValue | BIT_VALUE[offset];
}
public static void main(String[] args) {
BitMap bitMap = new BitMap(4294967296L);
bitMap.setBit(4294967295L);
System.out.println(bitMap.getBit(4294967295L));
System.out.println(bitMap.getBit(4294967294L));
}
}
等价于点再圆周的情况。
考虑n个点,n个点再统一半圆内,我们把逆时针方向最后一个点称作关键点。
选定一个点A,这n个点再以A为关键点的同一半圆内的充要条件是什么?
——每个点都是1/2,所以所有点都在的概率是1/2^(n-1)
由于对称性,这n个点在以其它点为关键点的同一半圆内的概率也是1/2^(n-1)
所以n个点都在同一半圆内的概率是:n / 2^(n-1)
故而:
三个点就是:3/4
四个点就是:4/8=1/2