• 为什么大多数编程语言中,数组要从 0 开始编号,而不是从 1 开始呢?

    从数组存储的内存模型上来看,“下标”最确切的定义应该是“偏移(offset)”。前面也讲到,如果用 a 来表示数组的首地址,a[0]就是偏移为 0 的位置,也就是首地址,a[k]就表示偏移 k 个 type_size 的位置,所以计算 a[k]的内存地址只需要用这个公式:a[k]_address = base_address + k type_size但是,如果数组从 1 开始计数,那我们计算数组元素 a[k]的内存地址就会变为:a[k]_address = base_address + (k-1)type_size对比两个公式,我们不难发现,从 1 开始编号,每次随机访问数组元素都多了一次减法运算,对于 CPU 来说,就是多了一次减法指令。


  • 散列表

散裂冲突?

  1. 开放寻执法
    1. 线性寻找
    2. 二次寻找
    3. 二次散裂
  2. 链表法 - bucket
  • 分治算法是一种处理问题的思想,递归是一种编程技巧。

实际上,分治算法一般都比较适合用递归来实现。分治算法的递归实现中,每一层递归都会涉及这样三个操作:

  1. 分解:将原问题分解成一系列子问题
  2. 解决:递归地求解各个子问题,若子问题足够小,则直接求解
  3. 合并:将子问题的结果合并成原问题。

Math & Bit

不用加号的加法 & ^ <<

  1. class Solution:
  2. def add(self, a: int, b: int) -> int:
  3. while b:
  4. a, b = a& 0xFFFFFFFF ^ b& 0xFFFFFFFF, (a&b)<<1 & 0xFFFFFFFF
  5. return a if a < 0x80000000 else ~(a^0xFFFFFFFF)

埃拉托斯特尼筛法求素数

  1. import math
  2. def prime(L):
  3. arr = list(range(2,L))
  4. for i in range(math.ceil(math.sqrt(len(arr)))+1):
  5. if arr[i] == 0:
  6. continue
  7. for j in range(i+1, len(arr)):
  8. if arr[j]!= 0 and arr[j]%arr[i]==0:
  9. arr[j]=0
  10. return [i for i in arr if i!=0]

