由於之前對算法題接觸不多,因此暫時只做easy和medium難度的題.
看完了《算法(第四版)》后重新開始刷LeetCode了,這次決定按topic來刷題,有一個大致的方向.有些題不止包含在一個topic中,就以我自己做的先后順序為准了.
Array
---11.Container With Most Water
給定許多條與y軸平行的直線,求其中兩條直線與x軸圍成的容器的最大容量.
這道題用到了雙指針的思想.我們在數軸的兩端分別放置一個left指針和right指針,因為容器容量=較短邊*兩邊位置之差,所以如果移動較大的那個指針,那么容量必定在減小.因此我們不斷往中間移動較小的指針才有可能使容量變大,直到兩指針相遇為止.
對於算法合理性的邏輯推理:我們假設在best_left和best_right位置取到最大容量,那么left指針到達best_left位置或right指針到達best_right位置至少有一種會發生.不妨令left指針到達best_left位置,此時right指針的位置有三種可能:
- 位於best_right位置左側.這說明best_right位置已經被計算過,成立.
- 位於best_right位置,同上.
- 位於best_right位置右側.因為left指針移動的條件是right指針所在邊大於left指針所在邊,如果符合此條件,且right指針在best_right右側,那么當前容量一定大於假設中的最大容量,與假設矛盾.所以left指針必定會一路移動至best_right位置.
class Solution(object): def maxArea(self, height): """ :type height: List[int] :rtype: int """ left = 0 right = len(height) - 1 most = 0 while left != right: h = min(height[left], height[right]) most = max(most, h * (right - left)) if height[left] < height[right]: left += 1 else: right -=1 return most Container With Most Water
---16.3Sum Closest
給定一個數組和一個目標值,找到數組中的三個數,使得這三個數之和與目標值之間的差距最小,返回它們的和.
這題的思路與15題類似,也是利用雙指針,只不過判定條件從三個數之和是否為零改成了三個數之和是否比目前已有的closest值更接近目標.
class Solution: def threeSumClosest(self, nums, target): """ :type nums: List[int] :type target: int :rtype: int """ closest = nums[0] + nums[1] + nums[2] nums.sort() length = len(nums) for i in range(length): l = i + 1 r = length - 1 while l < r: tmp = nums[i] + nums[l] + nums[r] if tmp == target: closest = target break elif tmp < target: if target - tmp < abs(target - closest): closest = tmp l += 1 elif tmp > target: if tmp - target < abs(target - closest): closest = tmp r -= 1 return closest 3Sum Closest
---18.4Sum
找出list中所有相加等於target的4個數的list.
一開始我的思路是令new_target=target-num1,然后轉換為一個3Sum問題,但這種做法的時間復雜度太高了.查看Solution后發現這道題要使用hash的思想,在python中對應的實現就是使用先dict存儲list中的兩數之和和它們在list中的位置,然后對於這個dict中的value,尋找一個key=target-value,然后將他們對應的數字存入list即可.需要注意的是python中的list,set,dict是不可哈希的,int,float,str,tuple是可哈希的.
class Solution: def fourSum(self, nums, target): """ :type nums: List[int] :type target: int :rtype: List[List[int]] """ d = dict() for i in range(len(nums)): for j in range(i+1,len(nums)): sum2 = nums[i]+nums[j] if sum2 in d: d[sum2].append((i,j)) else: d[sum2] = [(i,j)] result = [] for key in d: value = target - key if value in d: list1 = d[key] list2 = d[value] for (i,j) in list1: for (k,l) in list2: if i!=k and i!=l and j!=k and j!=l: flist = [nums[i],nums[j],nums[k],nums[l]] flist.sort() if flist not in result: result.append(flist) return result
---35.Search Insert Position
給定一個有序list和一個數target,求這個數在這個list中的位置.
有序,馬上想到了二分查找,只不過要針對找不到的情況進行一下特殊處理.普通的二分查找在找不到時返回的是-1,我們這里只要返回找不到時傳入函數的left就行了.
class Solution: def searchInsert(self, nums, target): """ :type nums: List[int] :type target: int :rtype: int """ def bs(numlsit, l, r): while l <= r: mid = int((l + r) / 2) if numlsit[mid] == target: return mid elif numlsit[mid] < target: return bs(numlsit, mid+1, r) else: return bs(numlsit, l, mid-1) return l return bs(nums, 0, len(nums)-1)
---41.First Missing Positive
給定一個無序list,求出其中缺失的最小正整數,要求時間復雜度O(n).
設list長度為l的話,最后的答案肯定是1~l+1之間的一個正整數,所以想到了設置標志數組flag,flag[i]為1表示i+1已經出現過了.遍歷一次list后再遍歷一次flag,遇到的第一個0的index就是答案.如果遍歷完整個flag都沒有輸出值,說明答案是l+1.
class Solution: def firstMissingPositive(self, nums): """ :type nums: List[int] :rtype: int """ if nums == []: return 1 l = len(nums) pi = [0] * l for i in range(l): tmp = nums[i] if tmp > 0 and tmp <= l: pi[tmp-1] = 1 for i in range(l): if pi[i] == 0: return i+1 return l+1
---45.Jump Game II
給定一個非負整數list,初始位置在list[0],list的值代表了該位置能向前走的最大步數,求走到list末尾所需的最小次數(假設一定能夠走到末尾).
既然要求最小次數,那么目的就是每一步都盡可能地往前走.這里的"盡可能"並非是每一步都走能走的最大步數,而是走到的位置加上該位置的最大步數,這代表了我們下一步的選擇范圍.理清這一點后代碼就很簡單了.
class Solution: def jump(self, nums): """ :type nums: List[int] :rtype: int """ l = len(nums) pos = 0 sum = 0 def find_next(pos): next = 0 pace = nums[pos] max = 0 if pos + pace >= l - 1: next = l - 1 else: for i in range(1, pace+1): tmp = i + nums[pos+i] if tmp > max: max = tmp next = pos + i return next while pos < l - 1: pos = find_next(pos) sum += 1 return sum
---55.Jump Game
給定一個非負整數list,初始位置在list[0],list的值代表了該位置能向前走的最大步數,求是否能走到list末尾.
這道題和45題類似,我們仍然采用45題的走法,但是加入一個判定條件,如果走到了list[i]=0的位置說明不能夠走到終點,輸出False,否則輸出True.
class Solution: def canJump(self, nums): """ :type nums: List[int] :rtype: bool """ l = len(nums) pos = 0 sum = 0 def find_next(pos): next = 0 pace = nums[pos] max = 0 if pos + pace >= l - 1: next = l - 1 else: for i in range(1, pace + 1): tmp = i + nums[pos + i] if tmp > max: max = tmp next = pos + i return next while pos < l - 1: if nums[pos] == 0: return False pos = find_next(pos) sum += 1 return True
---59.Spiral Matrix II
給定一個正整數n,生成一個n*n的矩陣,其中元素按照螺旋形從1一直到n^2
觀察這個生成的矩陣,會發現每一圈都是從左上角開始,沿着順時針方向遞增的規律生成的.按照這個思路,我們定義一個circle函數,它每次都在n*n的矩陣中生成一圈符合條件的數.這個函數接受三個參數,分別代表了出發點,邊長還有初始值.其中出發點從(0,0)開始,每次增加(1,1),邊長從n開始每次-2,初始值可以通過函數中加入數字的次數得到.
class Solution: def generateMatrix(self, n): """ :type n: int :rtype: List[List[int]] """ spiral = [[0 for i in range(n)] for j in range(n)] def circle(start, length, initial): sum = 0 for i in range(length): spiral[start][start+i] = i + 1 + initial sum += 1 for i in range(length-1): spiral[start+i+1][start+length-1] = i + length + 1 + initial sum += 1 for i in range(length-1): spiral[start+length-1][start+length-i-2] = i + 2 * length + initial sum +=1 for i in range(length-2): spiral[start+length-i-2][start] = i + 3 * length - 1 + initial sum += 1 return sum times = int((n+1)/2) sum = 0 for i in range(times): sum += circle(i, n-2*i, sum) return spiral
---63.Unique Paths II
給定一個m*n的矩陣,其中0代表可以走的路,1代表障礙物.機器人只能往下或往右走,初始位置在矩陣左上角,求可以讓機器人走到矩陣右下角的路徑的數量.
一開始想用深度優先遍歷解決,后來發現太費時間,於是想到了動態規划.公式如下:

