Day 1
Leetcode 53.求最大子数组和
-WA-
思路: 动态规划(前缀和)、嵌套循环(遍历数组)
//返回最大值(其实JS有Math.max())
var max = function (a, b) {
return a > b ? a : b
}
//动态规划(给定区间求前缀最大值)
var dpSome = function (start, end, nums) {
let dp = new Array(10000)
dp[0] = nums[0]
let i
for (i = start; i <= end; i++) {
if (i > 0) {
dp[i] = max(dp[i - 1] + nums[i], nums[i])
}
}
return dp[i - 1]
}
var maxSubArray = function (nums) {
let i
let max = undefined
//两层循环遍历所有子数组/序列
for (i = 0; i < nums.length; i++) {
for (let j = i; j < nums.length; j++) {
let temp = dpSome(i, j, nums)
if (max < temp||max === undefined) {
max = temp
}
}
}
return max;
}
结果:超时。动态规划部分不应该求前缀和,应该换种方法改求区间内最大和。两层循环导致时间复杂度过大。
改进:
- 没有必要遍历所有子数组并且和(嵌套循环时间复杂度达到 O(n2),而在此基础上,对子数组的任何操作,都会让时间复杂度更大)
- 遍历一次数组,对以 nums[i] 结尾的所有连续子数组和,动态规划求得最大值,变量 max 比较记录最大值,完毕。
//动态规划的抽象
var dpSome = function (end, nums) {
let dp = new Array(10000)
dp[0] = nums[0]
let i
for (i = 0; i <= end; i++) {
if (i > 0) {
dp[i] = Math.max(dp[i - 1] + nums[i], nums[i])
}
}
return dp[i - 1]
}
var maxSubArray = function (nums) {
let i
let max = undefined
//遍历数组得到每一个元素
for (i = 0; i < nums.length; i++) {
let temp = dpSome(i,nums)
//最大值比较更新
if(max===undefined||max<temp) max = temp
}
return max;
}
结果:超时。
- 还是超时了😥,代码本质上还存在嵌套循环(仅略小于O(n2))
- dp 部分不需要抽象出去,我的 dp 函数里包括了创建数组等一系列初始化操作,这些操作不应该被封装(封装后重复执行,也是超时的原因)。
-AC-
改进:dp 不需要通过嵌套循环使用,只需遍历一次,dp 出以 nums[i] 结尾的最大子序列和,最后求全局最优解,完毕。
var maxSubArray = function (nums) {
let max = undefined
let dp = new Array(nums.length)
dp[0] = nums[0]
//动态规划
for (i = 1; i < nums.length; i++) {
dp[i] = Math.max(dp[i - 1] + nums[i], nums[i])
}
//全局最优解
for(let i=0;i<nums.length;i++){
if(max == undefined||max<dp[i]) max = dp[i]
}
return max;
}
结果:通过。评价是:前两次思路混乱,甚至把前缀和与最大子序列和混淆了,下次先搞懂概念再来刷题🤐。
术语
- 动态规划:
- 前缀和:数组前 n 项和,动态规划的最简单的应用
- 局部最优解:对数组,即每个子元素的最优解
- 全局最优解:如上,对局部最优解的进一步处理
- 求最大子序列和:遍历数组 nums,动态规划求出以 nums[i] 结尾的最大子序列和,再次遍历,比较更新,遍历结束得整个数组的最大子序列和。
Day 2
Leetcode 1.两数之和
-AC-
思路:两层循环遍历所有组合,检查是否相加等于目标值
var twoSum = function (nums, target) {
let res = [-1,-1]
//遍历所有组合
for (let i = 0; i < nums.length; i++) {
for (let j = i +1 ; j < nums.length; j++) {
//一一检查
if(target == nums[i]+nums[j]){
res = [i,j]
}
}
}
return res
};
结果:通过。时间复杂度O(n2),空间复杂度O(1),还可以完善。
改进:哈希表。使用哈希表,可以将寻找
target - x的时间复杂度降低到从 O(n2) 降低到 O(1)。下面用到了Map,(在主流浏览器中)JavaScript 的 Map 存取已经封装了哈希表。
var twoSum = function (nums, target) {
//创建 Map
let map = new Map()
for (let i = 0; i < nums.length; i++) {
let tempKey = target - nums[i]
// 先检查再存入,防止出现同一个元素
if (!(map.has(tempKey))) map.set(nums[i], i)
else return [map.get(tempKey),i]
}
}
结果:通过。貌似通过 Map 间接用到了哈希表(
下面来自己实现一个hash)。
术语
'Anyone who considers arithmetical methods of producing random digits is, of course, in a state of sin. ' — John Von Neumann
“任何考虑产生随机数字的算术方法的人当然都处于罪恶状态。” — 约翰·冯·诺依曼 (1949)
- Map:廖雪峰:Map/Set
- 哈希表:[B站:哈希表是个啥?]
- 时间复杂度O(1)
- 空间换时间
- 其他语言的 Map/Set 与 哈希表的实现:C++中的Map与hash_map、java中的Map与HashMap
- 哈希/散列函数(hash funciton):
- 哈希函数确定哈希表中的元素(Hash fucntion --> Hash Table)
- Hash Collision( 哈希冲突 ):
不同输入共用同一个输出的情况,理论上无法证明一种算法不会造成Hash冲突,但是可以通过一些方法应对冲突的情况(日后再更新) - 简单的hash:平方取中法、随机数法、折叠法、直接寻址法 ......
- 成熟的hash:MD4、MD5、SHA-1 ......(顺便一提,JS原生并未实现这些算法,想要使用可以
自己手撸一个引入相关包)
- JavaScript 与浏览器引擎实现🤔?