数据结构与算法题库

数据结构和算法总结
  • map和set经常用来查询和计数
数组和链表
1.链表反转

Given the head of a singly linked list, reverse the list, and return the reversed list.

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

class Solution:
  def reserseList(self, head):
    cur, prev = head, None
    while cur:
      cur.next, prev, cur = prev, cur, cur.next
    return prev
2.环形链表

Given head, the head of a linked list, determine if the linked list has a cycle in it.

There is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the next pointer. Internally, pos is used to denote the index of the node that tail’s next pointer is connected to. Note that pos is not passed as a parameter.

Return true* if there is a cycle in the linked list*. Otherwise, return false.

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        slow = fast = head
        while slow and fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            if slow == fast:
                return True
        return False
3.两两交换链表中的节点

Given a linked list, swap every two adjacent nodes and return its head. You must solve the problem without modifying the values in the list’s nodes (i.e., only nodes themselves may be changed.)

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

class Solution:
    def swapPairs(self, head):
        pre, pre.next = self, head
        while pre.next and pre.next.next:
            a = pre.next
            b = a.next
            b.next, a.next = a, b.next
            pre.next = b
            pre = a
        return self.next
堆栈、队列
1.有效的括号

Given a string s containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid.

An input string is valid if:

  1. Open brackets must be closed by the same type of brackets.
  2. Open brackets must be closed in the correct order.
  3. Every close bracket has a corresponding open bracket of the same type.

给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。

# 数据结构为栈
class Solution:
    def isValid(self, s: str) -> bool:
        # 存左边的符号
        stack = []
        # 以右边的符号作为键值
        paren_map = {')': '(', ']': '[', '}': '{'}
        for c in s:
            if c not in paren_map:
                stack.append(c)
            elif not stack or paren_map[c] != stack.pop():
                return False
        return not stack
2.用队列实现栈

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。

实现 MyStack 类:

void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。

class MyStack:

    def __init__(self):
        self.stack = []


    def push(self, x: int) -> None:
        self.stack.append(x)


    def pop(self) -> int:
        return self.stack.pop()


    def top(self) -> int:
        return self.stack[-1]


    def empty(self) -> bool:
        return not self.stack



# Your MyStack object will be instantiated and called as such:
# obj = MyStack()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.top()
# param_4 = obj.empty()
优先队列

适合解决求一个数列前K大数的问题

1.数据流中的第K大元素

Design a class to find the kth largest element in a stream. Note that it is the kth largest element in the sorted order, not the kth distinct element.

Implement KthLargest class:

  • KthLargest(int k, int[] nums) Initializes the object with the integer k and the stream of integers nums.
  • int add(int val) Appends the integer val to the stream and returns the element representing the kth largest element in the stream.

设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。

请实现 KthLargest 类:

  • KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。
  • int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。
  • 数组解法:

    # 数据结构为数组
    class KthLargest:
    
        def __init__(self, k, nums):
            self.k = k
            self.nums = nums
            self.sort_nums = sorted(self.nums, reverse=True)
    
        def add(self, val):
            if len(self.sort_nums) > self.k:
                self.sort_nums = self.sort_nums[:self.k]
            if len(self.sort_nums) < self.k or self.sort_nums[self.k -1] <= val:
                self.sort_nums.append(val)
                self.sort_nums.sort(reverse=True)
            return self.sort_nums[self.k - 1]
    
  • 小顶堆解法:

    import heapq
    class TopKHeap(object):
      def __init__(self, k):
          self.k = k
          self.data = []
    
      def push(self, elem):
          if len(self.data) < self.k:
              heapq.heappush(self.data, elem)
          else:
              topk_small = self.data[0]
              if elem > topk_small:
                  heapq.heapreplace(self.data, elem)
    class KthLargest:
      def __init__(self, k, nums):
          self.th = TopKHeap(k)
          for i in nums:
              self.th.push(i)
    
      def add(self, val):
          self.th.push(val)
          return self.th.data[0]
    
  • python使用heapq实现小顶堆(TopK大)

    import sys
    import heapq
    
    class TopKHeap(object):
      def __init__(self, k):
          self.k = k
          self.data = []
    
      def push(self, elem):
          if len(self.data) < self.k:
              heapq.heappush(self.data, elem)
          else:
              topk_small = self.data[0]
              if elem > topk_small:
                  heapq.heapreplace(self.data, elem)
    
      def topk(self):
          return [x for x in reversed([heapq.heappop(self.data) for x in range(len(self.data))])]
        
      def main():
          list_num = [1, 2, 3, 4, 5, 6, 7, 8, 9]
          th = TopKHeap(5)
    
          for i in list_num:
              th.push(i)
          print(th.topk())
        
    if name == "main":
        main()
        
    

    2.滑动窗口最大

