参考:
贪心算法
贪心算法本质:通过问题中⼀些隐藏较深的规律,来减少冗余计算。
/**
* @param {number[]} gas
* @param {number[]} cost
* @return {number}
*/
var canCompleteCircuit = function(gas, cost) {
// 油量变化值为gas[i]-cost[i];
// 希望从起点出发,油量变化值的和始终大于0,那么就可以环绕一圈
// 因此,找到那个使得油量变化值之和最小的点,作为起点
let start = 0;
let sum = 0;
let minSum = Infinity;
for(let i=0; i< gas.length;i++) {
sum+= gas[i]-cost[i];
if(sum<minSum) {
start = i+1;
minSum = sum;
}
// console.log("sum="+sum);
}
if(sum<0) return -1;
return start===gas.length? 0 : start;
};
/**
* @param {number} n
* @return {number}
*/
var cuttingRope = function(n) {
// 通过数学公式推导可得:最好分隔为长度为3的多份;其次是最后一份长度为2;如果最后一份长度为1,则将前一份拆为2+2;
let a = Math.floor(n/3);
let b = n%3;
console.log("a=",a,"b=",b);
if(n<=3) return n-1;
if(b===0) return Math.pow(3,a);
if(b===2) return Math.pow(3,a)*2;
if(b===1) return Math.pow(3,a-1)*4;
};
贪心算法玩跳跃游戏
/**
* @param {number[]} nums
* @return {boolean}
*/
var canJump = function(nums) {
// 计算从当前位置出发,能否到达最后的坐标
// [2,4,3,4,8]
let n = nums.length;
// let jump = new Array(n).fill(0);
let maxDis = 0; //记录当前能走到的最大坐标
for(let i=0;i<n-1;i++) {
// jump[i] = i + nums[i];
if(i>maxDis) return false; //走不到i的位置的话,返回false
maxDis = Math.max(maxDis,i+nums[i]);
}
return maxDis >= n-1 ? true :false;
};
/**
* @param {number[]} nums
* @return {number}
*/
var jump = function(nums) {
let n = nums.length;
let farthest = 0;
let end = 0;
let jumps = 0;
for(let i = 0;i<n-1;i++){
// 假设你总是可以到达数组的最后一个位置
farthest = Math.max(farthest,nums[i]+i);
if(end == i) {
// 每次都选择能够跳到最远的位置
end = farthest;
jumps++;
}
}
return jumps;
};
贪心算法做区间调度
给你很多形如[start,end]的闭区间,请你设计一个算法,算出这些区间中最多有几个互不相交的区间。
例如:intvs=[[1,3],[2,4],[3,6]],这些区间最多有两个区间互不相交,即[[1,3],[3,6]],你的算法应该返回 2。注意边界相同并不算相交。
思路:
- 首先将各个闭区间,按照end数值升序排序;
- 每次选择最早结束的区间,并将与这个区间相交的区间去除;
- 重复步骤 2,直到 intvs 为空为止。之前选出的那些 x 就是最大不相交子集。
/**
* @param {number[][]} points
* @return {number}
*/
function sortInterval(a,b) {
return a[1] - b[1];
}
var findMinArrowShots = function(points) {
// 问题本质:求互不相交的区间的数量
points.sort(sortInterval);
// console.log(points);
let x_end = points[0][1];
let count = 1;
for(point of points) {
if(point[0]>x_end){
x_end = point[1];
count++;
}
}
return count;
};
/**
* @param {number[][]} intervals
* @return {number}
*/
// 去除区间的最小数量,使剩余区间互不重叠,因此只需要返回(n-最大不相交子集数)
// 二维数组的排序
var sortInterval = function(a,b) {
return a[1] - b[1];
}
var eraseOverlapIntervals = function(intervals) {
intervals.sort(sortInterval);
// console.log(intervals);
let count = 1; //用于记录不相交的区间
let x_end = intervals[0][1];//最早结束的区间
let n = intervals.length;
for(interval of intervals) {
if(interval[0]>=x_end) {
count++;
x_end = interval[1];
}
}
return n-count;
};
/**
* @param {number[][]} clips
* @param {number} time
* @return {number}
*/
var sortClips = function(a,b) {
if(a[0]===b[0]){
return b[1]-a[1];
}
return a[0]-b[0];
}
var videoStitching = function(clips, time) {
// 1.按照起点对clips中的视频升序排序,如果多个视频起点相同,那么按照终点降序排序
// 2.因此clip[0]必定会被选择
// 3.下一个选择start比上一个视频end小的(必定重合),且长度最大的
clips.sort(sortClips);
// console.log(clips);
let count = 0;
let n = clips.length;
let cur_end = 0;
let next_end = 0;
let i = 0;
while(i<n && clips[i][0]<=cur_end) {
while( i<n && clips[i][0]<=cur_end){
next_end = Math.max(clips[i][1],next_end);
i++;
}
cur_end = next_end;
count++;
if(cur_end>=time) return count;
}
return -1;
};
二分
针对有序数列进行查找时,优先考虑二分,最经典的二分搜索的算法复杂度为O(logN);
几个需要注意的地方:
mid =left+Math.floor((right-left)/2)与mid = Math.floor((left+right)/2)结果是一样的,但是可以有效防止由于left+right过大而导致溢出的情况;- 当
right = nums.length-1时,说明搜索的区间为[left,right],是一个闭区间;因此停止搜索的条件应该是该区间为空,即left>right的情况,故while循环中的条件应该为left<=right; - 因此
left = mid+1,right = mid -1
//二分搜索的基本思路
var binarySearch = function(nums,target){
let left = 0, right = ...;
while(...){
mid = left + Math.floor((right - left)/2);
if(nums[mid]===target){
return mid;
} else if(nums[mid]<target) {
left = mid + 1;
} else if(nums[mid]>target) {
right = mid -1;
}
}
return ...
}
//寻找左侧边界的二分搜索,如nums = [1,2,2,2,3],target 为 2,此算法返回的索引是找到的第一个target值
//寻找右边界:返回的索引是找到的最后一个target值
if(nums[mid]===target){
//找左边界
right = mid -1;
//找右边界
left = mid + 1;
} else if(nums[mid]<target) {
left = mid + 1;
} else if(nums[mid]>target) {
right = mid -1;
}
//边界情况(找左边界)
if(left>=nums,length || nums[left] !== target) return -1;
//边界情况(找右边界)
if(right<0 || nums[right] !== target) return -1;
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {
let left = 0,right = nums.length -1; //闭区间[left,right];
// 当区间为空的时候,停止搜索,即为left>right
let mid;
while(left<=right) {
mid = left+Math.floor((right-left)/2);
if(nums[mid]===target) {
return mid;
} else if(nums[mid]<target) {
left = mid+1;
} else if(nums[mid]>target) {
right = mid-1;
}
}
return -1;
};
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var searchRange = function(nums, target) {
let range = new Array(2).fill(0);
let left = 0;
let right = nums.length - 1;
let mid;
//找左边界
while(left <= right) {
mid = left + Math.floor((right-left)/2);
if(nums[mid]===target) {
right = mid -1;
} else if(nums[mid] > target) {
right = mid -1;
} else if(nums[mid] < target) {
left = mid + 1;
}
}
if(nums[left]!=target || left>=nums.length) return [-1,-1];
range[0] = left;
//找右边界
left = 0; right=nums.length-1;
while(left <= right) {
mid = left + Math.floor((right-left)/2);
if(nums[mid]===target) {
left = mid+1;
} else if(nums[mid] > target) {
right = mid -1;
} else if(nums[mid] < target) {
left = mid + 1;
}
}
range[1] = right;
return range;
};
/**
* @param {number[]} numbers
* @return {number}
*/
var minArray = function(numbers) {
let left = 0;
let right = numbers.length-1;
let mid;
while(left<right){
mid = left+Math.floor((right-left)/2);
if(numbers[right]<numbers[mid]) {
left = mid+1;
} else if(numbers[right] > numbers[mid]) {
right = mid;
} else {
right--;
}
}
return numbers[left];
};
剑指 Offer 53 - II. 0~n-1中缺失的数字 - 力扣(LeetCode)
/**
* @param {number[]} nums
* @return {number}
*/
//特征:找到使得 数组的值nums[mid]>数组下标mid 的最开始的那个值
var missingNumber = function(nums) {
let left = 0;
let right = nums.length-1;
let mid;
while(left<=right) {
mid = left+Math.floor((right-left)/2);
if(nums[mid]===mid) {
left = mid+1;
} else if(nums[mid]>mid) {
right = mid-1;
}
}
return left;
};
/**
* @param {number[][]} matrix
* @param {number} target
* @return {boolean}
*/
// 1. 选取左下角的值作为初始值key
// 2. 如果目标值大于key,因为是最左边的值(最小),所以col++
// 3. 如果目标值小于,那么更小的值只可能是上一行,所以row--
var searchMatrix = function(matrix, target) {
let row = matrix.length-1;
let col = 0;
while(row>=0 && col<=matrix[0].length-1){
if(matrix[row][col]===target) {
return true;
} else if(matrix[row][col]>target) {
row--;
} else if(matrix[row][col]<target) {
col++;
}
}
return false;
};
二分搜索的泛化
- 首先,你要从题目中抽象出一个自变量
x,一个关于x的函数f(x),以及一个目标值target。f(x)必须是在x上的单调函数(单调增单调减都可以)- 题目是让你计算满足约束条件
f(x) == target时的x的值。
- 找到
x的取值范围作为二分搜索的搜索区间,初始化left和right变量。 - 根据题目的要求,确定应该使用搜索左侧还是搜索右侧的二分搜索算法,写出解法代码。 875. 爱吃香蕉的珂珂
/**
* @param {number[]} piles
* @param {number} h
* @return {number}
*/
// 定义:速度为 x 时,需要 f(x) 小时吃完所有香蕉
// f(x) 随着 x 的增加单调递减
var f = function(piles,x) {
let hours = 0;
for(let i=0;i<piles.length;i++) {
hours += Math.floor(piles[i]/x);
if(piles[i]%x != 0) hours++;
}
return hours;
}
// 找最小的能吃完的速度,左边界
var minEatingSpeed = function(piles, h) {
let left = 1, right=0 , mid;
for(let i=0;i<piles.length;i++) right+=piles[i];
// console.log(left,right);
while(left<=right) {
mid = left + Math.floor((right-left)/2);
// console.log(left,right,mid,f(piles,mid));
if(f(piles,mid) === h) {
right = mid-1;
} else if(f(piles,mid)<h) {
right = mid-1;
} else if(f(piles,mid)>h) {
// 在这个速度下,所需的时间大于警卫回来的时间
left = mid + 1;
}
}
return left;
};
/**
* @param {number[]} weights
* @param {number} days
* @return {number}
*/
// f(x,weights) 为每天送x的量,返回的天数
// f(x)是一个单调递减函数
var f = function(x,weights) {
let day=0;
for(let i=0; i<weights.length;) {
let cap = x;
while (i < weights.length) {
if (cap < weights[i]) break;
else cap -= weights[i];
i++;
}
day++;
}
return day;
}
var shipWithinDays = function(weights, days) {
let left = 1,right=0, mid;
for(let i=0; i<weights.length;i++) {
left = Math.max(weights[i],left);
right += weights[i];
}
while(left<=right) {
mid = left + Math.floor((right-left)/2);
if(f(mid,weights)=== days){
right = mid -1;
} else if(f(mid,weights)>days){
left = mid + 1;
} else if(f(mid,weights)<days){
right = mid -1;
}
}
return left;
};