首发于微信公众号《前端成长记》,写于 2020.01.15

背景

本文记录刷题过程中的整个思考过程,以供参考。主要内容涵盖:

  • 题目分析设想
  • 编写代码验证
  • 查阅他人解法
  • 思考总结

目录

Easy

121.买卖股票的最佳时机

题目地址

题目描述

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。

注意你不能在买入股票前卖出股票。

示例 1:

  1. 输入: [7,1,5,3,6,4]
  2. 输出: 5
  3. 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5
  4. 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

示例 2:

  1. 输入: [7,6,4,3,1]
  2. 输出: 0
  3. 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0

题目分析设想

这道题,我的第一反应有点像求最大子序和,只不过这里不是求连续,是求单个,转换为增益的思想来处理。当然也可以使用两次遍历的笨办法来求解。我们分别来验证一下。

编写代码验证

Ⅰ.两次遍历

代码:

  1. /**
  2. * @param {number[]} prices
  3. * @return {number}
  4. */
  5. var maxProfit = function(prices) {
  6. if (prices.length < 2) return 0
  7. // 因为是利润,所以不考虑负数
  8. let profit = 0
  9. for(let i = 0; i < prices.length; i++) {
  10. for(let j = i + 1; j < prices.length; j++) {
  11. profit = Math.max(prices[j] - prices[i], profit)
  12. }
  13. }
  14. return profit
  15. };

结果:

  • 200/200 cases passed (384 ms)
  • Your runtime beats 25.89 % of javascript submissions
  • Your memory usage beats 19.85 % of javascript submissions (35.9 MB)
  • 时间复杂度 O(n^2)

Ⅱ.增益思想

代码:

  1. /**
  2. * @param {number[]} prices
  3. * @return {number}
  4. */
  5. var maxProfit = function(prices) {
  6. if (prices.length < 2) return 0
  7. // 因为是利润,所以不考虑负数
  8. let profit = 0
  9. let last = 0
  10. for(let i = 0; i < prices.length - 1; i++) {
  11. // 这里其实可以转换为每两项价格相减后,再求最大子序和
  12. // prices[i + 1] - prices[i] 就是增益,和0比较是因为求利润,不是求连续和
  13. last = Math.max(0, last + prices[i + 1] - prices[i])
  14. profit = Math.max(profit, last)
  15. }
  16. return profit
  17. };

结果:

  • 200/200 cases passed (64 ms)
  • Your runtime beats 94.53 % of javascript submissions
  • Your memory usage beats 19.85 % of javascript submissions (35.9 MB)
  • 时间复杂度 O(n)

查阅他人解法

这里看到两种不同的思考,一种是理解为波峰和波谷,找到波谷后的下一个波峰,判断每个波峰与波谷差值的大小。另外一种是基于状态机的动态规划,也就是说把可能性都前置运算后,再进行比较。

Ⅰ.波峰波谷

代码:

  1. /**
  2. * @param {number[]} prices
  3. * @return {number}
  4. */
  5. var maxProfit = function(prices) {
  6. if (prices.length < 2) return 0
  7. // 波谷
  8. let min = Infinity
  9. // 因为是利润,所以不考虑负数
  10. let profit = 0
  11. for(let i = 0; i < prices.length; i++) {
  12. if (prices[i] < min) {
  13. min = prices[i]
  14. } else if (prices[i] - min > profit) {
  15. // 这里是当前这个波峰和波谷的差值与历史的进行比较
  16. profit = prices[i] - min
  17. }
  18. }
  19. return profit
  20. };

结果:

  • 200/200 cases passed (68 ms)
  • Your runtime beats 86.75 % of javascript submissions
  • Your memory usage beats 21.34 % of javascript submissions (35.8 MB)
  • 时间复杂度 O(n)

Ⅱ.动态规划