You are given an array of integers nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position.

Return the max sliding window.

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值 。

  • 数据结构为双端队列

    class Solution:
      def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
          windows = []
          res = []
          for i, x in enumerate(nums):
              # 对窗口左边界进行判定,超出窗口边界则从窗口左侧移除一位元素
              if i >= k and windows[0] <= i - k:
                  windows.pop(0)
              # 逻辑的重点部分,新进的元素如果比左边的元素大,则移除,循环直至窗口中最大的元素都在最左边
              while windows and nums[windows[-1]] <= x:
                  windows.pop()
              # 对元素进行登记,记录的是元素x的下标
              windows.append(i)
              # 对窗口右边界进行判定,将结果记录到列表中
              if i >= k -1:
                  res.append(nums[windows[0]])
          return res
    
  • 双端队列

    from collections import deque
    class Solution:
    	  def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
          window = deque(maxlen=k)
          # max_list = []
          if len(nums) < k:
              yield max(nums)
          else:
              for index, val in enumerate(nums, 1):
                  window.append(val)
                  if index >= k:
                      yield max(window)
                      # max_list.append(max(window))
              # return max_list
    
哈希表
1.有效的字母异位词

Given two strings s and t, return true if t is an anagram of s, and false otherwise.

An Anagram is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once.

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。

  • 快排,时间复杂度为nlogn

    class Solution:
        def isAnagram(self, s: str, t: str) -> bool:
            return sorted(s) == sorted(t)
    
  • 哈希表,时间复杂度为n

    class Solution:
        def isAnagram(self, s: str, t: str) -> bool:
            dict1, dict2 = {}, {}
            for i in s:
                dict1[i] = dict1.get(i, 0) + 1
            for i in t:
                dict2[i] = dict2.get(i, 0) + 1
            return dict1 == dict2
    
2.两数之和

Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.

You may assume that each input would have exactly one solution, and you may not use the same element twice.

You can return the answer in any order.

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        # dict_nums = {}
        # for index, val in enumerate(nums, 0):
        #     dict_nums[val] = index
        # for index, val in enumerate(nums, 0):
        #     if (target-val) not in dict_nums:
        #         continue
        #     elif index != dict_nums[target-val]:
        #         return [dict_nums[target-val], index]
        hash_map = dict()
        for i, x in enumerate(nums, 0):
            if (target - x) in hash_map:
                return [i, hash_map[target - x]]
            hash_map[x] = i
3.三数之和

Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0.

Notice that the solution set must not contain duplicate triplets.

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

  • 暴力查询:三层for循环,时间复杂度为N^3
  • set查找,两层for循环,再查找-(a+b),时间复杂度为N^2
  • sort and find,时间复杂度为N^2
class Solution:
    def threeSum(self, nums):
        if len(nums) < 3:
            return []
        nums.sort()
        res = set()
        for i, v in enumerate(nums[:-2]):
            if i >= 1 and v == nums[i - 1]:
                continue
            d = {}
            for x in nums[i + 1:]:
                if x not in d:
                    d[-v - x] = 1
                else:
                    res.add((v, -v - x, x))
        return map(list, res)
二叉树、二叉搜索树
1.验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

  • 节点的左子树只包含 小于 当前节点的数。
  • 节点的右子树只包含 大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。
class Solution:
  	# 递归算法:
    # 中序搜索,返回的是升序排序
    # def isValidBST(self, root: Optional[TreeNode]) -> bool:
    #     inorder = self.inorder(root)
    #     return inorder == list(sorted(set(inorder)))

    # def inorder(self, root):
    #     if root is None:
    #         return []
    #     return self.inorder(root.left) + [root.val] + self.inorder(root.right)
    
    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        self.prev = None
        return self.helper(root)

    def helper(self, root):
        if root is None:
            return True
        if not self.helper(root.left):
            return False
        if self.prev and self.prev.val >= root.val:
            return False
        self.prev = root
        return self.helper(root.right)
