leetcode 題解 Part1

image

前言

2018年年末, 呆了不到一年的公司就這樣解散了。遙想當初3月份剛剛加入公司的時候, 公司剛剛拿到數億的B輪融資。6月份又是數億元的B+輪融資。又是換寫字樓又是在外灘大屏幕上打廣告好不熱鬧,結果12月公司就關門了。真是世事無常啊。javascript

1. 兩數之和

原題

給定一個整數數組 nums 和一個目標值 target,請你在該數組中找出和爲目標值的那 兩個 整數,並返回他們的數組下標。你能夠假設每種輸入只會對應一個答案。可是,你不能重複利用這個數組中一樣的元素。html

給定 nums = [2, 7, 11, 15], target = 9。由於 nums[0] + nums[1] = 2 + 7 = 9。因此返回 [0, 1]java

思路

最簡單的方法是經過兩層for循環進行遍歷, 使用暴力的查找兩個子元素。可是這種方法的時間複雜度爲O(n^2)。在大數據量的測試用例的狀況下執行時間超時。那麼咱們有什麼辦法能夠將時間複雜度降下來嗎?這時咱們能夠用到HashMap。經過HashMap咱們能夠將時間複雜度降爲O(2n)。若是是有序數組的狀況下, 時間複雜度能夠是O(n), 詳見下題算法

代碼

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function (nums, target) {
  let hashMap = new Map()
  // 初始化hashMap
  for (let i = 0; i < nums.length; i++) {
    if (!hashMap.has(nums[i])) {
      hashMap.set(nums[i], i)
    }
  }
  for (let i = 0; i < nums.length; i++) {
    let diff = target - nums[i]
    if (hashMap.has(diff) && hashMap.get(diff) !== i) {
      return [i, hashMap.get(diff)]
    }
  }
};

167. 兩數之和 II - 輸入有序數組

本題是 1. 兩數之和的引申問題。若是將無限數組變爲有序數組, 有什麼更好的辦法解決問題嗎?

思路

這一題咱們可使用對撞指針或者說雙指針的思路解決它, 並能夠將時間複雜度控制在O(n)。具體思路見下圖。數組

image

代碼

/**
 * @param {number[]} numbers
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(numbers, target) {
  const len = numbers.length
  let start = 0
  let end = len - 1
  while (start < end) {
    if (numbers[start] + numbers[end] === target) {
      return [start + 1, end + 1]
    } else if (numbers[start] + numbers[end] < target) {
      start += 1
    } else {
      end -= 1
    }
  }
  return []
};

3. 無重複字符的最長子串

原題

給定一個字符串,請你找出其中不含有重複字符的 最長子串 的長度。數據結構

輸入: "abcabcbb"。輸出: 3 。解釋: 由於無重複字符的最長子串是 "abc",因此其長度爲 3。測試

輸入: "bbbbb"。輸出: 1。解釋: 由於無重複字符的最長子串是 "b",因此其長度爲 1。大數據

思路

比較簡單的思路是依舊使用雙層for循環。代碼以下。時間複雜度爲O(n^3)。indexOf的複雜度爲O(n)。更好的解決辦法是使用雙指針配合HashMap。雖然使用了額外的空間。可是能夠將時間複雜度爲O(n)。具體思路見下圖。spa

// 暴力循環
if (s.length === 0) return 0
if (s.length === 1) return 1
let maxLen = 0
for1: for (let i = 0; i < s.length; i++) {
  let str = s[i]
  for2: for (let j = i + 1; j < s.length; j++) {
    maxLen = Math.max(maxLen, str.length)
    if (str.indexOf(s[j]) < 0) {
      str += s[j]
      maxLen = Math.max(maxLen, str.length)
    } else {
      continue for1
    }
  }
}
return maxLen

image

代碼

/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function (s) {
  // 使用雙指針解決 + hash
  const len = s.length
  let hashMap = new Map()
  let start = 0
  let end = 0
  let maxLen = 0

  while (end < len) {
    if (!hashMap.has(s[end])) {
      hashMap.set(s[end], 1)
      maxLen = Math.max(maxLen, [...hashMap.keys()].length)
      end += 1
    } else {
      hashMap.delete(s[start])
      start += 1
    }
  }

  return maxLen
};

6. Z 字形變換

原題

將一個給定字符串根據給定的行數,以從上往下、從左到右進行 Z 字形排列。設計

好比輸入字符串爲 "LEETCODEISHIRING" 行數爲 3 時,排列以下:

L   C   I   R
E T O E S I I G
E   D   H   N

以後,你的輸出須要從左往右逐行讀取,產生出一個新的字符串,好比:"LCIRETOESIIGEDHN"。

思路

這道題目個人解決思路是將字符串轉化爲二維數組。輸入時按照N字形輸入。最後逐行讀取二維數組,並將二維數組中空的填充去除。返回最後的結果。經過推導可知。當行數爲 2 時, 斜線上的字母數量爲0。當行數爲 3 時, 斜線上的字母數量爲1。當行數爲 4 時, 斜線上的字母數量爲2。

// 規律以下
L A C I R
E T O E S

L   C   I   R
E T O E S I I G
E   D   H   N

L     D     R
E   O E   I I
E C   I H   N
T     S     G

代碼

/**
 * @param {string} s
 * @param {number} numRows
 * @return {string}
 */