class Solution: def uniquePathsWithObstacles(self, obstacleGrid): """ :type obstacleGrid: List[List[int]] :rtype: int """ m = len(obstacleGrid) n = len(obstacleGrid[0]) dp = [[0 for i in range(n)] for j in range(m)] dp[0][0] = 0 if obstacleGrid[0][0] == 1 else 1 for i in range(m): for j in range(n): if obstacleGrid[i][j] == 1: dp[i][j] == 0 else: if i-1 >= 0: dp[i][j] += dp[i-1][j] if j-1 >= 0: dp[i][j] += dp[i][j-1] return dp[m-1][n-1]
---64.Minimum Path Sum
給定一個m*n的非負矩陣,矩陣中的數字代表權值,起點在矩陣左上角,只能往右或往下走,求走到矩陣右下角所需的最小路徑長度.
基本的動態規划題,dp[0][0]=grid[0][0],dp[i][j]=grid[i][j]+min(dp[i-1][j],dp[i][j-1]),第一排和第一列由於沒有上/左的格子,需要提前處理.
class Solution: def minPathSum(self, grid): """ :type grid: List[List[int]] :rtype: int """ m = len(grid) n = len(grid[0]) dp = [[0 for i in range(n)] for j in range(m)] dp[0][0] = grid[0][0] for i in range(1,n): dp[0][i] = grid[0][i] + dp[0][i-1] for i in range(1,m): dp[i][0] = grid[i][0] + dp[i-1][0] for i in range(1, m): for j in range(1, n): dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1]) return dp[-1][-1]
---66.Plus One
給定一個非空的個位數數組,這個數組整體代表了一個非負整數.將這個非負整數+1后用個位數數組的形式返回.
思路比較簡單,就是將數組中的數分別取出乘上位數后相加,再將這個數+1后存入另一個數組.值得注意的是python雖然支持大數運算,但如果超出了2^63-1的范圍后用int()類型轉換會丟失一部分值,所以要在運算過程中轉換為字符串來處理.
class Solution: def plusOne(self, digits): """ :type digits: List[int] :rtype: List[int] """ plus = [] sum = 0 l = len(digits) for i in range(l): sum *= 10 sum += digits[i] sum += 1 while sum != '' and sum > 0: plus.insert(0,sum%10) sum = str(sum)[:-1] if sum != '': sum = int(sum) return plus
提交過后雖然AC了,但是運算速度較慢,查看了Solution后發現這題應該用加法器的思想來做,有進位則本位置0,保留進位.
class Solution: def plusOne(self, digits): """ :type digits: List[int] :rtype: List[int] """ p = 1 for i in range(len(digits), 0 , -1): r = digits[i-1] + p if r >= 10: p = 1 digits[i-1] = 0 else: p = 0 digits[i-1] = r if p == 1: digits = [1] + digits return digits
---73. Set Matrix Zeroes
給定一個m*n的矩陣matrix,如果有一個元素是0,則將該元素的所在行和列都變為0.要求in-palce就地操作實現,也就是不使用臨時變量,空間復雜度O(1).
空間復雜度O(MN)的解法:新建一個m*n的矩陣,掃描matrix,掃到0就在新矩陣對應行和列賦0,最后把新矩陣賦給matrix
空間復雜度O(M+N)的解法:新建一個長度為M的數組記錄每一行是否有0,一個長度為N的數組記錄每一列是否有0
空間復雜度O(1)的解法:利用matrix本身記錄,首先定義row_flag和column_flag表示矩陣的第一行和第一列是否有0,然后掃描矩陣除了第一行和第一列以外的部分,用第一行和第一列置0來表示有0.
class Solution: def setZeroes(self, matrix): """ :type matrix: List[List[int]] :rtype: void Do not return anything, modify matrix in-place instead. """ m = len(matrix) n = len(matrix[0]) row_flag = False col_flag = False for i in range(n): if matrix[0][i] == 0: row_flag = True for i in range(m): if matrix[i][0] == 0: col_flag = True for i in range(1,m): for j in range(1,n): if matrix[i][j] == 0: matrix[i][0] = 0 matrix[0][j] = 0 for i in range(1,m): for j in range(1,n): if matrix[i][0] == 0 or matrix[0][j] == 0: matrix[i][j] = 0 if row_flag: for i in range(n): matrix[0][i] = 0 if col_flag: for i in range(m): matrix[i][0] = 0
---74.Search a 2D Matrix
給定一個m*n的整數矩陣,其中每行數從左到右升序排列,並且滿足每行的第一個數大於上一行的最后一個數,給定一個target,確定target是否在這個矩陣中.
看見有序還是先想到二分查找,因為每一行之間實際也隱含着順序了,所以只要先根據每一行的最后一個數判斷出我們想要查找的是哪一行,然后對那一行進行二分查找即可.注意[]和[[]]的特殊情況.
class Solution: def searchMatrix(self, matrix, target): """ :type matrix: List[List[int]] :type target: int :rtype: bool """ def binary_search(numlist, l, r, t): while l <= r: mid = int((l + r) / 2) if numlist[mid] == t: return True elif numlist[mid] < t: return binary_search(numlist, mid+1, r, t) else: return binary_search(numlist, l, mid-1, t) return False if matrix == [] or matrix == [[]]: return False if target < matrix[0][0] or target > matrix[-1][-1]: return False m = len(matrix) n = len(matrix[0]) for i in range(m): if matrix[i][-1] >= target: return binary_search(matrix[i], 0, n-1, target)
---78.Subsets
給定一個不含重復元素的數組,返回該數組可能的所有子集
一開始想到用遞歸來做,在list中記錄s,然后遍歷s中的每個元素i,使用s-i來遞歸,但是只要數組元素稍微多一點就超時,顯然是時間復雜度太高了.
查閱資料后發現這道題應該用回溯法+深度優先遍歷,以[1,2,3]為例,第一層元素為[],第二層元素為[1],[2],[3],每深入一層就要刪除剛剛加入的元素,直到數組里的元素全部用完后再回溯:
class Solution: def subsets(self, nums): """ :type nums: List[int] :rtype: List[List[int]] """ l = [[]] def dfs(nums, index, path, li): for i in range(index, len(nums)): li.append(path+[nums[i]]) path.append(nums[i]) dfs(nums, i+1, path, li) path.pop() if nums is None: return [] dfs(nums, 0, [], l) return l
---79.Word Search
給定一個二維list和一個word,判斷這個word是否能用二維list中相鄰的字母連接而成(不能重復使用)
一道dfs題目,終止條件是當所有字母找完時返回True,當沒找完並且四個方向都不能繼續走下去時返回False.找到一個字母后分別向四個方向走,如果其中一個方向返回True則整體為True.走過的位置設為'#',當四個方向都回來后將'#'重新變回原來的字母.
class Solution: def exist(self, board, word): """ :type board: List[List[str]] :type word: str :rtype: bool """ m = len(board) n = len(board[0]) l = len(word) def near(grid, i, j, word, pos): if pos == l: return True if i-1 < -1 or i+1 > m or j-1 < -1 or j+1 > n or word[pos] != grid[i][j]: return False tmp = board[i][j] board[i][j] = '#' res = near(grid, i+1, j, word, pos+1) or near(grid, i-1, j, word, pos+1)\ or near(grid, i, j+1, word, pos+1) or near(grid, i, j-1, word, pos+1) board[i][j] = tmp return res for i in range(m): for j in range(n): if near(board, i, j, word, 0): return True return False
---80.Remove Duplicates from Sorted Array II
給定一個有序list,使得其中的數字不能重復出現兩次以上,要求in-place做法,返回值為處理后的數組的長度
因為要求in-place,所以先用一個for循環找出重復兩次以上的數字的位置,將它們改為'#',然后在第二次for循環刪去這些'#'.注意這一次的循環要用倒序,否則會因為刪去元素導致索引不正確而出錯.
class Solution: def removeDuplicates(self, nums): """ :type nums: List[int] :rtype: int """ if nums == []: return 0 l = len(nums) dup = 0 tmp = nums[0] for i in range(1,l): if nums[i] == tmp: dup += 1 else: dup = 0 tmp = nums[i] if dup >= 2: nums[i] = '#' for i in range(l-1, -1, -1): if nums[i] == '#': nums.pop(i) return len(nums)
---81.Search in Rotated Sorted Array II
給定一個list,是由一個有序數組在某一樞紐處旋轉得到的,並且其中可能含有重復元素,要求判斷target是否在這個list中.
雖然這個list經過旋轉,但是還是可以用二分查找的思想,因為mid的左邊或右邊一定有一端是有序的.因此只需要在二分查找的時候對此進行判斷就行了.另外本題可能有重復值,所以當left,mid和right指向的值都相等時要移動指針來跳出循環.
class Solution: def search(self, nums, target): """ :type nums: List[int] :type target: int :rtype: bool """ left = 0 right = len(nums)-1 while left <= right: mid = int((left + right) / 2) if nums[mid] == target: return True if nums[mid] < nums[right] or nums[mid] < nums[left]: if nums[mid] < target <= nums[right]: left = mid + 1 else: right = mid - 1 elif nums[mid] > nums[left] or nums[mid] > nums[right]: if nums[mid] > target >= nums[left]: right = mid - 1 else: left = mid + 1 else: left += 1 return False
---105.Construct Binary Tree from Preorder and Inorder Traversal
給定二叉樹的前序遍歷和中序遍歷,輸出該二叉樹
前序遍歷也就是根-左-右,中序遍歷就是左-根-右.我們用遞歸的方式,preorder[0]必定是根結點,而這個根結點在inorder中的位置的左邊是它的左子樹,右邊是它的右子樹.只要抓住這個關鍵點就可以了.
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def buildTree(self, preorder, inorder): """ :type preorder: List[int] :type inorder: List[int] :rtype: TreeNode """ if len(preorder) == 0:return None root_node = TreeNode(preorder[0]) j = inorder.index(preorder[0]) root_node.left = self.buildTree(preorder[1:j+1],inorder[0:j]) root_node.right = self.buildTree(preorder[j+1:],inorder[j+1:]) return root_node
---106.Construct Binary Tree from Inorder and Postorder Ttaversal
給定二叉樹的中序遍歷和后序遍歷,輸出該二叉樹
中序遍歷是左-根-右,后序遍歷是左-右-根.preorder[-1]必定是根結點.然后就和105題類似了.
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def buildTree(self, inorder, postorder): """ :type inorder: List[int] :type postorder: List[int] :rtype: TreeNode """ if len(postorder) == 0:return None root_node = TreeNode(postorder[-1]) j = inorder.index(postorder[-1]) root_node.left = self.buildTree(inorder[0:j], postorder[0:j]) root_node.right = self.buildTree(inorder[j+1:],postorder[j:-1]) return root_node
---119.Pascal's Triangle II
楊輝三角問題,給定k,要求輸出楊輝三角的第k行
雖然看似是等腰三角形,但其實我們可以把它看做一個直角三角形,也就是矩陣的下半部分.這題如果用O(k^2)的空間的話非常簡單,抓住t[i][j] = t[i-1][j]+t[i-1][j-1]即可.題干給了一個挑戰,是用O(k)的空間完成,其實也非常簡單,只要設置兩個臨時變量,分別存儲我們要修改的位置的上一層的這一位和前一位即可(邏輯上的上層,實際上只有一維數組).
class Solution: def getRow(self, rowIndex): """ :type rowIndex: int :rtype: List[int] """ size = rowIndex+1 tri = [0] * size tri[0] = 1 for i in range(1,size): t1 = 1 for j in range(1,size): t2 = tri[j] tri[j] = tri[j] + t1 t1 = t2 return tri
---120.Triangle
給定一個三角形的list,求出從頂到底的最短路徑.
經典DP題,非常簡單.DP算式為 tri[n-1][i] += min(tri[n][i], tri[n][i+1])
class Solution: def minimumTotal(self, triangle): """ :type triangle: List[List[int]] :rtype: int """ def dp(tri, n): if n < 1: return for i in range(n): tri[n-1][i] += min(tri[n][i], tri[n][i+1]) dp(tri, n-1) dp(triangle, len(triangle)-1) return triangle[0][0]
---152.Maximum Product Subarray
給定一個list,找出其中一個連續的子數組,使得其中所有數的乘積最大.
首先對整個數組求積,如果大於0則這就是答案,如果小於0則遍歷數組,如果找到0則對0左右的數組重復上述操作,如果找到負數則求負數兩邊的乘積之和.
class Solution: def maxProduct(self, nums): """ :type nums: List[int] :rtype: int """ def product(nums): if len(nums) == 0: return 0 s = 1 for x in nums: s *= x return s def find_max(nums): pro = product(nums) if pro > 0: return pro else: for i in range(len(nums)): if nums[i] == 0: return max(0, find_max(nums[:i]), find_max(nums[i+1:])) if nums[i] < 0: if len(nums[:i]) > 0: pro_left = product(nums[:i]) else: pro_left = pro if len(nums[i+1:]) > 0: pro_right = product(nums[i+1:]) else: pro_right = pro pro = max(pro, pro_left, pro_right) return pro if len(nums) == 1: return nums[0] pro = find_max(nums) return pro
---153.Find Minimum in Rotated Sorted Array
給定一個排序過的list,在某一結點旋轉過,找出其中的最小值
類似81題,還是用二分查找的思想.雖然list被旋轉過,但是left-mid和mid-right其中的一段必定是有序的.
class Solution: def findMin(self, nums): """ :type nums: List[int] :rtype: int """ l = len(nums) left = 0 right = l-1 mid = (left+right)//2 while left < right: if nums[right] < nums[mid]: left = mid+1 else: right = mid mid = (left+right)//2 return min(nums[left], nums[right])
---167.Two Sum II - Input array is sorted
給定一個排序過的list,從中找到和等於target的兩個數的位置,返回它們以1為起始值的坐標.
雙指針的思想,比較簡單.值得一提的是,如果需要從無序數組中找到是否有兩數之和等於某一target,也是采用先排序再雙指針的方法.
class Solution: def twoSum(self, numbers, target): """ :type numbers: List[int] :type target: int :rtype: List[int] """ l = 0 r = len(numbers) - 1 while l < r: if numbers[l] + numbers[r] > target: r -= 1 elif numbers[l] + numbers[r] < target: l += 1 else: break ans = [l+1, r+1] return ans
---189.Rotate Array
給定一個list和一個k,使這個list旋轉k步
利用python的切片即可
class Solution: def rotate(self, nums, k): """ :type nums: List[int] :type k: int :rtype: void Do not return anything, modify nums in-place instead. """ l = len(nums) k = k % l nums[:] = nums[l-k:] + nums[:l-k]
---209.Minimum Size Subarray Sum
給定一個list和一個正數s,找到list中和大於等於s的最小連續區間的長度.如果沒有則返回0.
我的思路是雙指針法,用一個滑動的窗口去匹配,如果窗口內的值大於等於s則左移左邊框,否則右移右邊框,直到右邊框到達數組底部並且窗口值小於s位置.
class Solution: def minSubArrayLen(self, s, nums): """ :type s: int :type nums: List[int] :rtype: int """ if sum(nums) < s: return 0 elif max(nums) >= s: return 1 l, r = 0, 1 add = nums[0] + nums[1] minimum = len(nums) while l < len(nums): if add >= s: tmp = r-l+1 minimum = min(minimum, tmp) add -= nums[l] l += 1 else: if r < len(nums)-1: r += 1 add += nums[r] else: break return minimum
---216.Combination Sum III
給定一個數k和一個數n,要求找到1-9內的k個數,且滿足它們的和為n的所有可能組合.
這道題是回溯法的應用.回溯法相當於有剪枝的DFS.思路是:保存當前步驟,如果是一個解就輸出;維護狀態,使搜索路徑(含子路徑)盡量不重復.必要時,應該對不可能為解的部分進行剪枝.
- 遞歸函數的開頭寫好跳出條件,滿足條件才將當前結果加入總結果中
- 已經拿過的數不再拿
- 遍歷過當前結點后,為了回溯到上一步,要去掉已經加入到結果list中的當前結點.
代入到這題中,每一個dfs都遍歷1-9中當前index后面的數,這確保了已經拿過的數不再拿.進入下一層dfs,並令k-1,n-nums[index],跳出條件是k<0或n<0,滿足條件是k==0且n==0.
class Solution: def combinationSum3(self, k, n): """ :type k: int :type n: int :rtype: List[List[int]] """ nums = [1,2,3,4,5,6,7,8,9] res = [] def dfs(nums, k, n, index, path, res): if k < 0 or n < 0: return if k == 0 and n == 0: res.append(path) for i in range(index, len(nums)): dfs(nums, k-1, n-nums[i], i+1, path+[nums[i]], res) dfs(nums, k, n, 0, [], res) return res
---228.Summary Ranges
給定一個有序且無重復數字的list,將其中連續范圍的數字合並后返回
根據題意,我們需要確認的其實就是每段連續區間的首尾數字.首數字可能是list的第一個數或是前一個數和它不連續的數,尾數字可能是list的最后一個數或是后一個數和它不連續的數.並且每一個尾數字一定對應着一段連續區間,將這段區間存入一個字符list即可.
class Solution: def summaryRanges(self, nums): """ :type nums: List[int] :rtype: List[str] """ summary = [] start = 0 end = 0 for i in range(len(nums)): if i == 0 or nums[i-1]+1 != nums[i]: start = nums[i] if i == len(nums)-1 or nums[i+1]-1 != nums[i]: end = nums[i] if start == end: summary.append(str(start)) else: summary.append(str(start)+'->'+str(end)) return summary
---229.Majority Element II
給定一個長度為n的list,找到其中出現次數大於[n/3]的所有數.要求時間復雜度O(n),空間復雜度O(1).
我的想法是使用dict存儲這個list中每個數出現的次數,然后將其中次數大於[n/3]的存入一個list.但是則不符合空間復雜度的要求.
查閱solution后發現這題可以使用Boyer-Moore多數投票算法解決.這是一種尋找"多數元素"的好方法,基本思想是建立標志位和count,如果匹配到的數字不等於標志位則讓count-1,否則count+1,如果count為0時更換標志位.因為本題要求的是出現次數大於[n/3]的所有數,也就是最多可能有兩個數,因此要建立兩組標志位和count.
class Solution: def majorityElement(self, nums): """ :type nums: List[int] :rtype: List[int] """ count1, count2, tmp1, tmp2 = 0, 0, 0, 1 for i in nums: if i == tmp1: count1 += 1 elif i == tmp2: count2 += 1 elif count1 == 0: tmp1 = i count1 = 1 elif count2 == 0: tmp2 = i count2 = 1 else: count1 -= 1 count2 -= 1 ans = [n for n in (tmp1, tmp2) if nums.count(n) > len(nums) // 3] return ans
---283.Move Zeroes
給定一個list,將其中所有的0移到末尾,並且保持其他元素的順序不變.要求in-place完成.
如果我們采用的是交換位置或是移動0的話,index的變化將非常繁瑣.所以我們將思路放在非零元素上:總是將非零元素與第一個零元素交換位置,每一次交換后將對應第一個零元素的index+1即可.
Dynamic Programming
---95.Unique Binary Search Trees II
給定一個數字n,生成所有存儲了1~n的二叉查找樹的可能形式.
這題的思路是每次選取一個結點作為根,然后根據這個根把樹切分為左右兩個子樹,再在左右兩個子樹里選取結點作為根,直至子樹為空.注意子樹為空時要返回[None]而不是[],否則循環無法進行.
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def generateTrees(self, n): """ :type n: int :rtype: List[TreeNode] """ def dfs(nums): if not nums: return [None] result = [] for i in range(len(nums)): for l in dfs(nums[:i]): for r in dfs(nums[i+1:]): node = TreeNode(nums[i]) node.left, node.right = l, r result.append(node) return result nums = list(range(1,n+1)) if n == 0: return [] return dfs(nums)
---198.House Robber
給定一個list,代表一條街道上每棟房子里的財物.我們要盡可能多地搶這些財物,但是不能搶相鄰的兩棟房子.
遞推式是:
- f(0)=nums[0]
- f(1)=max(nums[0], nums[1])
- f(k)=max(f(k-1), f(k)+f(k-2))
class Solution: def rob(self, nums): """ :type nums: List[int] :rtype: int """ if not nums: return 0 elif len(nums) == 1: return nums[0] else: nums[1] = max(nums[0], nums[1]) for i in range(2,len(nums)): nums[i] = max(nums[i]+nums[i-2], nums[i-1]) return nums[-1]
---213.House Robber II
給定一個list,代表一條街道上每棟房子里的財物.我們要盡可能多地搶這些財物,但是不能搶相鄰的兩棟房子.這個街道是環形的.
這題和198很像,區別只是增加了一個第一棟房子與最后一棟房子不能同時搶的判定.所以我們分為兩種情況,同時為了節省空間,采用了臨時變量:
- 搶了第一棟房子,此時問題變為198題的求0~N-1
- 沒有搶第一棟房子,此時問題變為198題的求1~N
class Solution: def rob(self, nums): """ :type nums: List[int] :rtype: int """ if not nums: return 0 elif len(nums) < 4: return max(nums) else: pplast, plast = 0, 0 for i in nums[:-1]: tmp = plast plast = max(pplast+i, plast) pplast = tmp result = plast pplast, plast = 0, 0 for i in nums[1:]: tmp = plast plast = max(pplast+i, plast) pplast = tmp return max(result, plast)
---264.Ugly Number II
找到第n個ugly number(質因數只有2,3,5的數字,包括1)
因為ugly number的質因數只有3種可能性,所以每一個ugly number一定是由另一個ugly number乘上這三個數的其中之一得到的(1除外).所以想到了設立3個標志位,分別代表2,3,5的乘數在數組中的位置,判斷它們的乘積最小者就是下一個ugly number.
class Solution: def nthUglyNumber(self, n): """ :type n: int :rtype: int """ ugly = [1] tmp_2, tmp_3, tmp_5 = 0, 0, 0 for i in range(1,n): tmp = min(2*ugly[tmp_2], 3*ugly[tmp_3], 5*ugly[tmp_5]) if tmp == 2*ugly[tmp_2]: tmp_2 += 1 if tmp == 3*ugly[tmp_3]: tmp_3 += 1 if tmp == 5*ugly[tmp_5]: tmp_5 += 1 ugly.append(tmp) return ugly[-1]
---279.Perfect Squares
給定一個正整數n,找到相加之和等於它所需的完全平方數的最小個數.
只要找到表達式dp[i]=min(dp[i],dp[i-j*j])就可以了.
class Solution: def numSquares(self, n): """ :type n: int :rtype: int """ if n == 0: return 0 dp = list(range(0,n+1)) dp[1] = 1 for i in range(1,n+1): j = 1 while j*j <= i: dp[i] = min(dp[i], dp[i-j*j]+1) j += 1 return dp[-1]
---300.Longest Increasing Subsequence
給定一個無序list,找出其中最長的遞增子序列
dp的思路是比較容易想到的,使用一個dp數組存儲該位置的遞增子序列長度,dp[0]=1,對於i,遍歷所有小於i的j,只要nums[j]<nums[i],就使用表達式dp[i]=max(dp[i], dp[j]+1)來更新dp數組. 時間復雜度是O(n^2)
class Solution: def lengthOfLIS(self, nums): """ :type nums: List[int] :rtype: int """ l = len(nums) if l < 2: return l dp = [1 for i in range(l)] for i in range(l): tmp = nums[i] for j in range(i): if nums[j] < tmp: dp[i] = max(dp[i], dp[j]+1) return max(dp)
另一種在評論區看到的思路是使用一個tails數組,它的第i位代表的是nums中長度為i的遞增子序列的最小數值.易得tails是一個遞增數組.然后對於每一個數x,如果它比tails[-1]大,就在tails數組中增加一位,如果它滿足tails[i-1] < x <= tails[i],就更新tails[i]=x.這樣做的好處是可以用二分查找來確定x的位置.這種做法的時間復雜度是O(nlogn)
def lengthOfLIS(self, nums): tails = [0] * len(nums) size = 0 for x in nums: i, j = 0, size while i != j: m = (i + j) / 2 if tails[m] < x: i = m + 1 else: j = m tails[i] = x size = max(i + 1, size) return size
---309.Best Time to Buy and Sell Stock with Cooldown
給定一個list代表股票價格,要求賣完股票后的第一天不能買入股票,求買賣可以產生的最大利潤.
一開始沒有思路,在discuss看到這其實是一道狀態轉移的問題.總共有hold,notHold,cooldown三種狀態,它們之間的轉移方程如下:
- hold---不操作---hold
- hold---賣股票---cooldown
- notHold---不操作---notHold
- notHold---買股票---hold
- cooldown---不操作---notHold
初始狀態是notHold,然后只要遍歷prices的list即可.
class Solution: def maxProfit(self, prices): """ :type prices: List[int] :rtype: int """ hold = float('-inf') notHold = 0 cooldown = float('-inf') for p in prices: hold = max(hold, notHold-p) notHold = max(notHold, cooldown) cooldown = hold+p return max(notHold, hold, cooldown)
---322.Coin Change
給定一個list代表不同面值的錢,和一個總數amount,求出能湊出amount所需的錢的最小數量,如果湊不齊則返回-1.
找到表達式dp[i]=min(dp[i-coin]+1),注意湊不齊的金額設為float('inf')即可.
class Solution: def coinChange(self, coins, amount): """ :type coins: List[int] :type amount: int :rtype: int """ MAX = float('inf') dp = [0] + [MAX] * amount for i in range(1, amount + 1): dp[i] = min([dp[i - c] if i - c >= 0 else MAX for c in coins]) + 1 return [dp[amount], -1][dp[amount] == MAX]
---338.Counting Bits
給定一個非負整數num,對於0<=i<=num,返回一個list,代表i的二進制表示中1的數量.
只有0對應的二進制的1的數量是0,對於任意的正整數num,都可以寫成num = 2**i + k(k<num/2)的形式,如果k=0時對應的1的數量為1,否則就是1+dp[k].因為k<2**i,所以我們可以確保dp[k]一定已經存過數字.
class Solution: def countBits(self, num): """ :type num: int :rtype: List[int] """ dp = [0] * (num+1) carry = 1 for i in range(1, num + 1): if carry*2 == i: carry = i dp[i] = 1+dp[i-carry] return dp
---343.Integer Break
給定一個正整數n,將它拆分成至少兩個數字的和,使得這些數字之積最大.
我想到的是dp的做法,dp[n]為n對應的最大積,那么dp[2]=1,dp[n]=max(i*dp[n-i],i*(n-i))
class Solution: def integerBreak(self, n): """ :type n: int :rtype: int """ dp = [0]*(n+1) dp[0] = 1 dp[1] = 1 for i in range(n+1): for j in range(i): dp[i] = max(dp[i], j*(i-j), j*dp[i-j]) return dp[-1]
實際上這題通過數學推導,可以發現最好的就是將數字三等分,如果不行就二等分,這樣就可以很快求解了.
Tree
---94.Binary Tree Inorder Traversal
給定一棵二叉樹,返回它的中序遍歷結果.
遞歸,如果root為空則返回,否則遞歸遍歷左結點,存入根結點,遞歸遍歷右結點.
如果不用遞歸的話,可以采用棧來實現,也就是結點非空時就將根結點存入棧,然后進入左結點,直到結點為空時,從棧中彈出第一個結點加入res[],然后訪問該結點的右結點.
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def inorderTraversal(self, root): """ :type root: TreeNode :rtype: List[int] """ node = [] def it(root): if root == None: return else: it(root.left) node.append(root.val) it(root.right) it(root) return node
---100.Same Tree
給定兩棵二叉樹,判斷它們是否相同.
遞歸,如果p與q都存在的話,返回對p與q的val是否相等的判斷結果and對p.left和q.left的判斷結果and對p.right和q.right的判斷結果.如果p與q不存在的話,則用p==q判斷是否兩者都為None.這里用到的技巧是True and False = Flase.
這題也可以用棧來實現.
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def isSameTree(self, p, q): """ :type p: TreeNode :type q: TreeNode :rtype: bool """ if p and q: return p.val == q.val and self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right) else: return p == q
---103.Binary Tree Zigzag Level Order Traversal
給定一棵二叉樹,返回它的zigzag遍歷結果.(也就是同一層從左到右,下一層再從右到左,如此循環)
我的方法是設立標志位i代表第i層,如果結點存在,且res數組的長度小於i,就在res數組中加入一個[],然后將這個結點的值存入,並遞歸左結點和i+1,然后遞歸右結點和i+1.最后再將res中的偶數list做reverse操作.
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def zigzagLevelOrder(self, root): """ :type root: TreeNode :rtype: List[List[int]] """ res = [] def helper(root, i): if root: if len(res) < i: res.append([]) res[i-1].append(root.val) helper(root.left, i+1) helper(root.right, i+1) helper(root,1) for i in range(len(res)): if i % 2 != 0: res[i].reverse() return res
---107.Binary Tree Level Order Traversal II
給定一棵二叉樹,返回它自底向上,從左到右的遍歷結果.
與103題類似,只不過最后是對整個res做reverse操作.
如果不用遞歸的話,還可以用dfs+棧或bfs+隊列.
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def levelOrderBottom(self, root): """ :type root: TreeNode :rtype: List[List[int]] """ res = [] def helper(root, i): if root: if len(res) < i: res.append([]) res[i-1].append(root.val) helper(root.left, i+1) helper(root.right, i+1) helper(root,1) res.reverse() return res
---110.Balanced Binary Tree
給定一棵二叉樹,判斷它是不是一棵平衡二叉樹.
平衡二叉樹的定義:要么是一棵空樹,要么左右子樹都是平衡二叉樹,並且左右子樹的深度之差的絕對值不超過1.
如果采用求深度的方法,那么部分結點會被重復訪問很多次,所以想到了后序遍歷,它的特點是訪問到根結點時,根結點對應的左結點和右結點都已經被訪問過了.如果在訪問過程中發現左結點和右結點的深度之差大於1,就返回-1,同理如果左結點和右結點的返回值已經是-1了,也返回-1,不然就返回1+左結點和右結點的較大值.
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def isBalanced(self, root): """ :type root: TreeNode :rtype: bool """ def helper(root): if not root: return 0 left = helper(root.left) right = helper(root.right) if left == -1 or right == -1 or abs(left-right) > 1: return -1 return 1 + max(left, right) return helper(root) != -1
---111.Minimum Depth of Binary Tree
給定一棵二叉樹,返回它的最小深度
遞歸解決,對於一棵二叉樹的每一個結點,如果它同時有左右子樹,那么深度為1+min(left,right),否則深度為另一個子樹的最小深度+1.
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def minDepth(self, root): """ :type root: TreeNode :rtype: int """ if not root: return 0 if not root.left: return 1 + self.minDepth(root.right) elif not root.right: return 1 + self.minDepth(root.left) else: return 1 + min(self.minDepth(root.left), self.minDepth(root.right))
---112.Path Sum
給定一棵二叉樹和一個sum,判斷二叉樹中是否有一條從根結點到葉子結點的路徑,使得結點之和等於sum.
比較簡單,使用一個輔助值tmp記錄路徑的和,如果為葉子結點且路徑和加上值等於sum則返回True,否則返回helper(root.left) or helper(root.right)
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def hasPathSum(self, root, sum): """ :type root: TreeNode :type sum: int :rtype: bool """ def helper(root, sum, tmp): if not root: return False tmp += root.val if not root.left and not root.right and tmp == sum: return True else: return helper(root.left, sum, tmp) or helper(root.right, sum, tmp) return helper(root, sum, 0)
---113.Path Sum II
給定一棵二叉樹和一個sum,找出所有滿足和等於sum的根結點到葉子結點的路徑.
思路和112題大體一致,只不過需要在函數中加入兩個list參數,一個存儲路徑,最后如果判斷等於sum就加入到另一個中作為最后結果.
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def pathSum(self, root, sum): """ :type root: TreeNode :type sum: int :rtype: List[List[int]] """ def dfs(root, sum, ls, res): if not root.left and not root.right and sum == root.val: ls.append(root.val) res.append(ls) if root.left: dfs(root.left, sum-root.val, ls+[root.val], res) if root.right: dfs(root.right, sum-root.val, ls+[root.val], res) if not root: return [] res = [] dfs(root, sum, [], res) return res
---114.Flatten Binary Tree to Linked List
給定一棵二叉樹,將它變為鏈表,要求in-place操作
在對根結點操作時,如果已經將它的左右子樹都拉平過,就將左子樹加入到根結點和右子樹中間,所以采用后序遍歷順序來遞歸.
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def flatten(self, root): if not root: return if root.left: self.flatten(root.left) if root.right: self.flatten(root.right) left = root.left right = root.left while right and right.right: right = right.right if right: right.right = root.right if left: root.right = left root.left = None
---129.Sum Root to Leaf Numbers
給定一棵二叉樹,每一個結點都是0-9中的一位數字,求所有根結點到葉子結點的路徑上的數字之和.
遞歸,每深入一個結點就讓當前的值*10傳下去,直到葉子結點后將值存入一個list,最后對該ist求和即可.
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def sumNumbers(self, root): """ :type root: TreeNode :rtype: int """ def helper(root, value, res): if root: helper(root.left, root.val+value*10, res) helper(root.right, root.val+value*10, res) if not root.left and not root.right: res.append(root.val+value*10) if not root: return 0 else: res = [] helper(root, 0, res) return sum(res)
---144.Binary Tree Preorder Traversal
給定一棵二叉樹,返回前序遍歷
遞歸的方法非常簡單,這里用棧的方法,創建兩個list,pre用於保存最后的結果,stack用於保存過程中的結點.如果棧里還有結點,首先將這個結點出棧,存入pre,然后將這個結點的右結點和左結點存入棧中.
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def preorderTraversal(self, root): """ :type root: TreeNode :rtype: List[int] """ pre = [] stack = [root] while stack: node = stack.pop() if node: pre.append(node.val) stack.append(node.right) stack.append(node.left) return pre
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def preorderTraversal(self, root): """ :type root: TreeNode :rtype: List[int] """ def helper(root, prelist): if root: prelist.append(root.val) helper(root.left, prelist) helper(root.right, prelist) prelist = [] helper(root, prelist) return prelist
---199. Binary Tree Right Side View
給定一棵二叉樹,想象你站在這顆二叉樹的右邊,從上到下給出你能在這棵樹上看到的值.
定義一個數組view和一個輔助函數,它的功能是從右到左遍歷這顆樹,並且當遍歷到的結點深度等於當前view的長度時,表明這是該層最右邊的結點,將它加入view數組,然后遍歷這個結點的右子結點和左子結點.
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def rightSideView(self, root): """ :type root: TreeNode :rtype: List[int] """ def helper(root, depth): if root: if depth == len(view): view.append(root.val) helper(root.right, depth+1) helper(root.left, depth+1) view = [] helper(root, 0) return view
---222. Count Complete Tree Nodes
給定一棵完全二叉樹,求結點數
首先定義一個輔助函數get_depth用於求一顆完全二叉樹的深度,然后開始構造主函數.如果不存在結點則返回0,否則分別求當前結點的左子樹深度和右子樹深度.如果左子樹深度等於右子樹深度,說明左子樹是滿二叉樹,那么只需用深度求出左子樹的結點數,然后再對右子樹求結點數即可.如果左子樹深度不等於右子樹深度,那么說明右子樹是滿二叉樹,同理.
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def countNodes(self, root): """ :type root: TreeNode :rtype: int """ def get_depth(root): depth = 0 while root: root = root.left depth += 1 return depth depth = get_depth(root) if not root: return 0 left_depth = get_depth(root.left) right_depth = get_depth(root.right) if left_depth == right_depth: return pow(2, left_depth) + self.countNodes(root.right) else: return pow(2, right_depth) + self.countNodes(root.left)
---226.Invert Binary Tree
翻轉二叉樹
很簡單的一道題.
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def invertTree(self, root): """ :type root: TreeNode :rtype: TreeNode """ if root: tmp = root.right root.right = root.left root.left = tmp self.invertTree(root.left) self.invertTree(root.right) return root
---337.House Robber III
給定一棵二叉樹,其中結點的值代表財產,小偷不能偷兩個相連的結點,求小偷能偷到的最大財產價值.
這種要維護狀態的題首先想到遞歸,用一個大小為2的一維數組res,res[0]表示不包含當前結點的最大值,res[1]表示包含當前結點的最大值.開始遞歸,如果該結點不存在則返回[0,0],否則left_val等於左結點的遞歸調用,right_val等於右結點的遞歸調用,注意這兩個val實際上都是一個和res大小相同的數組.不包含該結點的話,res[0]=max(left_val)+max(right_val),包含該結點的話,res[1]=root.val+left_val[0]+right_val[0].
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def rob(self, root): """ :type root: TreeNode :rtype: int """ def helper(root): if not root: return [0,0] else: left_val = helper(root.left) right_val = helper(root.right) res = [0,0] res[0] = max(left_val) + max(right_val) res[1] = root.val + left_val[0] + right_val[0] return res return max(helper(root))
Hash Table
---187.Repeated DNA Sequences
DNA是由A,C,G,T四種核苷酸構成的,設計一種算法,能夠找到一個DNA里所有重復出現過的長度為10的核苷酸序列.
使用python的dict構造哈希表,用i遍歷DNA序列s的第一位到倒數第十位,s[i:i+10]就可以遍歷其中所有長度為10的序列.如果在dict中存在這個序列且值等於1(代表出現次數),就將它加入到output的list中,且將值加1.否則將該序列加入dict中,且令值等於1.
class Solution: def findRepeatedDnaSequences(self, s): """ :type s: str :rtype: List[str] """ sub = {} output = [] for i in range(len(s)-9): temp = s[i:i+10] if temp in sub: if sub[temp] == 1: sub[temp] += 1 output.append(temp) else: sub[temp] = 1 return output
---205. Isomorphic Strings
給定兩個字符串s和t,判斷它們是不是同構的.同構是指,將其中一個字符串中的相同字符用另一個字符替換,如果這個字符串可以變為另一個字符串,則稱他們是同構的.
這題我的思路是分別遍歷s和t,用兩個dict存儲結果.如果其中已經有了遍歷到的字符,就令值加1,否則添加該字符,然后用dict.values()進行比較即可.但是提交后出現了錯誤.思考以后發現dict內部存放的順序和key放入的順序沒有關系,因為它是采用哈希表的原理.
正確思路是只用一個dict,鍵為s的字母,值為t相同位置的字母,如果s中的字母已經在dict中了,則判斷對應的鍵是否與此時t中的字母相等,如果不相等則false.如果s中的字母不在dict中,判斷此時t中的字母是否在dict中有值相等,如果有則返回false,否則將該鍵值對存入dict.
class Solution: def isIsomorphic(self, s, t): """ :type s: str :type t: str :rtype: bool """ if len(s) != len(t): return False hashmap = {} for i in range(len(s)): if s[i] in hashmap: if hashmap[s[i]] != t[i]: return False else: if t[i] in hashmap.values(): return False else: hashmap[s[i]] = t[i] return True
---274.H-Index
給定一個非負數組,代表一位科學家的引用因子,求出這位科學家的H-Index.H-index是指他至多有h篇論文分別被引用了至少h次.
計算H-index的方法是將引用因子降序排好,然后找到第一個比引用因子大的序號,將序號-1就是H-index.
class Solution: def hIndex(self, citations): """ :type citations: List[int] :rtype: int """ if citations == []: return 0 citations.sort(reverse=True) for i in range(len(citations)): if i + 1 > citations[i]: return i return len(citations)
---299.Bulls and Cows
一個猜數字的游戲,給定目標數secret和猜測數guess,猜測數中和目標數大小相同且位置相同的叫bulls,大小相同但位置不同的叫cows,要求給出bulls和cows的數量.
首先用map將secret和guess變為數字list,另外定義兩個長度為10的list,然后同時遍歷這兩個list,如果數字相同則bulls+1,否則在對應的list的對應位置+1.遍歷結束后比較list每個位置的較小者,相加就得到cows的數量.
class Solution: def getHint(self, secret, guess): """ :type secret: str :type guess: str :rtype: str """ nums1 = list(map(int, secret)) nums2 = list(map(int, guess)) bulls = 0 cows = 0 l1 = [0]*10 l2 = [0]*10 for i in range(len(nums1)): if nums1[i] == nums2[i]: bulls += 1 else: l1[nums1[i]] += 1 l2[nums2[i]] += 1 for i in range(10): cows += min(l1[i], l2[i]) return str(bulls)+'A'+str(cows)+'B'
Depth-first Search
---117.Populating Next Right Pointers in Each Node ||
給定一棵二叉樹,將每個結點的next結點設為它右邊的相鄰結點,如果不存在這樣的結點則設為NULL.
首先建立一個tali結點和一個head結點,其中head結點用於保存tail結點最初的位置.然后遍歷當前root,首先將tail.next指向root.left,如果存在則將tail移動至tail.next,然后將tail.next指向root.right,如果存在則將tail移動至tali.next.遍歷完以后將root指向root.next(因為root比tail高一層,所以root層的next結構已經固定了).如果root存在則重復上述過程,否則將tail指向一開始的head結點,將root指向head的next,即將root下移了一層.
# Definition for binary tree with next pointer. # class TreeLinkNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None # self.next = None class Solution: # @param root, a tree link node # @return nothing def connect(self, root): tail = head = TreeLinkNode(0) while root: tail.next = root.left if tail.next: tail = tail.next tail.next = root.right if tail.next: tail = tail.next root = root.next if not root: tail = head root = head.next
---200.Number of Islands
給定一個二維網格,其中'1'代表陸地,'0'代表水.島是指一塊被水包圍的豎直方向和水平方向相連的陸地.假設網格的四周都是水,求其中島的數量.
經典的DFS思想,遍歷網格,如果當前位置是1就調用dfs函數.在dfs中首先進行邊界判斷,然后如果當前位置是'1'則改為'#',之后對位置的前后左右位置調用dfs函數.
class Solution: def numIslands(self, grid): """ :type grid: List[List[str]] :rtype: int """ if not grid: return 0 count = 0 for i in range(len(grid)): for j in range(len(grid[0])): if grid[i][j] == '1': self.dfs(grid, i, j) count += 1 return count def dfs(self, grid, i, j): if i<0 or j<0 or i>=len(grid) or j>=len(grid[0]) or grid[i][j] != '1': return grid[i][j] = '#' self.dfs(grid, i+1, j) self.dfs(grid, i-1, j) self.dfs(grid, i, j+1) self.dfs(grid, i, j-1)
---98.Validate Binary Search Tree
給定一棵二叉樹,判斷它是不是一棵二叉查找樹(左子樹的所有結點都比該結點小,右子樹的所有結點都比該結點大,且左右子樹都是二叉查找樹).
尚未分類
---1.Two Sum
在列表中找到兩個數,使得它們的和等於某一給定值,返回這兩個數的位置.時間復雜度:O(n),python中的字典其實就是哈希表的應用,所以我們通過字典用哈希表來降低查找的時間復雜度
class Solution: def twoSum(self, nums, target): """ :type nums: List[int] :type target: int :rtype: List[int] """ d = {} for i, n in enumerate(nums): m = target - n if m in d: return [d[m], i] else: d[n] = i
---2.Add Two Numbers
將兩個倒序存放在單鏈表里的數相加,將結果倒序存儲在單鏈表里返回.思路非常簡單,先將兩個單鏈表中的數字分別提取出來求和,然后將=求得的和存入一個單鏈表,實際上相加這一步也可以直接在原鏈表中完成,只需要添加判斷條件while(l1 or l2 or carry)即可.
# Definition for singly-linked list. # class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution: def addTwoNumbers(self, l1, l2): """ :type l1: ListNode :type l2: ListNode :rtype: ListNode """ node1 = l1 node2 = l2 l3 = ListNode(0) l3.next = ListNode(0)#[0],[0]的特殊情況 node3 = l3 sum1 = 0 coe = 1 while not node1 is None: sum1 += node1.val * coe coe *= 10 node1 = node1.next sum2 = 0 coe =1 while not node2 is None: sum2 += node2.val * coe coe *= 10 node2 = node2.next sum = sum1 + sum2 while sum > 0: node3.next = ListNode(sum % 10) node3 = node3.next sum //= 10 return l3.next
---3.Longest Substring Without Repeating Characters
找到字符串中沒有重復字符的最大子串.一開始沒有想到用字典,而是直接用str來存儲字串,時間復雜度是O(n^2),后來用字典將時間復雜度降到了O(n).注意到僅當字典中出現重復值且該重復值在strat區段里時才移動start.另外用了Sliding Window的思想,每次將strat移動到重復值的下一位置.
class Solution: def lengthOfLongestSubstring(self, s): """ :type s: str :rtype: int """ start = 0 max_length = 0 substring = {} for i, c in enumerate(s): if c in substring and start <= substring[c]:#只有當重復值是在start后面出現時才移動start start = substring[c] + 1#Slding Window的思想 else: max_length = max(max_length, i - start + 1) substring[c] = i return max_length
---5.Longest Palindromic Substring
最長回文子串問題,一開始我的思路如下:回文子串的特點是首尾字母相同,所以我對每一個字母都找到位於它后面的相同字母,利用切片判斷這一段是否為回文子串(str[i:j]==str[i:j][::-1]).雖然AC了但是時間復雜度很高,主要是因為str.find操作非常耗時.
后來看了Solution發現這是一道可以用動態規划解決的問題,思路是若s是回文字串,令s'=s加上s左右兩側的兩個字母,如果這兩個字母相同則s'也是回文字串.重寫代碼如下:
class Solution: def longestPalindrome(self, s): """ :type s: str :rtype: str """ max = 0 palindromic = '' if len(s) == 0 else s[0] for i in range(len(s)): length = 1 while i - length >=0 and i + length < len(s) and s[i-length] == s[i+length]: tmp = s[i-length:i+length+1] if len(tmp) > max: max = len(tmp) palindromic = tmp length += 1 length = 1 while i - length + 1 >=0 and i + length < len(s) and s[i-length+1] == s[i+length]: tmp = s[i-length+1:i+length+1] if len(tmp) > max: max = len(tmp) palindromic = tmp length += 1 return palindromic
這道題還有經典的Manacher算法,可以看這篇文章.Discuss里的算法實現在這里.
另外在Discuss里發現了另一種做法,思路是一段回文字符串的后面新增了一個字母,如果新字符串仍是回文字符串的話只有兩種可能:形如bb+b,也就是多了一個字母,或形如a(bb)+a,算上原回文字符串的前一個字母共多了兩個字母.基於這個思路可以寫出代碼.因為用到了切片,在題例上運行的速度甚至比Manacher算法還快.
---6.ZigZag Conversion
一道將字符串做之字形排列的題目.我們用n表示行數,將排列后得到的字符串分為完整豎列和折線兩部分.每個完整豎列有n個數,每兩個完整豎列之間的折線有n-2列,每列一個數,因此每兩個完整豎列中同一行的數的間隔是n+n-2=2n-2.同時我們發現,除了第一行和最后一行之外的第i行都有折線,第i行的第一個折線是第2n-i個數.於是可以遍歷輸出每一行,判定條件是這一行我們要輸出的數字是否超出了字符串的長度.