代码:

  1. /**
  2. * @param {number[]} prices
  3. * @return {number}
  4. */
  5. var maxProfit = function(prices) {
  6. if (prices.length < 2) return 0
  7. // 动态初始数组
  8. let dp = new Array(prices.length).fill([])
  9. // 0:用户手上不持股所能获得的最大利润,特指卖出股票以后的不持股,非指没有进行过任何交易的不持股
  10. // 1:用户手上持股所能获得的最大利润
  11. // 状态 dp[i][0] 表示:在索引为 i 的这一天,用户手上不持股所能获得的最大利润
  12. // 状态 dp[i][1] 表示:在索引为 i 的这一天,用户手上持股所能获得的最大利润
  13. // -prices[i] 就表示,在索引为 i 的这一天,执行买入操作得到的收益
  14. dp[0][0] = 0
  15. dp[0][1] = -prices[0]
  16. for(let i = 1; i < prices.length; i++) {
  17. dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i])
  18. dp[i][1] = Math.max(dp[i - 1][1], -prices[i])
  19. }
  20. return dp[prices.length - 1][0]
  21. };

结果:

  • 200/200 cases passed (72 ms)
  • Your runtime beats 75.01 % of javascript submissions
  • Your memory usage beats 12.43 % of javascript submissions (36.7 MB)
  • 时间复杂度 O(n)

这个思路还有一系列的优化过程,可以点击这里查看

思考总结

很多问题都可以转换成动态规划的思想来解决,但是我这里还是更推荐使用增益思想,也可以理解为差分数组。但是如果题目允许多次买入卖出,我会更推荐使用动态规划来解决问题。

122.买卖股票的最佳时机Ⅱ

题目地址

题目描述

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

  1. 输入: [7,1,5,3,6,4]
  2. 输出: 7
  3. 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4
  4. 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3

示例 2:

  1. 输入: [1,2,3,4,5]
  2. 输出: 4
  3. 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4
  4. 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
  5. 因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
  6. ```javascript
  7. 示例 3:
  8. ```javascript
  9. 输入: [7,6,4,3,1]
  10. 输出: 0
  11. 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0

题目分析设想

上面刚刚做了算最大收益的,这题明显是算累计收益的,所以可以按以下几个方向:

  • 一次遍历,直接遍历,不断比较前后两天价格,如果后一天收益高,则差值加到利润,可以理解为贪心算法。
  • 波峰波谷,找到所有波峰波谷,差值相加即可
  • 动态规划

编写代码验证

Ⅰ.一次遍历

代码:

  1. /**
  2. * @param {number[]} prices
  3. * @return {number}
  4. */
  5. var maxProfit = function(prices) {
  6. let profit = 0
  7. for(let i = 1; i < prices.length; i++) {
  8. if (prices[i] > prices[i - 1]) {
  9. profit += prices[i] - prices[i - 1]
  10. }
  11. }
  12. return profit
  13. };

结果:

  • 201/201 cases passed (68 ms)
  • Your runtime beats 77.02 % of javascript submissions
  • Your memory usage beats 13.55 % of javascript submissions (35.7 MB)
  • 时间复杂度 O(n)

Ⅱ.波峰波谷

代码:

  1. /**
  2. * @param {number[]} prices
  3. * @return {number}
  4. */
  5. var maxProfit = function(prices) {
  6. if (!prices.length) return 0
  7. let profit = 0
  8. // 波峰波谷
  9. let min = max = prices[0]
  10. let i = 0
  11. while (i < prices.length - 1) {
  12. while(prices[i] >= prices[i + 1]) {
  13. i++
  14. }
  15. min = prices[i]
  16. while(prices[i] <= prices[i + 1]) {
  17. i++
  18. }
  19. max = prices[i]
  20. profit += max - min
  21. }
  22. return profit
  23. };

结果:

  • 201/201 cases passed (68 ms)
  • Your runtime beats 77.02 % of javascript submissions
  • Your memory usage beats 14.4 % of javascript submissions (35.7 MB)
  • 时间复杂度 O(n)

Ⅲ.动态规划

