-
反转链接
创建表头,逐个反转 -
链表内指定区间反转
先移动到至于区域开头,逐个反转到区域结尾 -
链表中的节点每k个一组翻转
- 模拟法
构建了reverse函数对于[begin,end]内的节点进行翻转,由于翻转过程后,前一个节点和后一个节点会对该段链表的链接丢失,所以要先记录前后节点,翻转后重新链接上。 - 栈
每k装入栈取出重新链接
- 模拟法
-
合并两个排序的链表
- 迭代法
一般创建单链表,都会设一个虚拟头结点,也叫哨兵,因为这样每一个结点都有一个前驱结点。两个链表头中小的插入哨兵后面,直到有一方全部插入结束,另一方的头节点插在最后。
- 迭代法
-
合并k个已排序的链表
-
顺序排序
核心语句:return mergeTwoLists(mergeKLists(lists, begin, mid), mergeKLists(lists, mid + 1, end));
其他与第四题差不多
-
-
判断链表中是否有环
- 快慢指针
两个指针都指向链表头,一个走一步,另一个走两步,如果相遇则代表有环 - 哈希表
构建哈希表,即可查阅是否经历过该节点
- 快慢指针
-
链表中环的入口节点
- 快慢指针
与第六题不同的是,两个指针相遇后,将慢指针重新移到链表头,之后两个指针都走一步,相遇点就是环的入口。 - 哈希表
构建哈希表,第一次重复的节点就是起点
- 快慢指针
-
链表中倒数最后k个节点
- 双指针
先让快指针走k - 1步,注意原长度可能没有k
- 双指针
-
删除链表的倒数第n个节点
- 双指针
和上一题唯一的差别是,删除第n个节点,就需要多一个前置节点用于链表的连接。
- 双指针
-
两个链表的第一个公共结点
- 路线交换
A走完走B,B走完走A,相遇的时候就是第一个公共结点 - 哈希表
记录完A的路线,B中第一个重复的结点就是第一个公共结点
- 路线交换
-
链表相加(二)
- 栈
两个栈分别存放两个链表的各个结点,实现从右往左相加,需要注意的地方是进位问题。同时多两个循环处理其中一个链表多余的位数。其中为了节省空间,可以在第一链表上进行数值操作。 - 链表反转
两个链表翻转后依次相加,道理上和用栈一样
- 栈
-
单链表的排序
- 归并排序
快慢指针到链表中点后进行切割,然后分别对链表进行递归,然后两两进行合并。 - 利用辅助数组
遍历存储各个结点的值,排序后新建一个链表
- 归并排序
-
判断一个链表是否为回文结构
- 链表反转
将链表后半部分进行反转,然后前后依次进行比较。期间同样是利用快慢指针找到后半截链表的开头。 - 利用辅助数组
遍历存储各个结点的值,首尾向中间进行比对
- 链表反转
-
链表的奇偶重排
- 双指针
一个指向head,一个指向head→next,然后互相指向另一者的next
- 双指针
-
删除有序链表中重复的元素
- 双指针
前后同时移动删除即可 - 延伸——如果是无序链表的话
使用哈希表进行存储,出现过就跳过
- 双指针
-
删除链表中的所有重复出现的元素
- 双指针
和上一题相差不大,多一个哨兵记录重复部分的前一个结点,并且过程中哨兵要不断更新
- 双指针
-
二分查找-I
- 最直接的二分查找
-
二维数组中的查找
- 从右上开始找,比目标值大向左,比目标值小向下
-
寻找峰值(单边峰值也算峰值)
- 二分查找
首先查看首尾是否为峰值,不是的话开始二分查找,比右边小就往右找,否则往左找(往左找保留自身是峰值的可能) - 找最大值
直接比较找出最大值
- 二分查找
-
数组中的逆序对
- 归并排序
很经典的题,总体是归并排序,两两比较合并的期间,左边比右边大,那么左边的后面都会比右边的这个数大,增加的逆序对数量就是左边这个数到最后一共的数字个数
- 归并排序
-
旋转数组的最小数字
- 二分查找
中间比右边大朝右边走,中间比右边小往左边走(保留自身是最小的可能)
- 二分查找
-
比较版本号
- 双指针
先在两个版本号的末尾补上’.'保证格式一致,分别取出两个版本号的一部分进行比较
- 双指针
-
二叉树的前序遍历
ans.push_back(root -> val); preorderTraversal(root -> left); preorderTraversal(root -> right);
-
二叉树的中序遍历
preorderTraversal(root -> left); ans.push_back(root -> val); preorderTraversal(root -> right);
-
二叉树的后序遍历
preorderTraversal(root -> left); preorderTraversal(root -> right); ans.push_back(root -> val);
-
求二叉树的层序遍历
- 队列
队列存储每一层的结点,然后遍历结点存储下一层结点,知道遍历所有结点。
- 队列
-
按之字形顺序打印二叉树
- 队列
和上一题的方法一样,但是偶数层获取的值进行reverse
- 队列
-
二叉树的最大深度
- 深度优先搜索
DFS存储深度,记录最大深度
- 深度优先搜索
-
二叉树中和为某一值的路径
- 深度优先搜索
DFS记录路径和,如果是叶子结点,进行比较
- 深度优先搜索
-
二叉搜索树与双向链表
- 中序遍历
动态数组存储二叉搜索树中序遍历存储树的结点,然后更新每个结点的前后结点
- 中序遍历
-
对称的二叉树
- 遍历
isPart(left -> left, right -> right) && isPart(left -> right, right -> left); }
-
合并二叉树
- 递归
- 递归合并t1、t2的左右节点。
- 两个节点的值求和并赋给t1
- 递归
-
二叉树的镜像
- 递归
从下往上逐个交换左右结点
- 递归
-
判断是不是二叉搜索树
- 中序遍历
如果是二叉搜索树那么中序遍历一定是升序的
- 中序遍历
-
判断是不是完全二叉树
- 广度优先搜索
层次遍历,左边的节点(上一行最后一个节点)不存在,但下一个节点存在,则肯定不是完全二叉树。用pre存储上一个结点
- 广度优先搜索
-
判断是不是平衡二叉树
- DFS递归
全局变量flag记录是否存在不平衡
int leftLevel = DFS(root -> left) + 1; int rightLevel = DFS(root -> right) + 1; if(abs(leftLevel - rightLevel) > 1) flag = false;
- DFS递归
-
二叉搜索树的最近公共祖先
- 二叉搜索树
如果是公共祖先,那么肯定比其中一个大,比另一个小,或者等于其中一个
- 二叉搜索树
-
二叉树的最近公共祖先
- DFS
l || r || (root->val == o1 || root->val == o2);
其中l是左子树是否找得到结点,r是右子树能否找到结点
- DFS
-
序列化二叉树
- 先序遍历
假设序列化的结果为字符串 str, 初始str = “”.根据要求,遇到nullptr节点,str += “#” 遇到非空节点,str += “val” + “!”; 假设val为3, 就是 str += “3!”
反序列化的结果,就是根据先序遍历,再重建二叉树即可。
- 先序遍历
-
重建二叉树
- 递归
vector<int> vinLeft(vin.begin(), vin.begin() + rootPos); vector<int> vinRight(vin.begin() + rootPos + 1, vin.end()); vector<int> preLeft(pre.begin() + 1, pre.begin() + 1 + vinLeft.size()); vector<int> preRight(pre.begin() + 1 + vinLeft.size(), pre.end()); root -> left = reConstructBinaryTree(preLeft, vinLeft); root -> right = reConstructBinaryTree(preRight, vinRight);
-
输出二叉树的右视图
- 层次遍历
层次遍历输出每层最后一个结点,本题主要是还要重建二叉树
- 层次遍历
-
用两个栈实现队列
-
栈的基础知识
push操作就直接往stack1中push, pop操作需要分类一下:如果stack2为空,那么需要将stack1中的数据转移到stack2中,然后在对stack2进行pop,如果stack2不为空,直接pop就ok。
-
-
包含min函数的栈
- vector数组
利用vector数组存储每次更新后的最小值
- vector数组
-
有效括号序列
-
辅助栈
1)引入辅助栈stk,遍历字符串,每次遇到’(’,’{’,’[‘字符的时候将字符入栈stk
2)当遇到’)’,’}’,’]'字符的时候,则检查栈是否空,且顶元素是否为匹配元素(如{和}匹配等),如果栈空或者栈顶元素不为匹配元素则括号序列不合法
3)当栈非空,且栈顶元素为匹配元素,则栈顶元素出栈。
4)循环匹配字符串,直到每次字符处理完
5)检查栈stk是否为空,栈为空则序列合法,否则不合法(当括号以正确顺序关闭时则最后的栈为空)
-
-
滑动窗口的最大值
- 双向队列
用双向队列存储最大值的序号,而不是最大值,这样可以根据最大值的序号判断是否要删除该最大值,每次添加最大值时,删除末尾比当前添加的值要小的
- 双向队列
-
最小的K个数
- 最大堆
构建最大堆,先添加k个数进最大堆,之后如果有比堆顶值小的,那么删除堆顶,添加新值
- 最大堆
-
寻找第K大
-
快速排序
quick_sort(a, begin, right - 1);
quick_sort(a, right + 1, end);
-
-
数据流中的中位数
-
当我们已经添加的数据个数为奇数个时,小顶堆的数据个数比大顶堆多一个,此时若要再添加一个数据,必定添加在小顶堆
Rmin
上,**但是我们不知道当前添加的数是否大于中位数,故要先可插入Lmax
然后将Lmax
中的最大数放入Rmin
中,同时删除Lmax
中的最大数,即删除堆顶 -
当我们已经添加的数据个数为偶数个时,小顶堆的数据个数等于大顶堆,此时若要再添加一个数据,必定添加在大顶堆
Lmax
上,添加的手段和前者相同。这样我们便将数据进行了分隔,然后通过O(1)的复杂度访问堆顶即可计算出中位数。
-
-
没写出来
-
两数之和
- 哈希表
记得复习一下unordered_set和unordered_map
- 哈希表
-
数组中出现次数超过一半的数字
- 经典摩尔计数法
标记一个数字,记录它的出现次数,如果有不同的数字出现,那么次数减一,如果次数减为0,就标记下一个数字
- 经典摩尔计数法
-
数组中只出现一次的两个数字
- 位运算
第一步、全部数字进行异或运算得到的结果相当于两个数字的异或结果
第二步、找出第一步中异或结果中是1的最低位
第三步、从全部数字中找出同一位上是1的数进行异或,就能得到其中一个数字
第四步、将这个数字与第一步中的异或结果再次异或,剩下的就是另一个数字
- 位运算
-
缺失的第一个正整数
- 负数置为n + 1避免干扰,相当于目前数组内全为正数
如果num出现过了,将对应序号内的值置为负数
那么循环结束只剩下没有出现过的序号内会为正数
- 负数置为n + 1避免干扰,相当于目前数组内全为正数
-
三数之和
- 双指针
很正常的一道双指针,注意特殊情况
- 双指针
-
没有重复项数字的全排列
- 回溯+unordered_map
算是毕竟经典的回溯,但是一般回溯只会更新数组中的值,这里还需要更新hash表里的对应数字。哈希表中标记该数字是否出现。用的时候置为true,用完置为false
- 回溯+unordered_map
-
有重复项数字的全排列
- 回溯+map
上一题不会重复,所以标记数字是否出现就行,但这题会重复,所以是存储数字出现的个数。并且每次遍历的不是原数组了,而是map。
- 回溯+map
-
岛屿数量
- 递归
遍历二维数组,找到’1’的时候开始递归并且岛屿数量加一,向四周寻找同样为’1’的字符,全部替换成0。
- 递归
-
字符串的排列
- 回溯+map(如果字符串不需要按字典序排序也可以用unordered_map)
首先遍历原字符串,map中存储每个字符出现的次数。然后遍历map,使用该字符则次数减一。
- 回溯+map(如果字符串不需要按字典序排序也可以用unordered_map)
-
N皇后问题
- 回溯+unordered_set
特点是,需要3个set,分别存储列,正斜线,反斜线上的占用情况
- 回溯+unordered_set
-
括号生成
- 递归
已经左括号数量一定大于等于右括号,所以当左括号数量小于总共允许的左括号数量是,加一个左括号递归,同时,右括号数量小于左括号时,加一个右括号递归
- 递归
-
矩阵最长递增路径
- 递归
除了边界判断情况外,设置了一个pre,存储上一个的数组,如果当前数字小于等于pre,则终止递归,小于是因为不满足递归条件,等于除不满足以外,还避免了往回走
- 递归
-
斐波那契数列
- 动态规划
-
跳台阶
- 动态规划
只不过第二个数是2
- 动态规划
-
最小花费爬楼梯
- 动态规划
跳台阶的姊妹版- 状态转移: 每次到一个台阶,只有两种情况,要么是它前一级台阶向上一步,要么是它前两级的台阶向上两步,因为在前面的台阶花费我们都得到了,因此每次更新最小值即可,转移方程为:dp[i]=min(dp[i−1]+cost[i−1],dp[i−2]+cost[i−2])dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])dp[i]=min(dp[i−1]+cost[i−1],dp[i−2]+cost[i−2])。
- 动态规划
-
最长公共子序列
- 动态规划
属于是不开题解做不来的题目,创建dp表,第一行第一列扩出来填入预制位0。剩下的对应行和列进行比较,dp[i][j]代表从这个往前最多有dp[i][j]个数相等,如果s1[i -1]和s2[j - 1]相等的话,那么取dp[i - 1][j - 1] + 1,不能取max(dp[i - 1][j], dp[i][j - 1]),因为不能同时取相同位置。
接下来从表右下角开始,如果左和上都相等,移动到左上角,不相等,则往数值大的走。遇到s1[i -1]和s2[j - 1]的地方,将该字符加入字符串。
- 动态规划
-
最长公共子串
- 动态规划
其实就是暴力遍历的优化,i走字符串1,j走字符串2,找到相同的时候标记i和j,然后同时往下走,不相等的时候比较长度,如果大于最大长度就记录长度和字符串
- 动态规划
-
不同路径的数目(一)
- 动态规划
第一行和第一列置为1,dp[1][1]开始计算,dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
- 动态规划
-
矩阵的最小路径和
- 动态规划
初始化第一行第一列,然后dp[1][1]开始计算,dp[i][j] = min(dp[i - 1][j] + dp[i][j - 1]) + matrix[i][j]
- 动态规划
-
把数字翻译成字符串
- 动态规划
只要注意边界情况已经几种含零的特殊情况就可以了
- 动态规划
-
兑换零钱(一)
- 动态规划
外层循环是硬币数值,内层循环从硬币数值到总金额dp[i] = min(dp[i], dp[i - num] + 1);
- 动态规划
-
最长上升子序列(一)
- 动态规划
for(int i = 0; i < size; ++i) { for(int j = 0; j <= i; ++j) { if(arr[i] > arr[j]) { 只要前面某个数小于当前数,则要么长度在之前基础上加1,要么保持不变,取较大者 dp[i] = max(dp[i], dp[j] + 1); if(dp[i] > maxLen)//比当前最大长度更长,则更新长度 maxLen = dp[i]; } } }
-
连续子数组的最大和
- 动态规划
array[i] = max(array[i - 1] + array[i], array[i]);
- 动态规划
-
最长回文子串
- 中心扩散
分两种情况,奇数长度和偶数长度。奇数长度遍历数组,每个都慢慢往外扩一个长度,直到不相等。偶数长度采用i, i + 1,两者相同时,同理慢慢往外扩
- 中心扩散
-
数字字符串转化成IP地址
- 回溯
isValid(int left, int right, string s)//检测[left, right]是否有效
回溯有三种情况,长度为1,长度为2,长度为3
- 回溯
-
编辑距离(一)
- 动态规划
if(str1[i - 1] == str2[j - 1]) [i][j] = dp[i - 1][j - 1]; else dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1;
看了题解,相等的话,不需要进行操作,但如果不相等,肯定要插入、删除、修改选一个其中步骤最少的,然后加一
-
正则表达式匹配
不会,考到就寄 -
最长的括号子串
- 动态规划
for(int i = 1; i < len; ++i) { if(s[i] == ')') { //左括号默认都为0 if(s[i - 1] == '(') { dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2; } else if(i - dp[i - 1] > 0 && s[i - dp[i - 1] - 1] == '(') { dp[i] = dp[i - 1] + ((i - dp[i - 1]) >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2; } maxLen = max(maxLen, dp[i]); } }
左括号都默认置为0,只有遇到右括号时进行操作,如果上一个字符是左括号,那么长度至少是2,具体还要看再前一个字符存储的长度。如果上一个是右括号,并且对应的位置(根据dp[i - 1]去找)是左括号,那么长度至少是dp[i - 1] + 2,具体还要看左括号的左边是否还有长度。
-
打家劫舍(一)
- 动态规划
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
- 动态规划
-
打家劫舍(二)
- 动态规划
分两种情况,从第一间开始偷,或者从第二间开始偷。
从第一间开始偷dp1[i] = max(dp1[i - 1], dp1[i - 2] + nums[i]);
从第二件开始偷dp2[i] = max(dp2[i - 1], dp2[i - 2] + nums[i]);
- 动态规划
-
买卖股票的最好时机(一)(只能买卖一次
- 动态规划
int temp = pre[0];//第i - 1天买股票需要的钱 pre[0] = max(pre[0], -prices[i]);//第i天买股票需要的钱 pre[1] = max(pre[1], temp + prices[i]);//第i天卖股票后拥有的钱
-
买卖股票的最好时机(二)(可以多次购买
- 动态规划
int temp = pre[0];//第i - 1天买股票剩下的钱 pre[0] = max(pre[0], pre[1] - prices[i]);//第i天买股票剩下的钱 pre[1] = max(pre[1], temp + prices[i]);//第i天买股票后拥有的钱
-
买卖股票的最好时机(三)(只能买卖两次
- 动态规划
int temp = pre[0], temp2 = pre[1], temp3 = pre[2]; pre[0] = max(pre[0], -prices[i]); //第i天第一次买入股票 pre[1] = max(pre[1], temp + prices[i]); //第i天第一次卖出股票 pre[2] = max(pre[2], temp2 - prices[i]); //第i天第二次买入股票 pre[3] = max(pre[3], temp3 + prices[i]); //第i天第二次卖出股票
-
字符串变形
- 两次翻转
第一次反转对其中每个字符串进行翻转,第二次对整体进行反转,挑其中一次进行大小写转换就可以了
- 两次翻转
-
最长公共前缀
- 逐个比较
-
验证IP地址
- 分开算细心点就可以了,开始要在末尾补上对应的符号进行判断。
-
大数加法
- 先反转两个字符串,然后相加,注意进位,然后再反转回来
-
合并两个有序的数组
- 从头部开始合并
- 从尾部开始合并
-
判断是否为回文字符串
- 双指针
-
合并区间
- 双指针
先按照首区间进行升序排序,再拿intervals[i].start 和 ans.back().end进行比较
if(intervals[i].start <= ans.back().end) { ans.back().end = max(intervals[i].end, ans.back().end); } else { ans.push_back(intervals[i]); }
- 双指针
-
最小覆盖子串
- 双指针
string minWindow(string S, string T) { // write code here vector<int> hash(128, 0); for(auto ch : T) { ++hash[ch]; } int need = T.size(); int left = 0, right = 0; int minLen = INT_MAX; int minStart = -1; int size = S.size(); while(right < size) { if(hash[S[right]] > 0)//如果找到符合的字符,需求减一 --need; --hash[S[right]];//加入到滑动窗口,hash表减一 ++right;//滑动窗口右扩 while(need == 0) {//如果滑动窗口中字符串满足所有需求 if(right - left < minLen) {//如果比最小长度要小,则更新 minStart = left; minLen = right - left; } if(++hash[S[left]] > 0) {//最重要的地方,如果滑动窗口内字符串缺少该字符就不满足要求,那么需求要加一 ++need; } ++left;//滑动窗口左边缩减 } } return minStart == -1 ? "" : S.substr(minStart, minLen); }
-
反转字符串
- 双指针
啪的一下就反转好了
- 双指针
-
最长无重复子数组
- 双指针,滑动窗口
用哈希来判断有没有重复,重复的话滑动窗口一直左缩直到不重复为止,不重复了判断长度是否超过最大长度,超过则记录,滑动窗口右扩。
- 双指针,滑动窗口
-
盛水最多的容器
- 双指针
从两边往中间走,哪个小走哪个
- 双指针
-
接雨水问题
- 双指针
每次都动小的那个,因为小的这边的水位有大的包围,一定不会更低,如果比较小值大,则更新较小值,否则添加水量
- 双指针
-
分糖果问题
- 贪心
第一遍正序遍历,比前面大的都是前面的糖果数加一
第二遍逆序遍历,比后面的大,则糖果数要么原本就大,要么是后面的糖果数加一
- 贪心
-
主持人调度(二)
- 最小堆
首先将每个主持任务按开始时间和结束时间升序进行二次排序
(1) 如果小根堆的堆顶小于等于当前任务的开始时间,说明做堆顶任务的主持人此时是空闲的,可以把当前主持任务揽下来,因此不需要增加主持人,把堆顶任务弹出,当前任务入堆;
(2) 如果小根堆的堆顶大于当前任务的开始时间,这就说明这个最早能“收工”的主持人都没空接下当前任务,堆顶下面任务的主持人当然就更没空了,所以此时不出堆,新任务的结束时间直接入堆,表示需要增加一个主持人。
- 最小堆
-
旋转数组
- 翻转三次
翻转前半部分,翻转后半部分,全部翻转
- 翻转三次
-
螺旋矩阵
- 按圈输出
真的做吐了,不想做了,就是很无脑的按圈遍历,注意边界情况
- 按圈输出
-
顺时针旋转矩阵
- 线性代数题
temp[j][n-1-i] = mat[i][j];
- 线性代数题
牛客网模板速刷TOP101——个人笔记
最新推荐文章于 2023-04-14 17:31:12 发布