var convert = function (s, numRows) {
  if (numRows === 1) return s
  let result = []
  let matrix = []
  let rowCounter = 0
  let prevRowCounter = 0
  let colCounter = 0
  let prevColCounter = 0
  const other = numRows - 2
  for (let i = 0; i < numRows; i++) {
    matrix.push([])
  }
  // 填充二維數組
  for (let i = 0; i < s.length; i++) {
    matrix[rowCounter][colCounter] = s[i]
    if (prevRowCounter <= rowCounter) {
      prevRowCounter = rowCounter
      if (rowCounter >= numRows - 1) {
        rowCounter -= 1
        colCounter += 1
      } else {
        rowCounter += 1
      }
    } else {
      prevRowCounter = rowCounter
      if (rowCounter <= 0) {
        rowCounter += 1
      } else {
        rowCounter -= 1
        colCounter += 1
      }
    }
  }
  for (let i = 0; i < matrix.length; i++) {
    for (let j = 0; j < matrix[i].length; j++) {
      if (matrix[i][j] !== undefined) {
        result.push(matrix[i][j])
      }
    }
  }
  return result.join('')
};

11. 盛最多水的容器

原題

image

思路

本題的思路依然是使用對撞指針。咱們在這裏首先須要明確一個概念, 水的面積和高度和寬度有關。高度的取決於兩條邊框中最小的一邊。具體思路見下圖。

image

代碼

// https://leetcode-cn.com/explore/orignial/card/all-about-array/232/two-pointers/969/

/**
 * @param {number[]} height
 * @return {number}
 */
var maxArea = function (height) {
  if (height.length === 1 || height.length === 0) {
    return 0
  }
  
  const len = height.length
  let start = 0
  let end = len - 1
  let max = 0
  while (start < end) {
    max = Math.max(max, (Math.min(height[start], height[end]) * (end - start)))
    if (height[start] <= height[end]) {
      start += 1
    } else {
      end -= 1
    }
  }
  return max
};

15. 三數之和

原題

給定一個包含 n 個整數的數組 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?找出全部知足條件且不重複的三元組。

注意:答案中不能夠包含重複的三元組。

例如, 給定數組 nums = [-1, 0, 1, 2, -1, -4], 知足要求的三元組集合爲:[ [-1, 0, 1], [-1, -1, 2] ]

思路

最簡單最暴力的方法是使用三層for循環進行查找。可是時間複雜度爲O(n^3)。咱們的目標是將時間複雜度降爲O(n^2)。咱們只須要將原數組進行排序後, 結合167. 兩數之和 II - 輸入有序數組這道題目的思路(對撞指針)就能夠將時間複雜度控制在O(n^2)。

