LeetCode刷题 链表、堆、递归、回溯、DFS BFS、贪心

二叉树(常用递归)

在这里插入图片描述
前序顺序:根节点排最先,然后同级先左后右
中序顺序:先左后根最后右
后序顺序:先左后右最后根

颜色标记法(推荐)

原文 官方题解中介绍了三种方法来完成树的中序遍历,包括:

递归
借助栈的迭代方法
莫里斯遍历

在树的深度优先遍历中(包括前序、中序、后序遍历),递归方法最为直观易懂,但考虑到效率,我们通常不推荐使用递归。

栈迭代方法虽然提高了效率,但其嵌套循环却非常烧脑,不易理解,容易造成一看就懂,一写就废的窘况。而且对于不同的遍历顺序(前序、中序、后序),循环结构差异很大,更增加了记忆负担。

因此,我在这里介绍一种颜色标记法,兼具栈迭代方法的高效,又像递归方法一样简洁易懂,更重要的是,这种方法对于前序、中序、后序遍历,能够写出完全一致的代码。

其核心思想如下:

使用颜色标记节点的状态,新节点为白色,已访问的节点为灰色。
如果遇到的节点为白色,则将其标记为灰色,然后将其右子节点、自身、左子节点依次入栈。
如果遇到的节点为灰色,则将节点的值输出。

class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]: # 前序遍历
        WHITE, GRAY = 0, 1
        res = []
        stack = [(WHITE, root)]
        while stack:
            color, node = stack.pop()
            if node is None: continue
            if color == WHITE:
                stack.append((WHITE, node.right)) # 1
                stack.append((WHITE, node.left)) # 2
                stack.append((GRAY, node)) # 3
                # 1 2 3 就是 前序遍历
                # 3 1 2 就是 后续遍历
                # 1 3 2 就是 中序遍历
            else:
                res.append(node.val)
        return res
---
class Solution:# 层序遍历
    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        WHITE, GRAY = 0, 1
        stack = []
        stack.append((root, WHITE, 0)) # 初始化深度 = 0
        result = []
        while stack:
            node, color, level = stack.pop()
            if node:
                if color == WHITE:
                    stack.append((node.right, WHITE, level+1))
                    stack.append((node.left, WHITE, level+1))
                    stack.append((node, GRAY, level))
                else:
                    if len(result) == level: 
                        result.append([])
                    result[level].append(node.val)
        return result

颜色标记法(改版)

white对应TreeNode数据类型,gray对应int数据类型,所以不需要额外的颜色标记:

class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]: # 中序遍历
        stack,res = [root],[]
        while stack:
            i = stack.pop()
            if isinstance(i,TreeNode):
                stack.extend([i.right, i.left, i.val]) # 前序遍历
                stack.extend([i.right, i.val, i.left]) # 中序遍历
                stack.extend([i.val, i.right, i.left]) # 后序遍历
            elif isinstance(i,int):
                res.append(i)
        return res 
---
class Solution: 
# 层序遍历 使用队列结构进行广度优先搜索BFS,也可以进行深度优先搜索DFS,记录好层数就行。
    def levelTraversal(self, root: TreeNode) -> List[int]:
        queue,res = [root],[]
        while queue:
            i = queue.pop(0) # 左边拿第一个
            if isinstance(i,TreeNode):
                queue.extend([i.val,i.left,i.right])
            elif isinstance(i,int):
                res.append(i)
        return res

二叉树的前序遍历、中序遍历、后续遍历、层序遍历

前序遍历:根 左 右

递归写法:

public static void preOrderRecur(TreeNode head) {
    if (head == null) {
        return;
    }
    System.out.print(head.value + " ");
    preOrderRecur(head.left);
    preOrderRecur(head.right);
}

迭代写法(关键是自己搞一个栈即可):

    public List<Integer> preorderTraversal(TreeNode root) {
        	List<Integer> res = new ArrayList<>();
	        if(root == null) return res;
	        Stack<TreeNode> stack = new Stack<TreeNode>();
	        stack.push(root);
	        while(!stack.isEmpty()){
		        TreeNode node = stack.pop();
		        res.add(node.val);
		        if(node.right != null) stack.push(node.right);
		        if(node.left != null) stack.push(node.left);
	        }
            return res;
    }
后续遍历: 左 右 根

递归写法:

public static void postOrderRecur(TreeNode head) {
    if (head == null) {
        return;
    }
    postOrderRecur(head.left);
    postOrderRecur(head.right);
    System.out.print(head.value + " ");
}

迭代写法