代码:

  1. /**
  2. * @param {number[]} prices
  3. * @return {number}
  4. */
  5. var maxProfit = function(prices) {
  6. if (prices.length < 2) return 0
  7. // 动态初始数组
  8. let dp = new Array(prices.length).fill([])
  9. // 0:用户手上不持股所能获得的最大利润,特指卖出股票以后的不持股,非指没有进行过任何交易的不持股
  10. // 1:用户手上持股所能获得的最大利润
  11. // 状态 dp[i][0] 表示:在索引为 i 的这一天,用户手上不持股所能获得的最大利润
  12. // 状态 dp[i][1] 表示:在索引为 i 的这一天,用户手上持股所能获得的最大利润
  13. // -prices[i] 就表示,在索引为 i 的这一天,执行买入操作得到的收益
  14. dp[0][0] = 0
  15. dp[0][1] = -prices[0]
  16. for(let i = 1; i < prices.length; i++) {
  17. dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i])
  18. dp[i][1] = Math.max(dp[i - 1][1], dp[i][0] - prices[i])
  19. }
  20. return dp[prices.length - 1][0]
  21. };

结果:

  • 201/201 cases passed (76 ms)
  • Your runtime beats 37.68 % of javascript submissions
  • Your memory usage beats 5.13 % of javascript submissions (36.7 MB)
  • 时间复杂度 O(n)

查阅他人解法

这里看到了动态规划的优化版,主要是降低空间复杂度。其他的思路都区别不大。

Ⅰ.动态规划优化版

代码:

  1. /**
  2. * @param {number[]} prices
  3. * @return {number}
  4. */
  5. var maxProfit = function(prices) {
  6. if (prices.length < 2) return 0
  7. // cash 表示持有现金
  8. // hold 表示持有股票
  9. let cash = new Array(prices.length).fill(null)
  10. let hold = new Array(prices.length).fill(null)
  11. cash[0] = 0
  12. hold[0] = -prices[0]
  13. for(let i = 1; i < prices.length; i++) {
  14. cash[i] = Math.max(cash[i - 1], hold[i - 1] + prices[i])
  15. hold[i] = Math.max(hold[i - 1], cash[i - 1] - prices[i])
  16. }
  17. return cash[prices.length - 1]
  18. };

结果:

  • 201/201 cases passed (68 ms)
  • Your runtime beats 77.02 % of javascript submissions
  • Your memory usage beats 9.7 % of javascript submissions (36 MB)
  • 时间复杂度 O(n)

还可以进一步进行状态压缩

代码:

  1. /**
  2. * @param {number[]} prices
  3. * @return {number}
  4. */
  5. var maxProfit = function(prices) {
  6. if (prices.length < 2) return 0
  7. // cash 表示持有现金
  8. // hold 表示持有股票
  9. // 加了两个变量来存储上一次的值
  10. let cash = tempCash = 0
  11. let hold = tempHold = -prices[0]
  12. for(let i = 1; i < prices.length; i++) {
  13. cash = Math.max(tempCash, tempHold + prices[i])
  14. hold = Math.max(tempHold, tempCash - prices[i])
  15. tempCash = cash
  16. tempHold = hold
  17. }
  18. return tempCash
  19. };

结果:

  • 201/201 cases passed (72 ms)
  • Your runtime beats 58.45 % of javascript submissions
  • Your memory usage beats 10.55 % of javascript submissions (35.8 MB)
  • 时间复杂度 O(n)

思考总结

就这道题而言,我会推荐使用一次遍历的方式,也就是贪心算法,理解起来会十分清晰。当然,动态规划的解决范围更广,基本上可以解决这类型的所有题目。增益也是一个比较常见的手段。总体而言,这两道股票题还比较简单。

125.验证回文串

题目地址

题目描述

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。

说明:本题中,我们将空字符串定义为有效的回文串。

示例:

  1. 输入: "A man, a plan, a canal: Panama"
  2. 输出: true
  3. 输入: "race a car"
  4. 输出: false

题目分析设想

这道题我有两个方向,一是改变原输入串,二是不改变原输入串。

  • 改变原输入串,可以去掉非字母和数字的字符后,反转判断或者双指针判断或者单指针
  • 不改变原输入串,直接双指针判断

主要作答方法就是反转判断,双指针法以及二分法。

编写代码验证

Ⅰ.反转判断

代码:

  1. /**
  2. * @param {string} s
  3. * @return {boolean}
  4. */
  5. var isPalindrome = function(s) {
  6. // 正则去除不满足条件的字符
  7. let str = s.toLowerCase().replace(/[^0-9a-z]/g, '')
  8. return str === str.split('').reverse().join('')
  9. };

