2021-06-07
1.两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案。
暴力遍历 :固定一个数,遍历剩下的数,时间复杂度O(N^2)
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# 简单暴力
for i in range(len(nums)-1):
for j in range(i+1,len(nums)):
if nums[i] + nums[j] == target:
return [i,j]
HashTable:将每个数及其序号存到字典中,遍历一个数val时,直接用字典索引到target-val的下标。此处有两个情况需要考虑:1、构建字典时,当出现相同的数字,后出现的数字索引会覆盖先出现的数字索引,所以最后从前往后遍历时并不会干扰当前遍历的序号和字典索引的序号。2、当target刚好是val的2倍时,因为搜索字典会继续搜索val,所以搜索到的序号还是本身,就是重复用了本身2次。所以在这里需要加入条件,确保字典搜索到的不是现在遍历的序号。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# hash表
dic = {}
for i,val in enumerate(nums):
dic[val] = i
for i,val in enumerate(nums):
if target - val in dic and dic[target-val] != i:
return [i,dic[target-val]]
2021-06-08
2、两数相加
给你两个 非空的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位数字。请你将两个数相加,并以相同形式返回一个表示和的链表。你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
链表问题,模拟加法过程:
第一次,想着先把两个链表的值节点值提取出来,然后为了防止遍历时两个列表长度不一样,补成相同长度。然后设置当前位、进位、以及最后进位可能比原始长度多一位的情况。时间复杂度O(max(m,n))
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
a = []
b = []
while l1:
a.append(l1.val)
l1 = l1.next
while l2:
b.append(l2.val)
l2 = l2.next
head = ListNode(0)
l = head
# 补位
if len(a) > len(b):
b += [0]*(len(a)-len(b))
if len(a) < len(b):
a += [0]*(len(b)-len(a))
# 当前序号
i = 0
# 进位
n = 0
while True:
if i > len(a)-1:
break
if a[i]+b[i]+n > 9:
s = (a[i]+b[i]+n) % 10
n = 1
else:
s = a[i] + b[i] + n
n = 0
l.next = ListNode(s)
l = l.next
i += 1
if n == 1:
l.next = ListNode(1)
return head.next
第二次,看了题解,只需要遍历一次l1和l2,然后对于长度不同的情况也简化。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
head = ListNode(0)
l = head
# 进位
n = 0
while l1 or l2 or n:
# 初始化当前和
s = 0
if l1:
s += l1.val
l1 = l1.next
if l2:
s += l2.val
l2 = l2.next
if n:
s += n
val = s % 10
n = s // 10
l.next = ListNode(val)
l = l.next
return head.next
3、无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
滑动窗口:
第一次,确定字符串的左边界,然后对右边界进行遍历,每次把值记入set中,当右指针所指的数字已经出现在set中时,那么说明此时是以左指针为开头的最长不重复子串了,将其最长长度记录一下。接着遍历下一个左边界。执行速度较慢。
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
res = 0
for i in range(len(s)):
left = i
right = i
occur = set()
while right < len(s) and s[right] not in occur:
occur.add(s[right])
right += 1
res = max(res,len(occur))
return res
第二次,在看了题解后,自己的题解中,每次换左边界都需要重新更换set,这个比较浪费时间。所以,采用,在遍历左边界的基础上,每次换左边界,就把左边界的值从set中弹出,然后接着遍历右边界,若右边界不满足条件,则跳出,计算当前最大值。时间复杂度为O(N)
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
res = 0
right = 0
occur = set()
for left in range(len(s)):
if left != 0:
occur.remove(s[left-1])
# 向右移动右指针
while right < len(s) and s[right] not in occur:
occur.add(s[right])
right += 1
res = max(res,len(occur))
return res
4、寻找两个正序数组的中位数
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
合并、排序、计算:
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
if not nums1 and not nums2:
return float(0)
nums = nums1 + nums2
nums.sort()
n = len(nums)
if n % 2 == 1:
return float(nums[n//2])
else:
return (nums[n//2-1]+nums[n//2])/2
这个题之后有待发掘!!!
5、最长回文子串
给你一个字符串 s,找到 s 中最长的回文子串。
中心扩展法,分为以间隔为中心和以字母为中心两种情况。
class Solution:
def longestPalindrome(self, s: str) -> str:
def expand(left,right):
while left >= 0 and right < len(s) and s[left] == s[right]:
left -= 1
right += 1
return left + 1, right - 1
start, end = 0, 0
for i in range(len(s)):
left1, right1 = expand(i,i+1)
left2, right2 = expand(i,i)
if right1-left1 > end-start:
start, end = left1, right1
if right2-left2 > end-start:
start,end = left2, right2
return s[start:end+1]
6、盛水最多的容器
给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。说明:你不能倾斜容器。
双指针:左指针最初在最左边,右指针最初在最右边。因为此时底边最长,所以有可能是最大面积,故计算一下。然后比较左指针和右指针的大小,如果左指针更大,说明此时盛水量是由右指针决定的,因从,在缩小底边的情况下,只有让两边中最小边变大,才有可能面积变大。所以此时向左移动右指针,寻找可能存在的更高的右边。以这样的思想继续移动,直到左右指针重合。
class Solution:
def maxArea(self, height: List[int]) -> int:
# 左右指针
n = len(height)
left, right = 0, n-1
res = 0
while right > left:
# 左边比右边高的时候,只有移动右边才可能使值更大
if height[left] > height[right]:
s = height[right] * (right-left)
right -= 1
# 同理,此处移动左边
else:
s = height[left] * (right-left)
left += 1
res = max(res,s)
return res
2021-06-09
7、三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。注意:答案中不可以包含重复的三元组。
深度优先搜索超时O(N^3):
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
# dfs 超时:315/318
nums.sort()
self.res = []
def dfs(nums,k,s,l):
if k == 0:
if s == 0:
self.res.append(l)
flag = 1
return
visited = []
for i in range(len(nums)):
if nums[i] not in visited:
dfs(nums[i+1:],k-1,s+nums[i],l+[nums[i]])
visited.append(nums[i])
dfs(nums,3,0,[])
return self.res
双指针:深度优先搜索是O(N^3),时间复杂度太高。所以使用双指针,将问题转化为两数之和的求解。两数之和的前提得先固定第一个数,剩下两个数做和。所以外层首先是一个O(N)的循环,遍历第一个数,第二个和第三个数求和,这个和要满足 target = 0-第一个数。对数组排序后,设置左右两个指针,分别往中间走,相遇时结束,相当于时间复杂度O(N),故这样做(包含排序)总体的时间复杂度降低为
O(N^2)。主要是去重部分需要仔细思考,由于不满足和为target的数不需要加入到res里,所以不用考虑。所以只考虑和为target的重复。
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
res = []
# 第一个数字遍历
# 后两个数字双指针
n = len(nums)
if n < 3:
return []
visited_1 = []
for i in range(n-2):
if nums[i] in visited_1:
continue
visited_1.append(nums[i])
target = 0 - nums[i]
left = i+1
right = n-1
while left < right:
# 大于和小于target的情况都不会往res里添加值,所以这里重复无所谓
if nums[left] + nums[right] < target:
left += 1
elif nums[left] + nums[right] > target:
right -= 1
# 主要是这里去重
elif nums[left] + nums[right] == target:
res.append([nums[i],nums[left],nums[right]])
a = nums[left]
b = nums[right]
while nums[left] == a and left < right:
left += 1
while nums[right] == b and left < right:
right -= 1
return res
8、电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
深度优先搜索,在dfs中每次放入当前遍历的序号,然后对当前遍历的序号对应得字母进行遍历。进而进入下一层。
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
dic = {}
dic["2"] = ["a","b","c"]
dic["3"] = ["d","e","f"]
dic["4"] = ["g","h","i"]
dic["5"] = ["j","k","l"]
dic["6"] = ["m","n","o"]
dic["7"] = ["p","q","r","s"]
dic["8"] = ["t","u","v"]
dic["9"] = ["w","x","y","z"]
if not digits:
return []
res = []
n = len(digits)
def dfs(i,cur):
if i == n:
res.append(cur)
return
for j in dic[digits[i]]:
dfs(i+1,cur+j)
dfs(0,'')
return res
9、删除链表的倒数第N个结点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
通过优先判断长度来得到倒数节点,一开始写了新列表保存每个结点的值,后来有重创结点,这样的话时间复杂度为O(N),这里需要遍历链表两次,但是空间复杂度也为O(N)。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
l = []
cur = head
length = 0
while cur:
l.append(cur.val)
cur = cur.next
length += 1
l[:] = l[:length-n] + l[length-n+1:]
pre = ListNode(0)
cur = pre
for i in l:
cur.next = ListNode(i)
cur = cur.next
return pre.next
将上述方法简化为空间复杂度O(1):
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
l = []
cur = head
length = 0
while cur:
cur = cur.next
length += 1
pre = ListNode(0)
cur = pre
cur.next = head
for i in range(length-n):
cur = cur.next
cur.next = cur.next.next
return pre.next
只需一次遍历:
利用快慢指针,等快指针出发了n+1后,慢指针再出发。为了避免倒数的数字是第一位的数字,要从pre结点开始。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
pre = ListNode(0)
pre.next = head
fast = pre
slow = pre
m = 0
while m != n+1:
m += 1
fast = fast.next
while fast:
fast = fast.next
slow = slow.next
slow.next = slow.next.next
return pre.next
10、有效的括号
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。有效字符串需满足:左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。
用栈将左括号都存入,如果遇到右括号,则弹出栈顶元素,看看是否配对,不配对说明是不对的。还有就是栈底不能是右括号。
class Solution:
def isValid(self, s: str) -> bool:
if len(str) % 2 == 1:
return False
dic = {}
dic[")"] = "("
dic["}"] = "{"
dic["]"] = "["
stack = []
for i in s:
if i in dic:
if not stack:
return False
elif stack.pop() != dic[i]:
return False
else:
stack.append(i)
return not stack
用replace函数:
class Solution:
def isValid(self, s: str) -> bool:
l = len(s)+1
while l != len(s):
l = len(s)
s = s.replace('{}','').replace('()','').replace('[]','')
return len(s) == 0
2021-06-10
11、合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
迭代:先设置一个哑结点,然后哑结点的下一个结点就需要判断了。要是l1或l2其中一个为空,那么直接链接l1或l2;要是两个都不为空,就链接其中值较小的那个。直到有一个链接完了,就直接把另一个的剩余链接上就好了
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
# 有空链表
if not l1:
return l2
if not l2:
return l1
pre = ListNode(0)
cur = pre
while l1 and l2:
if l1.val > l2.val:
cur.next = l2
cur = cur.next
l2 = l2.next
else:
cur.next = l1
cur = cur.next
l1 = l1.next
if not l1:
cur.next = l2
else:
cur.next = l1
return pre.next
递归:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
# 递归的时候就尽量考虑第一步,其他放在函数中整体考虑
if not l1:
return l2
elif not l2:
return l1
elif l1.val > l2.val:
l2.next = self.mergeTwoLists(l1,l2.next)
return l2
else:
l1.next = self.mergeTwoLists(l1.next,l2)
return l1
12、括号生成
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
深度优先遍历:
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
res = []
def dfs(left,right,s):
if left > n or right > n:
return
if right > left:
return
if left == right and left == n:
res.append(s)
return
for i in ["(",")"]:
if i == "(":
dfs(left+1,right,s+"(")
else:
dfs(left,right+1,s+")")
dfs(0,0,'')
return res
13、合并K个升序链表
给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。
先把所有值保存在列表中,然后排序,然后重构链表。这个出奇的快,超越99.8%???。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
l = []
for i in lists:
while i:
l.append(i.val)
i = i.next
l.sort()
pre = ListNode(0)
cur = pre
for i in l:
cur.next = ListNode(i)
cur = cur.next
return pre.next
小根堆–优先队列:先把所有链表的第一个值都存入一个小根堆中,然后,每次提取最小值,之后将最小值对应链表的下一个值再次加入小根堆中。这样一直重复,直到队列为空时截至。注意,用heapq遍历小根堆时,比较的元素只能是值。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
import heapq
class Solution:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
pre = ListNode(0)
cur = pre
que = []
# 初始化小根堆
for i in range(len(lists)):
if lists[i]:
# 注意只能放值元素
heapq.heappush(que,(lists[i].val,i))
while que:
# 弹出最小值和其id
val, idx = heapq.heappop(que)
# 将最小值链接到上一个结点上
cur.next = lists[idx]
cur = cur.next
# 将最小值所在的链表重新定义(去掉刚用到的结点)
lists[idx] = lists[idx].next
# 若重新定义后链表还有结点,则将结点加入到队列中。
if lists[idx]:
heapq.heappush(que,(lists[idx].val,idx))
return pre.next
2021-06-12
14、下一个排列
实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。必须 原地 修改,只允许使用额外常数空间。
本题的思路想了老半天,总结下来就是两点:第一点,找到要变换的位置;第二点,要找到该位置变换的值。
第一:变换的位置。在变换的位置之后,应该是已经最大的,无变可变,不可变换,即从前往后应该是降序的。比如[4,5,7,6,2],从5后边开始,[7,6,2]是严格降序的,那么变化的位置应该是5。
第二:找到要变化的位置之后,该位置应该变成什么呢?因为要比当前的数字大,所以,肯定在该位置的变化上,要选一个比当前的大的数字,上述例子中,应该在[7,6,2]中找一个比5大的数,才能保证比原来大,这里有7和6,为了比原来的大,又相对的小,故选择6。然后将6填充在5的位置上后,对后面的部分进行排序。
class Solution:
def nextPermutation(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n = len(nums)
flag = 0
idx = n-1
contrast = nums[-1]
while True:
if contrast > nums[idx]:
break
elif idx < 0:
flag = 1
break
else:
contrast = nums[idx]
idx -= 1
for i in range(n-1,idx,-1):
if nums[idx] < nums[i]:
nums[idx],nums[i] = nums[i], nums[idx]
break
if flag == 0:
nums[:] = nums[:idx+1] + sorted(nums[idx+1:])
else:
nums[:] = nums[::-1]
15、最长的有效括号
给你一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长有效(格式正确且连续)括号子串的长度。
栈:在遍历过程中记录数量,关键是在遇到不对的情况下要调整起点位置。括号题基本都是栈,遇到左括号入栈,遇到右括号出栈。每次遍历完一个字符,计算当前的起点到终点的距离。当一系列完整的括号对遍历完成,然后出现一个右括号时,这时不能再继续了,所以要重新赋值起点。起点都为栈顶。
class Solution:
def longestValidParentheses(self, s: str) -> int:
stack = [-1]
res = 0
for i, val in enumerate(s):
if val == "(":
stack.append(i)
else:
stack.pop()
if not stack:
# 重新设置开头
stack.append(i)
res = max(res,i-stack[-1])
return res
动态规划:可以将状态设为以第i处结尾的连续有效括号的长度。那么分为两种情况。第一,第i处为(,那么以它结尾的长度肯定为0。第二,第i处为),此时又分为两种情况:1、若他的i-1处为(,那么说明当前i和i-1已经可以组成一个小的,所以长度和应该是i-2处的长度+2;2、若它的i-1处为),则长度在可能的情况下为i-1处的长度,加与i处配对的左括号的2,在加这个配对的左括号前面可能还会有的有效括号长度。
class Solution:
def longestValidParentheses(self, s: str) -> int:
n = len(s)
if n == 0: return 0
dp = [0] * n
res = 0
for i in range(n):
if i>0 and s[i] == ")":
if s[i - 1] == "(":
dp[i] = dp[i - 2] + 2
# i - dp[i-1] - 1 >= 0,保证了当前配对的整体的前面的位置不会变到-1处,例如:()|(()),保证前面的()在计算时,不会变成dp[-1]从而出错。
# 要能配对的话,也得保证dp与当前i对应的位置是左括号才行。
elif s[i - 1] == ")" and i - dp[i - 1] - 1 >= 0 and s[i - dp[i - 1] - 1] == "(":
dp[i] = dp[i - 1] + 2 + dp[i - dp[i - 1] - 2]
if dp[i] > res:
res = dp[i]
return res
2021-06-15
16、搜索旋转排序数组
整数数组 nums 按升序排列,数组中的值 互不相同 。在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
python内置函数:
class Solution:
def search(self, nums: List[int], target: int) -> int:
if target not in nums:
return -1
else:
return nums.index(target)
二分查找:思路很简单,细节是魔鬼。
数组具有特殊性,分为两半有序数组。可以计算target在严格递增区间还是混合区间。首先为什么是 left <= right?若没有等于:假设最后时刻,进行了一下right = mid - 1,此时right = left,此时跳出了while循环,若target刚好是right值呢?此时跳出了循环就没有考虑到最后一个数字。所以是<=。然后根据区间特点移动判断。
class Solution:
def search(self, nums: List[int], target: int) -> int:
if not nums:
return -1
left = 0
right = len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
# 左半段有序,[left,mid]为有序的。
if nums[mid] >= nums[left]:
# mid一定不是target,故右边界可以变为mid-1
if nums[left] <= target < nums[mid]:
right = mid - 1
# target在mid之后了,所以左边界可以变为mid+1
else:
left = mid + 1
# 右半段有序,[mid,right]为有序
else:
# target在(mid,right]区间时,左边界可以变为mid+1
if nums[mid] < target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1
二分模板
class Solution:
def search(self, nums: List[int], target: int) -> int:
if not nums:
return -1
# 先找到分界点将区间分为两个单调区间,然后在单调区间上使用二分
l, r = 0, len(nums)-1
t = nums[-1]
while l < r :
mid = l + r >> 1
if nums[mid] <= t:
r = mid
else:
l = mid + 1
if nums[l] == target:
return l
if target <= t:
l1 = l
r1 = len(nums)-1
else:
l1 = 0
r1 = l-1
while l1 < r1:
mid = l1 + r1 + 1 >> 1
if nums[mid] <= target:
l1 = mid
else:
r1 = mid - 1
if nums[l1] == target:
return l1
else:
return -1
17、在排序数组中查找元素的第一个和最后一个位置
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target,返回 [-1, -1]。进阶:你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
普通 O(N):
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
left = 0
right = len(nums)-1
if right == -1:
return [-1,-1]
while True:
if nums[left] == target and nums[right] == target:
return [left,right]
if nums[left] < target:
left += 1
if nums[right] > target:
right -= 1
if left > right:
return [-1,-1]
二分查找 O(logN):
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
# 二分查找
# 注意:分为两次判断,不能将边界设为 x = target,这样的话区间可能变为三段,不满足二分查找的二段性
# 特殊情况
if len(nums) == 0:
return [-1,-1]
# 开始位置
# 确定二分左右边界
l, r = 0, len(nums)-1
# 框架
while l < r:
mid = l+r >> 1
# 设计check条件:这里判断起点,应该是要x >= target的第一个数
if nums[mid] >= target:
r = mid
else:
l = mid + 1
# 若最后没有target值,那么直接返回[-1,-1]
if nums[r] != target:
return [-1,-1]
# 结束位置
# 确定二分左右边界
l1, r1 = 0, len(nums)-1
# 框架
while l1 < r1:
# 由于最后更新left和right时是l = mid,为了防止死循环,mid这里加1
mid = l1+r1+1 >> 1
# 设计check条件:这里判断终点,应该是x <= target的最后一个数
if nums[mid] <= target:
l1 = mid
else:
r1 = mid - 1
return [l,l1]
18、不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。问总共有多少条不同的路径?
DFS超时:
class Solution(object):
def uniquePaths(self, m, n):
"""
:type m: int
:type n: int
:rtype: int
"""
directs = [(1,0),(0,1)]
self.res = 0
def dfs(i,j):
if i == m-1 and j == n-1:
self.res += 1
return
if i > m-1 or j > n-1:
return
for x,y in directs:
dfs(i + x, j + y)
dfs(0,0)
return self.res
动态规划:
class Solution(object):
def uniquePaths(self, m, n):
"""
:type m: int
:type n: int
:rtype: int
"""
dp = [[0] * n for _ in range(m)]
for i in range(m):
dp[i][0] = 1
for i in range(n):
dp[0][i] = 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[m-1][n-1]
2021-06-16
19、x的平方根
实现 int sqrt(int x) 函数。计算并返回 x 的平方根,其中 x 是非负整数。由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
二分查找:
模板:
1、确定二分的边界
2、编写二分的框架
3、设计一个check
4、判断一下区间如何更新
5、如果更新方式是l=mid,r=mid-1,那么在计算mid时加1
class Solution:
def mySqrt(self, x: int) -> int:
# 1、确定二分边界
l, r = 0, x
# 2、编写框架
while l < r:
# 2、编写框架
mid = (l+r+1) // 2
# 3、确定check方式
if mid ** 2 <= x:
# 4、确定更新方式
l = mid
else:
r = mid - 1
return r
20、搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。你可以假设数组中无重复元素。
二分查找
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
# 特殊情况
if not nums or nums[-1] < target:
return len(nums)
# 二分边界
l, r = 0, len(nums)-1
# 框架
while l < r:
mid = l + r >> 1
# check方法:
# 1、nums存在target时,寻找x=target的点
# 2、nums不存在target时,寻找x>target的区间的第一个点,并占用改点位置作为输出
# 故区间应该为x >= target
if nums[mid] >= target:
# 更新条件
r = mid
else:
l = mid + 1
return r
21、搜索二维矩阵
编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:每行中的整数从左到右按升序排列。每行的第一个整数大于前一行的最后一个整数。
二分查找
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
if not matrix:
return False
up, down = 0, len(matrix)-1
while up < down:
mid = up + down + 1 >> 1
if matrix[mid][0] <= target:
up = mid
else:
down = mid - 1
l, r = 0, len(matrix[0])-1
while l < r:
mid = l + r + 1 >> 1
if matrix[up][mid] <= target:
l = mid
else:
r = mid - 1
if matrix[up][l] == target:
return True
else:
return False
22、寻找旋转排序数组中的最小值
已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:若旋转 4 次,则可以得到 [4,5,6,7,0,1,2];若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]。注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
二分查找:这里的check条件为min_value <= nums[-1]。用一个性质将区间分为两段,这个性质就是最小值的数,肯定比最后一个数要小。
class Solution:
def findMin(self, nums: List[int]) -> int:
l, r = 0, len(nums)-1
target = nums[-1]
while l < r:
mid = l + r >> 1
if nums[mid] <= target:
r = mid
else:
l = mid + 1
return nums[r]
2021-06-17
23、删除链表中的节点
请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点。传入函数的唯一参数为 要被删除的节点 。
单链表若要真正删除结点,应该将该结点的上一个结点指向该结点的下一个结点。这样就需要知道上一个结点的指针。但此处只给了当前结点。所以办法是将当前结点的下一个节点的值替换掉当前结点的值,然后再删除掉下一个结点。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def deleteNode(self, node):
"""
:type node: ListNode
:rtype: void Do not return anything, modify node in-place instead.
"""
node.val = node.next.val
node.next = node.next.next
24、删除排序链表中的重复元素
存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素 只出现一次 。返回同样按升序排列的结果链表。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def deleteDuplicates(self, head: ListNode) -> ListNode:
cur = head
while cur:
if cur.next and cur.next.val == cur.val:
cur.next = cur.next.next
else:
cur = cur.next
return head
25、 删除排序链表中的重复元素 II
存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的节点,只保留原始链表中 没有重复出现 的数字。返回同样按升序排列的结果链表。
首先创建哑节点避免涉及到开头后的不必要的麻烦。然后删除所有重复元素,在哑节点的基础上,需要判断当前结点的下一个结点和下下一个结点是否相同,若相同,则后面可能还有与这两个结点值相同的结点,因此需要存一下重复的值,然后用当前节点的下一个结点与这个值比较,如果相同的话,就将当前节点的下一个结点变为下下个结点,然后依此循环,直到当前结点的下一个结点不满足相同的情况。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def deleteDuplicates(self, head: ListNode) -> ListNode:
pre = ListNode(-1)
pre.next = head
cur = pre
while cur.next and cur.next.next:
if cur.next.val == cur.next.next.val:
x = cur.next.val
while cur.next and cur.next.val == x:
cur.next =cur.next.next
else:
cur = cur.next
return pre.next
26、旋转链表
给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def rotateRight(self, head: ListNode, k: int) -> ListNode:
if not head:
return head
# 若链表长度为5,旋转5次后,链表和原来相同
# 所以当 k > 链表长度n时,实际上旋转的时k%n
n = 0
cur = head
while cur:
n += 1
cur = cur.next
k = k % n
# 接下来旋转,旋转k次,也就是将倒数第k个结点及其以后的放在最开始
# 也就是要求倒数第k个结点是啥
pre = ListNode(-1)
pre.next = head
first = pre
second = pre
while k:
first = first.next
k -= 1
while first.next:
first = first.next
second = second.next
first.next = head
head = second.next
second.next = None
return head
27、(2021届vivo笔试题)编译依赖问题
一个完整的软件项目往往会包含很多由代码和文档组成的源文件。编译器在编译整个项目的时候,可能需要按照依赖关系来依次编译每个源文件。比如,A.cpp 依赖 B.cpp,那么在编译的时候,编译器需要先编译 B.cpp,才能再编译 A.cpp。 假设现有 0,1,2,3 四个文件,0号文件依赖1号文件,1号文件依赖2号文件,3号文件依赖1号文件,则源文件的编译顺序为 2,1,0,3 或 2,1,3,0。现给出文件依赖关系,如 1,2,-1,1,表示0号文件依赖1号文件,1号文件依赖2号文件,2号文件没有依赖,3号文件依赖1号文件。请补充完整程序,返回正确的编译顺序。注意如有同时可以编译多个文件的情况,按数字升序返回一种情况即可,比如前述案例输出为:2,1,0,3
因为是一个文件能运行只依赖于另一个文件,所以每当完成一个文件,其对应的文件就处于可以运行的状态。属于一对多关系。因此可以先将依赖关系存贮在字典中,然后每次将可以运行的文件加入队列中。最后存在多种可以运行的情况, 所以可以每次对队列进行排序,保证每次弹出的是最小值。
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
# 编译顺序
# @param input string字符串
# @return string字符串
#
import collections
class Solution:
def compileSeq(self , input ):
# write code here
inp = input.split(',')
inp = list(map(int,inp))
dic = collections.defaultdict(list)
stack = []
for i,val in enumerate(inp):
if val == -1:
stack.append(i)
else:
dic[val].append(i)
res = []
while stack:
a = stack.pop(0)
res.append(a)
l = dic[a]
for i in l:
stack.append(i)
stack.sort()
res = list(map(str,res))
res1 = ",".join(res)
return res1
28、回文字符串
回文字符串就是正读和反读都一样的字符串,如“viv”、“nexen”、“12321”、“qqq”、“翻身把身翻” 等。
给定一个非空字符串 str,在最多可以删除一个字符的情况下请编程判定其能否成为回文字符串;如果可以则输出首次删除一个字符所能得到的回文字符串,如果不行则输出字符串 “false” 。
根据题意,是必须要删除字符串的。所以定义一个判断回文的函数,然后删除一个字符后拿这个函数判断一下。
import sys
def ishw(s):
left = 0
right = len(s)-1
while left <= right:
if s[left] == s[right]:
left += 1
right -= 1
else:
return False
return True
for line in sys.stdin:
line = line.strip()
n = len(line)
flag = 0
for i in range(n):
if ishw(line[:i]+line[i+1:]):
print(line[:i]+line[i+1:])
flag = 1
break
if flag == 0:
print('false')
2021-06-19
29、棋子搜索
围棋起源于中国,是一种策略性的棋类游戏,使用格状棋盘及黑白两色棋子进行对弈,棋盘是方形,横竖各19条线组成。现有围棋残局棋谱,判断是黑子连成最大一片的个数多还是白子连成最大一片的个数多。所谓连成一片,是指沿着棋盘上下左右相邻,中间未出现对方棋子或空子。
输入:19*19矩阵,假设0代表空格,1代表白子,2代表黑子
输出:第一行:白子连成最大一片的棋子的个数;第二行:黑子连成一片的最大的个数;第三行:判断白字连成的最大一片多还是黑子连成的最大一片多。若黑多输出black,白多输出white,同样输出equal。
用DFS进行遍历,每次遍历后将遍历过的值都改变,然后保存最大值。
import sys
l = []
for line in sys.stdin:
a = list(map(int,line.strip().split(' ')))
l.append(a)
# 对(i,j)点进行上下左右方向上的试探
def dfs_1(a,i,j,res):
# 结果加1
res[0] += 1
# 将遍历过的位置减1
a[i][j] -= 1
# 上下左右遍历
if i-1 >-1 and a[i-1][j] == 1:
dfs_1(a,i-1,j,res)
if j - 1 > -1 and a[i][j-1] == 1:
dfs_1(a,i,j-1,res)
if i + 1 < 19 and a[i+1][j] == 1:
dfs_1(a,i+1,j,res)
if j + 1 < 19 and a[i][j+1] == 1:
dfs_1(a,i,j+1,res)
def dfs_2(a,i,j,res):
# 结果加1
res[0] += 1
# 将遍历过的位置减1
a[i][j] -= 1
# 上下左右遍历
if i-1 >-1 and a[i-1][j] == 2:
dfs_2(a,i-1,j,res)
if j - 1 > -1 and a[i][j-1] == 2:
dfs_2(a,i,j-1,res)
if i + 1 < 19 and a[i+1][j] == 2:
dfs_2(a,i+1,j,res)
if j + 1 < 19 and a[i][j+1] == 2:
dfs_2(a,i,j+1,res)
w = 0
b = 0
res = [0]
for i in range(19):
for j in range(19):
res[0] = 0
if l[i][j] == 1:
dfs_1(l,i,j,res)
w = max(w,res[0])
for i in range(19):
for j in range(19):
res[0] = 0
if l[i][j] == 2:
dfs_2(l,i,j,res)
b = max(b,res[0])
print(w)
print(b)
if w > b:
print('white')
elif w < b:
print('black')
else:
print('equal')
30、分层域名系统
最顶层为根域,根域之下设置了一层顶级域,即edu、com、gov、org等,顶级域之下又设立了若干二级域、三级域。。。(域的层数可能大于3),直到最终的主机。给定顶级域名,请输出该顶级域名下所有可解析的完整域名。
顶级域: com
二级域:google、vivo、sina
三级域:www、career
如给定com,可解析的域名如下:
google.com
www.vivo.com
career.vivo.com
sina.com
请按字母升序输出,如本例应输出:
career.vivo.com
google.com
sina.com
www.vivo.com
输入:第一行为整数N(N不大于20),后续的N行,每行由一个域名(或主机名)以及其父域名组成,中间用空格分割。最后一行为指定的顶级域名
输出:指定顶级域名下可解析的完整域名列表,按字母升序输出。
利用DFS将每级域名的下一个域名添加到后面。
import sys
l = []
for line in sys.stdin:
a = line.strip().split(' ')
l.append(a)
N = int(l[0][0])
root = l[-1]
inp = l[1:-1]
import collections
dic = collections.defaultdict(list)
for son, father in inp:
dic[father].append(son)
def dfs(res,tmp):
if not tmp[-1] in dic:
res.append(tmp)
else:
for j in dic[tmp[-1]]:
dfs(res,tmp+[j])
tmp = root
res = []
dfs(res,tmp)
r = []
for i in res:
r.append(".".join(i[::-1]))
r.sort()
print(r)
31、网络执行的最小时间
假设深度学习网络模型是一个有向无环图。若算子A依赖算子B的输出,则仅当算子B执行完成后才能执行算子A,没有依赖关系的算子可以并行执行。已知每个算子的执行时间,请计算运行整个网络所需的最小时间。请注意:1.不考虑数据在算子之间的传输时间;2.第一个算子为输入算子其仅有一个输入算子3.算子索引从0开始。
输入描述:总共N+1行,其中N为算子总个数。第一行输入整数 N(N<=100)。第j行(2<=j<=N+1)行输入代表索引为j-2算子的属性。内容包括:OpName OpCost NextOp1 NextOp2 … NextOpk,其中OpName为字符串(不包含空格),代表算子名;OpCost为正整数,代表该算子运算时间;NextOp1~NextOpk代表行算子该指向的下一层算子的索引。
输出描述:网络模型的最小执行时间。
import sys
l = []
for line in sys.stdin:
a = line.strip().split(" ")
l.append(a)
N = int(l[0][0])
inp = l[1:]
dic_cost = dict()
dic_next = dict()
for i,val in enumerate(inp):
dic_cost[i] = int(val[1])
dic_next[i] = list(map(int,val[2:]))
res = [0]
def dfs(k,cur):
if not dic_next[k]:
res[0] = max(res[0],cur)
return
for i in dic_next[k]:
dfs(i,cur+dic_cost[i])
dfs(0,dic_cost[0])
print(res[0])
2021-06-20
32、冒泡排序
def bubble_sort(nums):
# 数组长度
n = len(nums)
# 需要确定从后往前数的n位
for i in range(n):
# 从头开始交换
for j in range(n-1-i):
if nums[j] > nums[j+1]:
nums[j] ,nums[j+1] = nums[j+1], nums[j]
return nums
33、快速排序
def quick_sort(nums,start,end):
if start >= end:
return
left = start
right = end
mid = nums[left]
while left < right:
# 找到右面第一个比基准数小的数的位置
while left < right and nums[right] >= mid:
right -= 1
# 将小的数赋值在基准数的位置
nums[left] = nums[right]
# 找到左面第一个比基准数大的数的位置
while left < right and nums[left] < mid:
left += 1
# 将大的数赋值到刚才小的数(小的数已经移动到自己正确的位置了)那里
nums[right] = nums[left]
nums[left] = mid
# 此时以mid为基准的已经排序完成,接下来分割排序
quick_sort(nums,start,left)
quick_sort(nums,left+1,end)
34、生成杨辉三角
# n 代表生成杨辉三角的行数
def yanghui(n):
res = []
for i in range(n):
if i == 0:
cur = [1]
else:
cur = []
for j in range(i+1):
if j == 0 or j == i:
cur.append(1)
else:
cur.append(res[i-1][j-1] + res[i-1][j])
res.append(cur)
return res
2021-06-21
34、和等于 k 的最长子数组长度
给定一个数组 nums 和一个目标值 k,找到和等于 k 的最长子数组长度。如果不存在任意一个符合要求的子数组,则返回 0。注意: nums 数组的总和是一定在 32 位有符号整数范围之内的。
前缀和加哈希表。将所有的前缀和放入字典中,遇到相同的前缀和时不放,因为计算最远距离,起点越靠左越好,所以将前缀和和其索引保存在字典中,每次计算当前前缀和与目标值的差在之前的字典中是否出现过,出现过的话提取索引计算长度。
class Solution:
def maxSubArrayLen(self, nums: List[int], k: int) -> int:
n = len(nums)
s = 0
dic = {0:0}
res = 0
for i in range(n):
s += nums[i]
f = s - k
if f in dic:
res = max(res,i - dic[f]+1)
if s not in dic:
dic[s] = i+1
return res
2021-06-26
好些天没刷题了,对自己提出批评,懒惰。
35、组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。
经典DFS,直接上代码:
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
self.res = []
def dfs(a, cur, target):
if target == 0:
self.res.append(cur)
return
if target < 0:
return
for i in range(len(a)):
dfs(a[i:], cur+[a[i]], target-a[i])
dfs(candidates,[],target)
return self.res
36、接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
这个题虽然为困难,但是搞清楚怎么计算这个面积,其实不难。计算面积的方法:每一个格子能储存的水,取决于左右两边最高的柱子中最矮的那一个。
class Solution:
def trap(self, height: List[int]) -> int:
# 思路: 要知道一个柱子的左边最高的柱子和右边最高的柱子中低的柱子。
n = len(height)
# 左边最高的柱子:
max_left = 0
left = [0]
for i in range(n):
max_left = max(height[i],max_left)
left.append(max_left)
# 右边最高的柱子
max_right = 0
right = [0]
for i in range(n-1,-1,-1):
max_right = max(height[i],max_right)
right.append(max_right)
right = right[::-1]
# 开始计算雨水
res = 0
for i in range(n):
cur = min(left[i],right[i])
if cur < height[i]:
continue
else:
res += (cur - height[i])
return res
37、全排列
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
经典DFS,直接上代码:
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
self.res = []
def dfs(a, cur):
if len(cur) == n:
self.res.append(cur)
return
for i in range(len(a)):
dfs(a[:i]+a[i+1:], cur+[a[i]])
dfs(nums,[])
return self.res
38、旋转图像
给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
规律:先沿中间垂线翻折,再沿右上到左下的对角线翻折:
class Solution:
def rotate(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
# 先沿中间垂线翻折,再沿右上到左下的对角线翻折
n = len(matrix)
for i in range(n):
for j in range(n//2):
matrix[i][j], matrix[i][n-1-j] = matrix[i][n-1-j], matrix[i][j]
for i in range(n):
for j in range(n-i-1):
matrix[i][j], matrix[n-1-j][n-1-i] = matrix[n-1-j][n-1-i], matrix[i][j]
2021-06-28
39、字母异位词分组
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
由于可能出现多个相同字母,所以不能用set,那么就是用dict,也就是将属于同一个字母异位词的词放入一个字典中。这时候的方法就是将字母进行排序,那么字母异位词的词排序过后应该相同,因为字符串不能直接排序,所以先把字母变成列表然后排序。又由于字典的键不能是可变对象列表,所以将排序好的字符再变回去。
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
res = []
dic = collections.defaultdict(list)
for i in strs:
l = list(i)
l.sort()
l = "".join(l)
dic[l].append(i)
return list(dic.values())
40、LRU缓存机制
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。实现 LRUCache 类:LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存;int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
进阶:你是否可以在 O(1) 时间复杂度内完成这两种操作?
这个题用O(1)的时间复杂度,说明索引值的部分得是字典。但是每次又要更新字典值得顺序,说明得对字典里的元素进行排序。故想到用OrderedDict。
class LRUCache(collections.OrderedDict):
def __init__(self, capacity: int):
self.capacity = capacity
def get(self, key: int) -> int:
if key not in self:
return -1
self.move_to_end(key)
return self[key]
def put(self, key: int, value: int) -> None:
if key in self:
self.move_to_end(key)
self[key] = value
if len(self) > self.capacity:
self.popitem(last=False)
# Your LRUCache object will be instantiated and called as such:
# obj = LRUCache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)
2021-06-30
41、实现 Trie (前缀树)
Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。请你实现 Trie 类:
- Trie() 初始化前缀树对象。
- void insert(String word) 向前缀树中插入字符串 word 。
- boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
- boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。
class Trie:
def __init__(self):
"""
Initialize your data structure here.
"""
self.root = {}
self.end_of_word = '#'
def insert(self, word: str) -> None:
"""
Inserts a word into the trie.
"""
node = self.root
for char in word:
node = node.setdefault(char,{})
node[self.end_of_word] = self.end_of_word
def search(self, word: str) -> bool:
"""
Returns if the word is in the trie.
"""
node = self.root
for char in word:
if char not in node:
return False
node = node[char]
return self.end_of_word in node
def startsWith(self, prefix: str) -> bool:
"""
Returns if there is any word in the trie that starts with the given prefix.
"""
node = self.root
for char in prefix:
if char not in node:
return False
node = node[char]
return True
# Your Trie object will be instantiated and called as such:
# obj = Trie()
# obj.insert(word)
# param_2 = obj.search(word)
# param_3 = obj.startsWith(prefix)
2021-07-03
42、相交链表
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
A走a+b走完全程,B走c+b走完全程。那么A走a+b+c和B走c+b+a走的长度是一样的,也就是走这么多的时候A和B是相遇的状态。若没相遇,那么走这么多应该是到末尾后的空节点了。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
A = headA
B = headB
while A != B:
if not A:
A = headB
else:
A = A.next
if not B:
B = headA
else:
B = B.next
return A
43、二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
二分法模板:
1、确定左右边界
2、确定while框架
3、确定判断条件
4、确定更新方式
5、若更新方式为l=mid,则mid生成公式要+1
class Solution:
def search(self, nums: List[int], target: int) -> int:
# 确定左右边界
l, r = 0, len(nums)-1
# 编写框架
while l < r:
mid = (l+r+1) >> 1
# 确定check方式
if nums[mid] <= target:
# 更新方式
l = mid
else:
r = mid - 1
if nums[l] == target:
return l
else:
return -1
44、用栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):实现 MyQueue 类:void push(int x) 将元素 x 推到队列的末尾;int pop() 从队列的开头移除并返回元素;int peek() 返回队列开头的元素;boolean empty() 如果队列为空,返回 true ;否则,返回 false
说明:
你只能使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
class MyQueue:
def __init__(self):
"""
Initialize your data structure here.
"""
self.in_stack = []
self.out_stack = []
def push(self, x: int) -> None:
"""
Push element x to the back of queue.
"""
self.in_stack.append(x)
def pop(self) -> int:
"""
Removes the element from in front of queue and returns that element.
"""
if not self.out_stack:
while self.in_stack:
self.out_stack.append(self.in_stack.pop())
return self.out_stack.pop()
def peek(self) -> int:
"""
Get the front element.
"""
if not self.out_stack:
while self.in_stack:
self.out_stack.append(self.in_stack.pop())
return self.out_stack[-1]
def empty(self) -> bool:
"""
Returns whether the queue is empty.
"""
if not self.out_stack and not self.in_stack:
return True
else:
return False
# Your MyQueue object will be instantiated and called as such:
# obj = MyQueue()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.peek()
# param_4 = obj.empty()
45、反转链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
每次其实反转的是两个结点之后的箭头,所以每次操作的应该是两个结点,改变完方向之后,平移到下一个箭头所对应的两个结点处。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
# a = []
# while head:
# a.append(head)
# head = head.next
# head = ListNode(-1)
# for i in range(len(a)-1,-1,-1):
# head.next = a[i]
# head = head.next
# return head.next
pre = None
cur = head
while cur:
nxt = cur.next
cur.next = pre
pre = cur
cur = nxt
return pre
46、环形链表
给定一个链表,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。如果链表中存在环,则返回 true 。 否则,返回 false 。
方法一:快慢指针
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: ListNode) -> bool:
if not head or not head.next:
return False
fast = head
slow = head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
if fast == slow:
return True
return False
方法二:集合
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: ListNode) -> bool:
s = set()
while head:
if head not in s:
s.add(head)
head = head.next
else:
return True
return False
47、二叉树的中序遍历
给定一个二叉树的根节点 root ,返回它的 中序 遍历。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
if not root:
return []
return self.inorderTraversal(root.left) + [root.val] + self.inorderTraversal(root.right)
2021-07-04
48、二叉树的层序遍历
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
我的写法:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if not root:
return []
queue = [root]
res = []
l = len(queue)
res1 = []
while queue:
l -= 1
cur = queue.pop(0)
res1 += [cur.val]
if cur.left:
queue.append(cur.left)
if cur.right:
queue.append(cur.right)
if l == 0:
res.append(res1)
l = len(queue)
res1 = []
return res
别人的写法:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if not root:
return []
queue = [root]
res = []
while queue:
res.append([i.val for i in queue])
l = []
for cur in queue:
if cur.left:
l.append(cur.left)
if cur.right:
l.append(cur.right)
queue = l
return res
49、最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
以为这个题的通用解法是前缀和,没想到是动态规划!关键是找出状态转移方程,遍历到一个点时,确定是不是以这个点为起点还是说这个点连接到前面。那就是判断当前点加上前面的大还是当前点本身就大,也就是判断前面的点对当前点的影响。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
# 动态规划
# 状态定义
# dp表示以当前位置为结尾的最大和的连续子数组的和
dp = [0] * len(nums)
# 状态初始化
dp[0] = nums[0]
# 状态转移
for i in range(1,len(nums)):
dp[i] = max(nums[i],nums[i]+dp[i-1])
return max(dp)
50、斐波那契数列
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:F(0) = 0, F(1) = 1;F(N) = F(N - 1) + F(N - 2), 其中 N > 1。斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
注意这里第N项定义是从第0项开始的
class Solution:
def fib(self, n: int) -> int:
if n == 0:
return 0
if n == 1:
return 1
dp = [0] * (n+1)
dp[0] = 0
dp[1] = 1
for i in range(2,n+1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n] % 1000000007
51、翻转二叉树
翻转一棵二叉树。
递归:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def invertTree(self, root: TreeNode) -> TreeNode:
if not root:
return
a = root.left
root.left = self.invertTree(root.right)
root.right = self.invertTree(a)
return root
官方:
class Solution:
def invertTree(self, root: TreeNode) -> TreeNode:
if not root:
return root
left = self.invertTree(root.left)
right = self.invertTree(root.right)
root.left, root.right = right, left
return root
52、剑指 Offer 22. 链表中倒数第k个节点
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
快慢指针
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
pre = ListNode(-1)
pre.next = head
fast = pre
slow = pre
n = 0
while n < k:
fast = fast.next
n += 1
while fast:
fast = fast.next
slow = slow.next
return slow
53、合并两个有序数组
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。你可以假设 nums1 的空间大小等于 m + n,这样它就有足够的空间保存来自 nums2 的元素。
方法一:双指针
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 nums1:
return nums2
if not nums2:
return nums1
i = 0
j = 0
res = []
while i < m and j < n:
if nums1[i] < nums2[j]:
res.append(nums1[i])
i += 1
else:
res.append(nums2[j])
j += 1
if i == m:
res.extend(nums2[j:])
else:
res.extend(nums1[i:])
nums1[:] = res[:m+n]
方法二:python暴力
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.
"""
for i in range(n):
nums1[m+i]=nums2[i]
nums1.sort()
方法三:逆向合成,可以将空间复杂度变为O(1),用到了nums1后面的空余位置
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.
"""
k = m + n - 1
while m > 0 and n > 0:
if nums1[m - 1] > nums2[n - 1]:
nums1[k] = nums1[m - 1]
m -= 1
else:
nums1[k] = nums2[n - 1]
n -= 1
k -= 1
nums1[:n] = nums2[:n]
54、快速排序
再写一遍
def quick_sort(nums,start,end):
if start >= end:
return
left = start
right = end
mid = nums[left]
while left < right:
while left < right and nums[right] >= mid:
right -= 1
nums[left] = nums[right]
while left < right and nums[left] <= mid:
left += 1
nums[right] = nums[left]
nums[left] = mid
quick_sort(nums,start,left)
quick_sort(nums,left+1,end)
nums = [2,3,4,1,7,5]
quick(nums,0,len(nums)-1)
print(nums)
55、最大数
给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。注意:输出结果可能非常大,所以你需要返回一个字符串而不是整数。
这个题不是很懂,记一下答案吧
from functools import cmp_to_key
class Solution:
def largestNumber(self, nums):
ret = map(str, nums)
def cmp(a, b):
if a + b >= b + a:
return 1
else:
return -1
ret = sorted(ret, key=cmp_to_key(cmp), reverse=True)
return ''.join(ret) if ret[0] != '0' else '0'
56、只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
不能使用额外空间,异或经典题:
class Solution:
def singleNumber(self, nums: List[int]) -> int:
# 经典异或题:
s = 0
for i in nums:
s ^= i
return s
57、最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 “”。
设置一个基准字符,然后拿他的每一个字母与字符串数组中的每一个字母比较。需要注意的是超出长度的问题。
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
s = strs[0]
for i in range(len(s)):
for j in strs[1:]:
if i >= len(j):
return s[:i]
if j[i] != s[i]:
if i == 0:
return ""
else:
return s[:i]
return s
58、平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
判断左右子树的最大高度差是不是大于1,并且左右子树也分别得为平衡二叉树。
方法一:自顶向下。每个根节点算了一次高度后又算了一次左右结点的高度。时间复杂度为O(NlogN)
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
def height(root):
if not root:
return 0
return max(height(root.left),height(root.right)) + 1
if not root:
return True
return abs(height(root.left) - height(root.right)) <= 1 and self.isBalanced(root.left) and self.isBalanced(root.right)
方法二:自底向上。每个节点只需要处理一次,故时间复杂度为O(N)。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
def height(root):
if not root:
return 0
# 一直递归到叶子节点,从叶子节点开始,计算以其为根节点的左右节点的高度差,如果高度差在1以内,则返回此时的高度,如果为其他高度或者以其为根节点的左/右结点本身被判别为非平衡二叉树(即返回-1),则直接向上返回-1。
leftheight = height(root.left)
rightheight = height(root.right)
if leftheight == -1 or rightheight == -1 or abs(leftheight - rightheight) >= 2:
return -1
else:
return max(leftheight,rightheight) + 1
return height(root) != -1
2021-07-06
59、LRU 缓存机制
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。
实现 LRUCache 类:LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
进阶:你是否可以在 O(1) 时间复杂度内完成这两种操作?
自己写双向链表:
class DLinkedNode:
def __init__(self, key=0, value=0):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity: int):
self.cache = dict()
# 使用伪头部和伪尾部节点
self.head = DLinkedNode()
self.tail = DLinkedNode()
self.head.next = self.tail
self.tail.prev = self.head
self.capacity = capacity
self.size = 0
def get(self, key: int) -> int:
if key not in self.cache:
return -1
# 如果 key 存在,先通过哈希表定位,再移到头部
node = self.cache[key]
self.moveToHead(node)
return node.value
def put(self, key: int, value: int) -> None:
if key not in self.cache:
# 如果 key 不存在,创建一个新的节点
node = DLinkedNode(key, value)
# 添加进哈希表
self.cache[key] = node
# 添加至双向链表的头部
self.addToHead(node)
self.size += 1
if self.size > self.capacity:
# 如果超出容量,删除双向链表的尾部节点
removed = self.removeTail()
# 删除哈希表中对应的项
self.cache.pop(removed.key)
self.size -= 1
else:
# 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
node = self.cache[key]
node.value = value
self.moveToHead(node)
def addToHead(self, node):
node.prev = self.head
node.next = self.head.next
self.head.next.prev = node
self.head.next = node
def removeNode(self, node):
node.prev.next = node.next
node.next.prev = node.prev
def moveToHead(self, node):
self.removeNode(node)
self.addToHead(node)
def removeTail(self):
node = self.tail.prev
self.removeNode(node)
return node
用python的OrderedDict:
class LRUCache(collections.OrderedDict):
def __init__(self, capacity: int):
self.capacity = capacity
def get(self, key: int) -> int:
if key not in self:
return -1
self.move_to_end(key)
return self[key]
def put(self, key: int, value: int) -> None:
if key in self:
self.move_to_end(key)
self[key] = value
if len(self) > self.capacity:
# last = False时是以”先进先出“的方式弹出
self.popitem(last=False)
# Your LRUCache object will be instantiated and called as such:
# obj = LRUCache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)
60、移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
方法一:双指针
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
# 双指针:左指针用来寻找0数;右指针用来寻找非0数
left = 0
right = 0
while right < len(nums):
# 右指针找到非零数开始交换
if nums[right] != 0:
# 交换左右指针
nums[left], nums[right] = nums[right], nums[left]
# 交换完左右指针后左指针指向的肯定是非0数,故加1
left += 1
right += 1
方法二:通过逐一添加非零元素来完成
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
pos = 0
for i,val in enumerate(nums):
if val != 0:
nums[pos] = val
pos += 1
for i in range(pos,len(nums)):
nums[i] = 0
2021-07-07
61、跳跃游戏
给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标。
更新能到达的点 能到达的最远距离
class Solution:
def canJump(self, nums: List[int]) -> bool:
n= len(nums)
max_len = 0
for i in range(n):
if i <= max_len:
max_len = max(max_len,i+nums[i])
if max_len >= n-1:
return True
return False
2021-07-09
62、合并区间
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。
思路:先对数组intervals按第一位进行排序,这样起点就排序好了。然后将第一个数组先放入结果中,然后遍历第二个数组,看看第二个数组的开头是否比结果中最后一个数组的结尾大:如果大的话,说明没有重合,如果小的话则更新一下结果中最后一个数组的结尾。
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort(key = lambda x :x[0])
res = [intervals[0]]
for i in intervals[1:]:
if i[0] > res[-1][-1]:
res.append(i)
else:
res[-1][-1] = max(res[-1][-1],i[-1])
return res
63、最小路径和
给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。说明:每次只能向下或者向右移动一步。
DFS超时,用动态规划:
class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
m = len(grid)
n = len(grid[0])
dp = [[0] * n for _ in range(m)]
dp[0][0] = grid[0][0]
for i in range(1,m):
dp[i][0] = grid[i][0]+dp[i-1][0]
for i in range(1,n):
dp[0][i] = grid[0][i]+dp[0][i-1]
for i in range(1,m):
for j in range(1,n):
dp[i][j] = min(grid[i][j]+dp[i-1][j],grid[i][j]+dp[i][j-1])
return dp[-1][-1]
2021-07-12
64、爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?注意:给定 n 是一个正整数。
动态规划:
class Solution:
def climbStairs(self, n: int) -> int:
if n == 1:
return 1
dp = [0] * n
dp[0] = 1
dp[1] = 2
for i in range(2,n):
dp[i] = dp[i-1]+dp[i-2]
return dp[-1]
65、编辑距离
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。你可以对一个单词进行如下三种操作:插入一个字符、删除一个字符、替换一个字符。
这个题用动态规划,注意状态设定维度为n+1和m+1
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
if len(word1) == 0:
return len(word2)
if len(word2) == 0:
return len(word1)
# 状态设定
# dp[i][j]表示由word1的前i个字符到word2的前j个字符的编辑距离
dp = [[0] * (len(word2)+1) for _ in range(len(word1)+1)]
# 状态初始化
for i in range(len(word1)+1):
dp[i][0] = i
for j in range(len(word2)+1):
dp[0][j] = j
for i in range(1,len(word1)+1):
for j in range(1,len(word2)+1):
if word1[i-1] != word2[j-1]:
dp[i][j] = min(dp[i-1][j]+1,dp[i][j-1]+1,dp[i-1][j-1]+1)
else:
dp[i][j] = min(dp[i-1][j]+1,dp[i][j-1]+1,dp[i-1][j-1])
return dp[-1][-1]
66、颜色分类
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
方法一:记录每个颜色出现的次数,然后根据次数重新构建数组
class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
dic = collections.defaultdict(int)
for i in nums:
dic[i] += 1
nums[:] = [0] *dic[0] + [1] * dic[1] + [2] * dic[2]
方法二:单指针一次遍历
class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
# 单指针
p = 0
for i in range(len(nums)):
if nums[i] == 0:
nums[i], nums[p] = nums[p], nums[i]
p += 1
for i in range(len(nums)):
if nums[i] == 1:
nums[i], nums[p] = nums[p], nums[i]
p += 1
67、最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。注意:如果 s 中存在这样的子串,我们保证它是唯一的答案。
滑动窗口:过程有点复杂,自己写不出来
class Solution:
def minWindow(self, s: str, t: str) -> str:
need=collections.defaultdict(int)
for c in t:
need[c]+=1
needCnt=len(t)
i=0
res=(0,float('inf'))
for j,c in enumerate(s):
if need[c]>0:
needCnt-=1
need[c]-=1
if needCnt==0: #步骤一:滑动窗口包含了所有T元素
while True: #步骤二:增加i,排除多余元素
c=s[i]
if need[c]==0:
break
need[c]+=1
i+=1
if j-i<res[1]-res[0]: #记录结果
res=(i,j)
need[s[i]]+=1 #步骤三:i增加一个位置,寻找新的满足条件滑动窗口
needCnt+=1
i+=1
return '' if res[1]>len(s) else s[res[0]:res[1]+1] #如果res始终没被更新过,代表无满足条件的结果
68、子集
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
DFS经典题,为了不重复每次递归的DFS要从下一个字符开始
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
self.res = []
def dfs(nums,path):
self.res.append(path)
if not nums:
return
for i in range(len(nums)):
dfs(nums[i+1:],path+[nums[i]])
dfs(nums,[])
return self.res
69、单词搜索
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
DFS:
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
visited = set()
n = len(board)
m = len(board[0])
def dfs(i,j,k):
if board[i][j] != word[k]:
return False
if len(word)-1 == k:
return True
visited.add((i,j))
result = False
for x,y in directions:
new_i = x + i
new_j = y + j
if 0 <= new_i < n and 0 <= new_j < m and (new_i,new_j) not in visited:
if dfs(new_i,new_j,k+1):
result = True
visited.remove((i,j))
return result
for i in range(n):
for j in range(m):
if dfs(i,j,0):
return True
return False
2021-07-13
70、柱状图中最大的矩形
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。
整体思路:找到某个柱子左右比他高的最远的柱子
暴力法:超时
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
n = len(heights)
left_h = []
right_h = []
for i in range(n):
count = 0
j = i
while heights[j] >= heights[i]:
j += 1
count += 1
if j == n:
break
left_h.append(count)
for i in range(n-1,-1,-1):
count = 0
j = i
while heights[j] >= heights[i]:
j -= 1
count += 1
if j == -1:
break
right_h.append(count)
right_h = right_h[::-1]
res_h = [left_h[i] + right_h[i] - 1 for i in range(n)]
res = 0
for i in range(n):
res = max(res,res_h[i]*heights[i])
return res
单调栈(不太会):
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
stack = []
heights = [0] + heights + [0]
res = 0
for i in range(len(heights)):
#print(stack)
while stack and heights[stack[-1]] > heights[i]:
tmp = stack.pop()
res = max(res, (i - stack[-1] - 1) * heights[tmp])
stack.append(i)
return res
71、不同的二叉搜索树
给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
动态规划:
class Solution:
def numTrees(self, n: int) -> int:
# 二叉搜索树:左小右大
dp = [0] * (n+1)
dp[0] = 1
dp[1] = 1
# 每个结点做一次根节点
for i in range(2,n+1):
for j in range(1,i+1):
dp[i] += dp[j-1] * dp[i-j]
return dp[n]
72、验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。假设一个二叉搜索树具有如下特征:1、节点的左子树只包含小于当前节点的数。2、节点的右子树只包含大于当前节点的数。3、所有左子树和右子树自身必须也是二叉搜索树。
二叉搜索树的中序遍历得到的是一个递增的数组,故可以利用这个性质:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
# 二叉搜索树的性质与中序遍历的联系
def inorder(root):
if not root:
return []
return inorder(root.left) + [root.val] + inorder(root.right)
l = inorder(root)
for i in range(1,len(l)):
if l[i-1] >= l[i]:
return False
return True
73、对称二叉树
给定一个二叉树,检查它是否是镜像对称的。
只考虑某一层来写递归。如果这一层的左右结点都为空,那么这一层对称。如果这一层的左右结点有一个结点存在,另一个结点不存在,那么这一层不对称。如果这一层的左右两个结点不相等,那么肯定也不对称。排除上述情况,直接递归到对称位置的结点。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
if not root:
return True
def compare(l,r):
if not l and not r:
return True
elif not l or not r:
return False
elif r.val != l.val:
return False
else:
return compare(l.left,r.right) and compare(l.right,r.left)
return compare(root,root)
74、二叉树的最大深度
给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。说明: 叶子节点是指没有子节点的节点。
递归思想:只考虑某一层,终止条件考虑最后一层。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root:
return 0
return max(self.maxDepth(root.left),self.maxDepth(root.right))+1