一个一个筛选,能整除的肯定不是素数

  1. 当n是2的次幂, 那么 x%n == x & (n-1) 用位运算代替求模运算,可以提升效率
  2. 异或运算可以用来找出唯一不重复的数字
  3. & 1 可以用来做奇偶判断
  4. pow(x,n)可以把n转换为二进制,每次& 1来连乘

    剑指 Offer 56 - I. 数组中数字出现的次数

    https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/
    1. class Solution:
    2. def singleNumbers(self, nums: List[int]) -> List[int]:
    3. res = 0
    4. for num in nums:
    5. res ^= num
    6. mask = 1
    7. while res & mask == 0:
    8. mask <<= 1
    9. a, b = 0, 0
    10. for num in nums:
    11. if num & mask:
    12. a ^= num
    13. else:
    14. b ^= num
    15. return [a,b]

    169. 多数元素

    https://leetcode-cn.com/problems/majority-element/
    1. class Solution {
    2. public:
    3. int majorityElement(vector<int>& nums) {
    4. int res = 0;
    5. for(int i = 0 ; i < 32; ++i)
    6. {
    7. int ones = 0;
    8. for(int n : nums)
    9. ones += (n >> i) & 1; //位运算法统计每个位置上1出现的次数,每次出现则ones+1
    10. res += (ones > nums.size()/2) << i; //如果1出现次数大于1/2数组的长度,1即为这个位置的目标数字
    11. }
    12. return res;
    13. }
    14. };

    Sort

    冒泡排序

    每次都从数组最开始循环, 遇到前一个大于后一个的, 就把大的挪到后面, 所以一次循环之后,最大的元素会被放到最后
    1. def bubbleSort(arr):
    2. for i in range(len(arr)):
    3. for j in range(len(arr)-1-i):
    4. if arr[j] > arr[j+1]:
    5. arr[j], arr[j+1] = arr[j+1], arr[j]
    6. return arr

    选择排序

    每次循环找到最小的那个元素的index, 然后把它放到最前面
    1. def select(arr):
    2. for i in range(len(arr)):
    3. min_idx = i
    4. for j in range(i+1, len(arr)):
    5. if arr[j] < arr[min_idx]:
    6. min_idx = j
    7. if min_idx != i:
    8. arr[min_idx], arr[i] = arr[i], arr[min_idx]
    9. return arr

    插入排序

    每次循环从当前元素往前找, 把它插入到合适的位置
    1. def insert_sort(arr):
    2. for i in range(len(arr)):
    3. j = i
    4. while j > 0:
    5. if arr[j] < arr[j-1]:
    6. arr[j], arr[j-1] = arr[j-1], arr[j]
    7. j -= 1
    8. return arr

    快速排序

    分两部分
    递归函数是quick sort自身, 提供一个array, 提供一个low一个high, 把这个范围里的做个divide, 然后一边小于pivot, 一边大于pivot, 这个事情具体由partition函数来做, shuffle之后, 取第一个元素为pivot (也可以是最后一个,也可以是随机找的一个), 循环, 双指针: 如果小于pivot, 不操作, 左指针++, 如果大于pivot, 左右指针指向的元素调换, 右指针— ```python def partition(arr, low, high): pivot = arr[low] i = low + 1 j = high while i <= j: if arr[i] < pivot: i += 1 else: arr[i], arr[j] = arr[j], arr[i] j -= 1 arr[low], arr[j] = arr[j], arr[low] return j

def quick_sort(arr, low, high): if low < high: pivot = partition(arr, low, high) quick_sort(arr, low, pivot-1) quick_sort(arr, pivot+1, high) return arr

  1. <a name="XHPP9"></a>
  2. ### 堆排序
  3. - 堆排序是利用 **堆**进行排序的
  4. - **堆**是一种完全二叉树
  5. - **堆**有两种类型: **大根堆** **小根堆**
  6. - 两种类型的概念如下:<br />大根堆:每个结点的值都大于或等于左右孩子结点<br />小根堆:每个结点的值都小于或等于左右孩子结点
  7. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12408325/1611127316880-fc0ed86e-35bc-471a-8bfe-646efd618bc2.png#height=97&id=a1uox&margin=%5Bobject%20Object%5D&name=image.png&originHeight=290&originWidth=1154&originalType=binary&ratio=1&size=37884&status=done&style=none&width=384.6666666666667)<br />Find the maximum element and place the maximum element at the end.<br />a binary heap is a complete binary tree.
  8. 1. build a max heap from the input data
  9. 1. at this point, the largest item is stored at the root of the heap, replace it with the last item of the heap followed by reducing the size of heap by 1. finally heapify the root of the tree.
  10. 1. repeat step 2 while size of heap is greater than 1.
  11. ```python
  12. import time
  13. def heapify(arr,n, i):
  14. largest = i
  15. l = i*2 + 1
  16. r = i*2 + 2
  17. if l < n and arr[largest] < arr[l]:
  18. largest = l
  19. if r < n and arr[largest] < arr[r]:
  20. largest = r
  21. if largest!=i:
  22. arr[largest], arr[i] = arr[i], arr[largest]
  23. heapify(arr,n,largest)
  24. def heapSort(arr):
  25. n = len(arr)
  26. for i in range(n//2-1, -1, -1):
  27. heapify(arr, n, i)
  28. for i in range(n-1, 0,-1):
  29. arr[i], arr[0] = arr[0], arr[i]
  30. # heapify until i, because the rest have been sorted and removed from the heap
  31. heapify(arr,i,0)
  32. return arr

Sliding Window

2021-02是滑动窗口月, 开一篇单独的题解来记录二月份的滑动窗口

480. 滑动窗口中位数

https://leetcode-cn.com/problems/sliding-window-median/
相比滑动窗口的最大值, 维护一个当前窗口的最大值, 这道题就变成了要维护一个有序数组(因为要找中位数), 然后用二分法进行查找.

  1. import bisect
  2. class Solution:
  3. def medianSlidingWindow(self, nums: List[int], k: int) -> List[float]:
  4. res = []
  5. window = []
  6. left = 0
  7. for right in range(len(nums)):
  8. bisect.insort(window, nums[right])
  9. if right >= k-1:
  10. if k & 1:
  11. res.append(window[k//2])
  12. else:
  13. res.append((window[k//2]+window[(k-1)//2])/2.0)
  14. window.pop(bisect.bisect_left(window, nums[left]))
  15. left += 1
  16. return res

上面的题解主要是关于bisect的使用, 但是由于window.pop()的操作时间复杂度依然为O(k)

Design

146. LRU 缓存机制

https://leetcode-cn.com/problems/lru-cache/
关键点在于每次的挪动,删除,增加的操作要记得同时更新哈希表和链表
LRU:

  1. key-value的保存,O(1) search, then HashMap - > key-ListNode(value)
  2. Double LinkedList, first in will be put in the front
    1. add a node at the end ( the latest used one and new coming)
    2. delete a node from the front ( full capacity)
    3. move a random node at the end ( refresh a node)

What do we need?

  1. class LinkedList - > key, value, next, prev
  2. a HashMap -> key - ListNode(key,value)
  3. def get - > if not in HashMap, return -1, if in HashMap, (1) refresh the corresponding key’s value in the HashMap (2) move the key to the tail of LinkedList, at the end return the value
  4. def put ->
    if in HashMap -> move it to the tail of LinkedList,
    if not in HashMap -> if capacity full, remove head.next from HashMap and remove head.next from LinkedList, then add the new one to HashMap, add the new one to the tail of LinkedList ```python class ListNode: def init(self,key=None, val=None):
    1. self.key=key
    2. self.val=val
    3. self.next = None
    4. self.prev = None

class LRUCache:

  1. def __init__(self, capacity: int):
  2. self.HashMap = {}
  3. self.capacity = capacity
  4. self.head = ListNode()
  5. self.tail = ListNode()
  6. self.head.next, self.tail.prev = self.tail, self.head
  7. def move_to_tail(self, key):
  8. # 从自己所在位置拿出来
  9. node = self.HashMap[key]
  10. node.prev.next = node.next
  11. node.next.prev = node.prev
  12. # 放到tail之前
  13. self.add_to_tail(node)
  14. def remove_from_head(self):
  15. # delete from HashMap
  16. # 链表里存了key就为了这个时候反向定位,从hashmap弹出
  17. self.HashMap.pop(self.head.next.key)
  18. # delete from the LinkList
  19. self.head.next = self.head.next.next
  20. self.head.next.prev = self.head
  21. def add_to_tail(self, node):
  22. # node 的前后
  23. node.prev = self.tail.prev
  24. node.next = self.tail
  25. # assign node to its prev's next and next's prev
  26. self.tail.prev.next = node
  27. self.tail.prev = node
  28. def get(self, key: int) -> int:
  29. if key not in self.HashMap:
  30. return -1
  31. else:
  32. self.move_to_tail(key)
  33. return self.HashMap[key].val
  34. def put(self, key: int, value: int) -> None:
  35. if key in self.HashMap:
  36. self.HashMap[key].val = value # update HashMap
  37. self.move_to_tail(key) # update the LinkedList
  38. else:
  39. if len(self.HashMap)==self.capacity: # check capacity
  40. self.remove_from_head()
  41. newNode = ListNode(key,value)
  42. self.HashMap[key]=newNode
  43. self.add_to_tail(newNode)
  1. <a name="5CKDu"></a>
  2. #### [716. 最大栈](https://leetcode-cn.com/problems/max-stack/)
  3. [https://leetcode-cn.com/problems/max-stack/](https://leetcode-cn.com/problems/max-stack/)<br />两个栈解决,time complexity O(n)<br />每次两个栈都压入,区别是max_stack是用来存放当前stack里最大value的,如果push的value大于max_stack[-1],那么压入value, 如果小于,那么把max_stack[-1]再压入一次
  4. ```python
  5. class MaxStack:
  6. def __init__(self):
  7. """
  8. initialize your data structure here.
  9. """
  10. self.stack = []
  11. self.max_stack = []
  12. def push(self, x: int) -> None:
  13. self.stack.append(x)
  14. if not self.max_stack:
  15. self.max_stack.append(x)
  16. else:
  17. self.max_stack.append(max(self.max_stack[-1],x))
  18. def pop(self) -> int:
  19. if self.stack:
  20. self.max_stack.pop()
  21. return self.stack.pop()
  22. else:
  23. return -1
  24. def top(self) -> int:
  25. return self.stack[-1]
  26. def peekMax(self) -> int:
  27. return self.max_stack[-1]
  28. def popMax(self) -> int:
  29. tmp = []
  30. while self.stack and self.stack[-1]!=self.max_stack[-1]:
  31. tmp.append(self.stack.pop())
  32. self.max_stack.pop()
  33. res = self.stack.pop()
  34. self.max_stack.pop()
  35. while tmp:
  36. self.push(tmp.pop())
  37. return res

难点在于popMax(), O(logN)需要用到二叉搜索树+双向链表

实现哈希表
https://leetcode-cn.com/problems/design-hashmap/

  1. class LinkedNode:
  2. def __init__(self, key=0,val=0, next=None):
  3. self.key = key
  4. self.val = val
  5. self.next = next
  6. class HashMap:
  7. def __init__(self):
  8. self.capacity = 1<<3
  9. self.loader_factor = 0.75
  10. self.arr = [None]*self.capacity
  11. self.count = 0
  12. def reset(self):
  13. self.capacity = self.capacity << 1
  14. self.tmp_arr = self.arr[:]
  15. self.arr = [None]*self.capacity
  16. for node in self.tmp_arr:
  17. while node:
  18. self.put(node.key, node.value)
  19. node = node.next
  20. def get(self,key):
  21. hash_key = self.get_hash_key(key)
  22. if self.arr[hash_key]:
  23. head = self.arr[hash_key]
  24. while head:
  25. if head.key == key:
  26. return head.val
  27. head = head.next
  28. # if not find
  29. return
  30. def get_hash_key(self, key):
  31. return key & (self.capacity -1)
  32. def put(self,key,value):
  33. if self.count > self.capacity*self.loader_factor:
  34. self.reset()
  35. hash_key = self.get_hash_key(key)
  36. if not self.arr[hash_key]:
  37. self.arr[hash_key] = LinkedNode(key=key,val=value)
  38. self.count+=1
  39. else:
  40. head = self.arr[hash_key]
  41. while head:
  42. if head.key == key:
  43. head.val = value
  44. break
  45. if not head.next:
  46. head.next = LinkedNode(key=key,val=value)
  47. self.count+=1
  48. head = head.next

Stack

面试题 03.02. 栈的最小值

https://leetcode-cn.com/problems/min-stack-lcci/
维护一个最小值stack,每个元素对应当前位置下往前所有元素的最小值

  1. class MinStack:
  2. def __init__(self):
  3. """
  4. initialize your data structure here.
  5. """
  6. self.stack = []
  7. self.sorted_stack = []
  8. self.min_value = float('inf')
  9. def push(self, x: int) -> None:
  10. self.stack.append(x)
  11. if x < self.min_value:
  12. self.sorted_stack.append(x)
  13. self.min_value=x
  14. else:
  15. self.sorted_stack.append(self.min_value)
  16. def pop(self) -> None:
  17. if not self.stack:
  18. return None
  19. res = self.stack.pop()
  20. self.sorted_stack.pop()
  21. if res == self.min_value:
  22. self.min_value = float('inf') if not self.sorted_stack else self.sorted_stack[-1]
  23. return res
  24. def top(self) -> int:
  25. if self.stack:
  26. return self.stack[-1]
  27. else:
  28. return None
  29. def getMin(self) -> int:
  30. if self.sorted_stack:
  31. return self.sorted_stack[-1]
  32. else:
  33. return None

剑指 Offer 59 - II. 队列的最大值

https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof/

  1. from queue import Queue,deque
  2. class MaxQueue:
  3. def __init__(self):
  4. self.queue = Queue()
  5. self.deque = deque()
  6. def max_value(self) -> int:
  7. if self.deque:
  8. return self.deque[0]
  9. else:
  10. return -1
  11. def push_back(self, value: int) -> None:
  12. while self.deque and self.deque[-1]<value:
  13. self.deque.pop()
  14. self.deque.append(value)
  15. self.queue.put(value)
  16. def pop_front(self) -> int:
  17. if not self.deque:
  18. return -1
  19. res = self.queue.get()
  20. if res == self.deque[0]:
  21. self.deque.popleft()
  22. return res

剑指 Offer 31. 栈的压入、弹出序列

https://leetcode-cn.com/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/
场景还原:
新建一个临时stack,按照压栈顺序压入,每次压入后判断临时stack的最后一位和出栈序列的第一位是否相等,相等的话说明该弹出了,就持续弹出符合条件的元素,等循环结束后,如果临时stack和弹出序列都是空,那么就说明符合

  1. class Solution:
  2. def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
  3. if not pushed and not popped:
  4. return True
  5. stack = list()
  6. while pushed:
  7. num = pushed.pop(0)
  8. stack.append(num)
  9. while stack and popped:
  10. if stack[-1] == popped[0]:
  11. popped.pop(0)
  12. stack.pop()
  13. else:
  14. break
  15. return not popped and not stack

331. 验证二叉树的前序序列化

https://leetcode-cn.com/problems/verify-preorder-serialization-of-a-binary-tree/
栈的消消乐

  1. class Solution:
  2. def isValidSerialization(self, preorder: str) -> bool:
  3. stack = []
  4. for node in preorder.split(","):
  5. stack.append(node)
  6. while len(stack) > 2 and stack[-1]=='#' and stack[-2]=='#' and stack[-3].isdigit():
  7. for i in range(3):
  8. stack.pop()
  9. stack.append('#')
  10. return True if stack ==['#'] else False

入度和出度

UnionFind

放两个模板
模版一:
初始化状态下,每个节点都不在子集合里,根节点的parent是None

  1. class UnionFind:
  2. def __init__(self):
  3. """
  4. 记录每个节点的父节点
  5. """
  6. self.father = {}
  7. def find(self,x):
  8. """
  9. 查找根节点
  10. 路径压缩
  11. """
  12. root = x
  13. while self.father[root] != None:
  14. root = self.father[root]
  15. # 路径压缩
  16. while x != root:
  17. original_father = self.father[x]
  18. self.father[x] = root
  19. x = original_father
  20. return root
  21. def merge(self,x,y,val):
  22. """
  23. 合并两个节点
  24. """
  25. root_x,root_y = self.find(x),self.find(y)
  26. if root_x != root_y:
  27. self.father[root_x] = root_y
  28. def is_connected(self,x,y):
  29. """
  30. 判断两节点是否相连
  31. """
  32. return self.find(x) == self.find(y)
  33. def add(self,x):
  34. """
  35. 添加新节点
  36. """
  37. if x not in self.father:
  38. self.father[x] = None

模板二:
初始状态下所有节点的parent是他自己,所有没有了add函数,而且在find root函数里判断条件不是parent为None,而是parent为自身

1202. 交换字符串中的元素

  1. class UnionFind:
  2. def __init__(self,n):
  3. self.parent = [i for i in range(n)]
  4. def find(self, x):
  5. root = x
  6. while self.parent[root]!=root:
  7. root = self.parent[root]
  8. while x!=root:
  9. self.parent[x],x, = root ,self.parent[x]
  10. return root
  11. def union(self,x,y):
  12. root_x, root_y = self.find(x), self.find(y)
  13. if root_x!=root_y:
  14. self.parent[root_x]=root_y
  15. from collections import defaultdict
  16. class Solution:
  17. def smallestStringWithSwaps(self, s: str, pairs: List[List[int]]) -> str:
  18. uf = UnionFind(len(s))
  19. for i,j in pairs:
  20. uf.union(i,j)
  21. children = defaultdict(lambda: set())
  22. for i,j in pairs:
  23. root = children[uf.find(i)]
  24. root.add(i)
  25. root.add(j)
  26. arr = list(s)
  27. for v in children.values():
  28. for i,j in zip(sorted(v),sorted([arr[i] for i in v])):
  29. arr[i]=j
  30. return "".join(arr)

200. 岛屿数量

https://leetcode-cn.com/problems/number-of-islands/
岛屿的数量还是用DFS写起来好理解一些

1319. 连通网络的操作次数

https://leetcode-cn.com/problems/number-of-operations-to-make-network-connected/

  1. n台计算机至少需要n-1条线才能全部联通
  2. 用并查集计算有多少个cluster,那么至少需要cluster-1条多余的连接线才可以做到全连通
  3. 如何计算多余的线? 其实并不需要计算,因为如果两个cluster之间没有足够的线把他们链接起来,那么就已经不满足第一个条件了

    1. class UnionFind:
    2. def __init__(self, n):
    3. self.family = [i for i in range(n)]
    4. self.redundant = 0
    5. def find(self,x):
    6. root = x
    7. while self.family[root]!=root:
    8. root = self.family[root]
    9. while root!=x:
    10. self.family[x], x = root, self.family[x]
    11. return root
    12. def union(self,x,y):
    13. root_x, root_y = self.find(x), self.find(y)
    14. if root_x!=root_y:
    15. self.family[root_x] = root_y
    16. def is_connected(self,x,y):
    17. return self.find(x) == self.find(y)
    18. class Solution:
    19. def makeConnected(self, n: int, connections: List[List[int]]) -> int:
    20. if len(connections) < n-1:
    21. return -1
    22. uf = UnionFind(n)
    23. for a,b in connections:
    24. if uf.is_connected(a,b):
    25. uf.redundant+=1
    26. uf.union(a, b)
    27. clusters = 0
    28. for i in range(n):
    29. if uf.family[i]==i:
    30. clusters+=1
    31. if uf.redundant < clusters-1:
    32. return -1
    33. else:
    34. return clusters-1

    LinkedList

    24. 两两交换链表中的节点

    https://leetcode-cn.com/problems/swap-nodes-in-pairs/
    链表操作时尽量使用dummy节点
    注意post节点更新的时机

    1. class Solution:
    2. def swapPairs(self, head: ListNode) -> ListNode:
    3. if not head or not head.next:
    4. return head
    5. dummy = ListNode(-1)
    6. dummy.next = head
    7. pre = dummy
    8. while head and head.next:
    9. post = head.next
    10. # swap
    11. head.next = post.next
    12. post.next = head
    13. pre.next = post
    14. # update pointer
    15. pre = head
    16. head = head.next
    17. return dummy.next

    206. 反转链表

    https://leetcode-cn.com/problems/reverse-linked-list/
    迭代

    1. class Solution:
    2. def reverseList(self, head: ListNode) -> ListNode:
    3. cur = None
    4. while head:
    5. tmp = head.next
    6. head.next = cur
    7. cur = head
    8. head = tmp
    9. return cur

    递归
    image.png

    1. class Solution:
    2. def reverseList(self, head: ListNode) -> ListNode:
    3. if not head or not head.next:
    4. return head
    5. newHead = self.reverseList(head.next)
    6. head.next.next = head
    7. head.next = None
    8. return newHead

    92. 反转链表 II

    https://leetcode-cn.com/problems/reverse-linked-list-ii/
    第一个版本写的比较丑,一个指针指向m的pre,一个指针指向n,一直把pre_m.next移动到n的后面就可以了
    这种情况需要特殊考虑当m是1的时候,也就是要从第头指针开始反转的情况
    第二种: 其实没有必要找到n点,只需要每次把m.next挪到m之前, pre_m之后即可

    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 reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode:
    8. if m == n:
    9. return head
    10. dummy = ListNode(-1)
    11. dummy.next = head
    12. pre = dummy
    13. i = 0
    14. while i < m-1:
    15. pre = pre.next
    16. i += 1
    17. pre_m = pre
    18. while i < n:
    19. pre = pre.next
    20. i += 1
    21. node_n = pre
    22. i = 0
    23. while i < n - m :
    24. # 从前面拿出来
    25. node = pre_m.next
    26. pre_m.next = pre_m.next.next
    27. # 放到后面去
    28. tmp = node_n.next
    29. node_n.next = node
    30. node.next = tmp
    31. i += 1
    32. return dummy.next

    只需要找到反转的起始位置, 使用头插法
    用一个守卫节点指向反转起始位置,设为g
    起始位置为p, 每次将p.next放到g.next. 循环m-n次

    1. class Solution:
    2. def reverseBetween(self, head: ListNode, left: int, right: int) -> ListNode:
    3. if left == right:
    4. return head
    5. dummy = ListNode(-1)
    6. dummy.next = head
    7. pre = dummy
    8. i = 0
    9. while i < left-1:
    10. pre = pre.next
    11. i += 1
    12. g = pre
    13. p = g.next
    14. for i in range(right-left):
    15. tmp = p.next
    16. p.next = tmp.next
    17. tmp.next = g.next
    18. g.next = tmp
    19. return dummy.next

    445. 两数相加 II

    https://leetcode-cn.com/problems/add-two-numbers-ii/

    1. class Solution:
    2. def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
    3. stack_l1 = []
    4. stack_l2 = []
    5. while l1:
    6. stack_l1.append(l1.val)
    7. l1 = l1.next
    8. while l2:
    9. stack_l2.append(l2.val)
    10. l2 = l2.next
    11. carry = 0
    12. while stack_l2 or stack_l1 or carry:
    13. a = stack_l2.pop() if stack_l2 else 0
    14. b = stack_l1.pop() if stack_l1 else 0
    15. summary = a+b+carry
    16. node = ListNode(summary%10)
    17. carry = summary//10
    18. node.next = head
    19. head = node
    20. return head

    21. 合并两个有序链表

    https://leetcode-cn.com/problems/merge-two-sorted-lists/
    迭代

    1. class Solution:
    2. def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
    3. dummy = ListNode(-1)
    4. res = dummy
    5. while l1 and l2:
    6. if l2.val<l1.val:
    7. res.next = l2
    8. l2 = l2.next
    9. else:
    10. res.next = l1
    11. l1 = l1.next
    12. res = res.next
    13. res.next = l1 if l1 else l2
    14. return dummy.next

    递归

    1. class Solution:
    2. def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
    3. if not l1:return l2
    4. if not l2:return l1
    5. if l1.val<l2.val:
    6. l1.next = self.mergeTwoLists(l1.next, l2)
    7. return l1
    8. else:
    9. l2.next = self.mergeTwoLists(l1, l2.next)
    10. return l2

    23. 合并K个升序链表

    https://leetcode-cn.com/problems/merge-k-sorted-lists/
    21的升级版

  4. 循环遍历k个链表, python会TLE

    1. class Solution:
    2. def mergeKLists(self, lists: List[ListNode]) -> ListNode:
    3. if not lists:
    4. return
    5. dummy = ListNode(0)
    6. res = dummy
    7. size = len(lists)
    8. while True:
    9. minNode = None
    10. minPointer = -1
    11. for i in range(len(lists)):
    12. if not lists[i]:
    13. continue
    14. if not minNode or lists[i].val < minNode.val:
    15. minNode = lists[i]
    16. minPointer = i
    17. if minPointer==-1:
    18. break
    19. res.next = minNode
    20. res = res.next
    21. lists[minPointer] = lists[minPointer].next
    22. return dummy.next

    分治, 归并排序应用于链表

    1. class Solution:
    2. def mergeKLists(self, lists: List[ListNode]) -> ListNode:
    3. if not lists:
    4. return
    5. def mergeTwoLists(l1,l2):
    6. dummy = ListNode(-1)
    7. res = dummy
    8. while l1 and l2:
    9. if l2.val<l1.val:
    10. res.next = l2
    11. l2 = l2.next
    12. else:
    13. res.next = l1
    14. l1 = l1.next
    15. res = res.next
    16. res.next = l1 if l1 else l2
    17. return dummy.next
    18. def merge(lists, lo, hi):
    19. if lo==hi:
    20. return lists[lo]
    21. mid = lo + (hi-lo)//2
    22. l1 = merge(lists, lo, mid)
    23. l2 = merge(lists, mid+1, hi)
    24. return mergeTwoLists(l1,l2)
    25. return merge(lists, 0 , len(lists)-1)

    拍案叫绝,给ListNode class增加lt方法, 就可以用来比较大小

    1. class Solution:
    2. def mergeKLists(self, lists: List[ListNode]) -> ListNode:
    3. def __lt__(self, other):
    4. return self.val < other.val
    5. ListNode.__lt__ = __lt__
    6. import heapq
    7. heap = []
    8. dummy = ListNode(-1)
    9. p = dummy
    10. for l in lists:
    11. if l:
    12. heapq.heappush(heap, l)
    13. while heap:
    14. node = heapq.heappop(heap)
    15. p.next = ListNode(node.val)
    16. p = p.next
    17. if node.next:
    18. heapq.heappush(heap, node.next)
    19. return dummy.next

    25. K 个一组翻转链表

    https://leetcode-cn.com/problems/reverse-nodes-in-k-group/
    一旦看到翻转顺序, 请第一时间先想到stack!

    160. 相交链表

    https://leetcode-cn.com/problems/intersection-of-two-linked-lists/
    我一开始想到的是用哈希表,两个链表都往哈希表里存,如果一个链表存的时候发现已经存在了,那么肯定是另外一个链表存进去的,此时返回这个节点
    但是这样空间复杂度太高, 这道题可以用空间复杂度为0(1)的解法, 也就是长短链表拼接,假设两个链表相交,那么相交的部分长度是相等的,我们分为一个长链表一个短链表, 两个链表从表头开始循环,如果长的走完了,那么就走短的,如果短的走完了,那么就走长的; 如果有交点,那么他们会同时抵达交点,此刻走过的路程都是等于 长+短+相交, 顺序是 长+相交+短 和 短+相交+长。
    这里最巧妙的地方在于边界条件的处理,要把最后一个节点之后的None也加到循环中来, 这样如果两个链表不相交,那么最终会在等于None的地方相交(满足退出条件)

    1. class Solution:
    2. def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
    3. ha, hb = headA, headB
    4. while ha!=hb:
    5. ha = ha.next if ha else headB
    6. hb = hb.next if hb else headA
    7. return ha

    138. 复制带随机指针的链表

    https://leetcode-cn.com/problems/copy-list-with-random-pointer/ ```python “””

    Definition for a Node.

    class Node: def init(self, x: int, next: ‘Node’ = None, random: ‘Node’ = None):

    1. self.val = int(x)
    2. self.next = next
    3. self.random = random

    “””

class Solution: def copyRandomList(self, head: ‘Node’) -> ‘Node’: lookup = {} if not head: return None p = head while p: lookup[p] = Node(p.val) p = p.next p = head while p: lookup[p].next = lookup[p.next] if p.next else None p = p.next p = head while p: lookup[p].random = lookup[p.random] if p.random else None p = p.next return lookup[head]

  1. <a name="1lT4Q"></a>
  2. #### [234. 回文链表](https://leetcode-cn.com/problems/palindrome-linked-list/)
  3. [https://leetcode-cn.com/problems/palindrome-linked-list/](https://leetcode-cn.com/problems/palindrome-linked-list/)<br />简单的题不简单的做, 涉及快慢指针, 链表反转<br />用快慢指针找到链表的中间位置 ( 注意链表长度的奇偶分别判断)<br />反转链表后半部分<br />此时两个指针分别指向链表的开头 和 链表中间(后半部分已经翻转)<br />逐个对比, 判断回文
  4. ```python
  5. # Definition for singly-linked list.
  6. # class ListNode:
  7. # def __init__(self, val=0, next=None):
  8. # self.val = val
  9. # self.next = next
  10. class Solution:
  11. def isPalindrome(self, head: ListNode) -> bool:
  12. if not head or not head.next:
  13. return True
  14. fast,slow = head,head
  15. # fast pointer is twice faster than slow pointer
  16. while fast and fast.next:
  17. slow = slow.next
  18. fast = fast.next.next
  19. # if fast pointer is not None, it means the length of linkedlist is odd
  20. if fast:
  21. slow = slow.next
  22. # reverse the linkedlist leading by slow pointer
  23. pre = ListNode(-1)
  24. pre.next = slow
  25. while slow.next:
  26. post = slow.next
  27. slow.next = post.next
  28. post.next = pre.next
  29. pre.next = post
  30. slow = pre.next
  31. # compare from head
  32. while head and slow:
  33. if head.val!=slow.val:
  34. return False
  35. head = head.next
  36. slow = slow.next
  37. return True

DFS&BFS

200. 岛屿数量

https://leetcode-cn.com/problems/number-of-islands/
This question can be solved by DFS, BFS, Union-Find, I only practiced DFS and Union-Find
这道题也可以不用visited函数,在原来grid上修改,但是如果面试中,需要询问是否可以修改原数组

  1. class Solution:
  2. def numIslands(self, grid: List[List[str]]) -> int:
  3. rows = len(grid)
  4. if rows==0:
  5. return 0
  6. cols = len(grid[0])
  7. visited = [[False for _ in range(cols)] for _ in range(rows)]
  8. DIRECTION = ((1,0),(-1,0),(0,1),(0,-1))
  9. count = 0
  10. def isArea(x,y):
  11. return x>=0 and y>=0 and x<=rows-1 and y<=cols-1
  12. def dfs(i,j):
  13. visited[i][j] = True
  14. for k in range(4):
  15. x = i + DIRECTION[k][0]
  16. y = j + DIRECTION[k][1]
  17. if isArea(x,y) and not visited[x][y] and grid[x][y]=='1':
  18. dfs(x,y)
  19. for i in range(rows):
  20. for j in range(cols):
  21. if not visited[i][j] and grid[i][j]=='1':
  22. dfs(i,j)
  23. count+=1
  24. return count

BFS
主函数差不多,遍历函数用一个队列来维护了和当前岛屿连接的岛屿,注意DFS只需要向下或者向右,BFS需要把上下左右都加进来查看

  1. from collections import deque
  2. class Solution:
  3. def numIslands(self, grid: List[List[str]]) -> int:
  4. rows =len(grid)
  5. cols =len(grid[0])
  6. DIRECTION = ((1,0),(0,1),(-1,0),(0,-1))
  7. count=0
  8. def inArea(x,y):
  9. return x>=0 and y>=0 and x<rows and y<cols
  10. def bfs(x,y):
  11. queue = deque()
  12. queue.append([x,y])
  13. while queue:
  14. i,j = queue.popleft()
  15. if inArea(i,j) and grid[i][j]=='1':
  16. grid[i][j]='0'
  17. for k in range(4):
  18. queue.append([i+DIRECTION[k][0],j+DIRECTION[k][1]])
  19. for i in range(rows):
  20. for j in range(cols):
  21. if grid[i][j]=='1':
  22. bfs(i,j)
  23. count+=1
  24. return count

22. 括号生成

https://leetcode-cn.com/problems/generate-parentheses/
left和right从n开始递减,表示还剩多少个括号留下没用

  1. def generateParenthesis(self, n: int) -> List[str]:
  2. res = []
  3. cur_str = ''
  4. def dfs(cur_str, left, right):
  5. '''
  6. params:
  7. cur_str, the string from root to leaf
  8. left, how many left ( left
  9. right, how many right ) left
  10. '''
  11. if left==0 and right==0:
  12. res.append(cur_str)
  13. return
  14. if right<left:
  15. return
  16. if left > 0:
  17. dfs(cur_str+'(', left-1,right)
  18. if right >0:
  19. dfs(cur_str+')', left, right-1)
  20. dfs(cur_str, n,n)
  21. return res

left和right从0开始,逐渐增加,注意判断条件变成了right要严格小于left,如果大于了就要return

  1. def generateParenthesis(self, n: int) -> List[str]:
  2. res = []
  3. cur_str = ''
  4. def dfs(cur_str, left, right):
  5. '''
  6. params:
  7. cur_str, the string from root to leaf
  8. left, how many left ( left
  9. right, how many right ) left
  10. '''
  11. if left==n and right==n:
  12. res.append(cur_str)
  13. return
  14. if right>left:
  15. return
  16. if left < n:
  17. dfs(cur_str+'(', left+1,right)
  18. if right < n:
  19. dfs(cur_str+')', left, right+1)
  20. dfs(cur_str, 0,0)
  21. return res

剑指 Offer 12. 矩阵中的路径

https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof/

329. 矩阵中的最长递增路径

https://leetcode-cn.com/problems/longest-increasing-path-in-a-matrix/
拓扑排序, 课程表是为了找环,如果有环,说明无法学完全部课程,因为有的课程互为先决条件,导致有环出现。
这道题以数字大小来作为出度入度,是肯定没有环的,要计算的最长路径,那么就是每次把入度为0的作为一层,将这一层点挪去之后,还剩下的入度为0的点就是下一次,加入队列进入下一次循环, 经过多少次循环后再也没有入度为0的点就得到了最长递增路径

  1. from collections import deque
  2. class Solution:
  3. def longestIncreasingPath(self, matrix: List[List[int]]) -> int:
  4. if not matrix:
  5. return 0
  6. rows, cols = len(matrix), len(matrix[0])
  7. DIRECTION = ((1,0),(-1,0),(0,1),(0,-1))
  8. # indegrees每个坐标上的值,是matrix对应坐标位置的入度
  9. indegrees = [[0 for _ in range(cols)] for _ in range(rows)]
  10. def inArea(x,y):
  11. return x>=0 and y>=0 and x<rows and y<cols
  12. # calculate the indegrees
  13. for i in range(rows):
  14. for j in range(cols):
  15. for k in range(4):
  16. new_x = i + DIRECTION[k][0]
  17. new_y = j + DIRECTION[k][1]
  18. if inArea(new_x, new_y) and matrix[i][j] > matrix[new_x][new_y]:
  19. indegrees[i][j]+=1
  20. queue = deque()
  21. # 入度为0的坐标进入队列
  22. for i in range(rows):
  23. for j in range(cols):
  24. if indegrees[i][j] == 0:
  25. queue.append((i,j))
  26. res = 0
  27. while queue:
  28. # 每一层遍历 res+1
  29. res += 1
  30. for _ in range(len(queue)):
  31. x, y = queue.popleft()
  32. for k in range(4):
  33. new_x = x + DIRECTION[k][0]
  34. new_y = y + DIRECTION[k][1]
  35. if inArea(new_x,new_y) and matrix[x][y] < matrix[new_x][new_y]:
  36. indegrees[new_x][new_y] -= 1
  37. if indegrees[new_x][new_y] == 0:
  38. queue.append((new_x,new_y))
  39. return res

从最小的数字开始,遍历每个点附近的四个点, 取这四个点里(1.没有出界,2.值比当前点小)的当前路径最长的,然后这个点的路径就是那个附近最长路径+1, 如果附近四个点都比当前点大,那么这个点就是一个起始点,最长路径为1

  1. from collections import deque
  2. class Solution:
  3. def longestIncreasingPath(self, matrix: List[List[int]]) -> int:
  4. if not matrix:
  5. return 0
  6. rows, cols = len(matrix), len(matrix[0])
  7. DIRECTION = ((1,0),(-1,0),(0,1),(0,-1))
  8. dp = [[0]*cols for _ in range(rows)]
  9. def inArea(x,y):
  10. return x>=0 and y>=0 and x<rows and y<cols
  11. points = sorted([[matrix[i][j], (i,j)] for i in range(rows) for j in range(cols)])
  12. res = 0
  13. for point in points:
  14. value = point[0]
  15. x, y = point[1]
  16. max_path = 0
  17. for k in range(4):
  18. new_x = x + DIRECTION[k][0]
  19. new_y = y + DIRECTION[k][1]
  20. if inArea(new_x, new_y) and matrix[new_x][new_y] < value:
  21. max_path = max(max_path, dp[new_x][new_y])
  22. dp[x][y] = max_path+1
  23. res = max(res, dp[x][y])
  24. return res

130. 被围绕的区域

https://leetcode-cn.com/problems/surrounded-regions/
和边界的O连接的o不变, 其他位置的o都是被围绕的,需要被设置为x
那么dfs bfs 和并查集都可以解决,思路是先处理边界位置的O, 把和边界相连的o都置为,比如#,结束遍历后再重新遍历一次board, 把#置为o,把o置为x
dfs是递归, bfs是用队列

  1. class Solution:
  2. def solve(self, board: List[List[str]]) -> None:
  3. """
  4. Do not return anything, modify board in-place instead.
  5. """
  6. if not board:
  7. return board
  8. rows, cols = len(board), len(board[0])
  9. DIRECTION = ((0,1),(1,0),(0,-1),(-1,0))
  10. ans = []
  11. def inArea(x, y):
  12. return x >= 0 and y>=0 and x<rows and y<cols
  13. def dfs(x, y):
  14. board[x][y] = '#'
  15. for k in range(4):
  16. new_x = x + DIRECTION[k][0]
  17. new_y = y + DIRECTION[k][1]
  18. if inArea(new_x, new_y) and board[new_x][new_y] == 'O':
  19. dfs(new_x, new_y)
  20. # up&bottom boader
  21. for j in range(cols):
  22. if board[0][j] == 'O':
  23. dfs(0,j)
  24. if board[rows-1][j] == 'O':
  25. dfs(rows-1, j)
  26. # left&right boader
  27. for i in range(rows):
  28. if board[i][0] == 'O':
  29. dfs(i,0)
  30. if board[i][cols-1] == 'O':
  31. dfs(i,cols-1)
  32. for i in range(rows):
  33. for j in range(cols):
  34. board[i][j] = 'O' if board[i][j] == '#' else 'X'
  35. return board

778. 水位上升的泳池中游泳

https://leetcode-cn.com/problems/swim-in-rising-water/
优先队列, Python的实现是二叉堆. 相当于flood fill, 类似bfs的思维. 不是贪心只关注当前点周围可以去到的最低的点, 而是当前走过的点里面去找最低的点. 比如一个点如果选择过, 那么这个点的→和↓就会变为可接触的点,并且进入二叉堆heap, 每次我们都选择这些可接触点的最小值,也就是二叉堆的堆顶

  1. import heapq
  2. class Solution:
  3. def swimInWater(self, grid: List[List[int]]) -> int:
  4. if not grid:
  5. return 0
  6. rows, cols = len(grid), len(grid[0])
  7. visited = [[False]*cols for _ in range(rows)]
  8. heap = []
  9. heapq.heappush(heap, (grid[0][0],(0,0)))
  10. high = float('-inf')
  11. DIRECTION = ((1,0), (0,1), (-1,0), (0,-1))
  12. while heap:
  13. height, loc = heapq.heappop(heap)
  14. high = max(high, height)
  15. x, y = loc
  16. visited[x][y] = True
  17. if x == rows-1 and y == cols-1:
  18. return high
  19. for k in range(4):
  20. new_x = x + DIRECTION[k][0]
  21. new_y = y + DIRECTION[k][1]
  22. if 0<=new_x<rows and 0<=new_y<cols and not visited[new_x][new_y]:
  23. heapq.heappush(heap, (grid[new_x][new_y], (new_x, new_y)) )

BackTracking

回溯算法和深度优先遍历

「回溯算法」与「深度优先遍历」都有「不撞南墙不回头」的意思。我个人的理解是:「回溯算法」强调了「深度优先遍历」思想的用途,用一个 不断变化 的变量,在尝试各种可能的过程中,搜索需要的结果。强调了 回退 操作对于搜索的合理性。而「深度优先遍历」强调一种遍历的思想,与之对应的遍历思想是「广度优先遍历」
回溯算法用于搜索一个问题的所有的解,用到了DFS的思想
回溯和动态规划
DP只需要知道最优解是什么,不关心怎么来的
回溯要列举出所有的可能性

排列问题

按顺序枚举每一位可能出现的情况,已经选择的数字在 当前 要选择的数字中不能出现。按照这种策略搜索就能够做到 不重不漏
image.png
回溯和普通的DFS的区别在于,有回头的过程,且回头的时候需要把当前已经访问的节点状态重置

  1. 按引用传递状态
  2. 所有的状态修改在递归完成后回改

46. 全排列

https://leetcode-cn.com/problems/permutations/

  1. # res = []
  2. # def backtracking(nums, tmp):
  3. # if not nums:
  4. # res.append(tmp)
  5. # return
  6. # for i in range(len(nums)):
  7. # backtracking(nums[:i]+nums[i+1:], tmp+[nums[i]])
  8. # backtracking(nums,[])
  9. # return res
  10. depth = len(nums)
  11. visited = [False for _ in range(depth)]
  12. res = []
  13. def backtracking(temp_list, height):
  14. if height == depth:
  15. res.append(temp_list)
  16. return
  17. for i in range(depth):
  18. if not visited[i]:
  19. visited[i] = True
  20. backtracking(temp_list+[nums[i]],height+1)
  21. visited[i] = False
  22. backtracking([],0)
  23. return res

47. 全排列 II

https://leetcode-cn.com/problems/permutations-ii/
主要是去重的判断条件.

  1. 排序, 保证相同的数字都相邻
  2. 不选择的情况有:

    1. 当前元素已经visited
    2. i>0 且当前元素等于前一个元素 且 前一个元素已经被使用.

      1. class Solution:
      2. def permuteUnique(self, nums: List[int]) -> List[List[int]]:
      3. n = len(nums)
      4. if n <2:
      5. return [nums]
      6. res = []
      7. nums.sort()
      8. visited = [False for _ in range(n)]
      9. def backtracking(temp_list, depth):
      10. if depth==n:
      11. res.append(temp_list)
      12. for i in range(n):
      13. if visited[i] or (i>0 and nums[i]==nums[i-1] and not visited[i-1]):
      14. continue
      15. visited[i]=True
      16. backtracking(temp_list+[nums[i]], depth+1)
      17. visited[i]=False
      18. backtracking([],0)
      19. return res

      77. 组合

      https://leetcode-cn.com/problems/combinations/

  1. class Solution:
  2. def combine(self, n: int, k: int) -> List[List[int]]:
  3. res = []
  4. if k>n:
  5. return res
  6. if k == 0:
  7. return [res]
  8. def backtracking(nums,temp_list, index):
  9. if len(temp_list)==k:
  10. res.append(temp_list[:])
  11. return
  12. for i in range(index,n):
  13. temp_list.append(nums[i])
  14. backtracking(nums[index:],temp_list, i+1)
  15. temp_list.pop()
  16. nums = [i for i in range(1,n+1)]
  17. backtracking(nums,[],0)
  18. return res

39. 组合总和

https://leetcode-cn.com/problems/combination-sum/
什么时候使用 used/visited数组,什么时候使用 begin 变量

  • 排列问题: 讲究顺序(即 [2, 2, 3] 与 [2, 3, 2] 视为不同列表时,需要记录哪些数字已经使用过,此时用 used 数组;
  • 组合问题:不讲究顺序(即 [2, 2, 3] 与 [2, 3, 2] 视为相同列表时,需要按照某种顺序搜索,此时使用 begin 变量。

注意:具体问题应该具体分析, 理解算法的设计思想 是至关重要的,请不要死记硬背。

  1. class Solution:
  2. def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
  3. candidates.sort()
  4. res = []
  5. length = len(candidates)
  6. def backtracking(temp_list, index, target):
  7. if target==0:
  8. res.append(temp_list[:])
  9. return
  10. for i in range(index, length):
  11. residue = target - candidates[i]
  12. if residue < 0:
  13. break
  14. backtracking(temp_list+[candidates[i]], i, residue)
  15. backtracking([],0,target)
  16. return res

40. 组合总和 II

https://leetcode-cn.com/problems/combination-sum-ii/
这道题的核心相比上一题在于去重,也就是代码备注的部分
https://leetcode-cn.com/problems/combination-sum-ii/solution/hui-su-suan-fa-jian-zhi-python-dai-ma-java-dai-m-3/
参考高赞评论

  1. class Solution:
  2. def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
  3. size = len(candidates)
  4. res = []
  5. if size<1:
  6. return []
  7. def backtracking(temp_list, begin, target):
  8. if target == 0:
  9. res.append(temp_list[:])
  10. return
  11. for i in range(begin,size):
  12. residue = target - candidates[i]
  13. if residue<0:
  14. break
  15. #去重的核心在于这个判断
  16. # i>begin是为了保证同一层的第一个和上一级相同的数是允许的
  17. # i == i-1 是说同一层第二个及以后相同的数是不允许的
  18. if i > begin and candidates[i]==candidates[i-1]:
  19. continue
  20. backtracking(temp_list+[candidates[i]],i+1, residue)
  21. candidates.sort()
  22. backtracking([],0,target)
  23. return res

17. 电话号码的字母组合

https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/
难得被回溯虐了一天半之后, 自己调试出来一道回溯的题

  1. class Solution:
  2. def letterCombinations(self, digits: str) -> List[str]:
  3. lookup = {'2':'abc','3':'def','4':'ghi','5':'jkl','6':'mno','7':'pqrs','8':'tuv','9':
  4. 'wxyz'}
  5. size = len(digits)
  6. res = []
  7. if size==0:
  8. return []
  9. def backtracking(temp_list, index):
  10. if len(temp_list) == size:
  11. res.append("".join(temp_list[:]))
  12. # 不要试图去构建递归的整个过程,理解两层循环所对应的不同的树的层级
  13. for i in range(index,size): # 这一层循环对应每个数字
  14. panel = lookup[digits[i]] # 每个数字对应3 or 4个字母
  15. for j in range(len(panel)): # 这个字母里选一个
  16. backtracking(temp_list+[panel[j]],i+1)
  17. backtracking([],0)
  18. return res

79. 单词搜索

https://leetcode-cn.com/problems/word-search/
一开始我按照岛屿问题的方法写,发现返回不到想要的结果。

  1. line16标记,line22退回, 因为一个矩阵里,可能有多个相同的字母,有时候从一个位置出发不行,但从另一个位置出发是可以的

    1. class Solution:
    2. def exist(self, board: List[List[str]], word: str) -> bool:
    3. if not board:
    4. return False
    5. rows = len(board)
    6. cols = len(board[0])
    7. DIRECTION = ((1,0),(-1,0),(0,1),(0,-1))
    8. visited=[[False for _ in range(cols)] for _ in range(rows)]
    9. def inArea(x,y):
    10. return x>=0 and y>=0 and x<rows and y<cols
    11. def dfs(index,x,y):
    12. if index == len(word)-1:
    13. return board[x][y]==word[index]
    14. if board[x][y]==word[index]:
    15. visited[x][y]=True
    16. for k in range(4):
    17. new_x = x + DIRECTION[k][0]
    18. new_y = y + DIRECTION[k][1]
    19. if inArea(new_x,new_y) and not visited[new_x][new_y] and dfs(index+1,new_x,new_y):
    20. return True
    21. visited[x][y]=False
    22. for i in range(rows):
    23. for j in range(cols):
    24. if dfs(0,i,j):
    25. return True
    26. return False

    Array

    189. 旋转数组

    https://leetcode-cn.com/problems/rotate-array/
    我自己能想到的就是python的slicing

    1. class Solution:
    2. def rotate(self, nums: List[int], k: int) -> None:
    3. """
    4. Do not return anything, modify nums in-place instead.
    5. """
    6. if len(nums) < 2:
    7. return nums
    8. k = k%len(nums)
    9. nums[k:],nums[:k] = nums[:-k],nums[-k:]
    10. return nums

    这个题看到比较有趣的一个办法就是三次反转法
    先把[0, n - k - 1]翻转
    然后把[n - k, n - 1]翻转
    最后把[0, n - 1]翻转
    image.png

    剑指 Offer 29. 顺时针打印矩阵

    https://leetcode-cn.com/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/
    先来个普通的常规的思路

    1. class Solution:
    2. def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
    3. if not matrix:
    4. return []
    5. left,right,top,bottom = 0, len(matrix[0])-1, 0, len(matrix)-1
    6. res = []
    7. while True:
    8. for i in range(left,right+1):
    9. res.append(matrix[top][i])
    10. top+=1
    11. if top > bottom:break
    12. for i in range(top, bottom+1):
    13. res.append(matrix[i][right])
    14. right-=1
    15. if left > right:break
    16. for i in range(right,left-1,-1):
    17. res.append(matrix[bottom][i])
    18. bottom-=1
    19. if top > bottom:break
    20. for i in range(bottom,top-1,-1):
    21. res.append(matrix[i][left])
    22. left+=1
    23. if left > right:break
    24. return res

    再来一个牛逼plus的思路:拿着矩阵每次就从左向右输出最上面的一行,然后逆时针翻转90度,这样下一排刚输出的元素就又到了矩阵的第一行,且顺序是从左向右

    1. class Solution:
    2. def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
    3. res = []
    4. while matrix:
    5. res += matrix.pop(0)
    6. matrix = list(zip(*matrix))[::-1]
    7. return res

    剑指 Offer 59 - I. 滑动窗口的最大值

    https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/
    image.png

    215. 数组中的第K个最大元素

    https://leetcode-cn.com/problems/kth-largest-element-in-an-array/

    1. import random
    2. class Solution:
    3. def findKthLargest(self, nums: List[int], k: int) -> int:
    4. def partition(nums,lo,hi):
    5. random_no = random.randint(lo,hi)
    6. nums[hi], nums[random_no] = nums[random_no], nums[hi]
    7. pivot = nums[hi]
    8. left = lo
    9. for i in range(lo,hi):
    10. if nums[i] < pivot:
    11. nums[i], nums[left] = nums[left], nums[i]
    12. left+=1
    13. nums[left], nums[hi] = nums[hi], nums[left]
    14. return left
    15. low = 0
    16. high = len(nums)-1
    17. k = len(nums)-k
    18. while True:
    19. j = partition(nums,low, high)
    20. if j<k:
    21. low=j+1
    22. elif j>k:
    23. high=j-1
    24. else:
    25. return nums[j]

    692. 前K个高频单词

    https://leetcode-cn.com/problems/top-k-frequent-words/
    比215稍微复杂一下,除了比较数字,还需要比较对应的字符串的大小,注意用到cmp_to_key的功能

48. 旋转图像

https://leetcode-cn.com/problems/rotate-image/
可以先逐行翻转,再沿着斜对角翻转
也可以先沿对角线翻转,再逐行翻转 (这个方式代码写起来简单一些)

419. 甲板上的战舰

https://leetcode-cn.com/problems/battleships-in-a-board/
用战舰的头来判断数量

  1. 如果是第一行或者第一列为x, 那么计算一列战舰
  2. 如果在中间出现x,而这个点的左边或者上边是x,那么这肯定是战舰中部,这列战舰在遇到头部的时候已经计算过了,无需再重复计算
    1. class Solution:
    2. def countBattleships(self, board: List[List[str]]) -> int:
    3. count = 0
    4. for i in range(len(board)):
    5. for j in range(len(board[0])):
    6. if board[i][j] == '.': continue
    7. if i > 0 and board[i-1][j] == 'X': continue
    8. if j > 0 and board[i][j-1] == 'X': continue
    9. count+=1
    10. return count

    1314. 矩阵区域和

    https://leetcode-cn.com/problems/matrix-block-sum/
    所有的前缀和,都需要额外加个0前缀和,用来给第一个元素的前缀和用
    前缀和,注意包括和不包括的情况, 前缀和矩阵里的[rows,cols]是开区间,意思到[rows-1,cols-1]之间所有元素的和
    所以在计算answer的时候max_x需要+1,因为max_x的位置需要包括;而min_x的地方不+1
    1. class Solution:
    2. def matrixBlockSum(self, mat: List[List[int]], K: int) -> List[List[int]]:
    3. rows, cols = len(mat), len(mat[0])
    4. k = K
    5. answer = [[0 for _ in range(cols)] for _ in range(rows)]
    6. pre_sum = [[0 for _ in range(cols+1)] for _ in range(rows+1)]
    7. for i in range(1, rows+1):
    8. for j in range(1, cols+1):
    9. pre_sum[i][j] = pre_sum[i-1][j] + pre_sum[i][j-1] - pre_sum[i-1][j-1]+mat[i-1][j-1]
    10. for i in range(rows):
    11. for j in range(cols):
    12. min_rows = i-k if i-k>=0 else 0
    13. max_rows = i+k if i+k<rows else rows-1
    14. min_cols = j-k if j-k>=0 else 0
    15. max_cols = j+k if j+k<cols else cols-1
    16. answer[i][j] = pre_sum[max_rows+1][max_cols+1] - pre_sum[min_rows][max_cols+1] - pre_sum[max_rows+1][min_cols] + pre_sum[min_rows][min_cols]
    17. return answer

Dynamic Programming

152. 乘积最大子数组

https://leetcode-cn.com/problems/maximum-product-subarray/
如果都是正整数,那整个array都乘下来就是最大的,需要考虑的特殊情况是:

  1. 负整数, 会让结果从很大变成很小,但如果再来一个负整数,结果又会重新变的很大,所以需要同时保留一个最大的正数和最小的负数。
  2. 因为一旦array里包括0,那么整个数字都为0,所以如果出现0,应当排除,这就是为什么LINE 9max和min里加上了当前的num,这样不至于让current_max和current_min一直是0

算法与数据结构笔记 - 图5
算法与数据结构笔记 - 图6
算法与数据结构笔记 - 图7

  1. class Solution:
  2. def maxProduct(self, nums: List[int]) -> int:
  3. if not nums:
  4. return -1
  5. current_pos = nums[0]
  6. current_neg = nums[0]
  7. final_max = nums[0]
  8. for num in nums[1:]:
  9. current_pos, current_neg = max(current_pos*num,current_neg*num,num), min(current_pos*num,current_neg*num,num)
  10. if current_pos > final_max:
  11. final_max = current_pos
  12. return final_max

剑指 Offer 49. 丑数

https://leetcode-cn.com/problems/chou-shu-lcof/
关于是下一个丑数一定是前面某个数乘2或者乘3或者乘5得到的,而且是这三个乘积里最小的一个,因为是要求紧接着的那个丑数。
从第一个开始,每当下一个丑数是用2,3,5某一个乘到的,那么2,3,5对应的point就应该加一个

  1. class Solution:
  2. def nthUglyNumber(self, n: int) -> int:
  3. dp = [1] * n
  4. a ,b,c = 0,0,0
  5. for i in range(1,n):
  6. n2,n3,n5 = dp[a]*2,dp[b]*3,dp[c]*5
  7. dp[i] = min(n2,n3,n5)
  8. if dp[i] == n2: a+=1
  9. if dp[i] == n3: b+=1
  10. if dp[i] == n5: c+=1
  11. return dp[-1]

10. 正则表达式匹配

https://leetcode-cn.com/problems/regular-expression-matching/

  1. class Solution:
  2. def isMatch(self, s: str, p: str) -> bool:
  3. '''
  4. s : 等待匹配
  5. p : regex
  6. '''
  7. if not p: return not s
  8. if not s and len(p)==1: return False
  9. # 数组长度多加一个是为了匹配字符串为空的情况
  10. # 所以数组的下标-1才是字符串的下标
  11. rows = len(s)+1
  12. cols = len(p)+1
  13. dp = [[False for _ in range(cols)] for _ in range(rows)]
  14. # initialization
  15. dp[0][0]=True # s and p are all empty, match
  16. dp[0][1]=False # s is empty, p only have one char, not match
  17. for c in range(2,cols): # s is empty, p longer than 2
  18. j = c-1 # j for the index in string
  19. if p[j]=='*':
  20. dp[0][c] = dp[0][c-2]
  21. for x in range(1,rows): # s string be matched
  22. i = x-1
  23. for y in range(1,cols): # regex pattern
  24. j=y-1
  25. if s[i]==p[j] or p[j]=='.':
  26. dp[x][y]=dp[x-1][y-1]
  27. elif p[j]=='*':
  28. if p[j-1]==s[i] or p[j-1]=='.':
  29. dp[x][y]=dp[x][y-2] or dp[x-1][y]
  30. else:
  31. dp[x][y]=dp[x][y-2]
  32. else:
  33. dp[x][y]=False
  34. return dp[rows-1][cols-1]

5. 最长回文子串

https://leetcode-cn.com/problems/longest-palindromic-substring/

  1. class Solution:
  2. def longestPalindrome(self, s: str) -> str:
  3. m = len(s)
  4. # row is i, column is j
  5. dp = [[False for _ in range(m)] for _ in range(m)]
  6. max_len = 1
  7. start = 0
  8. for j in range(m):
  9. for i in range(j+1):
  10. if s[i]==s[j]:
  11. if j -i < 3:
  12. dp[i][j] = True
  13. else:
  14. dp[i][j] = dp[i+1][j-1]
  15. else:
  16. dp[i][j] = False
  17. if dp[i][j] and j - i + 1 > max_len:
  18. max_len = j - i +1
  19. start = i
  20. return s[start: start+max_len]

42. 接雨水

https://leetcode-cn.com/problems/trapping-rain-water/
动态规划的内存消耗比较高, 注意最左边的max_left是自己,最右边的max_right是自己
image.png

  1. class Solution:
  2. def trap(self, height: List[int]) -> int:
  3. if not height:
  4. return 0
  5. length = len(height)
  6. max_left, max_right = [0]*length, [0]*length
  7. max_left[0] = height[0]
  8. max_right[-1] = height[-1]
  9. for i in range(1,length):
  10. max_left[i] = max(max_left[i-1], height[i-1])
  11. for i in range(length-2, -1, -1):
  12. max_right[i] = max(max_right[i+1], height[i+1])
  13. res = 0
  14. for i in range(length):
  15. low_bound = min(max_left[i],max_right[i])
  16. res += max(0, low_bound-height[i])
  17. return res

双指针解法:
从两边往中间扫,如果left_max > right_max就处理左边的列, 反之则处理右边的列

  1. class Solution:
  2. def trap(self, height: List[int]) -> int:
  3. if not height:
  4. return 0
  5. left_max, right_max = 0, 0
  6. res = 0
  7. left = 0
  8. right = len(height)-1
  9. while left<=right:
  10. if left_max < right_max:
  11. res += max(0, left_max-height[left])
  12. left_max = max(left_max,height[left])
  13. left+=1
  14. else:
  15. res += max(0, right_max-height[right])
  16. right_max = max(right_max, height[right])
  17. right-=1
  18. return res

数学解法, 不需要额外空间
如下图,从左往右遍历,不管是雨水还是柱子,都计算在有效面积内,并且每次累加的值根据遇到的最高的柱子逐步上升。面积记为S1。
从左往右遍历得S1,每步S1+=max1且max1逐步增大
同样地,从右往左遍历可得S2。
从右往左遍历得S2,每步S2+=max2且max2逐步增大
于是我们有如下发现,S1 + S2会覆盖整个矩形,并且:重复面积 = 柱子面积 + 积水面积
最终, 积水面积 = S1 + S2 - 矩形面积 - 柱子面积

  1. class Solution:
  2. def trap(self, height: List[int]) -> int:
  3. if not height:
  4. return 0
  5. max_height = 0
  6. left_area = 0
  7. for h in height:
  8. max_height = max(max_height, h)
  9. left_area += max_height
  10. max_height = 0
  11. right_area = 0
  12. for i in range(len(height)-1, -1, -1):
  13. max_height = max(max_height, height[i])
  14. right_area += max_height
  15. return left_area + right_area - (max_height*len(height))- sum(height)

139. 单词拆分

https://leetcode-cn.com/problems/word-break/
如果用递归写,需要把结果memorized

  1. from functools import lru_cache
  2. class Solution:
  3. def wordBreak(self, s: str, wordDict: List[str]) -> bool:
  4. @lru_cache
  5. def backtrack(s):
  6. if not s:
  7. return True
  8. res = False
  9. for i in range(1, len(s)+1):
  10. if s[:i] in wordDict:
  11. res = backtrack(s[i:]) or res
  12. return res
  13. return backtrack(s)

动态规划:
判断一个字符串s的某个位置i是否可以被wordDict来表示,可以拆分为i之前某个位置j可以被wordDict表示,而且j:i之间的string在wordDict里
这里有一些优化方式和注意的点,比如

  1. base case, dp[0]=True, 所以数组长度是len(s)+1
  2. i不用从1开始遍历, 可以从单词长度最短的一个开始
  3. 当i长度大于最长的单词的时候,j也不用从0开始遍历,可以从最长单词的位置之后开始遍历,因为我们从最短单词长度开始遍历的,所以此时的j最为最长单词的位置,肯定是true的

    1. class Solution:
    2. def wordBreak(self, s: str, wordDict: List[str]) -> bool:
    3. if wordDict:
    4. wordDict = sorted(wordDict, key=len)
    5. min_len, max_len = len(wordDict[0]), len(wordDict[-1])
    6. else:
    7. min_len = max_len = 0
    8. dp = [True] + [False] * len(s)
    9. for i in range(min_len, len(s)+1):
    10. for j in range(max(0,i-max_len), i):
    11. if dp[j] and s[j:i] in wordDict:
    12. dp[i] = True
    13. break
    14. return dp[-1]

    887. 鸡蛋掉落

    https://leetcode-cn.com/problems/super-egg-drop/
    根据基本状态转移方程先写一个超时的基本解

    1. class Solution:
    2. def superEggDrop(self, K: int, N: int) -> int:
    3. '''
    4. dp[i][j], i floors, have j eggs
    5. floors is row, eggs is column
    6. '''
    7. dp = [[999 for _ in range(K+1)] for _ in range(N+1)]
    8. for i in range(N+1):
    9. dp[i][0] = 0
    10. dp[i][1] = i
    11. for j in range(2,K+1):
    12. dp[0][j] = 0
    13. dp[1][j]=1
    14. for i in range(2,N+1):
    15. for j in range(2, K+1):
    16. for k in range(1,i+1):
    17. dp[i][j] = min(dp[i][j], max(dp[k-1][j-1], dp[i-k][j]) + 1)
    18. return dp[N][K]

    把对k的寻找改为二分查找

    91. 解码方法

    https://leetcode-cn.com/problems/decode-ways/

    1. class Solution:
    2. def numDecodings(self, s: str) -> int:
    3. if s.startswith('0'):
    4. return 0
    5. dp = [0] * (len(s) + 1)
    6. dp[0], dp[1] = 1,1
    7. for i in range(2,len(s)+1):
    8. if s[i-1] == '0' and s[i-2] not in '12':
    9. return 0
    10. elif s[i-2:i] in ['10','20']:
    11. dp[i] = dp[i-2]
    12. elif '10' < s[i-2:i] < '27':
    13. dp[i] = dp[i-1] + dp[i-2]
    14. else:
    15. dp[i] = dp[i-1]
    16. return dp[-1]

    322. 零钱兑换

    https://leetcode-cn.com/problems/coin-change/
    比较容易想到的动态规划

    1. class Solution:
    2. def coinChange(self, coins: List[int], amount: int) -> int:
    3. dp = [0] + [amount+1]*amount
    4. for i in range(1,amount+1):
    5. for c in coins:
    6. if i < c:
    7. continue
    8. else:
    9. dp[i] = min(dp[i], dp[i-c]+1)
    10. return dp[amount] if dp[amount] != amount+1 else -1

    绝了的

    1. class Solution:
    2. def coinChange(self, coins: List[int], amount: int) -> int:
    3. n = len(coins)
    4. coins.sort(reverse=True) # 先给硬币排序,降序
    5. self.res = float("inf") # 定义最小的使用硬币的数量为self.res
    6. def dfs(index,target,count): # 定义深度优先搜索算法
    7. coin = coins[index]
    8. if math.ceil(target/coin)+count>=self.res:
    9. return
    10. if target%coin==0:
    11. self.res = count+target//coin
    12. if index==n-1:return
    13. for j in range(target//coin,-1,-1):
    14. dfs(index+1,target-j*coin,count+j)
    15. dfs(0,amount,0)
    16. return int(self.res) if self.res!=float("inf") else -1

    300. 最长递增子序列

    https://leetcode-cn.com/problems/longest-increasing-subsequence/
    为了保证计算子问题能够按照顺序、不重复地进行,动态规划要求已经求解的子问题不受后续阶段的影响。这个条件也被叫做「无后效性」。换言之,动态规划对状态空间的遍历构成一张有向无环图,遍历就是该有向无环图的一个拓扑序。有向无环图中的节点对应问题中的「状态」,图中的边则对应状态之间的「转移」,转移的选取就是动态规划中的「决策」。
    状态方程 dp[i]为加上nums[i]的时候的最长递增子序列, 所以dp[i]为 0这个最好理解,但是时间复杂度比较高,为O(N^2)

    1. class Solution:
    2. def lengthOfLIS(self, nums: List[int]) -> int:
    3. if not nums:
    4. return 0
    5. dp = [1 for _ in range(len(nums))]
    6. max_final = 1
    7. for i in range(len(nums)):
    8. max_sub = 0
    9. for j in range(i):
    10. if nums[j] < nums[i]:
    11. max_sub = max(max_sub, dp[j])
    12. dp[i] = max_sub+1
    13. max_final = max(max_final, dp[i])
    14. return max_final

    优化时间复杂度, 引入贪心和二分查找

  1. class Solution:
  2. def lengthOfLIS(self, nums: List[int]) -> int:
  3. if not nums:
  4. return 0
  5. tail = [nums[0]]
  6. for i in range(1, len(nums)):
  7. if nums[i] > tail[-1]:
  8. tail.append(nums[i])
  9. else:
  10. left = 0
  11. right = len(tail)-1
  12. while left < right:
  13. mid = left + (right-left)//2
  14. if tail[mid] < nums[i]:
  15. left = mid +1
  16. else:
  17. right = mid
  18. tail[left] = nums[i]
  19. return len(tail)

198. 打家劫舍

https://leetcode-cn.com/problems/house-robber/
打家劫舍系列经典动态规划问题, 从第一题开始.
dp[i] 表示在前i间房子满足条件的情况下可以偷到的最大金额
对于第i间房子来说, 分两种情况, 1) 不偷, 则dp[i]=dp[i-1] 2)偷,那么第i-1是不能偷的 则dp[i] = dp[i-2] + nums[i]
所以转移方程是:
dp[n+1] = max(dp(n), dp(n-1)+nums[n+1])
dp[0] = 0 代表前0间房子, 动态规划中有很多时候, dp数组需要比输入数组长度+1
因为dp[n]只和dp[n-1],dp[n-2]相关, 所以我们可以引入两个变量pre和cur,将空间复杂度降低到O(1)

Greedy

135. 分发糖果

https://leetcode-cn.com/problems/candy/
从前往后遍历,每次和前面一个元素比。
从后往前遍历,每次和后面一个元素比

  1. class Solution:
  2. def candy(self, ratings: List[int]) -> int:
  3. res = [1]*len(ratings)
  4. for i in range(1,len(ratings)):
  5. if ratings[i] > ratings[i-1]:
  6. res[i]=res[i-1] + 1
  7. for i in range(len(ratings)-2,-1,-1):
  8. if ratings[i] > ratings[i+1] and res[i]<=res[i+1]:
  9. res[i]=res[i+1] + 1
  10. return sum(res)

435. 无重叠区间

https://leetcode-cn.com/problems/non-overlapping-intervals/
这道题说的是算出需要移除区间的最少数量,那我们就要尽可能的把右边界小的区间留下来,如果区间交叉了,不更新边界,因为是用右边界排序的, 如果没有交叉,那更新目前无重叠区间的右边界

  1. class Solution:
  2. def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
  3. if len(intervals) < 2:
  4. return 0
  5. count = 0
  6. intervals = sorted(intervals, key = lambda a:a[1])
  7. end = intervals[0][1]
  8. for s,e in intervals[1:]:
  9. if s < end:
  10. count+=1
  11. else:
  12. end = e
  13. return count

Binary Search

69. x 的平方根

https://leetcode-cn.com/problems/sqrtx/
边界条件的判断是二分查找的唯一难点
start最终会留在平分刚好比x小的那个位置往右一点,也就是说 算法与数据结构笔记 - 图9

  1. class Solution:
  2. def mySqrt(self, x: int) -> int:
  3. if x < 2:
  4. return x
  5. start = 0
  6. end = x
  7. while start <
  8. = end:
  9. midpoint = start + (end-start)//2
  10. if midpoint * midpoint < x:
  11. start = midpoint+1
  12. elif midpoint * midpoint > x:
  13. end = midpoint-1
  14. else:
  15. return midpoint
  16. return start-1

153. 寻找旋转排序数组中的最小值

https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/

  1. class Solution:
  2. def findMin(self, nums: List[int]) -> int:
  3. start = 0
  4. end = len(nums)-1
  5. while start<end:
  6. midpoint = start + (end-start)//2
  7. if nums[midpoint] > nums[end]:
  8. start = midpoint+1
  9. else:
  10. end = midpoint
  11. return nums[end]

33. 搜索旋转排序数组

https://leetcode-cn.com/problems/search-in-rotated-sorted-array/
其实相对简单的办法还是先找到数组旋转点,也就是最小值在哪里,然后将数组分为两段,根据target和两段的关系来查找
注意等号的选择!

  1. class Solution:
  2. def search(self, nums: List[int], target: int) -> int:
  3. left = 0
  4. right = len(nums)-1
  5. while left<=right:
  6. mid = left + (right-left)//2
  7. if nums[mid]==target:
  8. return mid
  9. if nums[mid]>=nums[left]:
  10. if target>=nums[left] and target<nums[mid]:
  11. right = mid-1
  12. else:
  13. left = mid+1
  14. else:
  15. if target>nums[mid] and target<=nums[right]:
  16. left = mid+1
  17. else:
  18. right = mid-1
  19. return -1

1095. 山脉数组中查找目标值

https://leetcode-cn.com/problems/find-in-mountain-array/
有点类似在搜索旋转数组,但是这道题target有可能存在于左右两边各一个
关键在于先找到峰值,然后从峰值一分为二,在两侧都寻找

  1. # """
  2. # This is MountainArray's API interface.
  3. # You should not implement it, or speculate about its implementation
  4. # """
  5. #class MountainArray:
  6. # def get(self, index: int) -> int:
  7. # def length(self) -> int:
  8. class Solution:
  9. def findMountainTop(self, mountain_arr: 'MountainArray') -> int:
  10. left = 0
  11. right = mountain_arr.length()-1
  12. while left < right:
  13. mid = left + (right-left)//2
  14. mid_val = mountain_arr.get(mid)
  15. if mid_val < mountain_arr.get(mid+1):
  16. left = mid+1
  17. else:
  18. right = mid
  19. return left
  20. def binarySearch(self, arr, target, left, right, ascend=True):
  21. while left <= right:
  22. mid = left + (right-left)//2
  23. if arr.get(mid) == target:
  24. return mid
  25. elif arr.get(mid) < target:
  26. if ascend:
  27. left = mid+1
  28. else:
  29. right = mid-1
  30. else:
  31. if ascend:
  32. right = mid-1
  33. else:
  34. left = mid+1
  35. return -1
  36. def findInMountainArray(self, target: int, mountain_arr: 'MountainArray') -> int:
  37. peek_idx = self.findMountainTop(mountain_arr)
  38. idx = self.binarySearch(mountain_arr, target, 0, peek_idx,True)
  39. if idx!=-1:
  40. return idx
  41. else:
  42. return self.binarySearch(mountain_arr, target, peek_idx, mountain_arr.length()-1, False)

Merge Interval

Two Points

Binary Tree

Concepts

image.png
Full: 每个node只有0个或2个children
complete: 从上到下,从左到右
perfect: 每一层都是满的 2**k-1

Focus on a Node! 不要去想递归的过程,不要去想整棵树,就想这一个node要做什么!
What needs to happen at this node?

中序遍历中的一些点

  1. successor, 后驱节点,紧跟着当前节点比当前小的那个节点,先取root.right(如果root.right存在),然后一直取左子树

    1. def succeesor(root):
    2. root = root.right
    3. while root.left:
    4. root = root.left
    5. return root
  2. prece, 前驱,比当前节点大一个位置的节点

    1. def predecessor(root):
    2. root = root.left
    3. while root.right:
    4. root = root.right
    5. return root

    94. 二叉树的中序遍历

    https://leetcode-cn.com/problems/binary-tree-inorder-traversal/submissions/

    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. stack = list()
    10. arr = list()
    11. while root or stack:
    12. while root:
    13. stack.append(root)
    14. root = root.left
    15. root = stack.pop()
    16. arr.append(root.val)
    17. root = root.right
    18. return arr

    230.二叉搜索树中第K小的元素

    https://leetcode-cn.com/problems/kth-smallest-element-in-a-bst/

    1. class Solution:
    2. def kthSmallest(self, root: TreeNode, k: int) -> int:
    3. stack = list()
    4. while root or stack:
    5. while root:
    6. stack.append(root)
    7. root = root.left
    8. root = stack.pop()
    9. k -= 1
    10. if not k:
    11. return root.val
    12. else:
    13. root = root.right

同理找第K大的

  1. class Solution:
  2. def kthLargest(self, root: TreeNode, k: int) -> int:
  3. stack = list()
  4. while root or stack:
  5. while root:
  6. stack.append(root)
  7. root = root.right
  8. root = stack.pop()
  9. k-=1
  10. if k==0:
  11. return root.val
  12. root = root.left
  13. return

98. 验证二叉搜索树

https://leetcode-cn.com/problems/validate-binary-search-tree/
Iteration

  1. class Solution:
  2. def isValidBST(self, root: TreeNode) -> bool:
  3. if not root:
  4. return False
  5. stack = list()
  6. pre = None
  7. while root or stack:
  8. while root:
  9. stack.append(root)
  10. root = root.left
  11. root = stack.pop()
  12. if pre and root.val<=pre.val:
  13. return False
  14. pre = root
  15. root = root.right
  16. return True

Recursion
在纸上画一画,理解一下这个preNode的含义,出现的位置,preNode是如何保证整个左子树小于root,整个右子树都大于root的。