问:
- 现在有两个数组arr1,arr2。数组每个元素都是一种面值的硬币。arr1都为普通币,每一种可以取任意枚,arr2都为纪念币,每种只能取一枚。每种硬币都有一个面值,问能拼出面值M的方法数
- 4. 寻找两个正序数组的中位数
- 同时运行N台电脑的最长时间
解:
- 先写出只用arr1拼出0 ~ M的方法和只用arr2拼出0 ~ M的方法。然后遍历0 ~ M,分别选择让arr1拼i个,让arr2拼M-i个。
function getAllNums(arr1, arr2, M) {
const res1 = getAllPlans1(arr1, M)
const res2 = getAllPlans2(arr2, M)
let sum = 0
// res1是普通币拼出0~M的所有方法数 res2是纪念币拼出0~M的所有方法数
// 让普通币拼0,纪念币拼M。让普通币拼1,纪念币拼M-1。。。普通币拼M,纪念币拼0
// 以上所有结果累加返回
for (let i = 0; i <= M; i++) {
const one = res1[i]
const tow = res2[M - i]
sum += one * tow
}
return sum
function getAllPlans1(arr, target) {
const dp = []
// 创建二维表
for (let i = 0; i <= arr.length; i++) {
dp[i] = []
for (let j = 0 ;j <= target ;j++) {
if (i === arr.length) {
dp[i][j] = 0
}
if (j === 0) {
dp[i][j] = 1
}
}
}
for (let i = arr.length -1; i >=0;i--) {
for (let j =0;j<=target;j++) {
dp[i][j] = (dp[i][j-arr[i]] ?? 0 ) + dp[i+1][j]
}
}
return dp[0]
}
function getAllPlans2(arr, target) {
const dp = []
for (let i = 0; i <= arr.length; i++) {
dp[i] = []
for (let j = 0 ;j <= target ;j++) {
if (i === arr.length) {
dp[i][j] = 0
}
if (j === 0) {
dp[i][j] = 1
}
}
}
for (let i = arr.length - 1; i >= 0; i--) {
for (let j = 0; j <= target; j++) {
dp[i][j] = dp[i + 1][j] + (dp[i + 1][j - arr[i]] ?? 0)
}
}
return dp[0]
}
}
- 算法原型是两个有序数组中查找第k大的数
- 先看查找第k大的数如何解,因为是两个有序数组,所以使用二分查询
- 二分查询数组1,判断中位数的元素在另一个数组中可以比多少个数大(考虑到可能存在相同数字,所以要求两个值,分别是这个数在数组中最少压过多少数、最多压过多少数)
- 若二分查询结果中这个数最少能压过的数比k大,那么说明这个数太大了,若最多能压过的数比k小,说明这个数太小了。若k在两者之间,说明就是这个数,返回这个数即可
- 第k大的数不是在数组1中就是在数组2中,所以若数组1没查到第k大的数,那么就在数组2中
- 有了上述的算法原型,把k设定为中位即可
const findMedianSortedArrays = function(nums1, nums2) {
const len = nums1.length + nums2.length
const k = len / 2
const findMidNum = (arr, otherArr, left, right, target) => {
while (left <= right) {
const mid = (left + right) >> 1
// 判断midItem在另一个数组中最多能压几个数和最少能压几个数
const otherLenMax = findItemIdxMax(otherArr, arr[mid]) + 1
const otherLenMin = findItemIdxMin(otherArr, arr[mid]) + 1
// 如果当前压的个数最少都超过了target,说明这个值压多了
if (mid + otherLenMin > target) {
right = mid - 1
} else if (mid + otherLenMax >= target && mid + otherLenMin <= target) {
// 如果当前压的个数在范围内
return arr[mid]
} else {
// 当前压的个数最多都小于target,说明这个值压少了
left = mid + 1
}
}
}
const findItemIdxMax = (arr, target) => {
let left = 0
let right = arr.length - 1
let res = -1
while (left <= right) {
const mid = (left + right) >> 1
if (arr[mid] <= target) {
res = mid
left = mid + 1
} else {
right = mid - 1
}
}
return res
}
const findItemIdxMin = (arr, target) => {
let left = 0
let right = arr.length - 1
let res = -1
while (left <= right) {
const mid = (left + right) >> 1
if (arr[mid] < target) {
res = mid
left = mid + 1
} else {
right = mid - 1
}
}
return res
}
const getNum = (target) => findMidNum(nums1, nums2, 0, nums1.length - 1, target) ??
findMidNum(nums2, nums1, 0, nums2.length - 1, target)
if (Number.isInteger(k)) {
const num1 = getNum(~~k)
const num2 = getNum(~~k - 1)
return (num1 + num2) / 2
} else {
return getNum(~~k)
}
};
function maxRunTime(n, arr) {
arr.sort((a, b) => a- b)
// 预处理,从当前位置结束的电量累加和
const helpArr = []
for (let i = 0; i <arr.length; i++) {
helpArr[i] = arr[i] + (helpArr[i - 1] ?? 0)
}
// 运行时间可能的最大值。电池总和 / n
const possibleMax = Math.ceil(helpArr[arr.length - 1] / n)
// 二分尝试,找最大的结果
let left = 0
let right = possibleMax
let maxTime = 0
while (left <= right) {
const i = Math.floor((left + right) / 2)
// 小于i的最右下标
const smallIdx = getIdxOfItem(arr, i)
// 具备充足电量的电池个数
const num = arr.length - 1 - smallIdx
// 还需要搞定多少台电脑
const need = n - num
// 所有碎片电池总和
const remain = helpArr[smallIdx]
if (need <= 0 || remain >= need * i ) {
maxTime = i
left = i + 1
} else {
right = i - 1
}
}
return maxTime
// 找到小于item的最右的下标
function getIdxOfItem(arr, item) {
let left = 0
let right = arr.length - 1
let mostRight = -1
while (left <= right) {
const midIdx = Math.floor((right + left) / 2)
if (arr[midIdx] < item) {
left = midIdx + 1
mostRight = midIdx
}
if (arr[midIdx] >= item) {
right = midIdx - 1
}
}
return mostRight
}
}