代碼

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function(nums) {
  let result = []
  let hashMap = new Map()
  // 容錯處理
  if (nums.length < 3) return []  
  // 容錯處理
  if (nums.length === 3) {
      if (nums[0] + nums[1] + nums[2] === 0) return [nums]
      return []
  }
  nums = nums.sort((a, b) => a - b)
  for (let i = 0; i < nums.length - 3; i++) {
      let start = i + 1
      let end = nums.length - 1
      let target = 0 - nums[i]
      while (start < end) {
          if (nums[start] + nums[end] === target) {
              let arr = [nums[i], nums[start], nums[end]]
              let key = arr.join('')
              if (!hashMap.has(key)) {
                  hashMap.set(key, true)
                  result.push(arr)
              }
              end -= 1
              start += 1
          } else if (nums[start] + nums[end] > target) {
              end -= 1
          } else {
              start += 1
          }
      }
  }
  return result
};

16. 最接近的三數之和

原題

給定一個包括 n 個整數的數組 nums 和 一個目標值 target。找出 nums 中的三個整數,使得它們的和與 target 最接近。返回這三個數的和。假定每組輸入只存在惟一答案。

例如,給定數組 nums = [-1,2,1,-4], 和 target = 1。 與 target 最接近的三個數的和爲 2. (-1 + 2 + 1 = 2)。

思路

本題的思路與15. 三數之和基本相似。區別在於咱們須要在循環中記錄的是最小的差值(Math.abs(target - sum))。

代碼

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var threeSumClosest = function(nums, target) {
  let diff = Infinity
  let sums = undefined
  if (nums.length <= 3) return nums.reduce((a, b) => a + b, 0)
  nums = nums.sort((a, b) => a - b)
  for (let i = 0; i < nums.length; i++) {
    let start = i + 1
    let end = nums.length - 1
    while (start < end) {
      if (Math.abs(target - (nums[i] + nums[start] + nums[end])) < diff) {
         // 最接近的和
         sums = nums[i] + nums[start] + nums[end]
         // 當前最小的差值
         diff = Math.abs(target - (nums[i] + nums[start] + nums[end]))
      }
      if (nums[i] + nums[start] + nums[end] > target) {
          end -= 1
      } else if (nums[i] + nums[start] + nums[end] < target) {
          start += 1
      } else {
          return target
      }
    }
  }
  return sums
};

20. 有效的括號

原題

給定一個只包括 '(',')','{','}','[',']' 的字符串,判斷字符串是否有效。

  1. 輸入: "()[]{}", 輸出: true
  2. 輸入: "(]", 輸出: false

思路

本題的解決辦法須要使用這個數據結構(javascript中咱們使用數組進行模擬棧), 具體思路見下圖

image

代碼

/**
 * @param {string} s
 * @return {boolean}
 */
var isValid = function (s) {
  if (s.length === 0) return true
  if (s.length === 1) return false
  let queue = []
  for (let i = 0; i < s.length; i++) {
    if (!queue.length) {
      queue.push(s[i])
    } else {
      let tail = queue[queue.length - 1]
      if (s[i] === '}' && tail === '{') {
        queue.pop()
        continue
      } else if (s[i] === ']' && tail === '[') {
        queue.pop()
        continue
      } else if (s[i] === ')' && tail === '(') {
        queue.pop()
        continue
      } else {
        queue.push(s[i])
      }
    }
  }
  if (!queue.length) {
    return true
  } else {
    return false
  }
};

46. 全排列

原題

給定一個沒有重複數字的序列,返回其全部可能的全排列。輸入: [1,2,3]。 輸出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]

思路

此題擁有兩種思路字典法與遞歸法。遞歸法較爲容易理解。我採用的也是遞歸法,代碼以下。

代碼

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var permute = function(nums) {
    
  let result = []
  
  if (nums.length <= 1) result = [nums]

  function exchange (title, arr) {
    for (let i = 0; i < arr.length; i++) {
      let cloneArr = [...arr]
      let newFirst = [...title, ...cloneArr.splice(i, 1)]
      if (cloneArr && cloneArr.length) {
        exchange(newFirst, cloneArr)
      } else {
        result.push(newFirst)
      }
    }
  }

  for (let i = 0; i < nums.length; i++) {
    let cloneArr = [...nums]
    let first = cloneArr.splice(i, 1)
    exchange(first, cloneArr)
  }
  
  return result
};

53. 最大子序和

原題

給定一個整數數組 nums ,找到一個具備最大和的連續子數組(子數組最少包含一個元素),返回其最大和。

