1,两数之和 采用unordered_map[HTML_REMOVED] 记录扫描的数据,第二个数用于该数 的一个索引。
15, 三数之和 一定要排序 其中第一点遍历数组因为是从 0 开始遍历,判重的话要注意一下, nums[i] == nums[i-1] continue; 然后接下来两个点, 采用的主要是双指针遍历while(l[HTML_REMOVED]>可以这么返回res.push_back({a, b, c}).
560 和为k的子数组 看到连续的子数组的和可以放心大胆地求数组和。
2,两个(链表)相加 和的话要用(l1 || l2 || t)t 为检查是否前进1
先建立链表的头结点
auto dummt = new ListNode(-1);
auto cur = dummy;
之后每当索引一个节点就建立一个链节点 cur->next = new ListNode (t % 10), cur=cur->next;指针移到下一个节点
21 合并两个有序链表 新建立一个链表, 一边遍历一边创建新的链节点,一边往下移动节点指针。
23 合并k个升序有序链表, 采用优先队列的形式, 将每个链表的首节点都要存储到该队列中,由大到小排列,每次都要取出首节点最小的链表,存入到答案链表中,之后若刚存入的链表不是空节点的话, 将除了首节点之后的其他节点在从新放入到优先队列中;
19, 删除链表的倒数第n个节点 首先看一下第n个 节点是否存在;因为后面可能要用到新的链表, 先建立一下,然后接上这个头结点 即遍历一下,就行了, 然后找到这个节点就行, 倒数第n个就是正数 cnt——n个。
148 排序链表, 这个采用归并的思想, 即是按区间进行归并, 区间的长度由 长度2 4 8 的形式增长, 之后对于每一个区间的长度 , 要记住这个区间长度的头节点和尾节点,然后进行归并。每次对两段进行比较并且要求出第三段的头节点, 以便于更新head.
142环形链表, 对于该种链表设置两个指针, 都指向头节点, 一个为快指针, 另外一个为慢指针, 用快指针判断是否到达了链表尾部, 如果到达的话, 就说明没有环形, 否则当两个来指针相遇的时候, 说i名有环,让快指针指向头部, 当再次相遇的时候, 该点就是相遇的节点。
160 相交链表 设定两个头指针 依次遍历 找到两个指针指向一个节点的时候, 可以获取相交的点的值
200 反转链表 可以在循环外使用两个节点, 其中一个节点用来声明空节点, 另外一个节点指向头节点, 还可以设置一个新的节点, 存储临时的节点。
234 回文链表 对该链表进行遍历, 得到这个链表的节点个数, 然后将另一边翻转, 然后 同时遍历这两个链表, 期间如果值不一样的话, 就会报错(这个是在一条链表上改变指向, 而不是将其拆分成两条链表, 所以空间复杂度o(1), 时间复杂度o(n))
3, 无重复字符的最长子串问题 也是使用的是unordered_map[HTML_REMOVED] hash, 用双指针来记录每个字符出现的次数
首指针是否往前走,是根据尾指针是否大于1来判断的,即(j < i &&hash[s[i]] >1)
76 最小覆盖子串 字符串1 和字符串2 ,在字符串1中找出能够覆盖字符串2的最小覆盖子串, 可以采用两个哈希函数
将字符串2中的每个字符放入到哈希中,然后将另一个字符也放入到另一个哈希中,然后每次放入一个字符,都要将这个哈希与另一个哈希比较,比较的是j值, 是按第二个字符串数组的长度比较的。 如果如果小于的话就加加,使用另外一个变量记录长度, 记录的是两个哈希表中重合字符串的长度。
128 最长连续序列, 因为要求o(1) 时间度 ,所以采用哈希表unordered_set[HTML_REMOVED] hash, 讲每个数都存在这个表格中, 然后边遍历 一边查找 这个数的前后数字是否存在。。
4, 寻找两个正序数组的中位数 ,首先求一下每个数组的个数,求出中位数即k = (n + m )/ 2;
如果k为偶数的话, 中位数为 (k, k + 1)的位置
如果k为奇数的话, 中位数为(k + 1)的位置, 比如 总共五个数 5 / 2= 2, 实际上第2 + 1个数为中间的数。
然后在求得时候只需比较每个数组中k/2的位置即可,如果A[k / 2] < B[k / 2] 则A中的前k/2个数肯定不是中位数
即这样每次都删除(k/2)个数, 故复杂度是O(log(n + m))
287 寻找重复数 有n+1个数, 每个数都在1~n的范围内, 可以使用二分法进行查找, 对于一个中间值,遍历该数组中小于这个中间值的有多少个数, 如果大于等于的话,说明这个数在该中间点的左边 r = mid, 否则在右边,l = mid +1, 这样就能找到了
5,最长回文子串 选择中心点,遍历每个中心点即可,每次遍历都要分奇数(遍历该点的两边即i- 1, i+ 1)和偶数两种情况, 遍历中心点的两边是否相同, 更新遍历出的最长回文子串的长度即可 注意子串的截取方式: s.substr(a, b)即截取从a开始截取,截取b个字符。
注意注意注意 j–,k++, 对应的范围是 j + 1, 两者之间的个数为 k- j - 1;
10,正则表达式匹配 使用动态规划f[i][j] 表示第一个字符串的前i个字母与第二个字符串的前j个字母匹配,
匹配的情况可以分p[j] ==? ‘‘ 若不是“” 则s[i]== p[j] || p[j] == ‘.’ f[i][j] == f[i][j] | f[i-1][j-1];
若是的话则要注意一下范围因为该位置可以左右他的前一个字母的位置,分为不匹配或匹配多个情况
不匹配的话需要(j >= 2)f[i][j] = f[i][j] | f[i][j - 2]; 匹配一个字符的话, 需要 (i > 1 && s[i] == p[j - 1] || p[j - 1] == ‘.’)f[i][j] = f[i][j] | f[i-1][j];
11, 盛放最多水的容器, 容器是指两个数之间的间隔为容器的宽度,而高度取决于容器两侧的较小的值,
采用双指针遍历即可, 当left < right时, left++ , 而宽度为(right- left)
17 电话号码的组合 字符串数组的建立, string str[10] = {“”, “” } 主要是采用深度搜索, 对里面的每一个字母进行遍历即可 字符串遍历, dfs(digits, u +1, path+ x);
20, 有效的括号 满足的条件:左右括号数量相等, 左括号的个数要大于右括号的数目, 采用栈的形式,遍历数组,栈中存储他的对立的括号, 不满足的条件是 遍历的过程中,栈为空了,或者栈的首部与遍历的值不相等,或者数组已经遍历完了,而栈不为空。
22 括号生成 采用深度搜索,遵循以上两个条件。
32 最长的有效括号 只包含有效括号, 使用栈的方式存储‘(’,遇到‘)’就出栈,然后判断栈。因为要求长度最好记录一下右括号的索引
31 下一个排列 首先明确一下当前的排列都是12345都是最小值,是一个递增的排列,如果打乱的话,肯定比当前的值要大是一个递减的序列,所以只需要,从尾到头遍历一下即可,应该是递减的,找到第一个不满足递减排序的位置,然后找到改位置,与右边的大于该位置的数交换即可然后将右边的数重新由小到大排序即可。
33 搜索旋转排序数组 先采用二分法找到旋转的节点, 找到节点之后,在采用将目标值与数组的第一个元素进行比较, 看选择哪一段, 然后在按照二分法找到该值
二分法:
int l = 0, r = nums.size() -1;
while(l < r) {
int mid = l + (r - l) / 2;
if(nums[mid] >= nums[0]) r = mid; (< 处不要加等号, 若是l的话mid需要加1, 对于此处的大于等于r= mid即可,若是l的话l = mid即可)
else
l = mid + 1;
}
39 组合总数 (dfs)无重复元素的数组,但里面的每个元素可以选择多次。 只要组合相关的,都可以采用深度搜索, 但是要注意不要超出元素数组的范围,要有判断是否有超出范围的操作,由于每个元素可以重复选择, 可以采用for循环进行列举 (递归的时候有target的话直接-就可以,不需要使用-=)
78 子集 返回该数组中所有可能的子集, 子集中的元素是不确定的, 可以为0可以为1直到等于该数组的元素数。再返回的时候使用的组合 dfs, 与之前的不一样的是, 跟之前不一样的是对于每个数可以选也可以不选,需用两次dfs,一次是选择这个点,还有是不选这个点
79 单词搜索 由于这个单词不一定在哪儿, 需要每个位置都判断下, 应该用排列 即广度搜索bfs, 需用一个变量记录这个字母被搜索过。 一个变量记录和所给单词的字符串数相同, bfs(board, word, u, path)路径, 一般使用数组来表示方向
139 单词拆分 采用字符串哈希来进行单词拆分, 设置一个常量为 const int P = 131, 对于遇到的每个字符进行 h = h * p + C;C 表示给定的单词或者字符串,等到时候查询的时候 直接比对字符串哈希即可。可以用unordered_set[HTML_REMOVED] hash存储, 因为只有一个值
46 全排列 (bfs) 对路径进行赋值即可, 赋值完之后可能会重复使用即时恢复原来的状态。 st 记录状态 , 及时恢复, 注意开数组的时候要记录大小。
200 岛屿数量 初始一个数组,对每个点进行遍历, 看有多少个连续的 1 被包围就行。
单调栈 能解决的问题:
给你一个数可以找到这个数的左边离他最近的比他小的数是多少,
42 接雨水 相邻的三个元素之间要形成一个U型槽,才能接住雨水,可以使用 单调栈的形式,时间复杂度为O(n),里面存储的是柱子的索引遇到降序,就入栈,遇到升序就出栈,并且计算一下此时所接的雨水的量。可以预先定义一下左边柱子的高度。
也可以采用双指针的算法,左边和右边分别算,最后加在一起就可以
84 柱状图中的最大的面积 可以枚举左右边界, 也可以枚举矩形的上边界,以这个上边界看左边小于这个数的第一个数, 然后看一下右边第一个小于这个数的数, 这期间的部分就是, 满足条件的
155 最小栈 设计最小栈的时候 ,对于栈来说是 minst.top() >= val minst.push(val);
85 最大的矩形 怎么枚举所有的方案 上一个题是找到矩形最大的柱子 可以枚举下边界, 然后在枚举每个格子 向上数有多少个1 就是找到一个最大的矩形, 以下边界为 数组,
对于上面有多少个1的话, 可以使用 f[i][j] 表示该点是0还是1, 若是0的话, 为0, 否则的话,f[i][j] = 1 + f[i-1][j];
221 最大的正方形 动态规划:
状态表示:f[i][j] 表示所有以(i,j)为右下角并且有‘1’组成的面积的最大值
状态属性:
状态集合:右下角的点收上方, 左上角, 左边的限制, 只有取他们的最小值才可以
故 在判断的时候要判断上一个点是否为1, if(matrix[i- 1][j - 1] == ‘1’)f[i][j] = min(f[i-1][j-1], min(f[i-1][j], f[i][j - 1])) + 1; 两个角可以组成一个正方形
48 旋转图像 只是涉及到对行的操作,不对列进行操作, 顺时针旋转90°, 可以将其转化成先按照斜对角线对换, 然后在左右进行对换,之后即可完成。
49 字母异位词分组 可以使用unordered_map[HTML_REMOVED]> heap; 对于每个单单词中出现的字母的顺序进行排序,当遍历的字母正好有这些字母组成的时候,就插入到second中, 之后遍历second取出里面的单次即可。在某个索引的后面插入多个值可以这样,heap[k].push_back();
既可以插入。
50 最大的子数组和 for循环遍历即可, 用一个变量记录和,当和小于0的时候,将变量变为 0 从0开始计算。
152 乘积最大子数组
对于这个题目, 暴力解法, 枚举起始区间和终止区间, 然后遍历一下 所得的结果找到一个最大值
优化的话, 可以采用 dp思想进行优化 枚举完终点是i-1 的区间的最大值与终点是i的区间的最大值,
对于 区间i- 1,
对于 以区间 i- 1结尾的最大值为 f[i - 1], 以区间 i-1结尾的最小值为 g[i -1];
因为区间中的数有正负区分, 所以要区别对待
当 第 i个数为正数的时候, 分析他的最小值 g[i - 1] * i 最大值 为 f[i - 1] * i; 当a[i] == 0的时候 为 0;
当第 i 个数为负数的时候,分析他的最小值 f[i - 1] * i , 最大值为 g[i - 1] * i;
在记录的时候只需要记录 第f[i - 1], g[i - 1]个数即可, 写的时候可以将这些合并在一起, 总之就是 得到最小值和最大值。
238 除自身以外数组的乘积
对于一个点,可以枚举他左边的乘积, 然后再枚举他右边的乘积, 之后再将这个点左边的相乘, 然后得到的左边的值和右边的值相乘就会得到该值的乘积。
55 跳跃游戏 对于遍历的每一个点,都要记录一下,从前面能够跳过来的最大值,用另外一个值进行比较,如果能跳的最大的值小于当前的坐标的话,就不能否则可以。
56 合并区间 合并所有重叠的区间,按照左端点进行排序 一般上一个区间的右端点大于本区间的左端点, 就是重叠的就要合并在一起, 首先对这些区间进行排序。 附加按照右端点排序的写法
62 不同的路径 从f[0][0] 到 f[n][m] 的路径总共有多少条 采用动态规划做,其中集合表示从上一点到下一点的路径,if(j) f[i][j] +=f[i][j - 1], 不要忘了初始化
64 最小路径和 从f[0][0] 到 f[n][m]的最小路径和, f[i][j] 表示到[i][j] 点的最小的值是多少 if(i ) f[i][j] = min(f[i][j], f[i-1][j] + g[i][j]);
70 爬楼梯 类似于找规律 爬一层需要1中,两层两种解法,滚动和 法即可
72 编辑距离 将Word[1] 转换成Word[2] 采用动态规划 f[i][j] 表示Word1的第i个字母和word2的第j个字母是否相等
要先判别是否有一组单词为0 若 第一个单词为0 则for(int j = 1; j <= m; j++ )f[0][j] = j;
插入 f[i][j] = f[i][j - 1] + 1;
删除 f[i][j] = f[i-1][j] + 1;
若相等f[i][j] = f[i-1][j-1];
替换 f[i][j] = f[i-1][j -1] + 1;
198 打家劫舍 采用动态规划 因为相邻的房间只能取一个,可以对该房间取不取进行分析
f[i] 表示前 i个房间, 并且盗取了第i个房间所能得到的最大利益 f[0] = nums[0];
g[i] 表示不盗取第i个房间, 并且能得到的最大利益 g[0] = 0
状态表示:f[j]表示 前j - 1个房间内可以偷窃的金额;f[i] = g[i - 1] + nums[i -1]
状态转移的时候 g[i] = max(f[i-1], g[i- 1]);
最终得到 max(f(n - 1), g(n - 1));
75 颜色分类 对他进行遍历令他表示一个颜色比如说白色, 在遍历过程中如果不是白色,是红色的话就与j交换,否则与蓝色交换
94 二叉树的中序遍历, 左 中 右
递归法::: 递归结束的条件是 该节点为空if(!root) return;
栈 法:::: 使用栈的时候先根的左节点存到栈中, 之后在存储根的右节点
96 不同的二叉树 能够组合成多少中, 可以采用遍历的方法, 看一下 某个点左边有几种右边有几种解法 ,可以用递归,f[i] += f[j - 1]*[i - j]
98 验证二叉树 将二叉树中的每个值按照左中右的顺序 存放到数组中, 然后 再对数组中的值进行比较 看是否符合性质
101 对称二叉树 左子树的左节点值是否和右节点树的右节点值是否相同, 以及左子树的右节点值是否和右子树的左节点值是否相同
采用递归的话, 判断左右子树是否同时存在, 可以采用这个办法 if(!l || !r) return !l && !r
采用栈法 , 可以使用两个栈,分别存储左子树和右子树, 可以在遍历的时候遍历该树的右节点, cur = stk.top(); stk.pop(), cur = cur->right
用两个节点分别遍历左子树和有右子树
226 翻转二叉树 直接交换就行
102 二叉树的层序遍历
采用 采用对列进行进行, 采用一个数组 记录路径
104 二叉树的最大深度 采用公式就行, max(dfs(root->left, root->right)) + 1;
124二叉树中的最大路径和
用递归的方法,分别找到左子树的最大值和右子树的最大值left = max(0, dfs(root->?left)), 然后更新一下答案 ans = amx(ans, root->val + left + right0), 和递归的答案 root->val + max(left, right)
114 二叉树展开为链表
找规律, 将整个二叉树变为了一右子树, 可以将其根的右子树加到左节点的右子树后面, 然后将根的左节点放到根的右子树的位置。。。
236 二叉树的最近公共祖先
可以判断 if(root==p || root==q || root==NULL) return root;
看这两个点是否在同一条子树上, 遍历左子树和右子树,如果左右子树都有值得话
如果一个点为一个点的祖先的话, 则公共祖先就是为祖先的点,也就是两个点在一棵树上;
297二叉树的序列化和反序列化
二叉树的序列化 就是将一颗二叉树转化成一系列字符串, 按照左中右的顺序遍历二叉树, 遇到空的话就加入null;
中序遍历 将整数转化为字符串的方法为 to_string(kk);
105 从前序与中序遍历序列构造二叉树
前序遍历 中 左 右
中序遍历 左 中 右
可以在前序遍历中找到分界点, 即中间的点
然后在中序遍历中以该点为分界点 找到左边的值和右边的值
121 买卖股票的最佳时机
选择某一个 数为初值, 开始遍历查看 更新这个初值, 让他为一个最小的值, 然后其他的结果的话,让他为一个最大的值.
309 买卖股票时机含有冷冻时期 这个采用状态机进行描述, 将每种状态抽象为每种状态机, f[i][j] 表示第i天处于第j状态时的最高收益
其中含有:
冷冻期(0),这个可以从冷冻期或者卖出期转化而来 f[i][0] = max(f[i-1][0], f[i-1][2]);
买入期(1),这个必须是当天买入, 或者从冷冻期而来;f[i][1]= max(f[i-1][0] - prices[i], f[i-1][1]);
卖出(2), 这个可以是从买入期而来 f[i][2] = f[i-1][1] + prices[i]
146 LRU缓存 这个题需要记录存储的数据结构以及使用的时间, 由于数据结构有两个类型的数值组成并且要求在o(1)的时间复杂度内完成存储, 故需要使用双链表, 链表的插入和删除都是o(1)的时间的, 对于每个点的话采用unorder_map来存储每个点的值, 每次新的插入都从最左边插入, 右边删除
169 多数 元素 采用一个变量 记录是不是与初始值相同相同而话就加一,否则就减一, 当变量为 0 或小于0的时候, 就将该值变为初始值 同时也要对计数进行初始化
207课程表 拓扑排序 学习一些先修课程,课程顺序使用数组表示的, 必须先修完课程 后面的课程, 然后在修前面的课程;
可以转化成图论,判断该图中是否有环人,如果有的话就是不成立的。就不能修完, 否则可以修完
先找出每一个点的出度的边, 找出将出度的变为0 的点进入对列中, 然后遍历对列, 记录出度为0 的边数, 然后一边遍历一边将出度为0 的边找出来, 看最后边数是不是 给定的数目, 如果是的话则说明成功,否则失败。
208 实现前缀树 Trie树 的结构, 这是一个插入字符串的, 所以对于每个节点 , 都应该维护26个空子节点 所以把它建成数组的形式, Node son[26];
bool is_end;
Node() {
for(int i = 0; i < 26; i++) {
son[i] = NULL;
is_end = false;
}
} root; trie树就是一边遍历一边建立新的节点
TOP k 问题
215 求数组中的第 k 个最大的元素, 采用优先队列, 一般默认为大根堆, 此题采用的是小根堆, 先向队列中存入k个数, 然后 之后与堆顶进行比较, 若是堆顶小的话将他置换出
239 滑动窗口最大值
滑动窗口 类似于 queue 队列 这个队列的一个特点是 先进先出, 进的快, 出的快
要维护队列里的一个单调递增的数列, 就是左边是出口的话, 左边的数小,右边的数大, 则左边的数就会永无出头之日,
维护队列的数的数目的个数要为给定的值, 判断的的话, 队列里面的数存的是索引, 当 右边的数 - 左边的数 >= k 的话,说明里面的数有多个, 多余 k 了, 就要删除多余的数
当if(i >= k - 1) 就向容器里面添加数据
279 完全平方数
给你一个整数n, 返回和 为n 的完全平方数的最少数量,所以建立动态数组的时候, 一定要对每个值赋予一个较大的初值
采用动态规划
状态属性: 返回和为 n 的最小数量 ,
状态集合: f[i] 表示平方和能够凑出 i的方案数;
状态表示: f[i] = min(f[i], f[j - i * i] + 1);
283 移动零 将他们遍历一下, 对于是零的位置 填充下一个数, 最后通过 数组的大小与原数组进行比较看看如果小的话, 就用零填充。
312 戳气球:
难度:
这个数组的长度是在不断减小的,都是选择两边的数字相乘
并且每次都要选择数字最小的戳掉
明显是在一个区间内, 故应在使用区间dp
区间f[i][j] 表示从i 到j区间内和的最大值
for(Int len = 3; len < = n + 2; len) {
区间左边端点
for(int i= 0; i+ len- 1 < n + 2; i) {
区间右端点 int j = i + len - 1;
for(int k = i + 1; k < j; k++) {
f[i][j] = max(f[i][j], f[oi][k] + f[k][j] + a[i] * a[k] * a[j]);
}
}
}
399 除法求值
对于unordered_map<>可以用作一个二维数组,
406 身高重建队列
写的是一个谓词。
static bool cmp(vector[HTML_REMOVED]&a, vector[HTML_REMOVED]& b) {
if(a[0] == b[0]) return a[1] < b[1];
return a[0] > b[0];
}
首先按照身高由大到小进行排列,当身高相等的时候在按照前面有几个人在进行排列。
由于有随时的插入可以采用list链表来构建, 每个节点表示一个vector.每次寻找位置的时候采用迭代器的方式找到位置并且直接插入并且初始化即可。
node.insert(ite, vect) 表示在这个位置插入这个元素。
437 路径总和III 可以采用unordered_map[HTML_REMOVED] 记录一下之前搜索过的点, 当然数组也是可以记录的。对于树来说,可以用数组来记录当前数下有没有值, 对于数组来说,可以用vector[HTML_REMOVED]来记录, 也可以用哈希表来存储。
560 和