我们知道前序遍历是根左右,那么根右左的实现也很简单。只需要先压入左子树即可。
根右左的遍历结果翻转过来就是左右根,刚好就是后序遍历。
因此,后序遍历就是前序遍历的变形

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new LinkedList<>();
        if (root==null) return res;
        Deque<TreeNode> stack = new LinkedList<>();
        stack.push(root);
        while(!stack.isEmpty()) {
            root = stack.pop();
            res.add(0,root.val);// 每次往最前面加
            if (root.left!=null) {
                stack.push(root.left);
            }
            if (root.right!=null) {
                stack.push(root.right);
            }
        }
        return res;
    }
}
中序遍历:左 根 右
public static void preOrderRecur(TreeNode head) {
    if (head == null) {
        return;
    }
    preOrderRecur(head.left);
    System.out.print(head.value + " ");
    preOrderRecur(head.right);
}

迭代写法:

public static void inOrderIteration(TreeNode head) {
	if (head == null) {
		return;
	}
	TreeNode cur = head;
	Stack<TreeNode> stack = new Stack<>();
	while (!stack.isEmpty() || cur != null) {
		while (cur != null) {
			stack.push(cur);
			cur = cur.left; // 找到最左边
		}
		TreeNode node = stack.pop();
		System.out.print(node.value + " "); // 左边 
		if (node.right != null) cur = node.right;
	}
}
N叉树的层序遍历、后续遍历

使用队列结构进行广度优先搜索BFS

class Solution:
    def levelOrder(self, root: 'Node') -> List[List[int]]:
        if root is None: return []
        queue, res = [root,], []
        while queue:
            sub_res = []
            for _ in range(len(queue)):
                node = queue.pop(0) # 取值第一个
                sub_res.append(node.val)
                for child in node.children:
                    queue.append(child)
            res.append(sub_res)
        return res

深度优先搜索DFS,记录好层数就行

class Solution:
    def levelOrder(self, root: 'Node') -> List[List[int]]:
        res = []
        def dfs(root, depth):
            if not root: return
            if len(res) == depth:
                res.append([])
            res[depth].append(root.val)
            for ch in root.children:
                dfs(ch, depth + 1)
        dfs(root, 0)
        return res

N 叉树的 后续遍历

class Solution:
    def postorder(self, root):
        res = []
        def helper(root):
            if not root:
                return
            for child in root.children:
                helper(child)
            res.append(root.val)
        helper(root)
        return res
---
class Solution:
   def postorder(self, root):
        if root is None:
            return []
        stack, output = [root, ], []
        while stack:
            root = stack.pop()
            output.append(root.val)
            for c in root.children:
                stack.append(c)
        return output[::-1]

前中遍历推导出二叉树

前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]

    3
   / \
  9  20
    /  \
   15   7

思路:

先序遍历的顺序是根节点,左子树,右子树。中序遍历的顺序是左子树,根节点,右子树。所以我们只需要根据先序遍历得到根节点,然后在中序遍历中找到根节点的位置,它的左边就是左子树的节点,右边就是右子树的节点。生成左子树和右子树就可以递归的进行了。

Python
class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        def myBuildTree(preorder_left, preorder_right, inorder_left, inorder_right):
            if preorder_left > preorder_right: return 
            preorder_root = preorder_left # 根节点index
            inorder_root = index[preorder[preorder_root]] # 节点在中序索引的位置
            root = TreeNode(preorder[preorder_root]) # 根节点创建
            size_left_subtree = inorder_root - inorder_left # 左子树 长度
            root.left = myBuildTree(preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1)
            # 左子树 在前序遍历数组跟中序遍历数组位置
            root.right = myBuildTree(preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right)
            return root
        n = len(preorder)
        index = {element: i for i, element in enumerate(inorder)} # 中序遍历的位置
        return myBuildTree(0, n - 1, 0 ,n - 1)

中后遍历推导出二叉树

思路:思路与从前序和中序遍历序列构造二叉树相同,只是后序序列中的根节点值是从后面开始取的。

class Solution:
    def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
        def helper(in_left,in_right):
            if in_left > in_right:
                return 
            val = postorder.pop()
            root = TreeNode(val)
            index = idx_map[val]
            root.right = helper(index + 1, in_right)
            root.left = helper(in_left, index - 1)
            return root
        idx_map = {val: idx for idx, val in enumerate(inorder)}
        return helper(0,len(inorder) - 1)

前后遍历推导出二叉树

思路:跟上面类似,另外有一种以check的方式构建二叉树思维。

只有每个节点度为2或者0的时候前序和后序才能唯一确定一颗二叉树,只有一个子节点是无法确定的,因为你无法判断他是左子树还是右子树。可以有多个答案

class Solution(object):
    def constructFromPrePost(self, pre, post):
        def dfs(pre,post):
            if not pre:
                return None
            # 数组长度为1时,直接返回即可
            if len(pre)==1:
                return TreeNode(pre[0])
            # 根据前序数组的第一个元素,创建根节点     
            root = TreeNode(pre[0])
            # 根据前序数组第二个元素,确定后序数组左子树范围
            left_count = post.index(pre[1])+1
            # 递归执行前序数组左边、后序数组左边
            root.left = dfs(pre[1:left_count+1],post[:left_count])
            # 递归执行前序数组右边、后序数组右边
            root.right = dfs(pre[left_count+1:],post[left_count:-1])
            # 返回根节点
            return root
        return dfs(pre,post)

