1. Depth-first Search(DFS)
1.1 Palindrome Partitioning
题目跳转:https://www.lintcode.com/problem/palindrome-partitioning/description
因为每个path都要包含所有的字符,即第一个起点必在0
,所以不用在backTracking
的外层循环start。
图取自题解。
class Solution:
"""
@param: s: A string
@return: A list of lists of string
"""
def partition(self, s):
# write your code here
# 2020/7/7
def isPlindrome(s, start, end):
while start < end: # start == end不用判断
if s[start] != s[end]:
return False
start += 1
end -= 1
return True
def backTracking(s, start, path, res):
if start >= len(s):
res.append(path.copy())
return
for end in range(start, len(s)):
if not isPlindrome(s, start, end):
continue
path.append(s[start: end + 1])
backTracking(s, end + 1, path, res)
path.pop()
res = []
path = []
backTracking(s, 0, path, res)
return res
1.2 Split String
题目跳转:https://www.lintcode.com/problem/split-string/description
每个start
走一步或者两步,在此基础上再更新下一个start
,也是走一步或者两步。后面都走完了就pop pop pop回来。
class Solution:
"""
@param: : a string to be split
@return: all possible split string array
"""
def splitString(self, s):
# write your code here
# 2020/7/6
def mysplit(s, res, curr, start):
if start == len(s):
res.append(curr.copy())
return
chars = [1, 2]
for c in chars:
if start + c <= len(s):
curr.append(s[start: start + c])
mysplit(s, res, curr, start + c)
curr.pop()
res = []
curr = []
mysplit(s, res, curr, 0)
return res
1.3 Combination Sum II
题目跳转:https://www.lintcode.com/problem/combination-sum-ii/description
图自题解。
num
排序之后,每个递归阶段(start
相同为一个递归阶段),都只用重复数的第一个数来算,后面紧接的数如果和本递归阶段前一个数相同,就会导致组合重复,所以直接continue。
class Solution:
"""
@param num: Given the candidate numbers
@param target: Given the target number
@return: All the combinations that sum to target
"""
def combinationSum2(self, num, target):
# write your code here
# 2020/7/8
def dfs(num, start, curr, res, residue):
if not residue:
res.append(curr.copy())
return
for i in range(start, len(num)):
if num[i] > residue:
return
if i > start and num[i] == num[i - 1]:
continue
curr.append(num[i])
dfs(num, i + 1, curr, res, residue - num[i])
curr.pop()
num.sort()
curr = []
res = []
dfs(num, 0, curr, res, target)
return res
1.4 Combination Sum
题目跳转:https://www.lintcode.com/problem/combination-sum/description
相比于Combination Sum II,循环里dfs传入i
而非 i + 1
,是因为元素允许重复使用,但要先用list+set+sort
对candidates
预处理。
class Solution:
"""
@param candidates: A list of integers
@param target: An integer
@return: A list of lists of integers
"""
def combinationSum(self, candidates, target):
# write your code here
# 2020/7/8
def dfs(s, start, residue, curr, res):
if residue < 0: return
if residue == 0:
res.append(curr.copy())
return
for i in range(start, len(s)):
curr.append(s[i])
dfs(s, i, residue - s[i], curr, res)
curr.pop()
candidates = list(set(candidates))
candidates.sort()
curr = []
res = []
dfs(candidates, 0, target, curr, res)
return res
1.5 Combinations
题目跳转:https://www.lintcode.com/problem/combinations/description
n
不变,residue
用来计数,每个迭代阶段就是选的第几个数(residue
相同),循环 i
来遍历剩余的数,并且i + 1
是下一个迭代阶段的起始。
class Solution:
"""
@param n: Given the range of numbers
@param k: Given the numbers of combinations
@return: All the combinations of k numbers out of 1..n
"""
def combine(self, n, k):
# write your code here
# 2020/7/8
def dfs(start, n, residue, curr, res):
if residue < 1:
res.append(curr.copy())
return
for i in range(start, n + 1):
curr.append(i)
dfs(i + 1, n, residue - 1, curr, res)
curr.pop()
curr = []
res = []
dfs(1, n, k, curr, res)
return res
1.6 Subsets II
题目跳转:https://www.lintcode.com/problem/subsets-ii/description
观察题目,结果的长度覆盖了从0
到len(nums)
,所以用外层的循环用来宏观控制长度,每个循环进去只取这个长度的结果:
range(len(nums) + 1)
在每个迭代阶段内去重(前提是已排序):
if i > start and nums[i] == nums[i - 1]:
continue
class Solution:
"""
@param nums: A set of numbers.
@return: A list of lists. All valid subsets.
"""
def subsetsWithDup(self, nums):
# write your code here
# 2020/7/8
def dfs(nums, start, residue, curr, res):
if not residue:
res.append(curr.copy())
return
for i in range(start, len(nums)):
if i > start and nums[i] == nums[i - 1]:
continue
curr.append(nums[i])
dfs(nums, i + 1, residue - 1, curr, res)
curr.pop()
nums.sort()
curr = []
res = []
for residue in range(len(nums) + 1):
dfs(nums, 0, residue, curr, res)
return res
1.7 Subsets
题目跳转:https://www.lintcode.com/problem/subsets/description
与 Subsets II 类似,只是提供的nums
元素并不重复。
因此只需把去重的部分去掉即可。
class Solution:
"""
@param nums: A set of numbers
@return: A list of lists
"""
def subsets(self, nums):
# write your code here
# 2020/7/8
def dfs(nums, start, residue, curr, res):
if residue <= 0:
res.append(curr.copy())
return
for i in range(start, len(nums)):
curr.append(nums[i])
dfs(nums, i + 1, residue - 1, curr, res)
curr.pop()
nums.sort()
curr = []
res = []
for i in range(len(nums) + 1):
dfs(nums, 0, i, curr, res)
return res
1.8 Letter Combinations of a Phone Number
题目跳转:https://www.lintcode.com/problem/letter-combinations-of-a-phone-number/description
residue
代表还剩几个数字没被覆盖。用来显式让结果覆盖每个数字,也是控制每次dfs
的终止。
不能用if start > len(digits): return
来控制终止,因为它最终一定会大于len(digits)
,导致res
里会有长度小于digits
的结果。所以它的作用就是为了深入每个迭代阶段,并限制它循环的起始。
range(start, len(digits))
class Solution:
"""
@param digits: A digital string
@return: all posible letter combinations
"""
def letterCombinations(self, digits):
# write your code here
# 2020/7/9
relation = {
"2": ["a", "b", "c"],
"3": ["d", "e", "f"],
"4": ["g", "h", "i"],
"5": ["j", "k", "l"],
"6": ["m", "n", "o"],
"7": ["p", "q", "r", "s"],
"8": ["t", "u", "v"],
"9": ["w", "x", "y", "z"]
}
def dfs(digits, start, residue, relation, curr, res):
if residue <= 0 and len(curr) > 0:
res.append(curr[:])
return
for i in range(start, len(digits)):
for j in relation[digits[i]]:
curr += j
dfs(digits, i + 1, residue - 1, relation, curr, res)
curr = curr[:-1]
curr = ""
res = []
dfs(digits, 0, len(digits), relation, curr, res)
return res
1.9 N-Queens
题目跳转:https://www.lintcode.com/problem/n-queens/description
这类题无非按行或按列作为主方向。
这里按行进行递归,以3个条件来剪枝(因行方向肯定不重复,因此不考虑):
1、同一列内不重复,以col
储存;
2、同一主对角线方向不重复,以principal_diag
储存;
3、同一副对角线方向不重复,以counter_diag
储存。
分析剪枝的规律,主对角线方向和副对角线方向的横纵坐标是有规律的。
图自题解。
class Solution:
"""
@param: n: The number of queens
@return: All distinct solutions
"""
def solveNQueens(self, n):
# write your code here
# 2020/7/10
def stack2board(stack, rows):
return ["{}Q{}".format(("." * stack[i]), ("." * (len(rows) - stack[i] - 1))) for i in rows]
def backTracking(rows, row, col, p_diag, c_diag, stack, res):
if row == len(rows):
board = stack2board(stack, rows)
res.append(board)
return
for i in rows:
if i in col or (i - row) in p_diag or (i + row) in c_diag:
continue
col.add(i)
p_diag.add(i - row)
c_diag.add(i + row)
stack.append(rows[i])
backTracking(rows, row + 1, col, p_diag, c_diag, stack, res)
col.remove(i)
p_diag.remove(i - row)
c_diag.remove(i + row)
stack.pop()
rows = [i for i in range(n)]
stack = []
col = set()
principal_diag = set()
counter_diag = set()
res = []
backTracking(rows, 0, col, principal_diag, counter_diag, stack, res)
return res
1.10 Permutations II
题目跳转:https://www.lintcode.com/problem/permutations-ii/description
curr
要和nums
长度一致,这也是迭代结束的标志,这里可以直接用remain
来兼职做结束的条件判断。
remain
代表剩余哪个数没用的索引,刚开始时,remain = [0, 1, .., n]
,本次迭代用了哪个数,就把这个数的索引剔除,给到下一迭代阶段:remain[: i] + remain[i + 1:]
。
每个迭代的阶段里之所以要循环索引i
,是用来对结果去重的(前提是已经nums.sort()
)。在本次迭代中,i
和i - 1
对应的remain[i]
和remain[i - 1]
紧贴着,因为nums
已经排序,如果这两个remian
的取得的索引在nums
里取值相等,就证明重复了,直接conitnue
剪枝。
class Solution:
"""
@param: : A list of integers
@return: A list of unique permutations
"""
def permuteUnique(self, nums):
# write your code here
# 2020/7/10
def backTracking(nums, remain, curr, res):
if not len(remain):
res.append(curr.copy())
return
for i, j in enumerate(remain):
if i > 0 and nums[remain[i]] == nums[remain[i - 1]]:
continuea
curr.append(nums[j])
backTracking(nums, remain[: i] + remain[i + 1:], curr, res)
curr.pop()
curr = []
res = []
nums.sort()
remain = [i for i in range(len(nums))] # indexs of nums
backTracking(nums, remain, curr, res)
return res
1.11 Coin Change
题目跳转:https://www.lintcode.com/problem/coin-change/description
源自题解,选面值最大的不一定是最优解:[1, 7, 10]
对于14
的最优解并非10, 1, 1, 1, 1
而是7, 7
,因此选完面值大的结果也要看面值较小的结果。
这里for
循环的是coins的个数,而非索引。且一次性用最多数量的大面值,再逐渐减少大面值的个数。
通过if count + i >= res: continue
进行剪枝。
class Solution:
"""
@param coins: a list of integer
@param amount: a total amount of money amount
@return: the fewest number of coins that you need to make up
"""
def coinChange(self, coins, amount):
# write your code here
# 2020/7/13
def dfs(coins, c_index, residue, count, res):
if not residue:
return min(count, res)
if c_index >= len(coins): return res
# i是选coins[c_index]的个数,从最多往下减至0
for i in range(residue // coins[c_index], -1, -1):
if count + i >= res: continue
res = dfs(coins, c_index + 1, residue - (i * coins[c_index]), count + i, res)
return res
coins.sort(reverse=True)
res = 10001 # 因题目amount不超过10000
res = dfs(coins, 0, amount, 0, res)
return res if res != 10001 else -1
2. Data Structure
2.1 Moving Average from Data Stream
题目跳转:https://www.lintcode.com/problem/moving-average-from-data-stream/description
并不难,但有些细节。
保存self.sum
可以减少很多计算量。
class MovingAverage:
"""
@param: size: An integer
"""
def __init__(self, size):
# do intialization if necessary
self.queue = []
self.size = size
self.sum = 0.
"""
@param: val: An integer
@return:
"""
def next(self, val):
# write your code here
# 2020/7/10
self.queue.append(val)
self.sum += val
if len(self.queue) > self.size:
self.sum -= self.queue.pop(0)
return self.sum / len(self.queue)
# Your MovingAverage object will be instantiated and called as such:
# obj = MovingAverage(size)
# param = obj.next(val)
2.2 Insert Delete GetRandom O(1)
本题简单,仅作记录
题目跳转:https://www.lintcode.com/problem/insert-delete-getrandom-o1/description
import random
class RandomizedSet:
def __init__(self):
# do intialization if necessary
self.set = []
"""
@param: val: a value to the set
@return: true if the set did not already contain the specified element or false
"""
def insert(self, val):
# write your code here
# 2020/7/11
if val not in self.set:
self.set.append(val)
return True
return False
"""
@param: val: a value from the set
@return: true if the set contained the specified element or false
"""
def remove(self, val):
# write your code here
try:
self.set.remove(val)
return True
except Exception as e:
return False
"""
@return: Get a random element from the set
"""
def getRandom(self):
# write your code here
return self.set[random.randint(0, len(self.set) - 1)]
# Your RandomizedSet object will be instantiated and called as such:
# obj = RandomizedSet()
# param = obj.insert(val)
# param = obj.remove(val)
# param = obj.getRandom()
2.3 Merge Sorted Array
题目跳转:https://www.lintcode.com/problem/merge-sorted-array/description
得益于题目已排序,通过i
控制A
从后(m+n-1
)往前(0
)填充,就不用使用额外的内存空间来合并再排序。
class Solution:
"""
@param: A: sorted integer array A which has m elements, but size of A is m+n
@param: m: An integer
@param: B: sorted integer array B which has n elements
@param: n: An integer
@return: nothing
"""
def mergeSortedArray(self, A, m, B, n):
# write your code here
# 2020/7/12
i, m, n = m + n - 1, m - 1, n - 1
while m >= 0 or n >= 0:
if (n < 0) or (m >= 0 and A[m] > B[n]):
A[i] = A[m]
m -= 1
else:
A[i] = B[n]
n -= 1
i -= 1
2.4 Intersection of Two Arrays
题目跳转:https://www.lintcode.com/problem/intersection-of-two-arrays/description
class Solution:
"""
@param nums1: an integer array
@param nums2: an integer array
@return: an integer array
"""
def intersection(self, nums1, nums2):
# write your code here
# 2020/7/12
return list(set(nums1) & set(nums2))
3. Binary Tree
二叉搜索树、线段树
3.1 Closest Binary Search Tree Value
题目跳转:https://www.lintcode.com/problem/closest-binary-search-tree-value/description
"""
Definition of TreeNode:
class TreeNode:
def __init__(self, val):
self.val = val
self.left, self.right = None, None
"""
class Solution:
"""
@param root: the given BST
@param target: the given target
@return: the value in the BST that is closest to the target
"""
def closestValue(self, root, target):
# write your code here
# 2020/7/26
curr = root
gap = abs(curr.val - target)
res = curr.val
while curr:
this_gap = abs(curr.val - target)
if this_gap < gap:
res = curr.val
gap = this_gap
if target < curr.val:
curr = curr.left
else:
curr = curr.right
return res
3.2 Binary Tree Paths
题目跳转:https://www.lintcode.com/problem/binary-tree-paths/description
"""
Definition of TreeNode:
class TreeNode:
def __init__(self, val):
self.val = val
self.left, self.right = None, None
"""
class Solution:
"""
@param root: the root of the binary tree
@return: all root-to-leaf paths
"""
def binaryTreePaths(self, root):
# write your code here
# 2020/7/26
def list2str(l):
s = str(l[0])
for i in l[1:]:
s += "->"
s += str(i)
return s
def dfs(node, res, curr):
curr.append(node.val)
if not (node.left or node.right):
res.append(list2str(curr))
return
if node.left:
dfs(node.left, res, curr)
curr.pop()
if node.right:
dfs(node.right, res, curr)
curr.pop()
if not root: return []
res = []
curr = []
dfs(root, res, curr)
return res
3.3 Range Sum Query - Mutable
题目跳转:https://www.lintcode.com/problem/range-sum-query-mutable/description
思路源自题解,使用了线段树的数据结构,每个叶子节点存储了原数组的元素,每个非叶子节点存储了其子节点的和。
判断l
是否在范围内(即是否为左节点),不在则加上并+1
。同理,判断r
是否在范围内(即是否为右节点),不在则加上并-1
。
class NumArray:
def __init__(self, nums):
"""
:type nums: List[int]
"""
# 2020/7/12
self.n = len(nums)
self.tree = [0] * (2 * self.n - 1) # 树的最大长度是2n - 1
# 从n - 1开始,依次把nums放入self.tree
for i in range(self.n - 1, len(self.tree)):
self.tree[i] = nums[i - self.n + 1]
for i in range(self.n - 2, -1, -1): # range左开右闭,self.n - 2到0(不含-1)
self.tree[i] = self.tree[2 * i + 1] + self.tree[2 * i + 2]
def update(self, i, val):
"""
:type i: int
:type val: int
:rtype: void
"""
i += (self.n - 1)
self.tree[i] = val
i = (i - 1) // 2
while i >= 0:
self.tree[i] = self.tree[2 * i + 1] + self.tree[2 * i + 2]
i = (i - 1) // 2
def sumRange(self, i, j):
"""
:type i: int
:type j: int
:rtype: int
"""
res = 0
i += (self.n - 1)
j += (self.n - 1)
while i <= j:
if (i % 2) == 0:
res += self.tree[i]
i += 1
if (j % 2) == 1:
res += self.tree[j]
j -= 1
i = (i - 1) // 2
j = (j - 1) // 2
return res
# Your NumArray object will be instantiated and called as such:
# obj = NumArray(nums)
# obj.update(i,val)
# param_2 = obj.sumRange(i,j)
4. Hash Table
4.1 Sparse Matrix Multiplication
题目跳转:https://www.lintcode.com/problem/sparse-matrix-multiplication/description
class Solution:
"""
@param A: a sparse matrix
@param B: a sparse matrix
@return: the result of A * B
"""
def multiply(self, A, B):
# write your code here
# 2020/7/13
def compress(matrix):
cm = {}
for r in range(len(matrix)):
for c in range(len(matrix[0])):
if matrix[r][c]:
cm.setdefault(r, {})[c] = matrix[r][c]
return cm
# A: [[1,0,0],
# [-1,0,3]]
# B: [[7,0,0],
# [0,0,0],
# [0,0,1]]
ca, cb = compress(A), compress(B)
# ca: {0: {0: 1}, 1: {0: -1, 2: 3}}
# cb: {0: {0: 7}, 2: {2: 1}}
C = [[0 for _ in range(len(B[0]))] for _ in range(len(A))]
# C: [[0, 0, 0],
# [0, 0, 0]]
for i in ca: # 遍历A每行,i是A的行号
for j in ca[i]: # 遍历本行,j是A的列号,也是B的行号
for k in cb.get(j, {}): # 用B的行号去取B的列号k
C[i][k] += ca[i][j] * cb[j][k]
return C
4.2 Subarray Sum
题目跳转:https://www.lintcode.com/problem/subarray-sum/description
分析题目,从nums[0]
开始累加,当累加结果重复就证明中间的和为0,这里要记录和和对应索引,故使用Hash记录。
class Solution:
"""
@param nums: A list of integers
@return: A list of integers includes the index of the first number and the index of the last number
"""
def subarraySum(self, nums):
# write your code here
# 2020/7/12
map, prefix_sum = {0: 0}, 0
for i, n in enumerate(nums):
prefix_sum += n
if prefix_sum in map:
return [map[prefix_sum], i]
# 注意这里是 i + 1
map[prefix_sum] = i + 1
注意map[prefix_sum] = i + 1
要加一,因为当prefix_sum in map
是和子序列开始位置的前一个数的累和相等,所以前一个数应该放后一个数的index,所以i + 1
。
例如[3, 5, 1, -2, -4]
,加上-4后相等的是3,而结果是要从5开始,所以3记录的i
要加一。
4.3 Longest Palindrome
题目跳转:https://www.lintcode.com/problem/longest-palindrome/description
class Solution:
"""
@param s: a string which consists of lowercase or uppercase letters
@return: the length of the longest palindromes that can be built
"""
def longestPalindrome(self, s):
# write your code here
# 2020/7/14
has_odd = False
res = 0
dict = {}
for c in s:
if c not in dict:
dict[c] = 0
dict[c] += 1
for c, n in dict.items():
if n % 2 == 1:
res += (n - 1)
has_odd = True
else:
res += n
if has_odd: return res + 1
return res
5. Dynamic Programming(DP)
最重要是找到题目的规律,归纳出状态转移方程,再根据题目的个性化设定边界条件。
5.1 Unique Paths
题目跳转:https://www.lintcode.com/problem/unique-paths/description
因只能move right or down,故动态转移方程为:
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
且首行和首列dp
都为1。
class Solution:
"""
@param m: positive integer (1 <= m <= 100)
@param n: positive integer (1 <= n <= 100)
@return: An integer
"""
def uniquePaths(self, m, n):
# write your code here
# 2020/7/13
# Dynamic Programming
dp = [[0 for _ in range(n)] for _ in range(m)]
for i in range(len(dp[0])):
dp[0][i] = 1
for i in range(len(dp)):
dp[i][0] = 1
for i in range(1, m):
for j in range(1, n):
dp[i][j] += (dp[i - 1][j] + dp[i][j - 1])
return dp[-1][-1]
5.2 Unique Paths II
题目跳转:https://www.lintcode.com/problem/unique-paths-ii/description
相比于Unique Paths,多了障碍(标为1),动态转移方程为:
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
基于障碍,本题有如下特性:
- 首行和首列若遇障碍,障碍及后面
dp=0
- 有障碍的地方
dp=0
class Solution:
"""
@param obstacleGrid: A list of lists of integers
@return: An integer
"""
def uniquePathsWithObstacles(self, obstacleGrid):
# write your code here
# 2020/7/13
m, n = len(obstacleGrid[0]), len(obstacleGrid)
dp = [[0 for _ in range(m)] for _ in range(n)]
for i in range(m):
if obstacleGrid[0][i]: break
dp[0][i] = 1
for i in range(n):
if obstacleGrid[i][0]: break
dp[i][0] = 1
for i in range(1, m):
for j in range(1, n):
if not obstacleGrid[j][i]:
dp[j][i] += (dp[j - 1][i] + dp[j][i - 1])
return dp[-1][-1]
5.3 Maximum Subarray
题目跳转:https://www.lintcode.com/problem/maximum-subarray/description
class Solution:
"""
@param nums: A list of integers
@return: A integer indicate the sum of max subarray
"""
def maxSubArray(self, nums):
# write your code here
# 2020/7/12
sum, ans = 0, nums[0]
for n in nums:
if sum <= 0:
sum = n
else:
sum += n
ans = max(ans, sum)
return ans
sum
是num
之前不管多少个数的累加,如果当前 sum<=0
,对于把num
变大没有帮助了,直接sum=num
,反之就加上去sum+=num
。sum
累死累活遍历各个num
,若<=0
还得被嫌弃,而ans
就比较无情了,只取自身和sum
的最大值max(sum, ans)
。 (ans
:身不由己啊…)
每个遍历的状态都取决于上一状态,也有点动态规划的感觉。
5.4 Maximum Submatrix
题目跳转:https://www.lintcode.com/problem/maximum-submatrix/description
class Solution:
"""
@param matrix: the given matrix
@return: the largest possible sum
"""
def maxSubmatrix(self, matrix):
# write your code here
# 2020/7/12
if not len(matrix): return 0
n = len(matrix[0])
if not n: return 0
ans = matrix[0][0]
tmp = [0 for _ in range(n)]
for i in range(n): # 第i行
for z in range(n): # 首行换行后,tmp每个值重新累加
tmp[z] = 0
for j in range(i, n): # 第j行
# j每次换行,sum_和ans_j都得重置,本行结束后再更新ans=max(ans, ans_j)
sum_ = 0
ans_j = tmp[0] + matrix[j][0]
for k in range(n): # 遍历行内累计值
tmp[k] += matrix[j][k]
# if sum_ < 0: sum_ = tmp[k], else: sum_ += tmp[k]
sum_ = max(sum_ + tmp[k], tmp[k])
ans_j = max(sum_, ans_j)
ans = max(ans, ans_j)
return ans
思路源自题解,主要把二维压缩成一维,剩余解法类似题目Maximum Subarray。
5.5 Climbing Stairs
题目跳转:https://www.lintcode.com/problem/climbing-stairs/description
因只能跳1
或2
步,故动态转移方程为:
dp[i] = dp[i - 2] + dp[i - 1]
根据先验知识初始化dp[1]=1
和dp[2]=2
。
这里dp[0]
无实际意义,闲置。
class Solution:
"""
@param n: An integer
@return: An integer
"""
def climbStairs(self, n):
# write your code here
# 2020/7/13
if n == 0: return 0
if n == 1: return 1
dp = [0 for _ in range(n + 1)]
dp[1], dp[2] = 1, 2
for i in range(3, n + 1):
dp[i] = dp[i - 2] + dp[i - 1]
return dp[-1]
5.6 Minimum Path Sum
题目跳转:https://www.lintcode.com/problem/minimum-path-sum/description
动态转移转移方程可见代码:
class Solution:
"""
@param grid: a list of lists of integers
@return: An integer, minimizes the sum of all numbers along its path
"""
def minPathSum(self, grid):
# write your code here
# 2020/7/13
m, n = len(grid[0]), len(grid)
dp = [[0 for _ in range(m)] for _ in range(n)]
dp[0][0] = grid[0][0]
for i in range(1, m):
dp[0][i] = dp[0][i - 1] + grid[0][i]
for i in range(1, n):
dp[i][0] = dp[i - 1][0] + grid[i][0]
for i in range(1, n):
for j in range(1, m):
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]
return dp[-1][-1]
5.7 Perfect Squares
题目跳转:https://www.lintcode.com/problem/perfect-squares/description
动态转移方程如代码。
因前三i
的个数均为自身dp[i]=i
,故i
从4
开始遍历。
class Solution:
"""
@param n: a positive integer
@return: An integer
"""
def numSquares(self, n):
# write your code here
# 2020/7/13
dp = [i for i in range(n + 1)]
for i in range(4, n + 1):
for j in range(1, i): # 这里的上限无所谓,后面会用条件判断break
if i - j * j < 0: break
dp[i] = min(dp[i], dp[i - j * j] + 1)
return dp[n]
5.8 Jump Game
题目跳转:https://www.lintcode.com/problem/jump-game/description
思路源自题解。i+A[i]
是这一格能跳的最远距离,k
是至今能走的最远距离,最远距离之前的每个格子都能作为起跳点,并不断更新k
,若i
超出了k
,证明无法抵达终点。
class Solution:
"""
@param A: A list of integers
@return: A boolean
"""
def canJump(self, A):
# write your code here
# 2020/7/14
k = 0
for i in range(len(A)):
if i > k: return False
k = max(k, i + A[i])
return True
5.9 Triangle
题目跳转:https://www.lintcode.com/problem/triangle/description
从top不断加到bottom,排除最左和最右的边界条件,选上一层相邻两个数较小的来加。triangle_[i][j] += min(triangle_[i - 1][j - 1], triangle_[i - 1][j])
。
class Solution:
"""
@param triangle: a list of lists of integers
@return: An integer, minimum path sum
"""
def minimumTotal(self, triangle):
# write your code here
# 2020/7/13
triangle_ = triangle[:]
for i in range(1, len(triangle_)):
for j in range(len(triangle_[i])):
if j == 0:
triangle_[i][j] += triangle_[i - 1][j]
elif j == len(triangle_[i]) - 1:
triangle_[i][j] += triangle_[i - 1][j - 1]
else:
triangle_[i][j] += min(triangle_[i - 1][j - 1], triangle_[i - 1][j])
return min(triangle_[-1])
5.10 Longest Increasing Subsequence
题目跳转:https://www.lintcode.com/problem/longest-increasing-subsequence/description
遍历i
,并让dp[i]
等于前面比nums[i]
小的数里,dp
最大的值加一。
因答案不一定在dp
的末尾,所以每次dp[i]
确定后,都取max(dp[i], res)
。
class Solution:
"""
@param nums: An integer array
@return: The length of LIS (longest increasing subsequence)
"""
def longestIncreasingSubsequence(self, nums):
# write your code here
# 2020/7/14
if not len(nums): return 0
res = 0
dp = [0] * len(nums)
dp[0] = 1
for i in range(1, len(nums)):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j])
dp[i] += 1
res = max(dp[i], res)
return res
5.11 Longest Palindromic Substring
题目跳转:https://www.lintcode.com/problem/longest-palindromic-substring/description
长度为1和2的子串就像种子,长度再长一些的就要看他内部的子串是否为回文串。
class Solution:
"""
@param s: input string
@return: the longest palindromic substring
"""
def longestPalindrome(self, s):
# write your code here
# 2020/7/15
if not len(s): return ""
dp = [[False for _ in range(len(s))] for _ in range(len(s))]
head, tail = 0, 0
for i in range(len(s)): # gap between head and tail
for j in range(len(s) - i):
if s[j] == s[j + i] and (i < 2 or dp[j + 1][j + i - 1]):
dp[j][j + i] = True
if i > tail - head:
head = j
tail = j + i
return s[head: tail + 1]
在华为机试也遇到同样问题了,用DP方式会超时,于是借鉴了讨论区的回答:
因为是找最长回文串,那么maxlen
是可以潇洒地增大,而且字符串前面如果maxlen
已经变长了,后面就不用再判断短的回文串了。
而且长的回文串也是基于短回文串的,所以后面都判断[i-maxlen-1: i+1]
和[i-maxlen: i+1]
就好了。
第一种条件是当i
和maxlen
不相等,即回文串没有顶到最前面的时候,会试探性向前多一位。
第二种条件是为前者兜底的,试探不了的话,就按照保守的长度(比maxlen
长一位)去判断。
总而言之就是深得了回文串的精髓。
def main2(string):
maxlen = 0
for i in range(len(string)):
if i-maxlen >= 1 and string[i-maxlen-1: i+1] == string[i-maxlen-1: i+1][::-1]:
maxlen += 2
elif i-maxlen >= 0 and string[i-maxlen: i+1] == string[i-maxlen: i+1][::-1]:
maxlen += 1
return maxlen
if __name__ == "__main__":
while True:
try:
string = input()
except:
break
res = main2(string)
print(res)
5.12 购物单问题
题目跳转:https://www.nowcoder.com/practice/f9c6f980eeec43ef85be20755ddbeaf4?tpId=37&&tqId=21239&rp=1&ru=/ta/huawei/&qru=/ta/huawei/question-ranking
解题思路与实现~:华为机试:购物单问题(动态规划)
6. Heap
寻找最xx的k个值,循环调取最值(Merge问题),使用 堆结构(Heap) 可以很好解决这类问题。
6.1 K Closest Points
题目跳转:https://www.lintcode.com/problem/k-closest-points/description
heapq.nsmallest
和heapq.nlargest
已经有比较完善的解决方案,其中lambda
可以适配个性化的数据结构。
import heapq
"""
Definition for a point.
class Point:
def __init__(self, a=0, b=0):
self.x = a
self.y = b
"""
class Solution:
"""
@param points: a list of points
@param origin: a point
@param k: An integer
@return: the k closest points
"""
def kClosest(self, points, origin, k):
# write your code here
# 2020/7/11
# return heapq.nsmallest(k, points, lambda p: [(p.x - origin.x) ** 2 + (p.y - origin.y) ** 2, p.x, p.y])
heap_dis, res, dis2point = [], [], {}
for p in points:
d = (p.x - origin.x) ** 2 + (p.y - origin.y) ** 2
if d not in dis2point:
dis2point[d] = [p]
heapq.heappush(heap_dis, d)
else:
dis2point[d].append(p)
while len(res) < k:
d = heapq.heappop(heap_dis)
if len(dis2point[d]) + len(res) <= k:
res += sorted(dis2point[d], key=lambda p: [p.x, p.y])
else:
res += sorted(dis2point[d], key=lambda p: [p.x, p.y])[:(k - len(res))]
return res
6.2 Merge K Sorted Lists
题目跳转:https://www.lintcode.com/problem/merge-k-sorted-lists/description
得益于每个链表都已排序,直接把每个链接的头结点(最小值)放进小顶堆,不断取了放新的进来。
题解还可使用分治法,先mark了。
import heapq
"""
Definition of ListNode
class ListNode(object):
def __init__(self, val, next=None):
self.val = val
self.next = next
"""
class Solution:
"""
@param lists: a list of ListNode
@return: The head of one sorted list.
"""
def mergeKLists(self, lists):
# write your code here
# 2020/7/11
origin = ListNode(0)
p = origin
l = lists
heads = []
for idx in range(len(l)):
if l[idx]:
heapq.heappush(heads, (l[idx].val, idx))
l[idx] = l[idx].next
while heads:
val, idx = heapq.heappop(heads)
p.next = ListNode(val)
p = p.next
if l[idx]:
heapq.heappush(heads, (l[idx].val, idx))
l[idx] = l[idx].next
return origin.next
6.3 Merge Two Sorted Interval Lists
题目跳转:https://www.lintcode.com/problem/merge-two-sorted-interval-lists/description
Merge问题,和Merge K Sorted Lists差不多,都是用小顶堆处理(前提是各子序列已排序)。
本题是要start
按序取出,判断是否在res
的区间内,在则合并,不在则新增。
import heapq
"""
Definition of Interval.
class Interval(object):
def __init__(self, start, end):
self.start = start
self.end = end
"""
class Solution:
"""
@param list1: one of the given list
@param list2: another list
@return: the new sorted list of interval
"""
def mergeTwoInterval(self, list1, list2):
# write your code here
# 2020/7/11
ls = [list1, list2]
heads = []
res = []
for idx, l in enumerate(ls):
if len(l):
heapq.heappush(heads, (l[0].start, l[0].end, idx))
l.pop(0)
while heads:
start, end, idx = heapq.heappop(heads)
if not len(res):
res.append(Interval(start, end))
elif start <= res[-1].end:
res[-1].end = max(res[-1].end, end)
else:
res.append(Interval(start, end))
if len(ls[idx]):
heapq.heappush(heads, (ls[idx][0].start, ls[idx][0].end, idx))
ls[idx].pop(0)
return res
7. Binary Search & Log(n) Algorithm
针对给定数组有一定的规律的(如已排序、山峰状等),可以根据规律做Binary Search。
或针对题目的规律,实现二二拆解,达到Log(n)的复杂度。
7.1 Maximum Number in Mountain Sequence
题目跳转:https://www.lintcode.com/problem/maximum-number-in-mountain-sequence/description
循环到最后剩余nums[head]
和nums[tail]
,返回两者的最大值。
class Solution:
"""
@param nums: a mountain sequence which increase firstly and then decrease
@return: then mountain top
"""
def mountainSequence(self, nums):
# write your code here
# 2020/7/15
head, tail = 0, len(nums) - 1
while head < tail - 1:
mid = head + (tail - head) // 2
if nums[mid] > nums[mid - 1]:
head = mid
else:
tail = mid
return max(nums[head], nums[tail])
7.2 Pow(x, n)
题目跳转:https://www.lintcode.com/problem/powx-n/description
记得考虑n >0
的情况。
class Solution:
"""
@param x {float}: the base number
@param n {int}: the power number
@return {float}: the result
"""
def myPow(self, x, n):
# write your code here
# 2020/7/16
if n < 0:
n = -n
x = 1 / x
if n == 1: return x
if n == 0: return 1
if n % 2 == 0:
return self.myPow(x * x, n / 2)
return self.myPow(x * x, n // 2) * x
7.3 Find Minimum in Rotated Sorted Array
题目跳转:https://www.lintcode.com/problem/find-minimum-in-rotated-sorted-array/description
题解讲得非常好,梳理了Binary Search的分析过程。只要画出“左、中、右”的相互关系,就能推断出两个指针的变化规律。
而且因mid = head + (tail - head) // 2
是更接近head
的,即mid >= head
和mid < right
,所以head=mid+1
才有可能跳出循环,而right=mid
则无所谓。
class Solution:
"""
@param nums: a rotated sorted array
@return: the minimum number in the array
"""
def findMin(self, nums):
# write your code here
# 2020/7/16
head, tail = 0, len(nums) - 1
while head < tail:
mid = head + (tail - head) // 2
if nums[mid] < nums[tail]:
tail = mid
else:
head = mid + 1
return nums[head]
7.4 Fast Power
题目跳转:https://www.lintcode.com/problem/fast-power/description
根据公式:
a
n
%
b
=
(
a
n
/
2
%
b
⋅
a
n
/
2
%
b
)
%
b
a^n \% b = (a^{n/2} \% b \cdot a^{n/2} \% b) \% b
an%b=(an/2%b⋅an/2%b)%b
res
就会变得很小(比b
小)。
class Solution:
"""
@param a: A 32bit integer
@param b: A 32bit integer
@param n: A 32bit integer
@return: An integer
"""
def fastPower(self, a, b, n):
# write your code here
# 2020/7/16
if n == 0: return 1 % b
if n == 1: return a % b
if n % 2 == 0:
res = self.fastPower(a, b, n / 2)
return (res * res) % b
res = self.fastPower(a, b, n // 2)
a %= b
return (a * res * res) % b
7.5 Find Peak Element
题目跳转:https://www.lintcode.com/problem/find-peak-element/description
画出“中,中+1”的关系后,即可找出规律。再分析边界条件,确定循环可收敛。
class Solution:
"""
@param A: An integers array.
@return: return any of peek positions.
"""
def findPeak(self, A):
# write your code here
# 2020/7/16
left, right = 0, len(A) - 1
while left < right:
mid = left + (right - left) // 2
if A[mid] < A[mid + 1]:
left = mid + 1
else:
right = mid
return left
7.6 First Bad Version
题目跳转:https://www.lintcode.com/problem/first-bad-version/description
若SVNRepo.isBadVersion(mid) is True
,就像左找,并且不用担心miss了答案;反之,则向右找,因SVNRepo.isBadVersion(mid) is False
,那left=mid+1
也不担心miss了答案。
反正只要找好规律,再确保干掉边界条件,循环能收敛就好。
#class SVNRepo:
# @classmethod
# def isBadVersion(cls, id)
# # Run unit tests to check whether verison `id` is a bad version
# # return true if unit tests passed else false.
# You can use SVNRepo.isBadVersion(10) to check whether version 10 is a
# bad version.
class Solution:
"""
@param n: An integer
@return: An integer which is the first bad version.
"""
def findFirstBadVersion(self, n):
# write your code here
# 2020/7/16
left, right = 1, n
while left < right:
mid = left + (right - left) // 2
if SVNRepo.isBadVersion(mid):
right = mid
else:
left = mid + 1
return left
7.7 Search in Rotated Sorted Array
题目跳转:https://www.lintcode.com/problem/search-in-rotated-sorted-array/description
先判断在mid的左边还是右边有序,再看target
是否在有序区间内,根据结果移动left
和right
。
class Solution:
"""
@param A: an integer rotated sorted array
@param target: an integer to be searched
@return: an integer
"""
def search(self, A, target):
# write your code here
# 2020/7/17
if not len(A): return -1
left, right = 0, len(A) - 1
while left < right:
mid = left + (right - left) // 2
if A[left] <= A[mid]: # 左有序
if target >= A[left] and target <= A[mid]:
right = mid
else:
left = mid + 1
else: # 右有序
if target >= A[mid] and target <= A[right]:
left = mid
else:
right = mid - 1
if A[left] == target: return left
return -1
8. Stack
8.1 Longest Valid Parentheses
题目跳转:https://www.lintcode.com/problem/longest-valid-parentheses/description
思路自LeetCode评论区。先用Stack校验String里括号的有效性,把有效的括号都标为0,无效的为1。
最后只需计算最长的0串长度即可。
(果然括号这类问题还是用Stack最舒服…)
第一次用C++做题,要慢慢习惯~~
class Solution {
public:
/**
* @param s: a string
* @return: return a integer
*/
int longestValidParentheses(string &s) {
// write your code here
// 2020/10/17
// () (()) ()(())()
int res = 0;
int i = 0;
int len = 0;
stack<int> st;
vector<bool> mark(s.length());
for( i = 0; i < mark.size(); i++ ) mark[i] = false;
for( i = 0; i < s.length(); i++ ) {
if( s[i] == '(' ) st.push(i);
else { // ')'
if( st.empty() ) mark[i] = true;
else st.pop();
}
}
while( !st.empty() ) {
mark[st.top()] = true;
st.pop();
}
for( i = 0; i < mark.size(); i++ ) {
if( !mark[i] ) {
len++;
} else {
len = 0;
}
res = max(res, len);
}
return res;
}
};