数组类刷题总结
一味的做题是没有效果的,要学会多总结多思考,形成自己的模板,拿到题就知道用什么方法去解决出来。数组类的题目还是属于比较基础的,下面进行分类总结。
滑动窗口法
lc209长度最小的子数组(基础)
这道题是最基本的滑动窗口的做法,将指针内的元素相加,若大于等于target的时候记录长度,并移动left;小于的时候扩张,增加right
/**
* @param {number} target
* @param {number[]} nums
* @return {number}
*/
var minSubArrayLen = function(target, nums) {
let len = nums.length+1;
//利用滑动窗口来做;
let left = 0;
let right = 0;
let sum = nums[0];
while(right < nums.length){
if(sum < target){
right++;
sum += nums[right];
}
while(sum >= target) {
len = Math.min(len,right-left+1);
sum -= nums[left];
left++;
}
}
return len > nums.length ? 0:len;
};
此题目是寻找最小窗口,所以满足条件后,进行缩小
lc76最小覆盖子串(困难)
这道题看起来就稍微复杂了,实际上还是只是在判断这个窗口的条件时后稍微复杂,但还不算很难
记录字符串中,有多少种类型,然后每种有多少个,为0的时候是一个判断类型的条件
var minWindow = function(s, t) {
//let res = s.length+1;
let ans = '';
//t子串中的所有字符统计出来,几种类型,每种有多少个;
let need = new Map();//需要的各种字符,及个数
let needType = 0;//需要几种类型
for(let item of t){
if(need.has(item)){
need.set(item,need.get(item)+1);
}else{
need.set(item,1);
}
}
needType = need.size;
//下面接着用滑动窗口进行判断,条件是k=0;只有某个key的值减为0才能为0;
let right = 0;
let left = 0;
let str = '';
while(right < s.length){
// str += s[right];
let char = s[right];
if(need.has(char)){
need.set(char,need.get(char)-1);
if(need.get(char) === 0){
needType--;
}
}
while(needType === 0){
str = s.substring(left,right+1);
//let len = right - left +1;
if(!ans || str.length < ans.length){
ans = str;
}
//说明各个字符都已经出现过,可以开始收缩了
let ch = s[left];
if(need.has(ch) ){
need.set(ch,need.get(ch)+1);
if(need.get(ch) === 1){
needType++;
}
}
left++;
}
right++;
}
return ans;
};
此题也属于,寻找最小窗口,中心思想还是,在满足条件后进行窗口的缩小操作;
lc904水果成篮(中等)
这道题和之前不太一样的地方在,它是寻找最大的窗口,不过中心思想就是不满足条件是,进行最大窗口的寻找,记录一下位置
var totalFruit = function(fruits) {
if(fruits.length === 1 || fruits.length === 2){
return fruits.length;
}
let left = 0;
let right = 0;
let lf = fruits[0];
let rf = fruits[0];
let res = 0;
for(;right < fruits.length; right++){
if(fruits[right] != rf && fruits[right] != lf){
left = right - 1;
while(left >= 1 && fruits[left-1] == fruits[left]){
left--;
}
lf = fruits[left];
rf = fruits[right]
}
res =Math.max(res,right-left+1);
}
return Math.max(2,res);
};
lc1004 最大连续个数1(中等)
此题也是寻找最大窗口,再做完水果成篮子后能够很简单的做出来
var longestOnes = function(nums, k) {
//此题还是用滑动窗口来做
//记录0的位置,当k+1个0出现的时候,就要进行位置变化
//选择位置最近的两个0作为翻转
let stack = [];
let len = 0;
let right = 0;
let left = 0;
while(right < nums.length){
if(nums[right] === 1){
len = Math.max(len, right - left + 1 );
right++;
continue;
}else{
if(k== 0){
left = right+1;
right++;
continue;
}
if( k && stack.length < k){
stack.push(right);
}else{
//第k+1个0出现了
left = stack.shift()+1 ;
stack.push(right);
}
len = Math.max(len,right-left + 1 );
right++;
}
}
return len;
};
lc3 无重复字符的最长子串(中等)
这个也是一个简单的滑动窗口;
var lengthOfLongestSubstring = function(s) {
//依旧可以用滑动窗口来做;
//如果str出现重复的,就要把指针指到重复字符的下一个
let len = 0;
let left = 0;
let right = 0;
let str = '';
while(right < s.length){
let char = s[right];
str = s.slice(left,right)
if(str.includes(char)){
while(s[left] != char){
left++;
}
left ++;
}
len = Math.max(len,right-left + 1);
right++;
}
return len;
};
总结
滑动窗口,无非分为两大类,一是寻找满足条件的最小窗口的长度,或者是寻找最大窗口的长度。这样滑动窗口的问题大部分都能解决了
二分查找
二分查找也是数组类型中经常爱考到的题,思路很简单,但边界条件往往容易理不清楚,这里把这类问题拉出来,好好总结一下。
正如随想录所说,常见的问题都出现于
例如到底是 while(left < right) 还是 while(left <= right),到底是right = middle呢,还是要right = middle - 1呢?
在二分查找题中,要确定循环不变量的规则,就是定义好区间,一般是
- 左闭合右闭合,left == right 有效,right = mid - 1; left = mid + 1;
- 左闭合右开, left < right right = mid; left = mid + 1;
lc34 搜索插入位置(简单)
var searchInsert = function(nums, target) {
//左闭右闭
let left = 0;
let right = nums.length - 1;
let position = -1;
while(left <= right){
let mid = Math.floor((left + right)/2);
if(nums[mid] === target){
position = mid;
break;
}
if(nums[mid] < target){
left = mid + 1;
}else{
right = mid - 1;
}
}
return position === -1 ? left : position;
};
lc34 在排序数组中查找元素的第一个和最后一个位置(中等)
利用二分法,先查找出第一个大于target的,在查找第一个小于target,两次二分查找
var searchRange = function(nums, target) {
//二分法查找三个8,一个范围;
//先查找到第一个大于target的
let ans = [-1,-1];
let left = 0;
let right = nums.length-1;
while(left <= right){
let mid = Math.floor((left + right)/2);
if(nums[mid] === target && (mid === nums.length - 1 || nums[mid+1] > target)){
ans[1] = mid;
break;
}
//左闭合右开?
if(nums[mid] <= target){
left = mid+1;
}else{
right = mid-1;
}
}
//寻找左边界
left = 0;
while(left <= right){
let m = Math.floor((left + right)/2);
if(nums[m] === target && (m === 0 || nums[m-1] < target)){
ans[0] = m;
break;
}
if(nums[m] < target){
left = m + 1;
}else{
right = m - 1;
}
}
return (ans[0] === -1 || ans[1] === -1) ? [-1, -1] : ans;
};
lc69 x的平方根(简单)
var mySqrt = function(x) {
var beg=0
var end=x
if(x===1 || x===0)
return x
while(end-beg!==1){
var mid=Math.floor((beg+end)/2)
if(mid*mid===x)
return mid
else if(mid*mid > x){
end=mid
}
else{
beg=mid
}
}
return beg
};
lc875 爱吃香蕉的珂珂(中等)
这道题可能刚开始转换不了二分查找。仔细分析;
var minEatingSpeed = function(piles, h) {
//最快速度是,吃的是最大堆每个小时
//最慢速度一根,在这里面进行查找找到,能在h中吃完的最小速度
let fast = Math.max(...piles);
let slow = 1;
let speed = fast;
while(slow < fast){
let time = 0;
let mid = Math.floor((fast + slow)/2);
for(let item of piles){
time += Math.ceil(item/mid);
}
if(time > h){
slow = mid + 1;
}else{
fast = mid;
speed = mid;
}
}
return speed;
};