刷题记录

144 阅读4分钟

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_mapjava中的Map与HashMap
  • 哈希/散列函数(hash funciton):
    • 哈希函数确定哈希表中的元素(Hash fucntion --> Hash Table)
    • Hash Collision( 哈希冲突 ):不同输入共用同一个输出的情况,理论上无法证明一种算法不会造成Hash冲突,但是可以通过一些方法应对冲突的情况(日后再更新
    • 简单的hash:平方取中法、随机数法、折叠法、直接寻址法 ......
    • 成熟的hash:MD4、MD5、SHA-1 ......(顺便一提,JS原生并未实现这些算法,想要使用可以 自己手撸一个 引入相关包)
  • JavaScript 与浏览器引擎实现🤔?