class Solution: def convert(self, s, numRows): """ :type s: str :type numRows: int :rtype: str """ zigzag = '' if numRows == 1 or numRows == 0 or numRows >= len(s): return s space = 2 * numRows - 2 for i in range(1,numRows+1): n = 0 if i == 1 or i == numRows: while i + n * space <= len(s): zigzag += s[i+n*space-1] n += 1 else: while i + n * space <= len(s): zigzag += s[i+n*space-1] if (2 * numRows - i) + (n * space) <= len(s): zigzag += s[(2*numRows-i)+(n*space)-1] n += 1 return zigzag ZigZag Conversion
---7.Reverse Integer
將給定的數字倒序輸出.非常簡單的一道題
class Solution(object): def reverse(self, x): """ :type x: int :rtype: int """ tmp = abs(x) sum = 0 while tmp > 0: sum = sum * 10 + tmp % 10 tmp = tmp // 10 sum = sum if x >= 0 else -sum return sum if sum < 2**31 and sum > -2**31 else 0
---8.String to Integer(atoi)
將給定字符串中符合條件的一串數字字符轉化為int類型返回.我的思路是設定標志位start=0和符號位sign,遍歷字符串,當start=0時遇到空格則continue,遇到+則記錄sign=1,遇到-則記錄sign=-1,遇到數字則記錄數字;當strat=1時代表已經找到了第一個數字或符號位,此時遇到除數字之外的字符都break,遇到數字則繼續記錄數字.注意我們得到的整數值不能超過INT_MAX和INT_MIN.后來發現其實用str.strip()函數來去除字符串頭尾的空格會更方便.
class Solution(object): def myAtoi(self, str): """ :type str: str :rtype: int """ ans = 0 start = 0 sign = 0 if str.isspace() is True: print(0) for i in str: if start == 0: if i.isspace() is True: continue if i == '+': sign = 1 elif i == '-': sign = -1 elif i.isdigit() is True: sign = 1 ans = ans * 10 + int(i) else: break start = 1 else: if i.isdigit() is True: ans = ans * 10 + int(i) else: break ans = sign * ans if ans >= 2147483647: return 2147483647 elif ans <= -2147483648: return -2147483648 else: return ans
---9.Palindrome Number
判斷一個數字是否是回文數.題目要求不能用額外的空間,否則可以利用python的字符串切片輕松解決.我的思路是求出該整數的位數,判斷第一位數和最后一位數是否相同,如果相同則將位數/100,然后將原數字的首尾兩個數刪除,最后如果位數<1說明是回文數.
class Solution(object): def isPalindrome(self, x): """ :type x: int :rtype: bool """ if x < 0: return False high = 1 while x / high >= 10: high *= 10 while x // high == x % 10: x = x % high // 10 high /= 100 if high < 1: return True return False
---12.Integer to Roman
將十進制數字轉化為羅馬數字.比較簡單的一道題.我的思路是判斷當前位數,改變代表1/5/10的字符然后逐位輸出.也可以直接將每位上的各種字符表示存在列表里,然后直接取出.
class Solution(object): def intToRoman(self, num): """ :type num: int :rtype: str """ carry = 1 roman = '' while num != 0: n = num % 10 num //= 10 if carry == 1: numeral_1 = 'I' numeral_5 = 'V' numeral_10 = 'X' elif carry == 10: numeral_1 = 'X' numeral_5 = 'L' numeral_10 = 'C' elif carry == 100: numeral_1 = 'C' numeral_5 = 'D' numeral_10 = 'M' else: numeral_1 = 'M' numeral_5 = '' numeral_10 = '' if 1 <= n <= 3: roman = numeral_1 * n + roman elif n == 4: roman = numeral_1 + numeral_5 + roman elif 5 <= n <= 8: roman = numeral_5 + numeral_1 * (n - 5) + roman elif n == 9: roman = numeral_1 + numeral_10 + roman carry *= 10 return roman
---13.Roman to Integer
將羅馬數字轉化為十進制數字.非常無聊的一道題.比較簡單的方法是寫非常多的if語句來判斷,或者將羅馬數字與對應的十進制數字存入字典來轉換.下面是我在discuss里看到的一個方案,巧妙利用了羅馬數字"大數前面的小數用來減,大數后面的小數用來加"這個特點.
class Solution(object): def romanToInt(self, s): """ :type s: str :rtype: int """ roman_map = { "I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000, } result = 0 last_num = None for char in s: current_num = roman_map[char] if last_num is None or last_num >= current_num: result += current_num elif last_num < current_num: result += current_num - 2 * last_num last_num = current_num return result
---14.Longest Common Prefix
找最長公共前綴字符串.我的思路是找出列表中最短的字符串,然后對最短字符串的每個字符都在列表中遍歷,直到出現不同或者遍歷結束為止.在discuss里看到很多方法利用了python中的sort(),min(),max()這些內置方法對字符串排序,會使時間快很多.
class Solution(object): def longestCommonPrefix(self, strs): """ :type strs: List[str] :rtype: str """ prefix = '' if strs == []: return prefix minimum = float("inf") for s in strs: minimum = min(len(s), minimum) i = 0 for j in range(minimum): for i in range(len(strs)): while strs[i][j] != strs[0][j]: return prefix prefix = prefix + strs[0][j] return prefix
---15.3Sum
給定一個數組,找到其中三個數的和為零的所有可能,以列表形式返回.這道題的基本思路是先將數組排序,從左往右遍歷一次.在遍歷每個數的過程中設立兩個指針,如果三個數的和大於零則左移右指針,如果三個數的和小於零則右移左指針,直到兩個指針相遇.注意我們用的是set()來存儲找到的結果,可以避免list中出現重復.在此基礎上,我增加了一個對排序過的數組的操作,即當最左邊兩個數與最右邊一個數的和大於零時刪去最右邊的數,當最左邊一個數與最右邊兩個數的和小於零時刪去最左邊的數.這個操作大大提升了運行速度.
class Solution(object): def threeSum(self, nums): """ :type nums: List[int] :rtype: List[List[int]] """ zeros = set() nums.sort() if len(nums) < 3: return [] if nums.count(0) > len(nums)-2: return [[0, 0, 0]] while len(nums) > 3 and (nums[0]+nums[1]+nums[-1] > 0 or nums[-1]+nums[-2]+nums[0] < 0): if nums[0] + nums[1] + nums[-1] > 0: nums.remove(nums[-1]) else: nums.remove(nums[0]) for i in range(len(nums)-2): if nums[i] > 0: break j = i + 1 k = len(nums) - 1 while j < k: sum = nums[i] + nums[j] + nums[k] if sum == 0: zeros.add((nums[i], nums[j], nums[k])) j += 1 continue elif sum < 0: j += 1 else: k -= 1 return list(map(list,zeros))