二叉树的最近公共祖先

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

先写终止条件,没找到节点,返回空。然后写,如果找到指定节点了,需要终止,并回传节点。

回传节点特别重要,当遇到某个父节点接受到来自left和right的回传值,那么就更新回传值为此父节点。如果没有这种情况,则将回传值不断向父节点回传,保证最后得到的时最近公共祖先。

终止条件下好后,写本步操作,分辨获得节点的left和right的回传值,向下递归。

检查回传值是否两边都有,如果都有则更新回传值为父节点,如果只有一边,那么就更新为有值的一边。

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null || root == p || root == q) return root;
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        if(left == null && right == null) return null; 
        if(left == null) return right; 
        if(right == null) return left; 
        return root; //  if(left != null and right != null)
    }
}

思路二:并查集的思想,哈希存父节点的值,然后转化成求p节点所在的链表和q节点所在的链表相交位置的问题。

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        dic = {root:None}
        def dfs(node):
            if node:
                if node.left:
                    dic[node.left] = node
                if node.right:
                    dic[node.right] = node
                dfs(node.left)
                dfs(node.right)
        dfs(root)
        l1, l2 = p, q
        while(l1!=l2):
            l1 = dic.get(l1, q) # 没找到用 q
            l2 = dic.get(l2, p) # 没找到用 p
        return l1

二叉树的最小深度

使用递归和层序遍历两种方法实现。
与最大深度不同的是,最小深度不是下探到最下面一层,而是找到最浅的一层,最浅的一层的判断依据是一个节点的左右子节点均为空。同时要注意只有一个子树的情况。

class Solution:
    def minDepth(self, root: TreeNode) -> int:
        if not root:
            return 0 # 该节点为空
        if not root.left and not root.right: # 单独一个节点 没有左右节点
            return 1

        min_depth = float('inf') # 正无穷大
        if root.left:
            min_depth = min(self.minDepth(root.left), min_depth)
        if root.right:
            min_depth = min(self.minDepth(root.right), min_depth)
        return min_depth + 1

层序遍历:

class Solution:
    def minDepth(self, root: TreeNode) -> int:
        queue, res = [root,], 0
        if not root: return 0
        while queue:
            res += 1
            for _ in range(len(queue)):
                node = queue.pop(0) # 出第一个数据
                if not node: continue
                if not node.left and not node.right:
                    return res
                else: # 可能出现左为空或者右为空
                    queue.append(node.left)
                    queue.append(node.right)
        return res

二叉树的最大深度

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if root is None:
            return 0
        else:
            left_depth = self.maxDepth(root.left)
            right_depth = self.maxDepth(root.right)
        return max(left_depth,right_depth) + 1
---
class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if root is None: return 0
        queue = [root,]
        depth = 0
        while queue:
            depth += 1
            for _ in range(len(queue)):
                node = queue.pop(0)
                if node.left is not None:
                    queue.append(node.left)
                if node.right is not None:
                    queue.append(node.right)
        return depth

验证二叉搜索树

条件: left < root < right
思路:中序遍历时,判断当前节点是否大于中序遍历的前一个节点,如果大于,说明满足 BST,继续遍历;否则直接返回 false

class Solution {
    long pre = Long.MIN_VALUE;
    public boolean isValidBST(TreeNode root) {
        if (root == null) return true;
        // 访问左子树
        if (!isValidBST(root.left)) return false;
        // 访问当前节点:如果当前节点小于等于中序遍历的前一个节点,
        // 说明不满足BST,返回 false;否则继续遍历。
        if (root.val <= pre) return false;
        pre = root.val;
        return isValidBST(root.right);      // 访问右子树
    }
}
---
class Solution:
    def isValidBST(self, root):
        def helper(node, lower=float('-inf'), upper=float('inf')):
            if not node:
                return True
            val = node.val
            if val <= lower or val >= upper:
                return False
            return helper(node.right, val, upper) and helper(node.left, lower, val)
        return helper(root)

翻转二叉树

经典递归题:

class Solution:
    def invertTree(self, root: TreeNode) -> TreeNode:
        if not root:
            return None 
        root.left,root.right = self.invertTree(root.right),self.invertTree(root.left)
        return root 

堆 heap

丑数

我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
思路:

把所有丑数列出来,然后从小到大排序。而大的丑数必然是小丑数的2/3/5倍,所以有了那3个数组。每次就从那数组中取出一个最小的丑数归并到目标数组中