示例:

輸入: [-2,1,-3,4,-1,2,1,-5,4],

輸出: 6

解釋: 連續子數組 [4,-1,2,1] 的和最大,爲 6。

思路

若是要在O(n)的時間複雜度的狀況下解開本題, 須要使用動態規劃的思想。可是本人能力有限, 動態規劃不是很懂。這裏只能說一個大概的思路,敬請諒解。

咱們從數組中的第一位開始循環求和

若是sum(和) < 0。接下來的一位next不管大於0仍是小於0, 都應當取代當前的負數sum。由於若是next < 0, sum + next 將會更小, 因此應當捨棄以前的sum。若是next大於0, sum更應當從next開始從新計算。

若是sum(和) > 0。若是接下來的一位next與當前的sum的和大於MAX, next與當前sum的和將成爲新的MAX。不然繼續向下迭代。

代碼

/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function (nums) {
  if (nums.length <= 1) return nums[0]
  let sum = nums[0]
  let MAX = sum
  for (let i = 1; i < nums.length; i++) {
    if (sum >= 0) {
      if (sum + nums[i] >= MAX) {
        MAX = sum + nums[i]
      }
      sum = sum + nums[i]
    } else {
      if (nums[i] >= MAX) {
        MAX = nums[i]
      }
      sum = nums[i]
    }
  }
  return MAX
};

62. 不一樣路徑

原題

image

思路

所謂的不一樣路徑, 其實就是求排列組合。好比 3 * 7 的網格中。機器人從起點到終點所須要的步驟能夠抽象爲一個數組。[bottom, bottom, right, right, right, right, right, right],全部的路徑,便是這個數組的全部排列組合。

另一種思路, 第一行全部網格的可能路徑數均爲1。第一列全部網格的數可能的路徑均爲1。經過推導能夠獲得以下的表格。終點可能的路徑爲28。

image

代碼

/**
 * @param {number} m
 * @param {number} n
 * @return {number}
 */
var uniquePaths = function(m, n) {
  // 思路1
  let matrix = []
  for (let i = 0; i < n; i++) {
    let arr = new Array(m).fill(1)
    matrix.push(arr)
  }
  for (let i = 1; i < n; i++) {
    for (let j = 1; j < m; j++) {
      matrix[i][j] = matrix[i - 1][j] + matrix[i][j - 1]
    }
  }
  return matrix[n-1][m-1]

  // 思路二, 可行, 可是會超出時間限制
  let arr = []
  let hashMap = new Map()
  for (let i = 0; i < m - 1; i++) {
    arr.push('m')
  }
  for (let i = 0; i < n - 1; i++) {
    arr.push('n')
  }

  if (arr.length <= 1) return 1

  function exchange (title, arr) {
    for (let i = 0; i < arr.length; i++) {
      let cloneArr = [...arr]
      let newFirst = [...title, ...cloneArr.splice(i, 1)]
      if (cloneArr && cloneArr.length) {
        exchange(newFirst, cloneArr)
      } else {
        let key = newFirst.join('')
        if (!hashMap.has(key)) {
          hashMap.set(key, true)
        }
      }
    }
  }

  for (let i = 0; i < arr.length; i++) {
    let cloneArr = [...arr]
    let first = cloneArr.splice(i, 1)
    exchange(first, cloneArr)
  }
  return hashMap.size
};

63. 不一樣路徑 II

原題

image

思路

思路1使用遞歸法, 比較簡單不在贅述

思路2與62. 不一樣路徑相似。但不一樣的是出現了障礙物這個變量。因此咱們在初始化表格的第一行與第一列的時候須要格外的注意。假設一個 3 * 7的網格。具體思路見下圖。

假設地圖以下

image

初始化第一行(若是第一行中的前一個爲障礙物話, 那麼後續可能路徑的均爲0),與第一列後(若是第一列中的前一個爲障礙物話, 那麼後續可能路徑的均爲0), 障礙物由於自己不能行走因此可能路徑數直接設置爲0。

image

接下來的方法同62. 不一樣路徑同樣。

代碼

/**
 * @param {number[][]} obstacleGrid
 * @return {number}
 */