2.二叉搜索树的最近公共祖先

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉搜索树:  root = [6,2,8,0,4,7,9,null,null,3,5]

  • 递归

    # Definition for a binary tree node.
    # class TreeNode:
    #     def __init__(self, x):
    #         self.val = x
    #         self.left = None
    #         self.right = None
    
    class Solution:
        def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
            if root == None or root == p or root == q:
                return root
            left = self.lowestCommonAncestor(root.left, p, q)
            right = self.lowestCommonAncestor(root.right, p, q)
            if left == None:
                return right
            elif right == None:
                return left
            else:
                return root
    
  • 循环

    # Definition for a binary tree node.
    # class TreeNode:
    #     def __init__(self, x):
    #         self.val = x
    #         self.left = None
    #         self.right = None
    
    class Solution:
        def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
            # if root == None or root == p or root == q:
            #     return root
            # left = self.lowestCommonAncestor(root.left, p, q)
            # right = self.lowestCommonAncestor(root.right, p, q)
            # if left == None:
            #     return right
            # elif right == None:
            #     return left
            # else:
            #     return root
            while True:
                if root.val > p.val and root.val > q.val:
                    root = root.left
                elif root.val < p.val and root.val < q.val:
                    root = root.right
                else:
                    return root
    
3.二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if root==None:
            return None
        left=self.lowestCommonAncestor(root.left,p,q)
        right=self.lowestCommonAncestor(root.right,p,q)
        if root==p or root==q:
            return root
        if left and right:
            return root
        if left and right==None:
            return left
        if right and left==None:
            return right
4.二叉树的遍历
  • 前序遍历

    def preorder(self, root):
      
      if root:
        self.traverse_append(root.val)
        self.preorder(root.left)
        self.preorder(root.right)
    
  • 中序遍历

    def preorder(self, root):
      
      if root:
        self.preorder(root.left)
        self.traverse_append(root.val)
        self.preorder(root.right)
    
  • 后序遍历

    def preorder(self, root):
      
      if root:
        self.preorder(root.left)
        self.preorder(root.right)
        self.traverse_append(root.val)
    
递归代码模版

定义:一个函数或子程序,是由自身所定义或调用的。

满足两个条件:

  1. 递归公式:一个可以反复执行的递归过程;
  2. 终止条件:一个可以离开递归执行的出口。
def recursion(level, parm1, param2, ...):
  # recursion terminator
  if level > MAX_LEVEL:
    print_result:
      return
  
  # process logic in current level
  process_data(level, data...)
  
  # drill down
  self.recursion(level + 1, p1, ...)
  
  # reverse the current level status if needed
  reverse_state(level)
1.计算n!
  • 使用递归算法

    # n! = 1*2*3...*n
    def Factorial(n):
      if n <= 1:
        return 1
      return n * Factorial(n - 1)
    
    recursive
    factorial(6)
    6 * factorial(5)
    6 * (5 * factorial(4))
    6 * (5 * (4 * factorial(3)))
    6 * (5 * (4 * (3 * factorial(2))))
    6 * (5 * (4 * (3 * (2 * factorial(1)))))
    6 * (5 * (4 * (3 * (2 * 1))))
    6 * (5 * (4 * (3 * 2)))
    6 * (5 * (4 * 6))
    6 * (5 * 24)
    6 * 120
    720
    
  • 使用reduce函数和一个匿名函数计算阶乘

    from functools import reduce
    def fact(n):
      return reduce(lambda x, y: x * y, range(1, n + 1))
    
  • 使用reduce和operator.mul函数计算阶乘

    from functools import reduce
    from operator import mul
    def fact(n):
      return reduce(mul, range(1, n + 1))
    
2.Fibonacci array:1, 1, 2, 3, 5, 8, 13, 21, 34
  • 递归算法
from functions import lru_cache

# F(n) = F(n-1) + F(n-2)
@lru_cache
def fib(n):
  if n == 0 or n == 1:
    return n
  return fib(n -1) + fib(n -2)
  • 生成器函数
def fibonacci():
  a, b = 0, 1
  while True:
    yield a
    a, b = b, a + b
3.汽水瓶

描述

某商店规定:三个空汽水瓶可以换一瓶汽水,允许向老板借空汽水瓶(但是必须要归还)。

小张手上有n个空汽水瓶,她想知道自己最多可以喝到多少瓶汽水。

数据范围:输入的正整数满足 1 \le n \le 100 \1≤n≤100

注意:本题存在多组输入。输入的 0 表示输入结束,并不用输出结果。

输入描述:

输入文件最多包含 10 组测试数据,每个数据占一行,仅包含一个正整数 n( 1<=n<=100 ),表示小张手上的空汽水瓶数。n=0 表示输入结束,你的程序不应当处理这一行。

输出描述:

对于每组测试数据,输出一行,表示最多可以喝的汽水瓶数。如果一瓶也喝不到,输出0。

def drink(N, total):
  count = N // 3
  k = N % 3 + count
  if k >= 3:
    drink(k, count + total)
  elif k <= 2:
    print(count + total + k -1)
    
while True:
  rootNum = int(input())
  if rootNum == 0:
    break
  drink(rootNum, 0)
4.括号生成

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 **有效的 **括号组合。