dp实现:
  public int nthUglyNumber(int n) {
    int p2=0,p3=0,p5=0;
    int[] dp=new int[n];
    dp[0]=1;
    for(int i=1;i<n;i++){
        dp[i]=Math.min(dp[p2]*2,Math.min(dp[p3]*3,dp[p5]*5));
        if(dp[i]==dp[p2]*2) p2++;
        if(dp[i]==dp[p3]*3) p3++;
        if(dp[i]==dp[p5]*5) p5++; 
    }
    return dp[n-1];
}
---
利用最小堆实现
class Solution {
    private int[] uglyNumber = {2,3,5};
    public int nthUglyNumber(int n) {
        Queue<Long> queue = new PriorityQueue<>();//创建小根堆,每次出堆的都是最小值
        queue.add(1L);
        int count = 0; //记录出堆的个数,出堆的元素完全按照从小到大排序
        while (! queue.isEmpty()){
            long cut = queue.poll(); // 出最小值
            //如果出堆的个数>=n,当前cut就是第n个丑数
            if(++count >= n){
                return (int) cut;
            }
            for(int num : uglyNumber){
                //排除重复的数字
                if(! queue.contains(num * cut)){
                    queue.add(num * cut);
                }
            }
        }
        return -1;
    }
}

最小的K个数

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
思路: 用大根堆即可

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        // 特判
        if(k == 0 || k > arr.length){
            return new int[0];
        }
        // 优先队列默认是小顶堆:
        // Queue<Integer> heap = new PriorityQueue<>();
        //创建大顶堆: 根节点是最大值的那种。
        Queue<Integer> heap = new PriorityQueue<>(k,(x,y)->(y-x));
        
        //把最小的k个数存到heap中
        for(int num : arr){
            if(heap.size() < k){
                heap.offer(num);
            }else if(heap.peek() > num){
                heap.poll();
                heap.offer(num);
            }
        }
        
        //从heap中取出存放在数组中
        int[] res = new int[k];
        int i = 0;
        for(Integer e : heap){
            res[i] = e;
            i ++;
        }
        return res;
    }

前K个高频元素

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