结果:

  • 476/476 cases passed (72 ms)
  • Your runtime beats 95.33 % of javascript submissions
  • Your memory usage beats 47.7 % of javascript submissions (38.1 MB)
  • 时间复杂度: O(1)

Ⅱ.双指针法(预处理字符)

代码:

  1. /**
  2. * @param {string} s
  3. * @return {boolean}
  4. */
  5. var isPalindrome = function(s) {
  6. // 正则去除不满足条件的字符
  7. let str = s.toLowerCase().replace(/[^0-9a-z]/g, '')
  8. let len = str.length
  9. let l = 0
  10. let r = len - 1
  11. while(l < r) {
  12. if (str.charAt(l) !== str.charAt(r)) {
  13. return false
  14. }
  15. l++
  16. r--
  17. }
  18. return true
  19. };

结果:

  • 476/476 cases passed (76 ms)
  • Your runtime beats 89.25 % of javascript submissions
  • Your memory usage beats 70.96 % of javascript submissions (37.4 MB)
  • 时间复杂度: O(n)

Ⅲ.单指针法(预处理字符)

代码:

  1. /**
  2. * @param {string} s
  3. * @return {boolean}
  4. */
  5. var isPalindrome = function(s) {
  6. // 正则去除不满足条件的字符
  7. let str = s.toLowerCase().replace(/[^0-9a-z]/g, '')
  8. let len = str.length
  9. // 最多需要判断的次数
  10. let max = len >>> 1
  11. let i = 0
  12. while(i < max) {
  13. if (len % 2) { // 奇数
  14. if (str.charAt(max - i - 1) !== str.charAt(max + i + 1)) {
  15. return false
  16. }
  17. } else { // 偶数
  18. if (str.charAt(max - i - 1) !== str.charAt(max + i)) {
  19. return false
  20. }
  21. }
  22. i++
  23. }
  24. return true
  25. };

结果:

  • 476/476 cases passed (72 ms)
  • Your runtime beats 95.33 % of javascript submissions
  • Your memory usage beats 56.02 % of javascript submissions (38 MB)
  • 时间复杂度: O(n)

Ⅳ.双指针法

代码:

  1. /**
  2. * @param {string} s
  3. * @return {boolean}
  4. */
  5. var isPalindrome = function(s) {
  6. let len = s.length
  7. let l = 0
  8. let r = len - 1
  9. while (l < r) {
  10. if (!/[0-9a-zA-Z]/.test(s.charAt(l))) {
  11. l++
  12. } else if (!/[0-9a-zA-Z]/.test(s.charAt(r))) {
  13. r--
  14. } else {
  15. if(s.charAt(l).toLowerCase() !== s.charAt(r).toLowerCase()) {
  16. return false
  17. }
  18. l++
  19. r--
  20. }
  21. }
  22. return true
  23. };

结果:

  • 476/476 cases passed (76 ms)
  • Your runtime beats 89.25 % of javascript submissions
  • Your memory usage beats 13.06 % of javascript submissions (42 MB)
  • 时间复杂度: O(n)

查阅他人解法

这里看到一种利用栈的思路,先进后出,推一半入栈然后进行比较。

Ⅰ.利用栈

代码:

  1. /**
  2. * @param {string} s
  3. * @return {boolean}
  4. */
  5. var isPalindrome = function(s) {
  6. // 正则去除不满足条件的字符
  7. let str = s.toLowerCase().replace(/[^0-9a-z]/g, '')
  8. let mid = str.length >>> 1
  9. let stack = str.substr(0, mid).split('')
  10. // 起始位置如果字符个数为奇数则跳过中间位
  11. for(let i = str.length % 2 ? mid + 1 : mid; i < str.length; i++) {
  12. const last = stack.pop()
  13. if (last !== str.charAt(i)) {
  14. return false
  15. }
  16. }
  17. return true
  18. };

结果:

  • 476/476 cases passed (84 ms)
  • Your runtime beats 65.67 % of javascript submissions
  • Your memory usage beats 71.81 % of javascript submissions (37.4 MB)
  • 时间复杂度: O(n)

思考总结

总体而言,判断回文字符或者相关的题目,我更推荐采用双指针法,思路非常清晰。这里头尾递归比较也可以作答,就不在这里列举了。

136.只出现一次的数字

题目地址