class Solution(object):
    def generateParenthesis(self, n):
        self.list = []
        self._gen(0, 0, n, "")
        return self.list

    def _gen(self, left, right, n, result):
        if left == n and right == n:
            self.list.append(result)
            return
        if left < n:
            self._gen(left + 1, right, n, result + "(")
        if left > right and right < n:
            self._gen(left, right + 1, n, result + ")")
分治算法代码模板

核心思想是将一个难以直接解决的大问题依照相同的概念,分割成两个或更多的子问题,以便各个击破,即“分而治之”。

任何一个可以用程序求解的问题所需的计算时间都与其规模有关,问题的规模越小,越容易直接求解。分割问题也是遇到大问题的解决方式,可以使子问题规模不断缩小,直到这些子问题足够简单到可以解决,最后再将各子问题的解合并得到原问题的最终解答。

应用场景:

  1. 快速排序
  2. 递归算法
  3. 大整数乘法
def divide_conquer(problem, param1, param2, ...)
	# recursion terminator
	if problem is None:
    print_result
    return
  
  # prepare data
  data = prepare_data(problem)
  subproblems = split_problem(problem, data)
  
  # conquer subproblems
  subresult1 = self.divide_comquer(subproblems[0], p1, ...)
  subresult2 = self.divide_comquer(subproblems[1], p1, ...)
  subresult3 = self.divide_comquer(subproblems[2], p1, ...)
  ...
  
  # process and generate the final result
  result = process_result(subresult1, subresult2, subresult3, ...)
1.Pow(x, n)

实现 pow(x, n) ,即计算 x 的整数 n 次幂函数(即,x^n )。

class Solution:
    def myPow(self, x: float, n: int) -> float:
        # 1.调用库函数 O(1)
        # 2.暴力解决 O(N)
        result = 1
        for _ in range(n):
            result *= x
        return result
        # 3.分治算法 O(logn)
        if not n:
            return 1
        if n < 0:
            return 1/self.myPow(x, -n)
        if n % 2:
            return x * self.myPow(x, n-1)
        return self.myPow(x * x, n/2)
        # 4.非递归
2.多数元素

给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

  • 使用映射
class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        # 暴力:两层for循环 O(N^2)
        # Map: O(N)
        # sort: NlogN
        # 分治: NlogN
        val_dict = dict()
        for val in nums:
            val_dict[val] = val_dict.get(val, 0) + 1
        for key in val_dict:
            if val_dict[key] > len(nums) / 2:
                return key
  • 使用列表的计数方法

    class Solution:
        def majorityElement(self, nums: List[int]) -> int:
            set_nums = set(nums)
            for value in set_nums:
                count = nums.count(value)
                if count > len(nums)/2:
                    return value
    
3.找到字符串中所有字母异位词

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。


二分查找
  1. Sorted(单调递增或者递减)
  2. Bounded(存在上下界)
  3. Accessible by index(能够通过索引访问)
1.示例
left, right = 0, len(array) - 1
while left <= right:
  mid = (left + right) // 2
  if array[mid] == target:
    break or return result
  elif array[mid] < target:
    left = mid + 1
  else:
    right = mid - 1
2.求算术平方根

给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。


Trie数(字典树)
  • Trie 树的数据结构

Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于⽂本词频统计

它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。

  • Trie 树的核⼼思想

Trie的核⼼思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的⽬的。

  • Trie 树的基本性质
  1. 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
  2. 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
  3. 每个节点的所有子节点包含的字符都不相同。
1.字典树的实现
class Trie:
    def __init__(self):
        self.root = {}
        self.end_of_word = "#"

    def insert(self, word: str) -> None:
        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:
        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:
        node = self.root
        for char in prefix:
            if char not in node:
                return False
            node = node[char]
        return True
深度优先DFS和广度优先BFS
1. 岛屿数量:DFS

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

  • 实例1:

    输入:grid = [
      ["1","1","1","1","0"],
      ["1","1","0","1","0"],
      ["1","1","0","0","0"],
      ["0","0","0","0","0"]
    ]
    输出:1
    
  • 实例2:

    输入:grid = [
      ["1","1","0","0","0"],
      ["1","1","0","0","0"],
      ["0","0","1","0","0"],
      ["0","0","0","1","1"]
    ]
    输出:3
    
class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        def dfs(grid,r,c):
            if not 0<=r<len(grid) or not 0<=c<len(grid[0]):return 
            if grid[r][c]!="1":return  #注意题目这里输入的是字符"0",“1”
            grid[r][c]="2"
            dfs(grid,r-1,c)
            dfs(grid,r+1,c)
            dfs(grid,r,c-1)
            dfs(grid,r,c+1)
            return 1  #是岛屿,则返回1

        res=0
        for  r in range(len(grid)):
            for c in range(len(grid[0])):
                if grid[r][c]=="1": 
                    res+=dfs(grid,r,c)  #累加岛屿的数量
        return res

