leetcode面试刷题笔记--python
1.两数之和:
做题要点:字典
利用map性质,当target-nums[i]在map中时,直接返回,如果不在则将nums[i]加入到map中即可。
1 class Solution: 2 def twoSum(self, nums: List[int], target: int) -> List[int]: 3 dic = {} 4 for i in range(len(nums)): 5 if target - nums[i] in dic.keys(): 6 return([i, dic.get(target - nums[i])]) 7 dic[nums[i]] = i
时间空间复杂度O(n)
3.无重复字符的最长子串:
做题要点:滑动窗口,字典,双指针
利用滑动窗口方法,记指针i=0,j=0.同时建立字典。循环遍历数组元素,当数组元素不包含于字典时,i++,同时将该元素添加到字典中。如果字典中包含该元素,则将字典中s[j]删除,并且将j++。最后返回字典长度最大值即可。
1 class Solution: 2 def lengthOfLongestSubstring(self, s: str) -> int: 3 i = 0 # 左指针 4 j = 0 # 右指针 5 dic = {} 6 maxv = 0 7 while i < len(s): 8 if s[i] not in dic.values(): 9 dic[s[i]] = s[i] 10 i += 1 11 else: 12 dic.pop(s[j]) 13 j += 1 14 if maxv < len(dic): 15 maxv = len(dic) 16 return maxv
4.两数相加:
做题要点:数学
1.暴力还原整数再加再转换成链表:先将两个链表转换成两个整数,两整数相加,将结果转换为链表
1 class Solution: 2 def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode: 3 num1 = "" 4 num2 = "" 5 while l1 is not None: 6 num1 += str(l1.val) 7 l1 = l1.next 8 while l2 is not None: 9 num2 += str(l2.val) 10 l2 = l2.next 11 ans = str(int(num1[::-1]) + int(num2[::-1])) 12 length = len(ans) 13 result = ListNode(int(ans[0])) 14 for i in range(1, length): 15 result = ListNode(int(ans[i]), result) 16 return result
2.合理利用进位
用s来存储每位相加的结果,同时保存进位结果。遍历链表,当l1不为空或者l2不为空或者s不为0时,执行两链表按位相加的操作,将结果保存在s,用s的个位建立新节点,同时再将s除十取floor即可(保存进位)。
1 def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode: 2 head = p = ListNode(0) 3 s = 0 4 while l1 or l2 or s != 0: 5 s += (l1.val if l1 else 0) + (l2.val if l2 else 0) 6 p.next = ListNode(s % 10) 7 p = p.next 8 s //= 10 # 进位 9 l1 = l1.next if l1 else None 10 l2 = l2.next if l2 else None 11 return head.next
15.三数之和:
做题要点:双指针
本题目与第一题有一定的联系,我们首先将整个数组进行排序遍历nums数组中的元素nums[i]。同时令L为i+1,R为len(nums)-1。当nums[i]=nums[i-1]时,跳过该次循环,因为此时会造成重复解。当L小于R时,我们进行循环判断nums[L]+nums[R]+nums[i]是否为0,如果为0,将其加入答案并跳过L,R相邻重复元素。如果>0则说明nums[R]过大,故将R--。如果<0则说明nums[L]过小,将L++。
1 class Solution: 2 def threeSum(self, nums: List[int]) -> List[List[int]]: 3 if len(nums)<3: 4 return [] 5 nums.sort() 6 ans = [] 7 for i in range(len(nums)): 8 if nums[i]>0: 9 return ans 10 if(i>0 and nums[i]==nums[i-1]): 11 continue 12 L = i+1 13 R = len(nums)-1 14 while L < R: 15 if nums[i] + nums[L] + nums[R] == 0: 16 ans.append([nums[i],nums[L],nums[R]]) 17 # 去除重复解!! 18 while(L<R and nums[L]==nums[L+1]): 19 L=L+1 20 while(L<R and nums[R]==nums[R-1]): 21 R=R-1 22 L=L+1 23 R=R-1 24 elif nums[i] + nums[L] + nums[R] > 0: 25 R-=1 26 elif nums[i] + nums[L] + nums[R] < 0: 27 L+=1 28 return ans
时间复杂度O(n2),空间复杂度O(1)
15.K个一组翻转链表:
结合翻转链表的解法,加入count对已经反转的节点进行计数。当k=1或头节点为空或链表长度为1时表示不需要翻转,直接返回头节点。每翻转一次,将count自增一,当count%k为0时,将pre赋值为cur节点,同时,将cur节点向后挪动一个节点。并且将count自增。
1 class Solution: 2 def reverseKGroup(self, head: ListNode, k: int) -> ListNode: 3 if head is None or head.next is None or k == 1: 4 return head 5 count = 1 6 leng = 0 7 node = head 8 while node: 9 leng+=1 10 node = node.next 11 pre = ListNode(0) 12 ans = ListNode(0) 13 pre.next = head 14 ans.next = head 15 cur = head 16 while leng - count > leng % k and cur.next: 17 next_n = cur.next 18 cur.next = next_n.next 19 next_n.next = pre.next 20 count += 1 21 pre.next = next_n 22 if count == k: 23 ans.next = next_n 24 if count % k == 0: 25 pre = cur 26 cur = cur.next 27 count += 1 28 return ans.next
时间复杂度O(n),空间复杂度O(1)
11. 盛最多水的容器:
使用双指针法,从两侧向内收缩。注意指针变动的规律:将hi和hj中值较小的下标向内收缩!因为如果值较大的下标收缩面积必定减小!
1 def maxArea(self, height: List[int]) -> int: 2 i = 0 3 j = len(height)-1 4 max_v = 0 5 while i < j: 6 max_v = max((j-i)*min(height[i],height[j]),max_v) 7 if height[i+1] > min(height[i],height[j]): 8 i+=1 9 elif height[j-1] > min(height[i],height[j]): 10 j-=1 11 else: 12 i+=2 13 j-=2 14 return max_v
33. 搜索旋转排序数组:
本题目可以结合之前做过的一个题目:找寻旋转数组的最小值。我们可以先找到旋转数组的最小值,以此最小值为分界点,对左右两个排序数组进行二分查找。
1 class Solution: 2 def search(self, nums: List[int], target: int) -> int: 3 index = 0 4 left = 0 5 right = len(nums)-1 6 while left != right: 7 mid = int((right - left)/2) + left 8 if nums[mid] > nums[right]: left = mid +1 9 elif nums[mid] < nums[right]: right = mid 10 else: right-=1 11 index = right 12 # 对左右两侧分别进行二分查找 13 left1 = 0 14 right1 = index-1 15 while left1 < right1: 16 mid = int((right1 - left1)/2) + left1 17 if nums[mid] == target: return mid 18 elif nums[mid] > target: right1 = mid 19 else: left1 = mid+1 20 if nums[left1] == target: return left1 21 left2 = index 22 right2 = len(nums)-1 23 while left2 < right2: 24 mid = int((right2 - left2)/2) + left2 25 if nums[mid] == target: return mid 26 elif nums[mid] > target: right2 = mid 27 else: left2 = mid+1 28 if nums[left2] == target: return left2 29 return -1
时间复杂度O(nlogn),空间复杂度O(1)
23. 合并k个升序链表:
由本题可以联想到合并两个升序链表,结合分治算法,两两归并即可。
1 class Solution: 2 def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: 3 node = head = ListNode(0) 4 while l1 and l2: 5 if l1.val > l2.val: 6 node.next = l2 7 l2 = l2.next 8 node = node.next 9 else: 10 node.next = l1 11 l1 = l1.next 12 node = node.next 13 while l1: 14 node.next = l1 15 l1 = l1.next 16 node = node.next 17 while l2: 18 node.next = l2 19 l2 = l2.next 20 node = node.next 21 return head.next 22 23 def merge(self,left,right,lists): 24 if left == right - 1: 25 # 可以两两合并 26 return self.mergeTwoLists(lists[left],lists[right]) 27 if left == right: 28 # 不需要合并 29 return lists[left] 30 mid = int((right - left)/2) + left 31 l = self.merge(left,mid,lists) 32 r = self.merge(mid+1,right,lists) 33 return self.mergeTwoLists(l,r) 34 35 def mergeKLists(self, lists: List[ListNode]) -> ListNode: 36 if len(lists) == 0: 37 return None 38 return self.merge(0,len(lists)-1,lists)
时间复杂度O(nlogk),k是链表个数,n是元素总数。空间复杂度为O(1)
56. 合并区间:
先将整个列表按第一个元素进行排序,再新建列表ans,将其初始化为intervals[0]。遍历intervals[1:],将元组记为x,y。判断ans[-1][1]和x的大小,如果x>ans[-1][1],说明区间没有重叠,直接将[x,y]加入ans。如果x<=ans[-1][1]。说明区间有重叠。故将ans[-1][1]赋值为y和ans[-1][1]的较大值即可。
1 def takeFirst(ele): 2 return ele[0] 3 4 class Solution: 5 def merge(self, intervals: List[List[int]]) -> List[List[int]]: 6 if len(intervals) == 0: 7 return [] 8 intervals.sort(key=takeFirst) 9 ans = [] 10 ans.append(intervals[0]) 11 for x,y in intervals[1:]: 12 if x > ans[-1][1]: 13 # 说明没有区间重叠,故直接append 14 ans.append([x,y]) 15 else: 16 # 说明有区间重叠,此时将ans[-1][1]赋值为y和ans[-1][1]的较大值 17 ans[-1][1] = max(y,ans[-1][1]) 18 return ans
时间复杂度O(nlogn),空间复杂度O(1)
46. 全排列:
使用回溯算法,遍历所有可能即可!递归要熟练。
1 class Solution: 2 def permute(self, nums: List[int]) -> List[List[int]]: 3 ans = [] 4 used = [0 for i in range(len(nums))] 5 def backtrack(nums,path): 6 if not nums: 7 # 此时已经满足条件 8 ans.append(path) 9 return 10 for i in range(len(nums)): 11 # 遍历每一个可能的元素进行下一层递归! 12 backtrack(nums[:i] + nums[i+1:],path+[nums[i]]) 13 return 14 backtrack(nums,[]) 15 return ans 16 17
时间复杂度O(n!)
39. 组合总和:
使用回溯算法,遍历所有可能即可。要对递归熟练。同时要合理利用约束条件降低时间复杂度!!!
1 class Solution: 2 def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: 3 res = [] 4 def backtrack(candidates,path): 5 if sum(path) == target: 6 temp = sorted(path) 7 if temp not in res: 8 res.append(temp) 9 return 10 if sum(path) > target: #说明此时已经不可能找到了 11 return 12 for i in range(len(candidates)): 13 # 不可被重复选取 14 # backtrack(candidates[:i]+candidates[i+1:],path+[candidates[i]]) 15 # 可被无限制重复选取 16 backtrack(candidates,path+[candidates[i]]) 17 return 18 backtrack(candidates,[]) 19 return res
39. 滑动窗口的最大值:
使用双端队列,维护一个元素从队首到队尾一次变小的双端队列,每次取最大值即为队首值!注意新加入元素的位置以及队首元素是否还在窗口中即可。
1 class Solution: 2 def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: 3 if not nums: 4 return [] 5 ans = [] 6 L = 0 7 R = 0 8 deque = [] 9 # 初始化滑动窗口 10 for i in range(0,k): 11 while deque and nums[deque[-1]] < nums[i]: 12 deque.pop(-1) 13 deque.append(i) 14 R+=1 15 # 注意窗口L-R闭区间 16 ans.append(nums[deque[0]]) 17 # 窗口开始滑动 18 while R < len(nums): 19 # 找到新加入窗口的元素位置 20 while deque and nums[deque[-1]] < nums[R]: 21 deque.pop(-1) 22 deque.append(R) 23 # 将不在窗口中的元素从队首pop,注意窗口区间一开一闭! 24 while deque and (deque[0] <= L or deque[0] > R): 25 deque.pop(0) 26 deque.append(R) 27 ans.append(nums[deque[0]]) 28 L+=1 29 R+=1 30 return ans
时间复杂度:O(n),每个元素最多进一次队列出一次队列最多操作2n次,空间复杂度O(n)。
38. 字符串的排列:
仍然是使用回溯法,注意如何去重:对字符串排好序之后,在遍历元素递归之前,如果当前元素和前一个元素相等,则说明前面已经对该字母进行过递归!故跳过此循环。
1 class Solution: 2 def permutation(self, s: str) -> List[str]: 3 res = [] 4 # 排序后方便进行去重 5 s = list(sorted(s)) 6 def backtrack(now,path): 7 if not now: 8 res.append(path) 9 return 10 # 说明此排列已经完成 11 for i in range(len(now)): 12 # 如果当前元素和前一个元素相同,则重复! 13 if i > 0 and now[i] == now[i-1]: 14 continue 15 backtrack(now[:i]+now[i+1:],path+now[i]) 16 backtrack(s,"") 17 return res
时间复杂度O(n!)
31. 下一个排列:
针对本题目,可以设计如下算法:
1.遍历数组找到满足nums[k]<nums[k+1]的最大的k,如果没找到说明数组降序排列,此时直接数组逆序。
2.遍历数组找到满足nums[l]>nums[k]的最大的l,将nums[k]和nums[l]交换,再将nums[k+1:]逆序即可!
原理:找第一个的过程从后往前找nums[k] < nums[k+1], 意味着k+1之后是不上升的 nums[j]>=nums[j+1], j>k, 因此,后面的是逆序的,即最大字典序,找到的值是比nums[k]大的最小值,设为nums[t],意味着nums[t]>nums[k]>nums[t+1], 交换nums[k]与nums[t]后最大字典序不变,逆序之后即最小字典序。
1 class Solution: 2 def nextPermutation(self, nums: List[int]) -> None: 3 """ 4 Do not return anything, modify nums in-place instead. 5 """ 6 index1 = 0 7 flag = 0 8 index2 = 0 9 for i in range(len(nums)): 10 if i+1>=len(nums): 11 if flag == 0: 12 index1 = -1 13 break 14 if nums[i]<nums[i+1]: 15 flag = 1 16 index1 = i 17 if index1 == -1: 18 nums.reverse() 19 return 20 for i in range(len(nums)): 21 if nums[i]>nums[index1]: 22 index2 = i 23 nums[index1],nums[index2] = nums[index2],nums[index1] 24 # 翻转nums[index1+1:] 25 nums[index1+1:] = list(reversed(nums[index1+1:])) 26
时间复杂度:O(n)
199. 二叉树的右视图:
对于本题目,本人采用层序遍历的方法,从左到右遍历每一层时将最后一个节点加入到ans中即可。
1 # Definition for a binary tree node. 2 # class TreeNode: 3 # def __init__(self, x): 4 # self.val = x 5 # self.left = None 6 # self.right = None 7 8 class Solution: 9 def rightSideView(self, root: TreeNode) -> List[int]: 10 # 按层进行BFS,每次添加到list中的都是queue的最后一个元素即可。 11 if not root: 12 return [] 13 ans = [] 14 queue = [] 15 queue.append(root) 16 ans.append(root.val) 17 while queue: 18 temp = [] 19 # 将下一层的节点从左向右全部加入temp中 20 for node in queue: 21 if node.left: 22 temp.append(node.left) 23 if node.right: 24 temp.append(node.right) 25 #将最靠右的节点加入ans中即为该层的右视图 26 if temp: 27 ans.append(temp[-1].val) 28 queue = temp 29 return ans
时间复杂度:O(n)
215. 数组中的第k个最大元素:
老题重做,对于本题而言,利用快速排序的partition函数进行递归即可。注意左右边界的传入!
1 class Solution: 2 def partition(self,nums,left,right,target): 3 # 随机化 4 random_index = random.randint(left, right) 5 nums[random_index],nums[right] = nums[right],nums[random_index] 6 main = nums[right] 7 i = left-1 8 j = left 9 while j<right: 10 if nums[j]>main: 11 nums[i+1],nums[j] = nums[j],nums[i+1] 12 i+=1 13 j+=1 14 nums[i+1],nums[right] = nums[right],nums[i+1] 15 # 数组下标从0计数,故加一 16 now = i+1+1-left 17 if now == target: 18 return nums[now-1+left] 19 elif now < target: 20 # 说明当前元素比target对应元素大,故应从右半部分递归搜索第target-now大的元素 21 return self.partition(nums,left + now,right,target-now) 22 elif now > target: 23 # 说明当前元素比target对应元素小,故应从左半部分递归 24 return self.partition(nums,left,left-1 + now-1,target) 25 26 27 def findKthLargest(self, nums: List[int], k: int) -> int: 28 return self.partition(nums,0,len(nums)-1,k)
时间复杂度O(n)
9. 回文数:
可以用栈将数字进行倒序,可以转换为字符串进行判断。也可以按如下方式:
1 class Solution: 2 def isPalindrome(self, x: int) -> bool: 3 if x < 0: 4 return False 5 else: 6 num = x 7 ans = 0 8 while num > 0: 9 ans += num%10 10 num//=10 11 if num > 0: 12 ans *= 10 13 return ans == x
时间复杂度O(n),空间复杂度O(1)
88. 合并两个有序数组:
归并排序的merge操作即可,注意此处要将nums2合并到nums1中,因此我们需要倒序遍历。
1 class Solution: 2 def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None: 3 """ 4 Do not return anything, modify nums1 in-place instead. 5 """ 6 i = m-1 7 j = n-1 8 k = len(nums1)-1 9 while i >= 0 and j >= 0: 10 if nums1[i] > nums2[j]: 11 nums1[k] = nums1[i] 12 k-=1 13 i-=1 14 else: 15 nums1[k] = nums2[j] 16 k-=1 17 j-=1 18 while i >= 0: 19 nums1[k] = nums1[i] 20 k-=1 21 i-=1 22 while j >= 0: 23 nums1[k] = nums2[j] 24 k-=1 25 j-=1
时间复杂度O(n),空间复杂度O(1)
14. 最长公共前缀
遍历即可。
1 class Solution: 2 def longestCommonPrefix(self, strs: List[str]) -> str: 3 if len(strs) == 0: 4 return "" 5 min_len = 10086 6 ans = "" 7 for s in strs: 8 if len(s)<min_len: 9 min_len = len(s) 10 for i in range(min_len): 11 for j in range(1,len(strs)): 12 if strs[j][i] != strs[j-1][i]: 13 return ans 14 ans += strs[0][i] 15 return ans
时间复杂度O(s),s为所有字符串长度和,空间复杂度O(1)
141. 环形链表
快慢指针即可解决。快指针每次走两个节点,慢指针每次走一个节点,如果存在环则快慢指针必然相遇。
1 # Definition for singly-linked list. 2 # class ListNode: 3 # def __init__(self, x): 4 # self.val = x 5 # self.next = None 6 7 class Solution: 8 def hasCycle(self, head: ListNode) -> bool: 9 slow = head 10 fast = head 11 while fast and fast.next: 12 fast = fast.next.next 13 slow = slow.next 14 if fast == slow: 15 return True 16 return False
时间复杂度O(n),空间复杂度O(1)
112. 路径总和
对左右子树递归dfs即可。
1 # Definition for a binary tree node. 2 # class TreeNode: 3 # def __init__(self, x): 4 # self.val = x 5 # self.left = None 6 # self.right = None 7 8 class Solution: 9 def hasPathSum(self, root: TreeNode, sum: int) -> bool: 10 if not root: 11 return False 12 left = False 13 right = False 14 if root.left: 15 left = self.hasPathSum(root.left,sum-root.val) 16 if root.right: 17 right = self.hasPathSum(root.right,sum-root.val) 18 if root.val == sum and not root.left and not root.right : 19 # 说明此时root为叶子结点 20 return True 21 # 说明此时不为叶子结点或者是不满足和为sum 22 return left or right 23
时间复杂度O(n),空间复杂度O(n)
160. 相交链表
设置两个node,node1和node2,令其next分别为headA和headB。让node1和node2同时向后走一个节点。如果node走到了链表末尾也即为null,将其重置为另一个链表的head即可。当node1等于node2时,则为相交链表的第一个相交节点。
1 # Definition for singly-linked list. 2 # class ListNode: 3 # def __init__(self, x): 4 # self.val = x 5 # self.next = None 6 7 class Solution: 8 def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode: 9 # 双指针 10 node1 = ListNode(0) 11 node2 = ListNode(0) 12 node1.next = headA 13 node2.next = headB 14 while True: 15 if node1: 16 node1 = node1.next 17 elif not node1: 18 # 此时将node1重置到headB 19 node1 = headB 20 if node2: 21 node2 = node2.next 22 elif not node2: 23 # 此时将node2重置到headA 24 node2 = headA 25 if node2 == node1: 26 return node1
时间复杂度O(n),空间复杂度O(1)
176. 第二高的薪水
sql嵌套,先找最高薪水,再找小于最高薪水的最高薪水即可。
101. 对称二叉树
递归即可,注意对null情况进行完备地判断!
1 # Definition for a binary tree node. 2 # class TreeNode: 3 # def __init__(self, x): 4 # self.val = x 5 # self.left = None 6 # self.right = None 7 8 class Solution: 9 def isSymmetric(self, root: TreeNode) -> bool: 10 def symmetric(node1,node2): 11 if not node1 and not node2: 12 # 两节点都为null 13 return True 14 if node1 and not node2 or node2 and not node1: 15 # 一个null一个有值false 16 return False 17 if node1.val!=node2.val: 18 return False 19 if not node1.left and not node1.right and not node2.left and not node2.right and node1.val==node2.val: 20 # 叶子节点必对称 21 return True 22 return symmetric(node1.left,node2.right) and symmetric(node1.right,node2.left) 23 if not root or not root.right and not root.left: 24 return True 25 return symmetric(root.left,root.right) 26 27 28 29
时间复杂度O(n),空间复杂度O(n)
175. 组合两个表
person左连接address即可保留address所有属性,注意平时的限制关键词where应该改成on!
543. 二叉树的直径:
对于此题目,对二叉树进行后序遍历即可:当前节点为null时,说明此时直径为0,如果当前节点不为null,求其左子树直径和右子树直径。如果左右子树直径之和大于当前保存的最大直径,则将最大值替换为左右子树直径之和。并将左右子树直径较大值+1返回上一层。
1 # Definition for a binary tree node. 2 # class TreeNode: 3 # def __init__(self, x): 4 # self.val = x 5 # self.left = None 6 # self.right = None 7 8 class Solution: 9 def diameterOfBinaryTree(self, root: TreeNode) -> int: 10 path = [] 11 # 后序遍历即可 12 def dfs(node): 13 if not node: 14 return 0 15 left = dfs(node.left) 16 right = dfs(node.right) 17 # 1代表当前节点! 18 path.append(left + right) 19 return max(left+1,right+1) 20 dfs(root) 21 #print(path) 22 if not path: 23 return 0 24 return max(path)
时间复杂度O(n),空间复杂度O(n)
103. 二叉树的锯齿形层次遍历:
对于此题目,在二叉树的层序遍历基础上新加一个层数判断即可。
1 # Definition for a binary tree node. 2 # class TreeNode: 3 # def __init__(self, x): 4 # self.val = x 5 # self.left = None 6 # self.right = None 7 8 class Solution: 9 def zigzagLevelOrder(self, root: TreeNode) -> List[List[int]]: 10 if not root: 11 return [] 12 ans = [] 13 queue = [] 14 queue.append(root) 15 ans.append([root.val]) 16 count = 1 17 while queue: 18 temp = [] 19 for i in queue: 20 if i.left: 21 temp.append(i.left) 22 if i.right: 23 temp.append(i.right) 24 if temp: 25 if count %2 != 0: 26 # 说明此时需要逆序遍历 27 ans_t = [] 28 for i in temp[::-1]: 29 ans_t.append(i.val) 30 ans.append(ans_t) 31 else: 32 ans_t = [] 33 for i in temp: 34 ans_t.append(i.val) 35 ans.append(ans_t) 36 count+=1 37 queue = temp 38 return ans
时间复杂度O(n),空间复杂度O(n)
19. 删除链表的倒数第N个节点:
双指针法:定义node1和node2,两节点均初始化为头结点,先让node2走n+1个节点,同时如果node2为空同时还要继续前进时说明要删除头结点,此时直接返回第二个节点即可。待node2遍历完成后,让node1和node2同时前进,当node2为null时,说明node1.next为待删除节点,删除即可。
1 # Definition for singly-linked list. 2 # class ListNode: 3 # def __init__(self, val=0, next=None): 4 # self.val = val 5 # self.next = next 6 class Solution: 7 def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode: 8 node1 = head 9 node2 = head 10 ans = head 11 for i in range(n+1): 12 # node2提前走n个节点 13 if node2: 14 node2 = node2.next 15 else: 16 return ans.next 17 while node2: 18 node1 = node1.next 19 node2 = node2.next 20 node1.next = node1.next.next 21 return ans 22 23
时间复杂度O(n),空间复杂度O(n)
92. 反转链表II:
此题目在反转链表的基础上指定了要反转的链表范围,可以先记node1为pre,pre.next为head,让node1前进m个节点,则此时node1即为待反转部分起点的前一个节点。此时我们可以添加一个变量count记录已经反转的节点个数,每翻转一次count++,当count>=n-m时,停止反转操作。对于返回值进行特判:如果m=1说明从起点开始反转,此时返回pre1.next。如果m>1说明从中间开始反转,直接返回head即可。
1 # Definition for singly-linked list. 2 # class ListNode: 3 # def __init__(self, x): 4 # self.val = x 5 # self.next = None 6 7 class Solution: 8 def reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode: 9 pre = ListNode(0) 10 pre.next = head 11 node1 = pre 12 for i in range(m-1): 13 node1 = node1.next 14 # 得到开始反转的节点的前一个节点为node1 15 count = 0 16 pre = node1 17 pre_ret = node1 18 node = node1.next 19 while count < n-m: 20 n_next = node.next 21 node.next = n_next.next 22 n_next.next = pre.next 23 pre.next = n_next 24 count+=1 25 if m > 1: 26 return head 27 return pre_ret.next
时间复杂度O(n),空间复杂度O(1)
102. 二叉树的层序遍历:
用队列遍历即可
1 # Definition for a binary tree node. 2 # class TreeNode: 3 # def __init__(self, x): 4 # self.val = x 5 # self.left = None 6 # self.right = None 7 8 class Solution: 9 def levelOrder(self, root: TreeNode) -> List[List[int]]: 10 if not root: 11 return [] 12 queue = [] 13 queue.append(root) 14 ans = [[root.val]] 15 while queue: 16 temp = [] 17 ans_temp = [] 18 for node in queue: 19 if node.left: 20 ans_temp.append(node.left.val) 21 temp.append(node.left) 22 if node.right: 23 ans_temp.append(node.right.val) 24 temp.append(node.right) 25 if ans_temp: 26 ans.append(ans_temp) 27 queue = temp 28 return ans
时间复杂度O(n),空间复杂度O(n)
102. 两两交换链表中的节点:
对于本题而言,在反转链表的基础上添加了两两反转的限制。在每次反转之后,将pre设置为node,并将node赋为node.next即可。注意节点数为奇数时最后一个节点不反转!关键代码:
# 两个一组对应操作 pre = node node = node.next
1 # Definition for singly-linked list. 2 # class ListNode: 3 # def __init__(self, val=0, next=None): 4 # self.val = val 5 # self.next = next 6 class Solution: 7 def swapPairs(self, head: ListNode) -> ListNode: 8 if not head or not head.next: 9 return head 10 pre = ListNode(0) 11 pre.next = head 12 node = head 13 ans = head.next 14 while node: 15 if not node.next: 16 # 对于余下一个节点的情况的处理 17 break 18 node_next = node.next 19 node.next = node_next.next 20 node_next.next = pre.next 21 pre.next = node_next 22 # 两个一组对应操作 23 pre = node 24 node = node.next 25 return ans 26
时间复杂度O(n),空间复杂度O(1)
22.括号生成
对于本题目而言,对于输入的数字n,先初始化n对括号的字符串,然后类似于全排列算法,求得括号组合的全排列,在递归的过程中进行剪枝(对于本体而言因为括号只有左右括号,因此当左右括号都被处理过之后可以跳过循环。)
1 class Solution: 2 def generateParenthesis(self, n: int) -> List[str]: 3 # 遍历所有情况,再筛选所有有效的括号组合 4 ans = [] 5 str_in = "" 6 for i in range(n): 7 str_in+="()" #initialize input 8 def judge(brack_str): 9 stack = [] 10 for i in brack_str: 11 if i == "(": 12 stack.append(i) 13 elif i == ")" and stack: 14 if stack.pop() != "(": 15 return False 16 elif not stack: 17 return False 18 return True 19 def backtrack(str_input,path): 20 flag_left = 0 21 flag_right = 0 22 if not str_input: 23 if judge(path) and path not in ans: 24 ans.append(path) 25 path = "" 26 return 27 for i in range(len(str_input)): 28 if str_input[i] == "(" and flag_left == 1: 29 continue 30 if str_input[i] == ")" and flag_right == 1: 31 continue 32 if str_input[i] == "(" and flag_left == 0: 33 flag_left = 1 34 if str_input[i] == ")" and flag_right == 0: 35 flag_right = 1 36 backtrack(str_input[:i]+str_input[i+1:],path+str_input[i]) 37 backtrack(str_in,"") 38 return ans
时间复杂度O(n!)
解法2:可以记左括号剩余数为left,右括号剩余数为right,用dfs即可。终止条件为left与right均为0.同时如果right<left,则不合法。
1 class Solution: 2 def generateParenthesis(self, n: int) -> List[str]: 3 # 遍历所有情况,再筛选所有有效的括号组合 4 ans = [] 5 str_in = "" 6 def backtrack(left,right,path): 7 if left == 0 and right == 0: 8 ans.append(path) 9 if right < left: 10 # 说明此时右括号用得多,不合法 11 return 12 if left > 0: 13 backtrack(left-1,right,path+"(") 14 if right > 0: 15 backtrack(left,right-1,path+")") 16 backtrack(n,n,"") 17 return ans
时间复杂度O(n!)
22.螺旋矩阵
对于此题目而言,边界条件的限定十分重要,记上下左右边界分别为:up,down,left,right,依次从边界行数或列数进行模拟遍历即可。
1 class Solution: 2 def spiralOrder(self, matrix: List[List[int]]) -> List[int]: 3 up = 0 4 down = len(matrix)-1 5 left = 0 6 right = len(matrix[0])-1 7 ans = [] 8 while True: 9 i = left 10 while i <= right: 11 ans.append(matrix[up][i]) 12 i+=1 13 up+=1 14 if up > down: 15 break 16 17 i = up 18 while i <= down: 19 ans.append(matrix[i][right]) 20 i+=1 21 right-=1 22 if left > right: 23 break 24 25 i = right 26 while i >= left: 27 ans.append(matrix[down][i]) 28 i-=1 29 down-=1 30 if up > down: 31 break 32 33 i = down 34 while i >= up: 35 ans.append(matrix[i][left]) 36 i-=1 37 left+=1 38 if left > right: 39 break 40 return ans
时间复杂度O(m*n)
148.排序链表
对于此题目要求而言,其要求空间复杂度为O(1),时间复杂度为O(nlogn),想到归并排序,但是限于空间复杂度不能使用递归。因此采用自底向上的迭代计算。
主要函数有两个:
cut(node,step):将node链表的前step个节点截取下来,并返回cut完后的头节点
merge(l1,l2):将l1和l2两个链表进行合并,最后返回链表头节点。
先两两进行归并,再四四进行归并,依次类推。代码如下:
1 # Definition for singly-linked list. 2 # class ListNode: 3 # def __init__(self, val=0, next=None): 4 # self.val = val 5 # self.next = next 6 class Solution: 7 def sortList(self, head: ListNode) -> ListNode: 8 def cut(node,step): 9 #切下node链表的前step个节点并返回切完后的头节点 10 #若节点不够,则能切几个是几个 11 for i in range(step-1): 12 if node: 13 node = node.next 14 if node: 15 ans = node.next 16 node.next = None 17 return ans 18 else: 19 return None 20 def merge(l1,l2): 21 pre = ListNode(0) 22 head = pre 23 while l1 and l2: 24 if l1.val>l2.val: 25 pre.next = l2 26 pre = pre.next 27 l2 = l2.next 28 else: 29 pre.next = l1 30 pre = pre.next 31 l1 = l1.next 32 while l1: 33 pre.next = l1 34 pre = pre.next 35 l1 = l1.next 36 while l2: 37 pre.next = l2 38 pre = pre.next 39 l2 = l2.next 40 return head.next 41 if not head: 42 return None 43 node = head 44 length = 0 45 pre = ListNode(0) 46 pre.next = head 47 current = pre.next 48 tail = pre 49 while node: 50 node = node.next 51 length+=1 52 for i in range(int(log(length,2))+1):# 归并操作次数 53 # 最后一次操作将整个链表的两半进行一次merge! 54 step = pow(2,i) 55 current = pre.next 56 tail = pre 57 while current: 58 left = current 59 right = cut(current,step) 60 current = cut(right,step)#下一次循环的left 61 tail.next = merge(left,right) #merge获取到合并的头节点,将已经合并的链表的尾部与该合并链表连接! 62 while tail.next: 63 tail = tail.next 64 return pre.next
时间复杂度O(nlogn),空间复杂度为O(1)
94.二叉树的中序遍历
本题目要求非递归,故使用栈对递归过程进行模拟。
根节点入栈,进入循环
如果当前节点有左节点,此时将其左节点进栈。
如果不存在左节点但存在右节点,此时将根节点出栈,将根节点值加入list中,并将根节点的右节点进栈。
如果当前节点为叶子节点,则直接将当前节点出栈并将值加入list中,同时当前节点的根节点的左子节点设为空(防止重入造成死循环)。
执行如上循环即可。
1 # Definition for a binary tree node. 2 # class TreeNode: 3 # def __init__(self, val=0, left=None, right=None): 4 # self.val = val 5 # self.left = left 6 # self.right = right 7 class Solution: 8 def inorderTraversal(self, root: TreeNode) -> List[int]: 9 if not root: 10 return [] 11 stack = [] 12 ans = [] 13 stack.append(root) 14 while stack: 15 if stack[-1].left: 16 #当前节点有左节点 17 stack.append(stack[-1].left) 18 elif stack[-1].right: 19 #当前节点无左节点,有右节点,此时应该pop出根节点 20 root_temp = stack.pop() 21 ans.append(root_temp.val) 22 stack.append(root_temp.right) 23 else: 24 # 当前节点为叶子节点,pop出当前节点并且将当前节点值加入ans,同时将其设置为不可重入(设为null) 25 node = stack.pop() 26 ans.append(node.val) 27 if len(stack)>0:#最后一个节点pop出之后stack为空,故需要判断 28 stack[-1].left = None 29 return ans 30 31
时间复杂度O(n),空间复杂度O(1)
322.零钱兑换
对于此题目有两种解法:
dfs+剪枝:将coins数组逆序,然后对答案进行dfs搜索,当target值为零时说明搜索到一个可行解,此时将min(ans,count)赋值给ans即可。
同时注意剪枝!
1.coins[j]>target时说明硬币面值太大,此时跳过此重循环
2.(self.ans-count)*coins[j] < target
说明此时最大面值硬币都无法凑到target,因此不能得到比当前最优解更优的解,故直接break循环
1 class Solution: 2 def coinChange(self, coins: List[int], amount: int) -> int: 3 self.ans = pow(2,31) - 1 4 coins.sort(reverse=True) 5 def backtrack(i,target,count): 6 if target==0: 7 self.ans = min(self.ans,count) 8 return 9 for j in range(i,len(coins)): 10 if coins[j] > target: 11 # 说明硬币面值太大,需要跳过此次循环 12 continue 13 if (self.ans-count)*coins[j] < target: 14 # 说明此种情况下不可能有比self.ans更优的解 15 break 16 backtrack(j,target-coins[j],count+1) 17 for i in range(len(coins)): 18 backtrack(i,amount,0) 19 return self.ans if self.ans!=pow(2,31) - 1 else -1
时间复杂度O(N*amount)
动态规划:
记dp[i]为总金额i所需要的最少硬币数,则dp[i] = min(dp[i],dp[i-coin[j]]+1),遍历即可
1 class Solution: 2 def coinChange(self, coins: List[int], amount: int) -> int: 3 #dp[i] 金额为i所需要的最少coin个数 4 num = amount+1 5 dp = [num for i in range(amount+1)] 6 dp[0] = 0 7 for i in range(1,amount+1): 8 for j in range(len(coins)): 9 if i >= coins[j] and dp[i-coins[j]] != num: 10 dp[i] = min(dp[i],dp[i-coins[j]]+1) 11 return dp[-1] if dp[-1] != amount+1 else -1
时间复杂度O(N*amount)
93.复原ip地址
对于本题目而言,使用回溯法+剪枝进行求解
终止条件:
当前字符串为空,说明已经全部加入,此时将路径加入结果并返回
剪枝条件:
当前字符串大于count*3或者小于count,此时不可能构成有效的ip地址,直接返回(结合题目要求得到这个条件)
去除前导0:
判断待加入的字符串第一位是否为0并且其长度是否大于1,如果均满足说明有前导0,直接退出循环即可。
代码如下:
1 class Solution: 2 def restoreIpAddresses(self, s: str) -> List[str]: 3 ans = [] 4 def backtrack(num_str,path,count): 5 if len(num_str) < count or len(num_str)>count*3: 6 return 7 if not num_str: 8 #去掉最后一个点 9 ans.append(path[:len(path)-1]) 10 for i in range(len(num_str)): 11 p,q = num_str[:i+1],num_str[i+1:] 12 if int(p[0]) == 0 and len(p) > 1: 13 #过滤前导零 14 break 15 if 0 <= int(p) and int(p)<=255: 16 backtrack(q,path+p+".",count-1) 17 backtrack(s,"",4) 18 return ans 19
200.岛屿数量
对于本题本人采用bfs的方式,添加一个visit数组记录该元素是否被访问过,循环遍历每个元素,当当前元素没有被访问过同时还是陆地时,利用队列进行宽度优先搜索,当完全搜索不到时说明该片陆地已经被完全遍历。此时count+1即可。注意边界条件的处理!
代码如下:
1 class Solution: 2 def numIslands(self, grid: List[List[str]]) -> int: 3 visit = [[False for i in range(len(grid[0]))] for i in range(len(grid))] 4 # bfs遍历图 5 queue = [] 6 line = len(grid) 7 if line == 0: 8 return 0 9 col = len(grid[0]) 10 if col == 0: 11 return 0 12 count = 0 #岛屿数量 13 for i in range(line): 14 for j in range(col): 15 if grid[i][j] == "1" and not visit[i][j]: #尚未访问过此节点 16 visit[i][j] = True 17 queue.append([i,j]) 18 #进行bfs 19 while queue: 20 i1,j1 = queue.pop() 21 # 将该点上下左右的所有没有被visit过的岛屿加入 22 if i1<line-1 and not visit[i1+1][j1] and grid[i1+1][j1] == "1": 23 queue.append([i1+1,j1]) 24 visit[i1+1][j1] = True 25 if i1>0 and not visit[i1-1][j1] and grid[i1-1][j1] == "1": 26 queue.append([i1-1,j1]) 27 visit[i1-1][j1] = True 28 if j1<col-1 and not visit[i1][j1+1] and grid[i1][j1+1] == "1": 29 queue.append([i1,j1+1]) 30 visit[i1][j1+1] = True 31 if j1>0 and not visit[i1][j1-1] and grid[i1][j1-1] == "1": 32 queue.append([i1,j1-1]) 33 visit[i1][j1-1] = True 34 count+=1 35 return count 36
时间复杂度不便于分析。
79.单词搜索
本题采用dfs进行搜索,注意递归前后visit数组的状态赋值为true和false,同时当找到一个可行解的时候记得要直接返回!否则会超时。
1 class Solution: 2 def exist(self, board: List[List[str]], word: str) -> bool: 3 #dfs求解 4 line = len(board) 5 col = len(board[0]) 6 ans = [] 7 visit = [[False for i in range(col)] for i in range(line)] 8 def dfs(rem,i,j): 9 if not rem:#说明此时已经没有剩余字符 10 ans.append(True) 11 return 12 if i>0 and board[i-1][j] == str(rem[0]) and not visit[i-1][j]: 13 visit[i-1][j] = True 14 dfs(rem[1:],i-1,j) 15 if len(ans) > 0: 16 return 17 visit[i-1][j] = False 18 if i<line-1 and board[i+1][j] == str(rem[0]) and not visit[i+1][j]: 19 visit[i+1][j] = True 20 dfs(rem[1:],i+1,j) 21 if len(ans) > 0: 22 return 23 visit[i+1][j] = False 24 if j>0 and board[i][j-1] == str(rem[0]) and not visit[i][j-1]: 25 visit[i][j-1] = True 26 dfs(rem[1:],i,j-1) 27 if len(ans) > 0: 28 return 29 visit[i][j-1] = False 30 if j<col-1 and board[i][j+1] == str(rem[0]) and not visit[i][j+1]: 31 visit[i][j+1] = True 32 dfs(rem[1:],i,j+1) 33 if len(ans) > 0: 34 return 35 visit[i][j+1] = False 36 for i in range(line): 37 for j in range(col): 38 if board[i][j] == str(word[0]) and not visit[i][j]: 39 visit[i][j] = True 40 dfs(word[1:],i,j) 41 visit[i][j] = False 42 if len(ans)>0: 43 return True 44 return False