#python技巧
#直接使用Counter计数,直接取频数最大的几位
#collections.Counter(nums).most_common(k)代表取频数最大的k位,key和value打包成元组储存在列表中。
#*代表不确定几位,可有多位
#zip将对应位置元素打包成元组
#打包完后,第0个元素就是要求的,但是zip类型不能直接取,得先变成list,然后取第0位。
import collections
class Solution(object):
    def topKFrequent(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: List[int]
        """
        return list(zip(*collections.Counter(nums).most_common(k)))[0]

Java版本:

1、HashMap统计数据出现次数
2、构建小顶堆 存放数据结果

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> occurrences = new HashMap<Integer, Integer>();
        for (int num : nums) {
            occurrences.put(num, occurrences.getOrDefault(num, 0) + 1);
        }
        // int[] 的第一个元素代表数组的值,第二个元素代表了该值出现的次数
        PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>() {
            public int compare(int[] m, int[] n) {
                return m[1] - n[1];
            }
        });
        for (Map.Entry<Integer, Integer> entry : occurrences.entrySet()) {
            int num = entry.getKey(), count = entry.getValue();
            if (queue.size() == k) {
                if (queue.peek()[1] < count) {
                    queue.poll();
                    queue.offer(new int[]{num, count});
                }
            } else {
                queue.offer(new int[]{num, count});
            }
        }
        int[] ret = new int[k];
        for (int i = 0; i < k; ++i) {
            ret[i] = queue.poll()[0];
        }
        return ret;
    }
}

滑动窗口最大值

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

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]

python内置的堆是最小堆,所以在元素入堆的时候取负,以便求最大值。另外我们需要判断最大值是否在当前的窗口内,所以需要索引值,在入堆的时候一起存入。每次取堆内最大值,也就是堆顶元素的时候,检查索引值是否是在窗口内的。

class Solution:
	def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
		res, heap = [], []
		for i in range(len(nums)):
			heapq.heappush(heap, (-nums[i], i)) # 往堆中插入一条新的值 
			if i + 1 >= k:
				while heap and heap[0][1] <  i + 1 - k: # 看插入的数据是否 比最小值还小。
					heapq.heappop(heap) #从堆中弹出最小值 
				res.append(-heap[0][0])                                               
		return res

双端队列

class Solution:
    def maxSlidingWindow(self, nums, k):
        d = collections.deque()	#建立一个双端队列 这个是一个单调队列!! 从大到小
        out = []
        for i, n in enumerate(nums):	#遍历数组
            while d and nums[d[-1]] < n:	# 实现单调性
                d.pop()
            d.append(i)	#把当前值加入窗口中
            if d[0] == i - k:	# 检查最大值是否已经不在窗口中,不在就丢掉,因为每一步都检查,所以不会漏
                d.popleft()	
            if i >= k - 1: # 第一个窗口看到本窗口内最后一个值的时候才能找到最大值,所以从k-1处开始记录结果
                out.append(nums[d[0]])
        return out

递归

爬梯子

题意:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

class Solution:
    def climbStairs(self, n: int) -> int:
        a,b = 1,1
        for i in range(n):
            a,b = b,a+b
        return a
---
class Solution:
    def climbStairs(self, n: int) -> int:
        dp = [0]*(n+1)
        dp[0] = 1
        dp[1] = 1
        for i in range(2,n+1):         
            dp[i] = dp[i-1]+dp[i-2]
        return dp[n]

求根到叶子节点数字之和

题意:给定一个二叉树,它的每个结点都存放一个 0-9 的数字,每条从根到叶子节点的路径都代表一个数字。

#好好理解preTotal的作用,向下递归时,将参数带往子节点,用于求路径和;向上返回时将每个路径和依次相加,求得最终的总和。
class Solution:
    def sumNumbers(self, root: TreeNode) -> int:
        def dfs(root, prevTotal):
            if not root: return 0
            total = prevTotal * 10 + root.val
            if not root.left and not root.right:
                return total
            else:
                return dfs(root.left, total) + dfs(root.right, total)
        return dfs(root, 0)

pow(x, n) ,即计算 x 的 n 次幂函数。

题意: 注意n次幂分为单数双数的情况,也分整数负数的情况。

class Solution:
    def myPow(self, x: float, n: int) -> float:
        def quickMul(N):
            if N == 0:
                return 1.0
            y = quickMul(N // 2)
            ans = y * y if N % 2 == 0 else y * y * x
            return ans
        return quickMul(n) if n >= 0 else 1.0 / quickMul(-n)

生成括号

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> res=new ArrayList<String>();
        generate("",n,n,res);
        return res;
    }
    private static void generate(String item,int left,int right,List<String> res){
        //当n个左右括号都使用完了之后,则跳出结果
        if(left==0 && right==0){
            res.add(item);
            return;
        }
        //还剩下左括号个数不为0
        if(left>0){
            generate(item+"(",left-1,right,res);
        }
        //还剩下右括号大于剩下左括号个数
        if(left<right){
            generate(item+")",left,right-1,res);
        }
    }
}

回溯

17.电话号码的字母组合

class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        if not digits:
            return list()
        phoneMap = {"2": "abc","3": "def","4": "ghi","5": "jkl","6": "mno","7": "pqrs","8": "tuv","9": "wxyz"}
        def backtrack(idx):
            if idx == len(digits):
                res.append("".join(path))
            else:
                digit = digits[idx]
                for i in phoneMap[digit]:
                    path.append(i)
                    backtrack(idx + 1)
                    path.pop()
        path = []
        res = []
        backtrack(0)
        return res

46 全排列

给定一个 没有重复 数字的序列,返回其所有可能的全排列。
解题:核心点在于一个决策树

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        if len(nums) == 0: return []
        def backtrack(depth):
            if depth == len(nums):
                res.append(path[:]) # 记得加冒号,否则是添加的引用,无法真正将值存下来。
            for i in range(len(nums)):
                if not visited[i]: # 遍历备选值,如果值用过了,那么这个值不选。
                    visited[i] = True
                    path.append(nums[i])
                    backtrack(depth + 1)
                    visited[i] = False
                    path.pop()

        visited = [False for i in range(len(nums) )]
        res = []
        path = []
        backtrack(0)
        return res

47. 全排列 II

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        if len(nums) == 0: return []
        def backtrack(depth):
            if depth == len(nums):
                res.append(path[:])
            for i in range(len(nums)):
                if visited[i] == True:
                    continue
                if i > 0 and nums[i] == nums[i - 1] and visited[i - 1] == False:
                    continue
                visited[i] = True
                path.append(nums[i])
                backtrack(depth + 1)
                visited[i] = False
                path.pop()

        nums.sort()
        res = []
        path = []
        visited = [False for i in range(len(nums))]
        backtrack(0)
        return res

39 组合总和

题意:给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。
思路
在这里插入图片描述

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        if candidates == []: return [[]]
        def backtrack(index):
            if sum(path) == target:
                res.append(path[:])
            for i in range(index, len(candidates)):
                path.append(candidates[i])               
                if sum(path) <= target:
                    backtrack(i)
                path.pop()
        path = []
        res = []
        backtrack(0)
        return res

40 组合总和 II

题意:给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次
在这里插入图片描述

这道题的重点在于如何去重。 一种方案是Set,但是由于Set是红黑树底层,其效率过低。 一种巧妙的方案是:

            if(i>start&&candidates[i]==candidates[i-1])
            {
                continue;
            }

这样可以避免相同的情况筛选两次(一次原生For循环,一次是递归)。

class Solution:
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        if candidates == []: return [[]]
        def backtrack(index):
            if sum(path) == target:
                res.append(path[:])
            for i in range(index, len(candidates)):
                if i > index and candidates[i] == candidates[i - 1]: # 去重的意思
                    continue
                path.append(candidates[i])
                if sum(path) <= target:
                    backtrack(i + 1)  # 不能重复用的意思
                path.pop()
        path,res = [],[]
        candidates.sort()
        backtrack(0)
        return res

70. 各种组合

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。


#回溯法
class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        if n == 0 or k == 0:
            return []
        def backtrace(index):
            if len(path) == k:
                res.append(path[:]) # 浅拷贝,这一步很重要
            for i in range(index,n):	#这里,向后选择
                path.append(nums[i])
                backtrace(i+1)	#向下回溯,这里回溯传递的是向后选择的索引位置。
                path.pop()
                
        #先生成数
        nums = [i for i in range(1,n+1)]
        path = []
        res = []
        backtrace(0)
        return res

python利器:

class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        return list(itertools.combinations(range(1,n+1),k))

78. 子集

题意:给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        if nums == []: return [[]]

        def backtrack(idx):
            #不需要等一条路径回溯结束再保存值,每一步都要保存值。
            ans.append(path[:])
            for i in range(idx, len(nums)):
                path.append(nums[i])
                backtrack(i+1)
                path.pop()     
        path = []
        res = []     
        backtrack(0)
        return res

更加Python化的解法:

# 新元素增加的新的组合,就是前面元素所有的组合再加上这个元素,
# 再将新元素增加的组合融合进组合总体中,进入下一轮循环。
class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        res = [[]]
        for i in nums:
            res = res + [[i] + num for num in res]
        return res

利用Python自带工具:

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        res = []
        for i in range(len(nums)+1):
            # 返回 nums序列中 i个组合的全部样例
            for tmp in itertools.combinations(nums, i): 
                res.append(tmp)
        return res

90. 子集 II

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。说明:解集不能包含重复的子集。

class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        if nums == []: return [[]]

        def backtrack(idx):
            # 不需要等一条路径回溯结束再保存值,每一步都要保存值。
            res.append(path[:])
            for i in range(idx, len(nums)):
                if i > idx and nums[i] == nums[i - 1]:
                    continue
                path.append(nums[i])
                backtrack(i + 1)
                path.pop()
                
        nums.sort()
        path = []
        res = []
        backtrack(0)
        return res

51、N皇后(Python题解)

经典题目:可以看下此文
在这里插入图片描述

class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        if n == 0: return []
        def backtrack(depth):
            if depth == n:
                res.append(path[:])
            #按列寻找符合条件的位置
            for i in range(n):
                 # 上方不可用,主对角线不可放置,副对角线不可放置
                if cols[i] == False and hill_diagonals[depth + i] == False and dale_diagonals[depth - i] == False:
                    cols[i] = True
                    hill_diagonals[depth + i],dale_diagonals[depth - i] = True,True
                    path.append(i)
                    
                    backtrack(depth + 1)

                    cols[i] = False
                    hill_diagonals[depth + i],dale_diagonals[depth - i] = False,False
                    path.pop()

        cols = [False] * n # 这一列
        hill_diagonals = [False] * (2 * n - 1)  #主对角线差是定值
        dale_diagonals = [False] * (2 * n - 1)  #副对角线和是定值
        path = []
        res = []
        backtrack(0)
        return [ ["." * i + "Q" + "." * (n-1 - i) for i in path ] for path in res]

980. 不同路径 III

在这里插入图片描述

一是可以在4个方向上行走,二是不允许走回头路的要求,三是每个路径都需要包含所有无障碍方格。

首先遍历网格找到起始点,然后再4个方向上进行搜索。搜索的时候注意,每条路径走过一个网格后将将其标记为-1(障碍),这样就保证了不允许走回头路的条件,搜索下一个路径时恢复网格原有状态。

当遇到终点时,终止回溯。判断走过的网格数是否等于无障碍网格总数,如果是,则找到了一条符合要求的路径,保存。

class Solution:
    def uniquePathsIII(self, grid: List[List[int]]) -> int:
        rows = len(grid)
        cols = len(grid[0])
        steps,m,n = 1,0,0
        count = 0
        for r,row in enumerate(grid):
            for c,val in enumerate(row):
                if val == 1:
                    m,n = r,c
                if val == 0:
                    steps += 1

        def traceback(r,c,step):
            nonlocal count
            if  grid[r][c] == 2:
                if step == 0:
                    count += 1
                return
            grid[r][c] = -1
            for i,j in [(r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)]:
                if 0<=i<rows and 0<=j<cols and grid[i][j] != -1:
                    temp = grid[i][j]
                    traceback(i, j, step-1)
                    grid[i][j] = temp
        traceback(m, n, steps)
        return count

DFS or BFS

200. 岛屿数量

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围

DFS(Deep First Search)深度优先搜索
题解思路:对每一块陆地进行搜索,搜索过的陆地就标记成海洋,不再搜索,则搜索陆地的次数就是陆地的数量

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        def dfs(r,c):
            grid[r][c] = 0  #  地变水
            nr,nc = len(grid),len(grid[0])
            for x,y in [(r-1,c),(r+1,c),(r,c-1),(r,c+1)]:
                if 0<=x <nr and 0<=y < nc and grid[x][y] == '1':
                    dfs(x,y)
                    
        nr = len(grid)
        if nr == 0:
            return 0
        nc = len(grid[0])
        nums = 0
        for r in range(nr):
            for c in range(nc):
                if grid[r][c] == '1':
                    nums +=1
                    dfs(r,c)
        return nums

BFS(Breath First Search)广度优先搜索

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        count = 0
        for row in range(len(grid)):
            for col in range(len(grid[0])):
                if grid[row][col] == '1':  # 发现陆地
                    count += 1  # 结果加1
                    grid[row][col] = '0'  # 将其转为 ‘0’ 代表已经访问过
                    # 对发现的陆地进行扩张即执行 BFS,将与其相邻的陆地都标记为已访问
                    # 下面还是经典的 BFS 模板
                    land_positions = collections.deque()
                    land_positions.append([row, col])
                    while len(land_positions) > 0:
                        x, y = land_positions.popleft()
                        for new_x, new_y in [[x, y + 1], [x, y - 1], [x + 1, y], [x - 1, y]]:  # 进行四个方向的扩张
                            # 判断有效性
                            if 0 <= new_x < len(grid) and 0 <= new_y < len(grid[0]) and grid[new_x][new_y] == '1':
                                grid[new_x][new_y] = '0'  # 因为可由 BFS 访问到,代表同属一块岛,将其置 ‘0’ 代表已访问过
                                land_positions.append([new_x, new_y])
        return count

贪心

55. 跳跃游戏

给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个位置。
题解

class Solution:
    def canJump(self, nums: List[int]) -> bool:
        maxPos = 0
        for i in range(len(nums)):
            if i > maxPos :
                 return False
            maxPos  = max(maxPos ,i + nums[i])
        return True

45. 跳跃游戏 II

给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。你的目标是使用最少的跳跃次数到达数组的最后一个位置。

题解: 从当前点进行一次跳跃,然后将end的边界拉到本次跳跃最远的边界,遍历这个区间内第二次跳跃能到达的最远点,当这个区间遍历结束后,更新最远点,将end拉到这个最远点。跳跃次数+1。因为开始的时候边界是第 0个位置,steps 已经加 1 了

class Solution:
    def jump(self, nums: List[int]) -> int:
        n = len(nums)
        maxPos, end, step = 0, 0, 0
        for i in range(n - 1):
            if i <= maxPos:
                maxPos = max(maxPos, i + nums[i])
                if i == end:
                    end = maxPos
                    step += 1
        return step

763. 划分字母区间

字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。

这道题的思路和跳跃游戏II基本一致。
首先我们需要得到跳跃的步长,即本题中同字符出现的首末位置。然后从第一个字符开始“跳跃”,查找本字符长度区间内其他字符所能到达的最远位置,不断向更远的位置跳跃,直到无法再向后跳跃,说明本区间恰好由几个同种字符(每种字符包含所有个体)构成,为一个合法最小区间。

class Solution:
    def partitionLabels(self, S: str) -> List[int]:
        endList = [0] * 26
        for i, ch in enumerate(S):
            endList[ord(ch) - ord("a")] = i
        
        res = []
        left = most_right = 0
        for i, ch in enumerate(S):
            most_right = max(most_right, endList[ord(ch) - ord("a")])
            if i == most_right:
                res.append(most_right - left + 1)
                left = most_right + 1
        return res

1024 拼接视频

在这里插入图片描述
贪心:

  1. 相同开始点,选择结束点时间最长的时间片
  2. 在选择的片中同时记录最大值。
#贪心
#跳跃游戏
class Solution:
    def videoStitching(self, clips: List[List[int]], T: int) -> int:
        step = [0] * T
        start = rightmost = 0
        res = 0

        # 记录从每一步起点开始能跳跃到的最远位置
        for begin, end in clips:
            if begin < T:
                step[begin] = max(step[begin], end)

        for i in range(T):
            rightmost = max(rightmost, step[i]) # 纪律最大值
            if i == rightmost: # 如果连接失败 中断了
                return -1
            if i == start:
                res += 1
                start = rightmost
        return res

动态规划
在这里插入图片描述

class Solution:
    def videoStitching(self, clips: List[List[int]], T: int) -> int:
        dp = [float('inf')] * (T + 1)
        dp[0] = 0

        for i in range(1, T + 1):
            for start, end in clips:
                if start < i <= end:
                    dp[i] = min(dp[i], dp[start] + 1)
        return -1 if dp[T] == float('inf') else dp[T]

300、最长上升子序列,经典题

题意: 给定一个无序的整数数组,找到其中最长上升子序列的长度。

一、动态规划
观察数组,要求数组的最长上升子序列,可以想到长度为i的子序列可以由长度为i - 1的递推而来,因此考虑动态规划。那么动态规划的状态空间该如何定义呢?
思考最长上升子序列的寻找过程,当我们遍历到数组的i位置时,无法判断最终的最长子序列是由i结尾的所属的子序列组成还是由i - 1结尾或者其他的子序列组成。因此我们需要把位置i之前的所有位置所在的子序列的长度都记录下来。
因此我们这样定义状态空间,dp[i]代表的是以nums[i]结尾的子序列的最长子序列的长度。
递推公式为:dp[i] = max(dp[i][, dp[j] + 1) if nums[i] > nums [j]
最后取以每个元素结尾的子序列长度的最大值。

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        if len(nums) == 0:
            return 0
        
        dp = [1]*len(nums)
        for i in range(len(nums)):
            for j in range(i):
                if nums[i] > nums[j]:
                    dp[i] = max(dp[i],dp[j] +1)
        return max(dp)

二分法:
在这里插入图片描述

    public int lengthOfLIS(int[] nums) {
        //list中保存的是构成的上升子序列
        ArrayList<Integer> list = new ArrayList<>(nums.length);
        for (int num : nums) {
            //如果list为空,我们直接把num加进去。如果list的最后一个元素小于num,
            //说明num加入到list的末尾可以构成一个更长的上升子序列,我们就把num
            //加入到list的末尾
            if (list.size() == 0 || list.get(list.size() - 1) < num)
                list.add(num);
            else {
                //如果num不小于list的最后一个元素,我们就用num把list中第一
                //个大于他的值给替换掉,这样我们才能保证list中的元素在长度不变的情况下,元素值尽可能的小
                int i = Collections.binarySearch(list, num);
                //因为list是从小到大排序的,所以上面使用的是二分法查找。当i大于0的时候,说明出现了重复的,我们直接把他替换即可,如果i小于
                //0,我们对i取反,他就是list中第一个大于num值的位置,我们把它替换即可
                list.set((i < 0) ? -i - 1 : i, num);
            }
        }
        return list.size();
    }

二分查找

74. 搜索二维矩阵

编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:

  • 每行中的整数从左到右按升序排列。
  • 每行的第一个整数大于前一行的最后一个整数。

思路: 将数据串流起来,认为一大行。

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        if len(matrix) == 0:
            return False
        row = len(matrix)
        col = len(matrix[0])
        left , right = 0 , row*col-1
        while left <= right:
            mid  = (left + right)//2
            element = matrix[mid//col][mid%col]
            if element == target:
                return True
            else:
                if target < element:
                    right = mid - 1
                else:
                    left = mid + 1
        return False

逻辑模拟

螺旋矩阵

在这里插入图片描述
灵活使用zip

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

普通化思路:

class Solution:
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        if not matrix:return []

        x=y=0                                     # 矩阵元素位置初始化
        res = []                                  # 初始化,存储遍历后的矩阵元素
        dx = [ 0, 1, 0,-1]                        # 方向:右,下,左,上
        dy = [ 1, 0,-1, 0]                        # 注:与通常平面坐标系 记号 不同
        di = 0                                    # 初始化方向变量
        visited = set()                           # 初始化集合,存储已走过的坐标
        m,n = len(matrix),len(matrix[0])          # 矩阵的行列 
                
        for i in range(m*n):                                     # 
            res.append(matrix[x][y])                             # 存储遍历矩阵过的元素
            visited.add((x,y))                                   # 存储遍历过的坐标
            tx,ty = x+dx[di],y+dy[di]                            # 先记录下一步坐标,用于判断下一步怎么走
            if 0<=tx<m and 0<=ty<n and (tx,ty) not in visited:   # 判断坐标是否需变向,且没有遍历过
                x,y = tx,ty                                       
            else:                                                
                di = (di+1)%4                                    # 改变方向,右下左上为一圈,防止方向坐标越界
                x,y = x + dx[di],y+dy[di]                        # 下一步坐标
        return res

59. 螺旋矩阵 II

题意: 给定一个正整数 n,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。
在这里插入图片描述

class Solution:
    def generateMatrix(self, n: int) -> [[int]]:
        l, r, t, b = 0, n - 1, 0, n - 1 # 左,右,上,下
        mat = [[0 for _ in range(n)] for _ in range(n)]
        num, tar = 1, n * n
        while num <= tar:
            for i in range(l, r + 1): # left to right
                mat[t][i] = num
                num += 1
            t += 1
            for i in range(t, b + 1): # top to bottom
                mat[i][r] = num
                num += 1
            r -= 1
            for i in range(r, l - 1, -1): # right to left
                mat[b][i] = num
                num += 1
            b -= 1
            for i in range(b, t - 1, -1): # bottom to top
                mat[i][l] = num
                num += 1
            l += 1
        return mat
SoWhat1412 CSDN认证博客专家 CSDN签约作者 后端coder
微信搜索【SoWhat1412】,第一时间阅读原创干货文章。人之患、在好为人师、不实知、谨慎言。点点滴滴、皆是学问、看到了、学到了、便是收获、便是进步。
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 猿与汪的秘密 设计师:上身试试 返回首页
实付 19.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值