2. 迷宫问题

定义一个二维数组 N*M ,如 5 × 5 数组下所示:

int maze[5][5] = {
0, 1, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 1, 0,
};

它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的路线。入口点为[0,0],既第一格是可以走的路。

  • 实例1:

    输入:
    5 5
    0 1 0 0 0
    0 1 1 1 0
    0 0 0 0 0
    0 1 1 1 0
    0 0 0 1 0
    复制
    输出:
    (0,0)
    (1,0)
    (2,0)
    (2,1)
    (2,2)
    (2,3)
    (2,4)
    (3,4)
    (4,4)
    
  • 实例2:

    输入:
    5 5
    0 1 0 0 0
    0 1 0 1 0
    0 0 0 0 1
    0 1 1 1 0
    0 0 0 0 0
    复制
    输出:
    (0,0)
    (1,0)
    (2,0)
    (3,0)
    (4,0)
    (4,1)
    (4,2)
    (4,3)
    (4,4)
    
n,m=map(int,input().split())
maze=[]
maze_visit=[[0]*m for _ in range(n)] # 记录访问过的结点,0:未访问 1:已访问
maze_way=[] # 记录当前路径
# move=[(0,1),(1,0),(0,-1),(-1,0)]  # 下右上左四个方向
move=[(0,-1),(0,1),(-1,0),(1,0)]  # 上下左右个方向
for i in range(n):
    maze.append(list(map(int,input().split())))

# 深度优先遍历 (x,y)是当前遍历的结点坐标
def dfs(x,y):

    if maze_visit[x][y]==0:
        maze_way.append((x,y)) 
    maze_visit[x][y]=1   # 当前节点(x,y)加入到当前路径 并设置状态为已访问

    # 墙不能走 回溯
    if maze[x][y]==1:
        maze_way.pop()
        last_x=maze_way[-1][0]
        last_y=maze_way[-1][1]
        dfs(last_x,last_y)
        return 'finish'
    # 走到终点 结束
    if x==n-1 and y==m-1:
        return 'finish'
    # 尝试往四个方向走
    for step in move:
        
        next_x=x+step[0]
        next_y=y+step[1]
        # 访问迷宫内没有走过的结点
        if 0<=next_x<n and 0<=next_y<m:
            if maze_visit[next_x][next_y]==0:
                dfs(next_x,next_y)
                return 'finish' # 表示本次递归结束
            else:
                continue
    # 如果四个方向都走不了,回溯到上一个节点
    maze_way.pop()
    last_x=maze_way[-1][0]
    last_y=maze_way[-1][1]
    dfs(last_x,last_y)
    return 'finish'

dfs(0,0)
for way in maze_way:
    print('(%d,%d)' %(way[0],way[1]))
贪心算法
3.最大子数组和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

**子数组 **是数组中的一个连续部分。

解题思路:贪心算法,局部最优:从第一个元素开始向后遍历,当“连续累加和”为负数时,说明前面这些数只能成为后边数的累赘,因此从下一个元素重新开始(即count初始化为0)继续向后累加.
这里需要注意的是:遍历每个元素都会让count和res比较取二者最大值作为新的res(遍历完整个序列,就代表找到了全局最优res),操作完这个步骤后再判断是否count需要初始化

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        res = -float('inf')  # 记录当前最大值
        count = 0
        for ind in range(len(nums)):
            # 记录从count初始化为0后的值累加,直到再次满足count<=0表示本次局部遍历结束
            count += nums[ind]
            # 每个局部中的每次count和res比较都相当于找到了遍历过的序列的全局最优,当遍历完整个序列后就相当于找到了整个序列的全局最优
            if count > res:
                res = count
            # 只要count<=0,就让count初始化为0,从当前开始往后继续相加(每次初始化为0,都相当于一个当前的局部)
            if count <= 0:
                count = 0

        return res
回溯算法

也是枚举法中的一种,对于某些问题而言,回溯法是一种可以找出所有(或一部分)解的一般性算法,同时避免枚举不正确的数值。一旦发现不正确的数值,就不在递归到下一层,而是回溯到上一层,以节省时间,是一种走不通就退回再走的方式。

应用场景:在搜索过程中寻找问题的解,当发现不满足求解条件时,就回溯(返回),尝试别的路径,避免无效搜索。

1.背包
#!/usr/bin/python
# -*- coding: UTF-8 -*-

from typing import List

# 背包选取的物品列表
picks = []
picks_with_max_value = []


