难点在于原地删除的同时不想时间复杂度太高的话,可以考虑双指针。一个指针遍历数组判断是否为重复元素,一个指针用来搜集不重复元素并把他们安排到数组前列。
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
if not nums:
return 0
#index为接下来不重复元素的插入位置
index = 0
for i in range(1,len(nums)):
if nums[i] != nums[i-1]:
index += 1
nums[index] = nums[i]
return index + 1
1.分析题目要求,最大的利润是在每一次价格的波峰波谷完成买卖。所以统计每一段价格上升的幅度就是答案。
2.利用dp思想,dp[i][0]和dp[i][1]分别记录第i天不持有股票和持有股票的最大收益,则dp[len-1][0]就是要求的输出,又因为每天状态和前一天有关,可以把dp数组压缩为2个变量。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
dp0 = 0
dp1 = -prices[0]
for i in range(1,len(prices)):
dp0,dp1 = max(dp0,dp1+prices[i]),max(dp1,dp0-prices[i])
return dp0
1.环状替代
2.利用3次反转
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n = len(nums)
k %= n
nums[:] = nums[::-1]
nums[:k],nums[k:]= nums[:k][::-1],nums[k:][::-1]
利用hash表达到最优时间效率,python中set类型即可
异或操作可以把出现2次的数字归零,让一个变量等于0然后异或数组中每个数字,剩下的结果就是答案。
1.把一个数组元素存成hash表记录出现次数;遍历另外一个数组元素,如果在hash表出现且次数大于0则加入结果。
2.把两个数组排好序,双指针遍历过去,相等就加入结果,否则根据移动较小元素的指针。
在用数组保存的数字上执行加一操作,用变量carry标志是否进位,从数组尾到头遍历更新数组元素和carry即可。若最后carry为1,说明在数组最右扩招一位。
双指针,一个用来遍历数组,一个用来记录非0元素应该放入的位置/
经典题目,利用hash表即可。
设置9*3个hash表,记录横,竖以及子数独各个数字出现情况。
class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
rows = [{} for i in range(9)]
columns = [{} for i in range(9)]
boxes = [{} for i in range(9)]
for i in range(9):
for j in range(9):
num = board[i][j]
if num != '.':
num = int(num)
box_index = (i // 3 ) * 3 + j // 3
rows[i][num] = rows[i].get(num, 0) + 1
columns[j][num] = columns[j].get(num, 0) + 1
boxes[box_index][num] = boxes[box_index].get(num, 0) + 1
if rows[i][num] > 1 or columns[j][num] > 1 or boxes[box_index][num] > 1:
return False
return True
矩阵旋转问题
1.矩阵转置以后,每行反转。
2.分成4个矩形块处理,遍历一个矩形块中每个元素时同时考虑其他3块内对应位置元素互换。
原地反转字符串,s[i]与s[n-1-i]交换即可
整数反转可利用如下代码,但是需要主要python中负数整除往下约等,-123//10 = -13,所以为了方便处理,对x输入取绝对值。
while x:
res = 10*res+ x%10
x //= 10
遍历字符串,用hash表存储出现状况即可。但是因为要求是第一次字符:
1.可以再遍历一遍字符串,先出现的hash值为1的就是要求。
2.python的字典是有序字典,直接遍历dict.keys()也可
遍历两个字符串,一个再hash表对应字母数量+1,一个-1,最后如果hash有元素值不为0就是false
头尾双指针,往中间遍历,遇到数字字母就检查判断。
边界细节比较多,最大最小值判断,符号以及符号位过滤,非法字符。
class Solution:
def myAtoi(self, str: str) -> int:
str = str.strip() # 删除首尾空格
if not str:
return 0 # 字符串为空则直接返回
int_max, int_min = 2 ** 31 - 1, -2 ** 31
boudry = 2 ** 31 // 10
res, i, sign = 0, 1, 1
if str[0] == '-': sign = -1 # 保存负号
elif str[0] != '+': i = 0 # 若无符号位,则需从 i = 0 开始数字拼接
for c in str[i:]:
if not '0' <= c <= '9' :
break
if res > boudry or(res == boudry and c > '7'):
return int_max if sign == 1 else int_min
res = 10 * res + ord(c) - ord('0')
return sign*res
暴力匹配或者kmp算法
nex = [0]*len(needle)
kmp = 0
for i in range(1,len(needle)):
while kmp >0 and needle[kmp] != needle[i]:
kmp = nex[kmp-1]
if needle[kmp] == needle[i]:
kmp += 1
nex[i] = kmp
j = 0
for i in range(len(haystack)):
while j >0 and needle[j] != haystack[i]:
j = nex[j-1]
if needle[j] == haystack[i]:
j += 1
if j == len(needle):
return i - j + 1
return -1
按层次遍历计数即可
base = '1'
for i in range(n-1):
num = 1
s = []
for j in range(1,len(base)):
if base[j] == base[j-1]:
num += 1
else:
s.append(str(num))
s.append(base[j-1])
num = 1
else:
s.append(str(num))
s.append((base[len(base)-num]))
base =''.join(s)
return base
采用水平或者垂直扫描法
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
if len(strs) == 0:
return ''
s = strs[0]
for i in range(1, len(strs)):
while strs[i].find(s) != 0 :
s = s[:-1]
return s
一般我们首先想到的思路是找到要删除节点的前一个节点,然后执行p.next = p.next.next
把节点从链表上去除。
但是找到前一个节点就要考虑一些边界条件,所以我们可以采用复制的方法
node.val = node.next.val
node.next = node.next.next
用下一个节点的信息覆盖掉要删除的节点,达到一样的目的。
利用快慢指针,相隔N节点,一次遍历就可以找到要删除的节点位置。
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
if not head or n ==0:
return None
new_head = p1 = p2 = ListNode()
# p2定位到要删除的节点之前。
new_head.next = head
for i in range(n):
p1 = p1.next
if not p1:
break
while p1.next:
p1 = p1.next
p2 = p2.next
p2.next = p2.next.next
return new_head.next
1.正常遍历,利用python多变量赋值语句可以剩下很多工作
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
p1 = None
p2 = head
while p2:
p2.next,p1,p2 = p1,p2,p2.next
return p1
2.递归,不同于常规问题中递归更好理解,该题的递归模式反而更难理解
我们假设递归的子问题为后续链表已经完成了逆转,那么接下来处理就是怎么把现在处理的节点加入到反转后的链表中。比如下面的 n k n_k nk怎么加入链表,让 n k − > n e x t n_k->next nk−>next(就是 n k + 1 n_{k+1} nk+1)指向 n k n_k nk即可。
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
if not head or not head.next:
return head
new_head = self.reverseList(head.next)
head.next.next = head
head.next = None
return new_head
类似归并排序处理即可
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
new_head = p = ListNode()
while l1 and l2:
if l1.val < l2.val:
p.next = l1
l1= l1.next
p = p.next
else:
p.next = l2
l2= l2.next
p = p.next
#合并剩余部分
if l1:
p.next = l1
else:
p.next = l2
return new_head.next
反转后半链表,然后逐一比较即可
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
if not head or not head.next:
return True
fast = head.next
slow = head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
#slow就是前半链表的末尾
p1 = None
p2 = slow.next
while p2:
p2.next,p1,p2, = p1,p2,p2.next
#反转链表,p1为反转后的链表头
while p1 and head:
if p1.val != head.val:
return False
else:
p1 = p1.next
head = head.next
return True
快慢指针结合python一点小语法
class Solution:
def hasCycle(self, head: ListNode) -> bool:
fast = slow =head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
if fast == slow:
break
else:
return False
return True
树的深度等于左右子树的最大深度再加1,所以很容易处理成递归形式。
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root:
return 0
return max(self.maxDepth(root.left),self.maxDepth(root.right))+1
二叉搜索树的性质为根节点大于左子树而小于右子树,我们可以设计一个辅助递归函数设置最大值和最小值,如果节点不在这个范围内就失败,如果符合,则遍历它的左右子树同时更新左右子树值的应在范围。
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
def helper(root,max_value,min_value):
if not root:
return True
elif root.val <= min_value or root.val >= max_value:
return False
else:
#左右子树值随着root.val更新
return helper(root.left,root.val,min_value) and helper(root.right,max_value,root.val)
return helper(root,float('inf'),float('-inf'))
画图对照一下就可以发现,如果一个二叉树的对称的,那么它的镜像反转二叉树要和它相等,既root.left == root2.right and root.right == root2.left
,递归很好解决,这里给出迭代代码。
import queue
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
q = queue.Queue()
q.put(root)
q.put(root)
#当作一个对称树
while q.qsize():
n1 = q.get()
n2 = q.get()
if not n1 and not n2:
continue
elif not n1 or not n2 or n1.val != n2.val:
return False
else:
q.put(n1.left)
q.put(n2.right)
q.put(n1.right)
q.put(n2.left)
return True
用队列完成辅助,每次处理一层的节点,并存入返回结果里面。
import queue
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
q = queue.Queue()
q.put(root)
res = []
if not root:
return res
while q.qsize():
temp = []
for i in range(q.qsize()):
x = q.get()
temp.append(x.val)
if x.left:
q.put(x.left)
if x.right:
q.put(x.right)
res.append(temp)
return res
因为是有序数组,很容易就可以想到一种构建方式,用数组的中间元素做树根,划分数组为左右子数组然后递归构建。(构建方式不止这一种,但是这种省心,左右子树节点个数还相当,比较平衡)
class Solution:
def sortedArrayToBST(self, nums: List[int]) -> TreeNode:
def helper(l,r):
if l > r:
return None
mid = l + (r -l)//2
root = TreeNode(nums[mid])
root.left = helper(l,mid-1)
root.right = helper(mid+1,r)
return root
return helper(0,len(nums)-1)
仍然是归并排序的思想,但是因为要原地合并,细节处理上有所不同。需要从尾开始向前填充数字。
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
if not nums2:
return
p1 = m - 1
p2 = n - 1
p = m + n - 1
while p1 >= 0 and p2 >=0:
if nums1[p1] < nums2[p2]:
nums1[p] = nums2[p2]
p2 -= 1
else:
nums1[p] = nums1[p1]
p1 -= 1
p -= 1
if p2 >= 0:
nums1[:p+1] = nums2[:p2+1]
抽象出来的话就是个二分搜索问题。
class Solution:
def firstBadVersion(self, n):
"""
:type n: int
:rtype: int
"""
l = 1
r = n
while l < r:
mid = l + (r-l)//2
if isBadVersion(mid):
r = mid
#如果r = mid - 1,可能会跳过答案要把while改成l<=r
else:
l = mid + 1
return l
经典dp,当作初始为0的斐波那契数列
class Solution:
def climbStairs(self, n: int) -> int:
dp1 = 0
dp2 = 1
for i in range(n):
dp1,dp2 = dp2,dp1+dp2
return dp2
保存历史最低价,遍历时每次计算当前可得最大收益
class Solution:
def maxProfit(self, prices: List[int]) -> int:
if not prices:
return 0
minp = prices[0]
res = 0
for i in range(1,len(prices)):
res = max(res,prices[i] - minp)
minp = min(minp,prices[i])
return res
满足贪心结构,当然写成dp形式也可以
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
out = nums[0]
temp = nums[0]
for i in nums[1:]:
if temp < 0:
temp = i
else:
temp += i
out = max(out,temp)
return out
对于一家屋子有打劫和不打劫两种状态,分别记录一下这两种状态的最大值收益。则每一间屋子的收益最大值仅取决于前一间屋子。
class Solution:
def rob(self, nums: List[int]) -> int:
dp1 = dp2 = 0
for i in range(len(nums)):
dp1,dp2 = dp2 + nums[i],max(dp1,dp2)
#如果当前屋子打劫,收益就是前一个屋子不打劫+当前屋子价值
#如果当前屋子不打劫,收益就是前一个屋子打劫和不打劫的最大值
return max(dp1,dp2)