给定两个整数 n 和 k,返回从 1 到 n 中选 k 个数的所有组合,顺序不限。这是回溯的另一道基础题,与全排列的区别是:`[1, 2]` 和 `[2, 1]` 是同一个组合,所以每次选数只能选比上一个数更大的,用一个 `start` 指针记录下一个可选数字的起点。它有一个非常
给定一个元素互不相同的数组,返回它的所有子集(幂集),包括空集和它本身,顺序不限。n 个元素有 2^n 个子集,因为每个元素"选"或"不选"。这道题有三种常见做法:回溯(每个结点都是一个子集)、位掩码(用 0 到 2^n - 1 的每个整数的二进制位表示选哪些元素)、逐个翻倍(每
给定一个字母网格和一个单词,判断单词能否由网格中相邻(上下左右)的格子依次连成,同一个格子在一条路径里不能重复使用。做法是回溯(深度优先搜索):对每个格子作为起点,尝试匹配单词的第一个字符,然后向四个方向继续匹配下一个字符,走过的格子标记为已使用,一条路走不通就撤销标记退回来。本
给定一个升序数组,原地删除重复元素,使每个值最多出现两次,返回处理后的长度 k,数组前 k 个位置放保留下来的元素,后面的内容不重要。这是第 26 题的推广:那一题每个值保留 1 个,这一题保留 2 个。做法是双指针,但比较的对象变了:`k` 是已保留部分的长度,对于当前元素 `
第 33 题的进阶:数组同样是升序被旋转过,但这次允许有重复的元素。二分查找的关键是"每次总有一半是有序的",可以据此判断目标在不在有序的那半边。重复元素会破坏这个判断:当 `nums[lo]`、`nums[mid]`、`nums[hi]` 三者都相等时,无法知道哪半边是有序的(
给定一个升序链表,把所有出现过重复的数字的结点都删掉,只保留没有重复的数字。注意和下一题的区别:这一题的"重复"是整体删除(`[1, 2, 3, 3, 4, 4, 5]` 变成 `[1, 2, 5]`,两个 3 和两个 4 都没了),下一题是重复的只保留一个。因为头结点也可能被删
给定一个升序链表,删除所有重复的元素,使每个元素只出现一次(重复的保留一个)。因为链表有序,重复的元素一定挨在一起,所以只需要比较相邻的两个结点:如果当前结点 `cur` 和它的下一个值相同,就把下一个结点摘掉(`cur.next = cur.next.next`);不同才让 `
给定一排宽度为 1 的柱子的高度,求柱状图里能勾勒出的最大矩形的面积(矩形必须完全在柱子里面)。一个矩形的高度由它覆盖的柱子里最矮的那根决定,所以对每根柱子 `i`,以它为最矮柱子的最大矩形,向左向右分别延伸到第一根比它矮的柱子为止。暴力做法是对每个起点向右扩展并维护最小高度,O
给定一个只包含 `0` 和 `1` 的二维矩阵,找出只包含 `1` 的最大矩形,返回它的面积。这道题的巧妙之处是把它转化成上一题:把每一行当作矩形的底边,统计每一列"从这一行往上连续有多少个 1",这就得到一个柱状图;这个柱状图里最大的矩形,就是以这一行为底边的最大全 1 矩形。
给定一个链表和一个值 x,把链表重新排列,使所有小于 x 的结点都排在大于或等于 x 的结点之前,并且保持每一部分里结点原来的相对顺序(稳定)。做法是拆成两条链:遍历原链表,小于 x 的接到 `small` 链的末尾,其余的接到 `large` 链的末尾,最后把 `small`
把一个字符串按下面的规则"扰乱":如果长度是 1,停止;否则在某个位置把它切成两个非空子串 x 和 y,可以选择交换它们(得到 y + x)或者不交换(x + y),然后对 x 和 y 递归地做同样的操作。给定两个长度相同的字符串 s1 和 s2,判断 s2 是否可以由 s1 扰
给定两个升序数组 `nums1` 和 `nums2`,把 `nums2` 合并进 `nums1`,合并后 `nums1` 仍然有序。`nums1` 的长度是 m + n,前 m 个是有效元素,后面 n 个位置是 0 占位,正好留给 `nums2` 的元素。因为要求原地合并、不返回