题目描述

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

  1. 输入: [2,2,1]
  2. 输出: 1
  3. 输入: [4,1,2,1,2]
  4. 输出: 4

题目分析设想

这题说明了线性时间复杂度,所以最多一次遍历。很容易想到用 Hash 表或者其他方式对各数字出现次数做个统计来求解,但是需要考虑如何不适用额外空间。这里很明显就指向了离散数学中的异或运算。

  • Hash 法,需要额外 O(n) 的空间
  • 异或运算

编写代码验证

Ⅰ.Hash 法

代码:

  1. /**
  2. * @param {number[]} nums
  3. * @return {number}
  4. */
  5. var singleNumber = function(nums) {
  6. let hash = {}
  7. for(let i = 0; i < nums.length; i++) {
  8. if (hash[nums[i]]) {
  9. hash[nums[i]] = false
  10. } else if (hash[nums[i]] === undefined) {
  11. hash[nums[i]] = true
  12. }
  13. }
  14. for(let i in hash) {
  15. if(hash[i]) {
  16. return parseInt(i)
  17. }
  18. }
  19. };

结果:

  • 16/16 cases passed (72 ms)
  • Your runtime beats 68.39 % of javascript submissions
  • Your memory usage beats 5.49 % of javascript submissions (38.6 MB)
  • 时间复杂度: O(n)

Ⅱ.异或运算

简单列一下几条运算规则,利用这规则,发现很容易作答这道题。

  • 交换律: abc = acb
  • 任何数和 0 异或为本身:a^0 = a
  • 相同的数异或为 0:a^a = 0

代码:

  1. /**
  2. * @param {number[]} nums
  3. * @return {number}
  4. */
  5. var singleNumber = function(nums) {
  6. let n = 0
  7. for(let i = 0; i < nums.length; i++) {
  8. n ^= nums[i]
  9. }
  10. return n
  11. };

结果:

  • 16/16 cases passed (60 ms)
  • Your runtime beats 95.77 % of javascript submissions
  • Your memory usage beats 74.07 % of javascript submissions (35.3 MB)
  • 时间复杂度: O(n)

查阅他人解法

没有发现其他不同方向的解法。

思考总结

这里的话第一想法大多都是借助哈希表来实现,但是由于有补充说明,所以更推荐使用异或算法。纯粹是数学公式的应用场景之一,没有什么太多好总结的地方。

141.环形链表

题目地址

题目描述

给定一个链表,判断链表中是否有环。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

  1. 示例 1
  2. 输入:head = [3,2,0,-4], pos = 1
  3. 输出:true
  4. 解释:链表中有一个环,其尾部连接到第二个节点。
  5. 示例 2
  6. 输入:head = [1,2], pos = 0
  7. 输出:true
  8. 解释:链表中有一个环,其尾部连接到第一个节点。
  9. 示例 3
  10. 输入:head = [1], pos = -1
  11. 输出:false
  12. 解释:链表中没有环。

进阶:

你能用 O(1)(即,常量)内存解决此问题吗?

题目分析设想

这道题的本质其实就是对象的比较,而对应的相等应当是引用同样的内存,可以想象成数组中找到同样的元素。所以第一个想法就是哈希表,当然也可以使用快慢指针来做处理。由于哈希表需要额外的内存,所以可以做优化,比如直接改变原对象,做特殊标识或者其他方式。

  • 哈希表,直接利用哈希表存储,也可以使用 Map/Set 等等,直接判断对象相等即可
  • 特殊标识,哈希表需要额外空间,可以直接在原对象上打标识,或者置为空等等特殊标识均可
  • 双指针法,一快一慢,如果是环,那必然会存在相等的时候,如果不是环,那快的先走完

编写代码验证

Ⅰ.哈希表

代码:

  1. /**
  2. * @param {ListNode} head
  3. * @return {boolean}
  4. */
  5. var hasCycle = function(head) {
  6. let hashArr = []
  7. // val 可能为 0 ,所以不能直接 !head
  8. while (head !== null) {
  9. if (hashArr.includes(head)) {
  10. return true
  11. } else {
  12. hashArr.push(head)
  13. head = head.next
  14. }
  15. }
  16. return false
  17. };