def bag(capacity: int, cur_weight: int, items_info: List, pick_idx: int):
    """
    回溯法解01背包,穷举
    :param capacity: 背包容量
    :param cur_weight: 背包当前重量
    :param items_info: 物品的重量和价值信息
    :param pick_idx: 当前物品的索引
    :return:
    """
    # 考察完所有物品,或者在中途已经装满
    if pick_idx >= len(items_info) or cur_weight == capacity:
        global picks_with_max_value
        if get_value(items_info, picks) > \
                get_value(items_info, picks_with_max_value):
            picks_with_max_value = picks.copy()
    else:
        item_weight = items_info[pick_idx][0]
        if cur_weight + item_weight <= capacity:    # 选
            picks[pick_idx] = 1
            bag(capacity, cur_weight + item_weight, items_info, pick_idx + 1)

        picks[pick_idx] = 0                         # 不选
        bag(capacity, cur_weight, items_info, pick_idx + 1)


def get_value(items_info: List, pick_items: List):
    values = [_[1] for _ in items_info]
    return sum([a*b for a, b in zip(values, pick_items)])


if __name__ == '__main__':
    # [(weight, value), ...]
    items_info = [(3, 5), (2, 2), (1, 4), (1, 2), (4, 10)]
    capacity = 8

    print('--- items info ---')
    print(items_info)

    print('\n--- capacity ---')
    print(capacity)

    picks = [0] * len(items_info)
    bag(capacity, 0, items_info, 0)

    print('\n--- picks ---')
    print(picks_with_max_value)

    print('\n--- value ---')
    print(get_value(items_info, picks_with_max_value))
2.从nums选取n个数的全排列
#!/usr/bin/python
# -*- coding: UTF-8 -*-

from typing import List

permutations_list = []  # 全局变量,用于记录每个输出


def permutations(nums: List, n: int, pick_count: int):
    """
    从nums选取n个数的全排列

    回溯法,用一个栈记录当前路径信息
    当满足n==0时,说明栈中的数已足够,输出并终止遍历
    :param nums:
    :param n:
    :param pick_count:
    :return:
    """
    if n == 0:
        print(permutations_list)
    else:
        for i in range(len(nums) - pick_count):
            permutations_list[pick_count] = nums[i]
            nums[i], nums[len(nums) - pick_count - 1] = nums[len(nums) - pick_count - 1], nums[i]
            permutations(nums, n-1, pick_count+1)
            nums[i], nums[len(nums) - pick_count - 1] = nums[len(nums) - pick_count - 1], nums[i]


if __name__ == '__main__':
    nums = [1, 2, 3, 4]
    n = 3
    print('--- list ---')
    print(nums)

    print('\n--- pick num ---')
    print(n)

    print('\n--- permutation list ---')
    permutations_list = [0] * n
    permutations(nums, n, 0)
迭代法

指无法使用公式一次求解,而需要使用迭代,如用循环去重复执行程序代码的某些部分来得到答案。

1.使用for循环来设计一个计算1! ~ n!阶乘的递归程序
sum = 1
n = int(input('请输入n='))
for i in range(0, n + 1):
    for j in range(i, 0, -1):
        sum *= j
    print('%d! = %3d' % (i, sum))
    sum = 1
枚举法

核心思想是列举所有的可能。根据问题要求,逐一列举问题的解答,或者为了便于解决问题,把问题分为不重复、不遗漏的有限种情况,逐一列举各种情况,并加以解决,最终达到解决整个问题的目的。

1.列出1到500之间所有5的倍数(整数)
for num in range(1, 501):
    if num % 5 == 0:
        print('%d 是5的倍数' % num)
动态规划
  1. 递归+记忆化 —> 递推
  2. 状态的定义:opt[n], dp[n], fib[n]
  3. 状态转移(DP)⽅程:opt[n] = best_of(opt[n-1], opt[n-2], …)
  4. 最优子结构

类似于分治法,用于研究多个阶段决策过程的优化过程与求得一个问题的最佳解。

动态规划的主要做法:如果一个问题答案与子问题相关的话,就能大问题拆解成各个小问题,其中与分治法最大不同的地方是可以让每一个子问题的答案被存储起来,以供下次求解时直接取用。这样的做法不但能减少再次计算的时间,并可将这些解组合成大问题的解答,故而使用动态规划可以解决重复计算的问题。

1.求解斐波那契数列的解
output = [None] * 1000 # fibonacci的暂存区

def fib(n):
    return n if n <= 1 else fib(n-1) + fib(n-2)

def Fibonacci(n):
    result = output[n]
    
    if result == None:
        if n == 0:
            result = 0
         elif n == 1:
            result = 1
         else:
            result = Fibonacci(n - 1) + Fibonacci(n - 2)
         output[n] = result
     return result