var uniquePathsWithObstacles = function (obstacleGrid) {
  // 思路1, 使用動態規劃和遞歸
  // 沒有經過大數據量的測試用例
  let counter = 0
  const targetX = obstacleGrid[0].length - 1
  const targetY = obstacleGrid.length - 1
  /**
   * @param {number} x 當前矩陣的x座標
   * @param {number} y 當前矩陣的y座標
   * @param {string} direction 方向 right, bottom
   */
  const pathfinding = (x, y, direction) => {
    switch (direction) {
      case 'right':
        x = x + 1
        break
      case 'bottom':
        y = y + 1
        break
      default:
        break
    }
    // 遇到障礙物或者越界的狀況下, 思路一條
    if (y >= targetY + 1) {
      return
    }
    if (x >= targetX + 1) {
      return
    }
    if (obstacleGrid[y][x] === 1) {
      return
    }
    if (x === targetX && y === targetY) {
      counter += 1
    } else if (x !== targetX && y === targetY) {
      // 只能向右走
      pathfinding(x, y, 'right')
    } else if (x === targetX && y !== targetY) {
      // 只能向下走
      pathfinding(x, y, 'bottom')
    } else {
      // 可能向右走
      // 可能向下走
      pathfinding(x, y, 'right')
      pathfinding(x, y, 'bottom')
    }
  }
  pathfinding(0, 0)
  return counter

  // 思路二
  // 帶有條件的初始化第一行與第一列
  // 初始化x方向
  // 初始化y方向
  const xLen = obstacleGrid[0].length
  const yLen = obstacleGrid.length
  for (let i = 0; i < xLen; i++) {
    if (i - 1 >= 0) {
      if (obstacleGrid[0][i-1] === 0) {
        obstacleGrid[0][i] = 0
      } else if (obstacleGrid[0][i-1] === 1 && obstacleGrid[0][i] !== 1) {
        obstacleGrid[0][i] = 1
      } else if (obstacleGrid[0][i] == 1) {
        obstacleGrid[0][i] = 0
      }
    } else {
      if (obstacleGrid[0][i] === 0) {
        obstacleGrid[0][i] = 1
      } else {
        obstacleGrid[0][i] = 0
      }
    }
  }
  for (let i = 0; i < yLen; i++) {
    if (i - 1 >= 0) {
      if (obstacleGrid[i-1][0] === 0) {
        obstacleGrid[i][0] = 0
      } else if (obstacleGrid[i-1][0] !== 0 && obstacleGrid[i][0] !== 1) {
        obstacleGrid[i][0] = 1
      } else if (obstacleGrid[i-1][0] !== 0 && obstacleGrid[i][0] === 1) {
        obstacleGrid[i][0] = 0
      }
    }
  }
  for (let i = 1; i < yLen; i++) {
    for (let j = 1; j < xLen; j++) {
      if (obstacleGrid[i][j] === 1) {
        obstacleGrid[i][j] = 0
      } else {
        obstacleGrid[i][j] = obstacleGrid[i - 1][j] + obstacleGrid[i][j - 1]
      }
    }
  }

  return obstacleGrid[yLen - 1][xLen - 1]
};

121. 買賣股票的最佳時機

原題

給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。若是你最多隻容許完成一筆交易(即買入和賣出一支股票),設計一個算法來計算你所能獲取的最大利潤。

輸入: [7,1,5,3,6,4]

輸出: 5

解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 5 天(股票價格 = 6)的時候賣出,最大利潤 = 6-1 = 5 。注意利潤不能是 7-1 = 6, 由於賣出價格須要大於買入價格。

思路

最大利潤即(最高賣出價格 - 最小買入價格)。咱們只須要找到最小買入價格後, 計算每一天的利潤,取最大值便可

代碼

/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function (prices) {

  if (prices.length === 0) return 0  
  
  // 利潤
  let result = 0
  
  let min = prices[0]
  // 找到最小的谷以後的最大的峯
  for (let i = 0; i < prices.length; i++) {
      if (prices[i] < min) {
          min = prices[i]   
      }
      result = Math.max(prices[i] - min, result)
  } 
  
  return result
};
相關文章
相關標籤/搜索