结果:

  • 17/17 cases passed (116 ms)
  • Your runtime beats 12.03 % of javascript submissions
  • Your memory usage beats 5.05 % of javascript submissions (38.5 MB)
  • 时间复杂度: O(n)

Ⅱ.特殊标识法

代码:

  1. /**
  2. * @param {ListNode} head
  3. * @return {boolean}
  4. */
  5. var hasCycle = function(head) {
  6. while (head && head.next) {
  7. if (head.FLAG) {
  8. return true
  9. } else {
  10. head.FLAG = true
  11. head = head.next
  12. }
  13. }
  14. return false
  15. };

结果:

  • 17/17 cases passed (76 ms)
  • Your runtime beats 78.6 % of javascript submissions
  • Your memory usage beats 16.32 % of javascript submissions (37.5 MB)
  • 时间复杂度: O(n)

Ⅲ.双指针法

代码:

  1. /**
  2. * @param {ListNode} head
  3. * @return {boolean}
  4. */
  5. var hasCycle = function(head) {
  6. if (head && head.next) {
  7. let slow = head
  8. let fast = head.next
  9. while(slow !== fast) {
  10. if (fast && fast.next) {
  11. // 快指针需要比慢指针移动速度快,才能追上,所以是 .next.next
  12. fast = fast.next.next
  13. slow = slow.next
  14. } else {
  15. // 快指针走到头了,所以必然不是环
  16. return false
  17. }
  18. }
  19. return true
  20. } else {
  21. return false
  22. }
  23. };

结果:

  • 17/17 cases passed (76 ms)
  • Your runtime beats 78.6 % of javascript submissions
  • Your memory usage beats 56.97 % of javascript submissions (36.6 MB)
  • 时间复杂度: O(n)

查阅他人解法

这里发现一个有意思的思路,通过链路导致。如果是环,那么倒置后的尾节点等于倒置前的头节点。如果不是环,那么就是正常的倒置不相等。

Ⅰ.倒置法

代码:

  1. /**
  2. * @param {ListNode} head
  3. * @return {boolean}
  4. */
  5. var hasCycle = function(head) {
  6. if (head === null || head.next === null) return false
  7. if (head === head.next) return true
  8. let p = head.next
  9. let q = p.next
  10. let x = head
  11. head.next = null
  12. // 相当于每遍历一个链表,就把后面的指向前面一项,这样当循环的时候,会反方向走出环形
  13. while(q !== null) {
  14. p.next = x
  15. x = p
  16. p = q
  17. q = q.next
  18. }
  19. return p === head
  20. };

结果:

  • 17/17 cases passed (72 ms)
  • Your runtime beats 90.05 % of javascript submissions
  • Your memory usage beats 35.91 % of javascript submissions (36.8 MB)
  • 时间复杂度: O(n)

思考总结

一般去重或者找到重复项用哈希的方式都能解决,但是在这题里,题目期望空间复杂度是 O(1),要么是改变原数据本身,要么是使用双指针法。这里我比较推荐双指针法,当然倒置法也比较巧妙。

(完)


本文为原创文章,可能会更新知识点及修正错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验

如果能给您带去些许帮助,欢迎 ⭐️star 或 ✏️ fork

