算法基础篇(1)
本科自大三开始自学前端非计算机相关专业,不懂算法,所以需要勤能补拙.
很多题思路相似,而且内容鸡肋,这里分享一点有必要掌握的
最短无序连续子数组(581题)
题目:
给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。
你找到的子数组应是最短的,请输出它的长度。
输入: [2, 6, 4, 8, 10, 9, 15]
输出: 5
解释: 你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。
/**
* @param {number[]} nums
* @return {number}
*/
var findUnsortedSubarray = function(nums) {
const oldNums = [...nums];
const arr = nums.sort((a,b)=>a-b);
let a=0;
let b=0;
for(let i=0;i< oldNums.length;i++){
if(oldNums[i] !== arr[i]){
a= i;
break;
}
}
for(let i=oldNums.length;i>0;i--){
if(oldNums[i] !== arr[i]){
b= i;
break;
}
}
if(a===0&& b===0) return 0;
return b-a+1;
};
思路: 先排序,然后找到第一位和最后一位不一致的索引,相减加1(都为0代表没有不同的) (排序后,找到首位首次不同的,那么这个区间里面所有的数字都是需要排序的)
42. 接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水
输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
var trap = function(heightList) {
if (!heightList.length) return 0
function getMaxIndex(arr){
var maxIndex = 0;
arr.forEach((_, i) => {
if (_ > arr[maxIndex]) {
maxIndex = i
}
});
return maxIndex;
}
var maxIndex = getMaxIndex(heightList)
let leftArr = heightList.slice(0, maxIndex + 1)
let rightArr = heightList.slice(maxIndex).reverse()
function getRect (arr) {
let leftMax = arr[0] || 0
return arr.reduce((all, height) => {
if (height > leftMax) {
leftMax = height
}
let currentHeight = leftMax - height >=0 ? leftMax - height : 0
return all + currentHeight
}, 0)
}
return getRect(leftArr) + getRect(rightArr)
};
思路
- 获取到最大坐标
- 从最大坐标拆分为左右两个数组(右数组reverse)
- 每个数组执行函数,每次更新最大值后获取最大值和当前值的差进行累加
- 左右两个数组执行函数的和就是最终结果
计数质数(204题)
题目:
统计所有小于非负整数 n 的质数的数量。
输入: 10
输出: 4
解释: 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。
/**
* @param {number} n
* @return {number}
*/
var countPrimes = function(n) {
let count = 0;
let signs = new Uint8Array(n);
for (let i = 2; i < n; i=i+1) {
if (!signs[i - 1]) {
count++;
for (let j = i; i * j <= n; j++) {
signs[i * j - 1] = true;
}
}
}
return count;
};
删除链表的倒数第N个节点(19)
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
/**
* @param {ListNode} head
* @param {number} n
* @return {ListNode}
*/
var removeNthFromEnd = function(head, n) {
let target = head,
cur = head;
while (n--) {
cur = cur.next;
console.log({
cur
})
}
while (cur && cur.next) {
cur = cur.next;
target = target.next;
console.log({
cur,target
})
}
if (!cur) return head.next;
target.next = target.next.next;
return head;
};
思路:
- 双指针思路,第一个指针先走n下
- 两个指针同时走,直到第一个指针走到结束,第二个指针就是我们的目标节点
- 第二个指针的next指向next的next
同构字符串(205)
给定两个字符串 s 和 t,判断它们是否是同构的。
如果 s 中的字符可以被替换得到 t ,那么这两个字符串是同构的。
所有出现的字符都必须用另一个字符替换,同时保留字符的顺序。两个字符不能映射到同一个字符上,但字符可以映射自己本身。(你可以假设 s 和 t 具有相同的长度。)
输入: s = "egg", t = "add"
输出: true
输入: s = "foo", t = "bar"
输出: false
输入: s = "paper", t = "title"
输出: true
/**
* @param {string} s
* @param {string} t
* @return {boolean}
*/
let isIsomorphic = function (s, t) {
for (let i = 0; i < s.length; i++) {
if (s.indexOf(s[i]) !== t.indexOf(t[i])) return false;
}
return true;
};
说明: 枚举每一位,找到对应另一个字符串i位以后最先出现的该字符,有一个不相同就代表,两个字符转不是同构的。 (因为如果对于每一位来说,都能在对应的另一个字符串下一个相同字符找到相同的索引, 那么代码第i位符合同构,一次枚举完成就可以判断出来结果)
缺失的第一个正数(41)
给定一个未排序的整数数组,找出其中没有出现的最小的正整数。
输入: [1,2,0]
输出: 3
输入: [3,4,-1,1]
输出: 2
输入: [7,8,9,11,12]
输出: 1
/**
* @param {number[]} nums
* @return {number}
*/
var firstMissingPositive = function(nums) {
const len = nums.length;
const obj = new Set(nums);
let index = 1;
while(true){
if(obj.has(index) ) ++index;
else return index;
}
};
该题相对简单, 用set存储整个数组,然后依次查询Set的是否有下一位正整数,没有就返回
爬楼梯(70)
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
/**
* @param {number} n
* @return {number}
*/
var climbStairs = function(n) {
const cache = [1,1];
function find(i){
if(cache[i])return cache[i];
cache[i] = find(i-1) +find(i-2);
return cache[i];
}
return find(n);
};
该题目经典而简单,注意用个cache对象做存储,避免栈溢出,或者用while避免栈溢出问题
只出现一次的数字(136)
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
输入: [2,2,1]
输出: 1
输入: [4,1,2,1,2]
输出: 4
/**
* @param {number[]} nums
* @return {number}
*/
var singleNumber = function(nums) {
const n = nums.length
if (n === 1) {
return nums[0]
}
return nums.reduce((a, b) => a ^ b)
}
该问题简单,但是解法较多, 目前个人觉得不错的方法是用位运算符, 举个例子
6^6. -> 0
7^1. -> !==0
2^7^2. -> 7(因为7只出现了一次)
利用这个特性一次对比很容易找出来只出现一次的数字
寻找两个有序数组的中位数(4)
给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number}
*/
var findMedianSortedArrays = function(nums1, nums2) {
const getMedium = (arr) => {
if (arr.length % 2 == 0) {
return (arr[arr.length / 2] + arr[arr.length / 2 - 1]) / 2;
} else {
return arr[Math.floor(arr.length / 2)]
}
}
const newArr = [];
while (nums1.length || nums2.length) {
const num1Len = nums1.length;
const num2Len = nums2.length;
if (!num2Len || !num1Len) {
const arr = num1Len ? nums1 : nums2;
while (arr.length) {
newArr.push(arr.shift())
};
return getMedium(newArr);
}
const min = Math.min(nums1[0], nums2[0])
if (min === nums1[0]) {
newArr.push(nums1.shift());
} else {
newArr.push(nums2.shift());
}
}
};
思路:两个数组合并成一个有序数组,然后取中位数
优化1.0: index只获取中间两位之后就break; 减少了一半的遍历
优化2.0: 每次两个数组二分获取中间数,然后动态调整,直到两遍数字变为可排序数字,然后移动指针找到中间索引。
1.0很容易写,但是2.0写完了总有case过不去,等以后再反过来重新看这个问题吧。。。。
获取数组的最大子串
const maxSubArray = function(nums) {
let max = nums[0]; // 初始化最大值
let newMax = nums[0]; // 数组元素相加的缓存值
for (let i = 1; i < nums.length; i++) {
newMax = Math.max(newMax + nums[i], nums[i]); // 相加是否大于当前值
max = Math.max(newMax, max); // 与最大值相比
}
return max;
};
var arr = [-1,2,3,4,1-5,3,0,10,-10];
maxSubArray(arr)
思路:动态规划
规律:
加到第i位和第i位比较,取最大的,因为如果加上第i位小于第i位,证明(1)前面i-1位是负数, 就可以放弃了
(2)如果是正数,那么加上第i位一定大于不加第i位
!!!!!注意,对比的是i位总和与第i位的大小
获取几个字符串公共的prefix
方案一,遍历
var longestCommonPrefix = function(strs) {
if(strs.length === 0) return '';
let result = '';
let len = Math.min.apply(Math, strs.map(item => item.length));
for(let i = 0; i < len; i++) {
let tmp = strs.map(item => item.substring(0, i+1));
if (new Set(tmp).size === 1) result = tmp[0];
}
return result;
};
方案二,动态规划
function lcs(str1, str2) {
let record = [];
let max = 0;
let pos = 0;
let result = "";
//初始化记录图
for (let i = 0; i < str1.length; i++) {
record[i] = [];
for (let j = 0; j < str2.length; j++) {
record[i][j] = 0;
}
}
//动态规划遍历
for (let i = 0; i < str1.length; i++) {
for (let j = 0; j < str2.length; j++) {
if (i === 0 || j === 0) {
record[i][j] = 0;
} else {
if (str1[i] === str2[j]) {
record[i][j] = record[i - 1][j - 1] + 1;
} else {
record[i][j] = 0;
}
}
//更新最大值指针
if (record[i][j] > max) {
max = record[i][j];
pos = [i];
}
}
}
//拼接结果
if (!max) {
return "";
} else {
for (let i = pos; i > pos - max; i--) {
result = str1[i] + result;
}
return result;
}
}
console.log(lcs("havoc", "raven"));
console.log(lcs("abbcc", "dbbcc"));
01背包问题
function max(a, b) {
return a > b ? a : b;
}
/**
*
* @param {[type]} capacity 背包容量
* @param {[type]} size 物品体积数组
* @param {[type]} value 物品价值数组
* @param {[type]} n 物品个数
* @return {[type]} 最大价值
*/
function knapsack(capacity, size, value, n) {
//K[n][capacity]表示0~n-1这n个物品入选时的最优值
let K = [];
let pick = [];
let result = 0;
for (let i = 0; i <= n; i++) {
K[i] = [];
for (let j = 0; j <= capacity; j++) {
if (j === 0 || i === 0) {
//j=0表示背包容量为0,无法放入故结果为0,边界数值,避免动态规划时候益处
K[i][j] = 0;
} else if (size[i - 1] > j) {
K[i][j] = K[i - 1][j];
} else {
//动态规划解,当第i个物品可以放入时,K[i][j]等同于放入i时最值和不放i时的值取最大
K[i][j] = max(
K[i - 1][j - size[i - 1]] + value[i - 1],
K[i - 1][j]
);
// 对比上方
}
}
}
console.table(K);
console.log({ K });
result = K[n][capacity];
//如何求解到底选了哪些物品?
while (n > 0) {
if (
K[n - 1][capacity - size[n - 1]] + value[n - 1] >
K[n - 1][capacity]
) {
capacity -= size[n - 1];
n--;
pick[n] = 1;
} else {
n--;
pick[n] = 0;
}
}
console.log("答案的选择情况为:", pick);
return result;
}
let value = [4, 5, 10, 11, 13];
let size = [3, 4, 7, 8, 9];
// let value = [10,5,10,11,13];
// let size = [1,40,70,8,9];
let capacity = 16;
let n = 5;
let result = knapsack(capacity, size, value, n);
console.log("结果:", result);
思路: 动态规划
1、建模生成一个K数组i行j列,i表示物品,j表示背包。
2、讨论
1)0和0一定为0,用来支持边界数,注意for循环需要去到等号,然后value[i-1]才表示当前位。
2.1)如果背包容量比第i个物品的重量还小,则第i个物品必然无法放入,相当于前i-1个物品放入j容量背包时的最值
2.2)最后动态规划,(当前包能承载的最大数量为背包容量减1的情况)和(当前空间减去size[i-1]的容量时的value加上当前物品的value和)取最大值