2.三角形最小路径和

给定一个三角形 triangle ,找出自顶向下的最小路径和。

每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 i 或 i + 1 。

输入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
输出:11
解释:如下面简图所示:
   2
  3 4
 6 5 7
4 1 8 3
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。

class Solution:
    def minimumTotal(self, triangle: List[List[int]]) -> int:
        if not triangle: return 0
        res = triangle[-1]
        for i in range(len(triangle)-2, -1,-1):
            for j in range(len(triangle[i])):
                res[j]=min(res[j], res[j+1]) + triangle[i][j]

        return res[0]
闭包
1.计算移动平均值的类
class Averager():
  def __init__(self):
    self.series = []
  
  def __call__(self, new_calue):
    self.series.append(new_calue)
    total += sum(self.series)
    return total / len(self.series)
2.计算移动平均值的高阶函数
def make_averager():
  series = []
  
  def averager(new_value):
    # 没有给 series 赋值,我们只是调用 series.append,并把它传给 sum 和 len。也就是说,我们利用了列表是可变的对象这一事实。
    series.append(new_value) # series是自由变量
    total = sum(series)
    return total / len(series)
  return averager
def make_averager():
  count = 0
  total = 0
  def averager(new_value):
    nonlocal count, total
    count += 1
    total += new_value
    return total / count
  return averager
协程
1.计算移动平均值
def averager():
    total = 0.0
    count = 0
    average = None
    while True:  # ➊ 这个无限循环表明,只要调用方不断把值发给这个协程,它就会一直接收值,然后生成结果。仅当调用方在协程上调用 .close() 方法,或者没有对协程的引用而被垃圾回收程序回收时,这个协程才会终止。
        term = yield average  # ➋ 这里的 yield 表达式用于暂停执行协程,把结果发给调用方;还用于接收调用方后面发给协程的值,恢复无限循环。
        total += term
        count += 1
        average = total / count


"""
调用 next(coro_avg) 函数后,协程会向前执行到 yield 表达式,产出 average 变量的初始值——None,因此不会出现在控制台中。此时,协程在 yield 表达式处暂停,等到调用方发送值。coro_avg.send(10) 那一行发送一个值,激活协程,把发送的值赋给 term,并更新 total、count 和 average 三个变量的值,然后开始 while 循环的下一次迭代,产出 average 变量的值,等待下一次为 term 变量赋值。
"""
coro_avg = averager()  # ➊ 创建协程对象。
# 调用 next(coro_avg) 函数后,协程会向前执行到 yield 表达式,产出 average 变量的初始值——None
print(next(coro_avg))  # ➋ 调用 next 函数,预激协程。
# None
print(coro_avg.send(10))  # ➌ 计算移动平均值:多次调用 .send(...) 方法,产出当前的平均值。
# 10.0
print(coro_avg.send(30))
# 20.0
print(coro_avg.send(5))
# 15.0
运算符重载
自定义转进制
1.N进制转十进制
nums = input(":")
N = int(input(":"))
result = 0
list_nums = list(reversed(nums))
for i in range(len(list_nums)):
    result += int(list_nums[i]) * N ** i

print(result)
2.十进制转N进制
num = int(input())
N = int(input())
result = ""
if num ==0:
  pass
elif num > 0:
  signal = ""
else:
  num < 0:
    signal = "-"
while abs(num):
  result, num = str(abs(num) % N) + result, abs(num) // 7
print(int(signal + result))
排序算法
1. 冒泡排序
  • 冒泡排序是稳定原地排序算法

  • 时间复杂度O(n^2)

  • 排序方式

    遍历列表并比较相邻的元素对,如果元素顺序错误,则交换它们。重复遍历列表未排序部分的元素,直到完成列表排序

"""
冒泡排序
3 8 2 5 1 4 6 7
"""
def bubble_sort(li):
    # 代码第2步: 如果不知道循环几次,则举几个示例来判断
    for j in range(0,len(li)-1):
        # 代码第1步: 此代码为一波比对,此段代码一定一直循环,一直比对多次至排序完成
        for i in range(0,len(li)-j-1):
            if li[i] > li[i+1]:
                li[i],li[i+1] = li[i+1],li[i]

    return li

li = [3,8,2,5,1,4,6,7]
print(bubble_sort(li))
2. 快速排序
  • 快速排序是⾮稳定原地排序算法
  • 时间复杂度O(nlogn)。空间复杂度是O(logn)。
  • 排序步骤:
    1. 首先选择一个元素,称为数组的基准元素;
    2. 将所有小于基准元素的元素移动到基准元素的左侧;将所有大于基准元素的移动到基准元素的右侧
    3. 递归地将上述两个步骤分别应用于比上一个基准元素值更小和更大的元素的每个子数组