(转载请注明出处:https://chenjiahao.xyz)

【Leetcode 做题学算法周刊】第七期的更多相关文章

  1. 【Leetcode 做题学算法周刊】第四期

    首发于微信公众号<前端成长记>,写于 2019.11.21 背景 本文记录刷题过程中的整个思考过程,以供参考.主要内容涵盖: 题目分析设想 编写代码验证 查阅他人解法 思考总结 目录 67 ...

  2. 【Leetcode 做题学算法周刊】第一期

    首发于微信公众号<前端成长记>,写于 2019.10.28 背景 本文记录刷题过程中的整个思考过程,以供参考.主要内容涵盖: 题目分析设想 编写代码验证 查阅他人解法 思考总结 目录 1. ...

  3. 【Leetcode 做题学算法周刊】第二期

    首发于微信公众号<前端成长记>,写于 2019.11.05 背景 本文记录刷题过程中的整个思考过程,以供参考.主要内容涵盖: 题目分析设想 编写代码验证 查阅他人解法 思考总结 目录 20 ...

  4. 【Leetcode 做题学算法周刊】第六期

    首发于微信公众号<前端成长记>,写于 2019.12.15 背景 本文记录刷题过程中的整个思考过程,以供参考.主要内容涵盖: 题目分析设想 编写代码验证 查阅他人解法 思考总结 目录 11 ...

  5. 【Leetcode 做题学算法周刊】第八期

    首发于微信公众号<前端成长记>,写于 2020.05.07 背景 本文记录刷题过程中的整个思考过程,以供参考.主要内容涵盖: 题目分析设想 编写代码验证 查阅他人解法 思考总结 目录 15 ...

  6. 【Leetcode 做题学算法周刊】第三期

    首发于微信公众号<前端成长记>,写于 2019.11.13 背景 本文记录刷题过程中的整个思考过程,以供参考.主要内容涵盖: 题目分析设想 编写代码验证 查阅他人解法 思考总结 目录 35 ...

  7. 【Leetcode 做题学算法周刊】第五期

    首发于微信公众号<前端成长记>,写于 2019.12.06 背景 本文记录刷题过程中的整个思考过程,以供参考.主要内容涵盖: 题目分析设想 编写代码验证 查阅他人解法 思考总结 目录 10 ...

  8. C#LeetCode刷题-贪心算法

    贪心算法篇 # 题名 刷题 通过率 难度 44 通配符匹配   17.8% 困难 45 跳跃游戏 II   25.5% 困难 55 跳跃游戏   30.6% 中等 122 买卖股票的最佳时机 II C ...

  9. C#LeetCode刷题-分治算法

    分治算法篇 # 题名 刷题 通过率 难度 4 两个排序数组的中位数 C#LeetCode刷题之#4-两个排序数组的中位数(Median of Two Sorted Arrays)-该题未达最优解 30 ...

随机推荐

  1. kindeditor编辑器微软雅黑样式font-family值变成&quot;

    http://www.100cm.cn/article-126-764.html kindeditor编辑器中选中文字, 修改字体(字体名称中带有空格, 例如"Microsoft YaHei ...

  2. js最简单的对数字的排序

    文章地址 https://www.cnblogs.com/sandraryan/ JS自己有sort可以用来排序,可以排string会转为ASCII比较,但是,ASCII对数字的排序不合理   < ...

  3. 获取exe和dll里面的资源

    有时候需要仿照另一个程序实现一些对话框,比较笨的办法是打开那个程序,照着样子自己在VC里面画啊画.这样的效率实在有点低. 现在有很多工具可以从exe和dll里面取出图片.图片.字符串.对话框等资源.比 ...

  4. C# 传入 params object 长度

    刚刚 LiesAuer 大神问了一个问题,如果在 params object 传入 object 数组,那么拿到的值是的长度是多少 我做了测试在传入不同的值可能拿到不同的长度 先来说总结 传入一个数组 ...

  5. java 文件拷贝

    需求:源和目标! 那么我们需要源文件和目标文件! 构建管道的时候就需要两个:输出流和输入流管道! Eg: package july7file; //java7开始的自动关闭资源 import java ...

  6. 中和IOS七层架构和TCP/IP四层架构的五层架构

    五层架构分别为应用层.运输层.网络层.数据链路层.物理层. IOS架构把应用层又细分为应用层.表示层.会话层 TCP/IP把网络层改名网际层,数据链路层和物理层结合成网络接口层 其实只要学习五层协议, ...

  7. H3C 示例:根据子网数划分子网

  8. tf.squeeze()

    转载自:https://www.cnblogs.com/mdumpling/p/8053376.html 原型 tf.squeeze(input, squeeze_dims=None, name=No ...

  9. linux 短延时

    当一个设备驱动需要处理它的硬件的反应时间, 涉及到的延时常常是最多几个毫秒. 在这 个情况下, 依靠时钟嘀哒显然不对路. The kernel functions ndelay, udelay, an ...

  10. Linux 内核kobject 层次, kset, 和子系统

    kobject 结构常常用来连接对象到一个层级的结构中, 匹配正被建模的子系统的结构. 有 2 个分开的机制对于这个连接: parent 指针和 ksets. 在结构 kobject 中的 paren ...