刷题
1.求两个数的最大公约数
//1. 最小值递减求余法
//直接用最小值一直递减与 samll和big 求余得0则表示找到
function getDiv(a,b){
let big = a>b ? a:b
let small = a<b ? a:b
for (let i = small; i >1; i--) {
if(small%i == 0 && big%i == 0) {
return i
}
}
return i
}
//2. 辗转相除法
// 欧几里得算法:两个正整数a和b(a>b),他们最大公约数等于a除于b的余数c和b之间的最大公约数
// a%b = c , 则 b%c 的最大公约与 a和b的最大公约等值。
//缺点 求余速度慢,而且如果数值很大运算慢
function getDiv(a,b){
let big = a>b ? a:b
let small = a<b ? a:b
if(a%b == 0){
return small
}
return getDiv(samll,big%small)
}
//3.更相减损法
//两个正整数a和b(a>b),则a和b的最大公约 等于 a-b的差c和b的最大公约
//缺点如果两个只相差很大 ,要减很久
function getDiv(a,b){
let big = a>b ? a:b
let small = a<b ? a:b
if(a%b == 0){
return small
}
return getDiv(samll,big-small)
}
//最优解
//辗转相除法 + 更相减损法 + 位移做求余
/* 解法:
* 当a和b均为偶数,gcb(a,b) = 2*gcb(a/2, b/2) = 2*gcb(a>>1, b>>1)
* 当a为偶数,b为奇数,gcb(a,b) = gcb(a/2, b) = gcb(a>>1, b)
* 当a为奇数,b为偶数,gcb(a,b) = gcb(a, b/2) = gcb(a, b>>1)
* 当a和b均为奇数,利用更相减损术运算一次,gcb(a,b) = gcb(b, a-b), 此时a-b必然是偶数,又可以继续进行移位运算。
* @param a
* @param b
* @return
*/
function getGreatestCommonDivsor2(a, b) {
if (a == b) {
return a;
}
var aIsEven = a % 2 === 0
var bIsEven = b % 2 === 0
// a&1 == 0说明整数a是偶数,否则为奇数
if (aIsEven && bIsEven) {
// 当a和b均为偶数,gcb(a,b) = 2*gcb(a/2, b/2) = 2*gcb(a>>1, b>>1)
return getGreatestCommonDivsor2(a >> 1, b >> 1) << 1;
} else if (aIsEven && !bIsEven) {
// 当a为偶数,b为奇数,gcb(a,b) = gcb(a/2, b) = gcb(a>>1, b)
return getGreatestCommonDivsor2(a >> 1, b);
} else if (!bIsEven && bIsEven) {
// 当a为奇数,b为偶数,gcb(a,b) = gcb(a, b/2) = gcb(a, b>>1)
return getGreatestCommonDivsor2(a, b >> 1);
} else {
// 当a和b均为奇数,利用更相减损术运算一次,gcb(a,b) = gcb(b, a-b), 此时a-b必然是偶数,又可以继续进行移位运算。
var big = a > b ? a : b;
var small = a < b ? a : b;
return getGreatestCommonDivsor2(big - small, small);
}
}
2.如果判断一个数是2个整数次幂
答题
//1.使用暴力循环
function isPower2(num) {
let temp = 1
while(temp < num ){
if(temp % 2 == 0){
return true
}
temp = temp * 2
}
return false
}
//利用 2的次方 都是第一位为1,后面都是0
//如:
//4二进制为 100
//8二进制为 1000
//又因为 8-1的二进制位 0111 , 1000 & 0111 == 0 刚好等于0
//所以只要n & n-1 求& 一定为0
function isPower2(num) {
return num & (num -1)
}
3.最小栈实现
设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。
- push(x) -- 将元素 x 推入栈中。
- pop() -- 删除栈顶的元素。
- top() -- 获取栈顶元素。
- getMin() -- 检索栈中的最小元素。
解题思路
使用两个栈来记录
- stack记录数据的信息 push,pop
- stackMin记录最小值信息:每次有新收据都判断是否比之前栈底小,是则放入,所以栈顶放的都是越来越小的值
代码实现
/**
* initialize your data structure here.
*/
var MinStack = function() {
this.stack = [];
this.min = [];
};
/**
* @param {number} x
* @return {void}
*/
MinStack.prototype.push = function(x) {
this.stack.push(x);
//判断x和min当前栈顶的数哪个小,如果x小则把x推入min的栈顶
if(this.stackMin.length==0 || this.stackMin[this.min.length-1]>=x){
this.stackMin.push(x);
}
};
/**
* @return {void}
*/
MinStack.prototype.pop = function() {
//this.stack.pop();
//判断stack中即将弹出的元素和min栈顶的元素是否相等,若相等,则要把min栈顶的元素弹出,防止找不到最小值
if(this.stack[this.stack.length-1]==this.min[this.stackMin.length-1]){
this.stackMin.pop();
}
this.stack.pop();
};
/**
* @return {number}
*/
MinStack.prototype.top = function() {
return this.stack[this.stack.length-1];
};
/**
* @return {number}
*/
MinStack.prototype.getMin = function() {
return this.stackMin[this.stackMin.length-1];//min栈顶保存当前栈中的最小值
};
4.无序数组排序后的最大相邻差
如 2,6,3,4,5,10,9 数组中,找出最大的相邻差
思路
方案1:先把数组排序,然后一次判断两个数字间距
排序后:2,3,4,5,6,9,10
依次比较:找出是 6,9 相差 3
- 方案2:使用计数排序,依次放入0到10 的数组里
- 方案3:使用桶排序,填满不同桶后,桶内取出摆放的最大值和最小值。 然后判断两桶之间的差。用 右边的min - 左边的max = 3
5.如何用栈实现队列
思路
使用两个栈实现。
- stack1正常记录入栈信息。 stack2 把stack1的数据pop一遍在push回stack2。
- 只要stack一直有数据就一直pop对应数据,直到不能再pop,再把stack1新插入的数据再一次转化为stack2的数据。
let stack1 = []
let stack2 = []
function push(num) {
stack1.push(num)
}
function pop() {
if(stack1.length == 0 && stack2.length == 0) {
return null
}
if(stack2.length == 0) {
//把stack1 转化为stack2
while(stack1.length) {
stack2.push(stack.pop())
}
}
return stack2.pop()
}
6.寻找全排列的下一个数
已知:
- 12345,则需要返回 12354
- 12354,则需要返回 12435
- 12435,则需要返回 12453
思路
求最大数时:逆序排序下是应该最大越好,如12345
求最小数时:顺序排序下是应该最小越好,如54321
尽量保持高位不变,低位在最小范围内变换顺序。变化顺序就是 逆序=》顺序,顺序=〉逆序。
如12345 ,要变换顺序 取决于逆序区域 5,4 =》 4,5
要接近原数并且比原数大,则必须从倒数第三位开始改变。
如 12354 -》 12453
再 12453 -》 12435
核心逻辑
1.从后向前查看逆序区域,找到逆序区域的前一位,也就是数字置换的边界。 2.让逆序区域的前一位和逆序区域中大于它的最小的数宇交换位置。 3.把原来的逆序区域转为顺序状态。 最后让我们用代码来实现一下。这里为了方便数宇位置的交换, 入参和返回值的类型都来用了整型数组。
7.删除k个数字后能组合的最小值
如 1593212
删除1个数,能得到最小值。 那应该是 删9 ,得 153212
删除3个数,能得到最小值。 那应该是 删5,9,3 ,得 1212
思路
当只删一个字时,求最小,关键是移除最高位的数
把原整数的所有数字从左到右进行比较,如果发现某一位数宇大于它右面的数字,那么在删除该数字后,必然会使该数位的值降低,因为右面比它小的数字顶替了它的位置。
如 541270936 ,依次删除顺序是 5, 4, 7
代码实现可以通过栈 优化删除数字的判断。
8.如何实现大整数的相加
js最大的浮点长度是10位,超过相加就会溢出
思路
通过把大数字,每一个数字转化成数组,然后两个数组根据下标进行合计,最终再把数组转化为字符串显示。
优化方案:可以把每一位数字直接放入9位的数字计算 ,把整个数组长度缩短
9.如何求解进矿
有一位国王拥有5座金矿,每座金矿的黄金储量不同,雳要参与挖掘的工人人数也不同。
例如有的金矿储量是500kg黄金,需要5个工人来挖掘;有的金矿储量是200kg黄金,需要3个工人来挖掘…• 如果参与挖矿的工人的总数是10。每座金矿要么全挖,要么不挖,不能派出一半人挖取一半的金矿。要求用程序求出,要想得到尽可能多的黄金,应该选择挖取哪几座金矿?
- 总共10名工人
- 200kg 黄金/3人
- 300kg 黄金/4人
- 350kg黄金/3人
- 400kg 黄金/5人
- 500kg黄金/5人
思路
1.贪心算法
找出性价比最高的队伍。然后排序后投入
第一名:350kg路金分人的金我,人均产值为116.6。 第一名:500kg地金5人的金矿,人均产值为100。 第一名:200kg黄金15人的金矿,人均产值80。 第一名:300kg黄金/4人的金矿,人均产值为75。 第一名:200kg黄金/3人的金矿,人均产值为66。 所以,小灰得出的最佳 金矿收益是350+500日7850kg黄金。
2.动态规划
状态转移方程式
- 金矿数量 n , 工人数为 w
- 金矿含金量为 数组g[], 开采人数为 p[]
-
当金矿数量为0,工人数为0,则有 F(n,w) = 0 (n=0 或 w= 0)
-
当所剩工人不够挖当前金矿,只有一个最优的子结构 F(n,w) = F(n-1,w)(n>=1,w<p[n-1])
-
在正常情况下,有两种最优的子结构,(要么挖 要么不挖) F(n,w) = max(F(n-1,w),F(n-1,w-p[n-1]+g[n-1] ) ) (n>=1,w>=p[n-1])
逻辑实现
- 递归方式 多做很多重复的判断
2. 动态规划
(1) 二维数组实现
(2) 1维数组实现 我们只保存一行数据,在计算下一行时候,要从右边往左边统计,把旧的数据一个个替换掉
/*
@param w 工人数量
@param p [] 金矿开采所盂的工人数量
@param g [] 金矿储量
*/
function getBestGoldMiningV3 ( w, p, g) {
let results = []
for (let i = 0; i < g.length; i++) {
for (let j = w; j >= 1; j--) {
if(j >= p[i-1]) {
results[j] = Math.max(results[j],results[j- p[i-1]] + g[i-1])
}
}
}
return results[w]
}
10.寻找缺失的整数
在一个无序的数组有99个不重复的正整数,范围是1-100,缺少一个1-100之间的一个数,求出这个数?
思路
-
创建1-100的哈希表,读到一个旧删除一个,最后剩下的就是缺失的。
-
把所有数字排序后,依次遍历,如何相差不是1,则找到缺失值。
-
先把1-100 所有数都相加。然后依次减去所有值,剩下的就是所缺失的值。
扩展
- 在范围1-100之间正整数的无序数组 ,有99个出现了偶次数,有一个出现了奇次数。求这个出现奇数次的值。
使用位运算的 异或
如 [3,1,3,2,4,1,4] 异或 后得到 2 就是那个奇数
- 在范围1-100之间正整数的无序数组 ,有98个出现了偶次数,有两个出现了奇次数。求这个两个奇数次的值。
如 [4,1,2,2,5,1,4,3], 异或 后得到 00000110B
这个结果 是。3和5 的异或结果,所以使用分治法,倒数第二位为0的为一个新数组,倒数第二位为1的为第二个新数组。得到
数组1:[4,1,5,1,4]
数组2:[2,2,3]
对于两个数组再分别进行异或,求出是 5和 3.
实际应用
1.bitmap应用
用户信息系统,包含用户多个标签:名字,性别,年龄,职业,电话。可以通过标签快速搜索指定人群。
思路
1.数据库列保存
- 使用数据库记录所有用户和标签信息。每次只需要执行sql,就可以查询对应标签用户。
缺点:由于标签是动态添加的,表的字段会越来越多,查询的效率也会越来越慢。
2. 使用bitmap
可以理解是一个很长很长的bit位。通过把用户id转化为下标,存在的进行标1。每一个bitmap代表不同的标签。如 性别bitmap,年龄bitmap。
1. 记录
这里下标 直接对应 用户的id,当要查询性别是男的用户。直接便利bitmap就ok了
同理:年龄也是一样做法
2. 求与
如果需要同时检查性别:男 并且 年龄 :80,只需要把两个bitmap求余即可
3. 求或
如果需要同时检查性别:男 或者 年龄 :80,只需要把两个bitmap求或 即可
4. 求非
通过提前求出所有全量用户,再进行异或得到 非的数据。
-
先求年龄的全量 80 + 90
-
求异或 得到非80
2.LRU算法的应用
定义
least recently used 最近最少使用
把hashmap的每个元素,通过双向链表的方式关联起来。
- 最右边是最常用数据,最左边为带移除数据。
- 每次都从右边插入,并从左边移除。
- 每次读取数据,如果已存在,则把当前数据移动到最后
- 每次从最左边开始删除数据。
插入
读取
删除
当超过容量限制,优先移除最前面的元素,再输入新元素
3.A星寻路算法
有效路径算法。通过全局路径节点,求解起始点到目标点的最短路径 ,如果存在最短路径,无论在什么情况之下,都能够保证找到这条最短路径。 又名:启发式算法
问题
求起点(1,2) 到终点(5,2) 的最佳路径?
题解
两个集合:
- OpenList:可到达的格子
- CloseList:已到达的格子 一个公式:
- F = G +H
每一个格子都具有F、G、H这三个属性
- G: 从起点走到当前格子的成本,也就是已经花费了多少步。
- H: 在不考虑障碍的情况下,从当前格子走到目标格子的距离,也就是离目标还有多远。
- F: G和H的综合评估,也就是从起点到达当前格子,再从当前格子到达目标格子的总步数。
A星寻路的具体步骤
第1步,把起点放入OpenList,也就是刚才所说的可到达格子的集合。
第2步,找出OpenList中F值最小的方格作为当前方格。虽然我们没有直接计算起点方格的F值,但此时OpenList中只有唯一的方格Grid(1,2), 把当前格子移出OpenList,放入CloseList。代表这个格子已经到达并检查过了。
第三步,找出当前方格(刚刚检查过的格子)上、下、左、右所有可到达的格子,看它们是否在OpenList或CloseList当中。如果不在,则将它们加入OpenList,计算出相应的G,H,F值,并把当前格子作为它们的"父节点"。
通过不断判断F(一般以F最大的优先)来新增或移除 openList 和colseList两个集合。直到openList出现终点(5,2),则寻路结束。
4.红包分配算法实现。
如何把100元红包随机分配给5个人。 要求:
- 所有金额之和要等于总金额
- 每个人最少有一分钱
- 红包要尽可能均匀,不能差异化太大
实现
1. 使用随机数切割
每次拆分金额 = 随机区间[1分, 剩余金额-1分]
每一次的金额都是基于上一次的剩余
如:
- 第1次: [0.01, 99.99] ,中50
- 第2次: [0.01, 50] , 中20
- 第3次: [20, 50]
后面可以随机的金额就越来越小。
2. 使用二倍均值法
m为红包总金额,n为剩余人数
每次抢到红包 = 随机区间[0.01, m/n*2 -0.01]
每一次的金额都是基于上一次的剩余
如
- 第1次: 100 /5 * 2 = 40, [0.01, 39.99] ,中20,剩80
- 第2次: 80 /4 * 2 = 40, [0.01, 39.99] ,中20
- 第3次: 60 /3 * 2 = 40, [0.01, 39.99] ,中20 ,当然并不能保证每次都是中间值20,但是上浮动会保持在一定范围内。
3. 线段切割法
把红包的总金额看成一条很长的线段,而且每个人抢到的金额,则主线段拆分出来的子线段。
最开始就使用随机函数生成多个区间段的点。
当n个人抢红包时候,就需要确定n-1个切割点。做n-1次随机运算。
需要注意两点:
- 当随机切割点出现重复,要特殊处理
- 如何尽量降低时间和空间的复杂度