"""
快速排序
    1、left找比基准值大的暂停
    2、right找比基准值小的暂停
    3、交换位置
    4、当right<left时,即为基准值的正确位置,最终进行交换
"""
def quick_sort(li, first, last):
    if first > last:
        return

    # 找到基准值的正确位置下表索引
    split_pos = part(li, first, last)
    # 递归思想,因为基准值正确位置左侧继续快排,基准值正确位置的右侧继续快排
    quick_sort(li, first, split_pos-1)
    quick_sort(li, split_pos+1, last)


def part(li, first, last):
    """找到基准值的正确位置,返回下标索引"""
    # 基准值、左游标、右游标
    mid = li[first]
    lcursor = first + 1
    rcursor = last
    sign = False
    while not sign:
        # 左游标右移 - 遇到比基准值大的停
        while lcursor <= rcursor and li[lcursor] <= mid:
            lcursor += 1
        # 右游标左移 - 遇到比基准值小的停
        while lcursor <= rcursor and li[rcursor] >= mid:
            rcursor -= 1
        # 当左游标 > 右游标时,我们已经找到了基准值的正确位置,不能再移动了
        if lcursor > rcursor:
            sign = True
            # 基准值和右游标交换值
            li[first],li[rcursor] = li[rcursor],li[first]
        else:
            # 左右游标互相交换值
            li[lcursor],li[rcursor] = li[rcursor],li[lcursor]

    return rcursor

if __name__ == '__main__':
    li = [6,5,3,1,8,7,2,4,666,222,888,0,6,5,3]
    quick_sort(li, 0, len(li)-1)

    print(li)
3. 归并排序
  • 归并排序是稳定⾮原地排序算法
  • 时间复杂度O(nlogn)。空间复杂度是O(n)。
  • 排序步骤
    1. 连续划分未排序列表,直到有N个子列表,其中每个子列表有1个"未排序"元素,N是原始数组中的元素数
    2. 重复合并,即一次将两个子列表合并在一起,生成新的排序子列表,直到所有元素完全合并到一个排序数组中
"""
归并排序
"""

def merge_sort(li):
    # 递归出口
    if len(li) == 1:
        return li

    # 第1步:先分
    mid = len(li) // 2
    left = li[:mid]
    right = li[mid:]
    # left_li、right_li 为每层合并后的结果,从内到外
    left_li = merge_sort(left)
    right_li = merge_sort(right)

    # 第2步:再合
    return merge(left_li,right_li)

# 具体合并的函数
def merge(left_li,right_li):
    result = []
    while len(left_li)>0 and len(right_li)>0:
        if left_li[0] <= right_li[0]:
            result.append(left_li.pop(0))
        else:
            result.append(right_li.pop(0))
    # 循环结束,一定有一个列表为空,将剩余的列表元素和result拼接到一起
    result += left_li
    result += right_li

    return result

if __name__ == '__main__':
    li = [1,8,3,5,4,6,7,2]
    print(merge_sort(li))
牛客
1. 判断是否为素数prime

质数/素数一般指质数。质数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。

在一般领域,对正整数n,如果用2到n的开方之间的所有整数去除,均无法整除,则n为质数。

from math import sqrt
def is_prime(n):
    if n == 1:
        return False
    for i in range(2, int(sqrt(n))+1):
        if n % i == 0:
            return False
    return True
2. 按N位长度截取字符串
s = "100000111111110000000111101010111"

for i in range(0, len(s), N):
  print(s[i, i + N])
3. 查找组成一个偶数最接近的两个素数

任意一个偶数(大于2)都可以由2个素数组成,组成偶数的2个素数有很多种情况,本题目要求输出组成指定偶数的两个素数差值最小的素数对。

数据范围:输入的数据满足4 <= n <= 1000

输入描述:

输入一个大于2的偶数

输出描述:

从小到大输出两个素数

import sys
from math import sqrt


# 判断是否为质数
def is_prime(n):
    if n == 1:
        return False
    # 在一般领域,对正整数n,如果用2到n的开方之间的所有整数去除,均无法整除,则n为质数。
    for i in range(2, int(sqrt(n)) + 1):
        if n % i == 0:
            return False  # 不为素数
    return True


# 循环输入
for n in sys.stdin:
    a = int(n)
    mid = a // 2  # 从中间值开始找,最先找到的素数对为最接近
    while mid > 0:
        if is_prime(mid) and is_prime(a - mid):
            print(mid)
            print(a - mid)
            break
        else:
            mid -= 1
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值