为了一个面试,挑战三天速通算法(不是
因为时间有限,只看高频题目,共计112道。(忽略其中的Hard)
三天时间,一天38道,早中晚各12道,每道题控制在10分钟内。
......阿门。
一.链表篇(13)
LeetCode19: 删除链表的第K个节点
- 技巧:双指针
- 技巧::添加dummy结点
ListNode* dummyHead = new ListNode(0);
- 思路:双重指针间的距离为n,后指针指到nullptr时,删除前指针指向节点。
牛客Top200高频: 删除有序链表中重复出现的元素
- 思路:无需双指针,判断前后两个节点是否相等,相等则删除后继续判断,不相等才next。
LeetCode25: K个一组翻转链表 Hard Pass
剑指 Offer II 024. 反转链表
- 技巧:存三个节点
- 思路:存三个节点后,cur->next=pre; ,后移动指针。
LeetCode24: 两两交换链表中的节点
- 技巧:添加dummy结点
- 技巧:存三个节点(不需要四个
- 思路:递归 高逼格 没时间看
- 思路:迭代 注意顺序
temp.next = node2 node1.next = node2.next node2.next = node1
LeetCode142: 环形链表II
- 技巧:双指针(快慢指针)
- 思路:每走一次,fast指针比slow快1,若有环,两指针一定相遇。
LeetCode160: 相交链表
- 题外话:这个题居然是Easy哎(周迅脸
- 提示:走过你来时的路~~~
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *A = headA, *B = headB;
while (A != B) {
A = A != nullptr ? A->next : headB;
B = B != nullptr ? B->next : headA;
}
return A;
}
};
作者:Krahets
链接:https://leetcode.cn/problems/intersection-of-two-linked-lists/solutions/12624/intersection-of-two-linked-lists-shuang-zhi-zhen-l/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
LeetCode234: 回文链表
- 技巧:快慢指针
- 思路1:复制链表值到数组列表中,利用双指针判回文。
- 思路2:反转后半部分列表
LeetCode206: 翻转链表 == 剑指 Offer II 024. 反转链表
LeetCode445: 两数相加II
- 技巧:巧妙使用已经解决的题目。
- 思路1:翻转链表
- 思路2:利用栈,先压后构造新链表
LeetCode21: 合并两个有序链表
- 思路1:递归
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (l1 == NULL) {
return l2;
}
if (l2 == NULL) {
return l1;
}
if (l1->val <= l2->val) {
l1->next = mergeTwoLists(l1->next, l2);
return l1;
}
l2->next = mergeTwoLists(l1, l2->next);
return l2;
}
};
作者:腐烂的橘子
链接:https://leetcode.cn/problems/merge-two-sorted-lists/solutions/103891/yi-kan-jiu-hui-yi-xie-jiu-fei-xiang-jie-di-gui-by-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
- 思路2:设定一个哨兵节点,不断调整其指针pre。
LeetCode23: 合并K个排序链表 Hard Pass
LeetCode148: 排序链表
- 技巧:自顶向下
-
找到链表的中点,以中点为分界,将链表拆分成两个子链表。寻找链表的中点可以使用快慢指针的做法,快指针每次移动 2 步,慢指针每次移动 1 步,当快指针到达链表末尾时,慢指针指向的链表节点即为链表的中点。
-
对两个子链表分别排序。
-
将两个排序后的子链表合并,得到完整的排序后的链表。可以使用「21. 合并两个有序链表」的做法,将两个有序的子链表进行合并。
return merge(sortList(head, mid), sortList(mid, tail));
LeetCode143: 重排链表
- 思路1:线性表,利用线性表可以下标访问的特点。
vector<ListNode *> vec; ListNode *node = head; while (node != nullptr) { vec.emplace_back(node); node = node->next; }
- 思路2:寻找链表中点 + 链表逆序 + 合并链表。
面试题16.25: LRU缓存 太难 Pass
二.位运算&递归(2)
LeetCode143: 只出现一次的数字
- 技巧:异或
LeetCode22: 括号生成
- 技巧:DFS,具体来说是回溯,回溯=DFS+状态重置
- 思路:如果左括号数量不大于 n,我们可以放一个左括号。如果右括号数量小于左括号的数量,我们可以放一个右括号。
class Solution { void backtrack(vector<string>& ans, string& cur, int open, int close, int n) { if (cur.size() == n * 2) { ans.push_back(cur); return; } if (open < n) { cur.push_back('('); backtrack(ans, cur, open + 1, close, n); cur.pop_back(); } if (close < open) { cur.push_back(')'); backtrack(ans, cur, open, close + 1, n); cur.pop_back(); } } public: vector<string> generateParenthesis(int n) { vector<string> result; string current; backtrack(result, current, 0, 0, n); return result; } }; 作者:力扣官方题解 链接:https://leetcode.cn/problems/generate-parentheses/solutions/192912/gua-hao-sheng-cheng-by-leetcode-solution/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
三.字符串(3)
剑指offer 67: 把字符串转换成整数
- 提示:细节题,难点主要在越界判断上。
class Solution {
public:
int strToInt(string str) {
bool sign = true; //默认为正数
//先舍弃开头可能存在的空格
int i = 0;
while(i < str.size() && str[i] == ' ') i++;
//接着判断首个字符是否为正负号
if(str[i] == '-') {
sign = false; //该字符串片段为负数
i++; //移至下一个字符接着判断
}
else if(str[i] == '+') i++; //如果首个字符为‘+’则sign已经默认为true而无须更改,直接移动到下一位即可
//下面开始对非正负符号位进行判断
if(str[i] < '0' || str[i] > '9') return 0; //如果第一个正负号字符后的首个字符就不是数字字符(也可能第一个字符就不是正负号),那么直接返回0
int res = 0; //这里res用的int型,需要更加仔细考虑边界情况,但如果用long的话可以省去一些麻烦
int num; //用来单独存储单个字符转换而成的数字
int border = INT_MAX / 10; //用来验证计算结果是否溢出int范围的数据
while(i < str.size()){
if(str[i] < '0' || str[i] > '9') break; //遇到非数字字符则返回已经计算的res结果
if(res > border || res == border && str[i] > '7') //注意这句话要放在字符转换前,因为需要验证的位数比实际值的位数要少一位
//这里比较巧妙的地方在于 1. 用低于int型数据长度一位的数据border判断了超过int型数据长度的值 2. 将超过最大值和低于最小值的情况都包括了
return sign == true ? INT_MAX : INT_MIN;
//开始对数字字符进行转换
num = str[i] - '0';
res = res * 10 + num;
i++;
}
//最后结果根据符号添加正负号
return sign == true ? res : -res;
}
};
LeetCode76: 最小覆盖子串 Hard Pass
LeetCode3: 无重复字符的最长子串
- 技巧:利用vector<char>实现滑动窗口。
- 什么是滑动窗口?其实就是一个队列,比如 abcabcbb。现在,队列 abc 满足题目要求。而当a再次入队,队列就变成了 abca,不满足题目要求。所以,我们需要移动这个队列,把队列的左边的元素移出。
class Solution { public: int lengthOfLongestSubstring(string s) { vector<char> unset; int maxLen = 0; for(int i = 0; i < s.size(); ++i){ vector<char>::iterator it = find(unset.begin(),unset.end(),s[i]); if(it != unset.end() ) unset.erase(unset.begin(),it+1); unset.push_back(s[i]); maxLen = max(maxLen,int(unset.size())); } return maxLen; } };
四.栈和队列(6)
LeetCode20: 有效的括号
- 思路:栈,重点是如何实现。
class Solution
{
public:
bool isValid(string s)
{
stack<char>S;
for (char ch : s)
{
//如果当前栈为空,但是ch是右括号应该返回false
if (S.empty() && ch == ')' || S.empty() && ch == ']' || S.empty() && ch == '}')
return false;
//如果ch是左括号,应该将其入栈,等待匹配
if (ch == '(' || ch == '[' || ch == '{')
S.push(ch);
else
{
//如果输入的右括号和当前栈顶元素匹配,那么弹出栈顶元素
if (S.top() == '(' && ch == ')' || S.top() == '[' && ch == ']' || S.top() == '{' && ch == '}')
S.pop();
//当前栈顶元素与ch不匹配,直接返回false
else
return false;
}
}
//如果栈为空,返回true
if (S.empty())
return true;
else
return false;
}
};
LeetCode84: 柱状图中的最大矩形 单调栈 但Hard Pass
LeetCode42: 接雨水 单调栈 但Hard Pass
......算了,不Pass了,来学习一下单调栈吧。
主要逻辑:围绕栈顶元素。
// 栈不为空且当前柱子高度<栈顶索引对应的柱子高度
// 说明栈顶元素的右边界已经确定,就是索引为i的柱子(不含)
// 此时将栈顶元素出栈,栈顶矩形左边界为栈顶元素下面的索引(首个小于栈顶)
class Solution {
public int largestRectangleArea(int[] heights) {
/*
只做单调栈思路:参考"编程狂想曲"思路比较好理解
1.核心思想:求每条柱子可以向左右延伸的长度->矩形最大宽度;矩形的高->柱子的高度
计算以每一根柱子高度为高的矩形面积,维护面积最大值
2.朴素的想法:遍历每一根柱子的高度然后向两边进行扩散找到最大宽度
3.单调栈优化:因为最终的目的是寻找对应柱子height[i]右边首个严格小于height[i]的柱子height[r]
左边同理找到首个严格小于height[i]的柱子height[l]
维护一个单调递增栈(栈底->栈顶),那么每当遇到新加入的元素<栈顶便可以确定栈顶柱子右边界
而栈顶柱子左边界就是栈顶柱子下面的柱子(<栈顶柱子)
左右边界确定以后就可以进行面积计算与维护最大面积
时间复杂度:O(N),空间复杂度:O(N)
*/
// 引入哨兵
// 哨兵的作用是 将最后的元素出栈计算面积 以及 将开头的元素顺利入栈
// len为引入哨兵后的数组长度
int len = heights.length + 2;
int[] newHeight = new int[len];
newHeight[0] = newHeight[len - 1] = 0;
// [1,2,3]->[0,1,2,3,0]
for(int i = 1; i < len - 1; i++) {
newHeight[i] = heights[i - 1];
}
// 单调递增栈:存储每个柱子的索引,使得这些索引对应的柱子高度单调递增
Stack<Integer> stack = new Stack<>();
// 最大矩形面积
int res = 0;
// 遍历哨兵数组
for(int i = 0; i < len; i++) {
// 栈不为空且当前柱子高度<栈顶索引对应的柱子高度
// 说明栈顶元素的右边界已经确定,就是索引为i的柱子(不含)
// 此时将栈顶元素出栈,栈顶矩形左边界为栈顶元素下面的索引(首个小于栈顶)
while(!stack.empty() && newHeight[i] < newHeight[stack.peek()]) {
// 栈顶索引出栈并记录
int pop = stack.pop();
// 计算出栈顶元素矩形的宽度如(0,1,2)->[1,2,1],两边都不包含
// 因此右索引-左索引-1=矩形宽度
int w = i - stack.peek() - 1;
// 栈顶索引对应的柱子高度就是矩形的高度
int h = newHeight[pop];
// 计算矩形面积
int area = w * h;
// 维护矩形面积最大值
res = Math.max(res, area);
}
// 每当弹出一个索引就计算一个矩形面积
// 直到当前元素>=栈顶元素(或者栈为空)时,栈顶柱子的右边界还没确定
// 因此当前元素索引入栈即可
stack.push(i);
}
return res;
}
}
LeetCode155: 最小栈
- 技巧:同步辅助栈
class MinStack { stack<int> x_stack; stack<int> min_stack; public: MinStack() { min_stack.push(INT_MAX); } void push(int x) { x_stack.push(x); min_stack.push(min(min_stack.top(), x)); } void pop() { x_stack.pop(); min_stack.pop(); } int top() { return x_stack.top(); } int getMin() { return min_stack.top(); } }; 作者:力扣官方题解 链接:https://leetcode.cn/problems/min-stack/solutions/242190/zui-xiao-zhan-by-leetcode-solution/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
LeetCode232: 用栈实现队列
- 技巧:双栈
- 提示:实现in2out函数
class MyQueue { private: stack<int> inStack, outStack; void in2out() { while (!inStack.empty()) { outStack.push(inStack.top()); inStack.pop(); } } public: MyQueue() {} void push(int x) { inStack.push(x); } int pop() { if (outStack.empty()) { in2out(); } int x = outStack.top(); outStack.pop(); return x; } int peek() { if (outStack.empty()) { in2out(); } return outStack.top(); } bool empty() { return inStack.empty() && outStack.empty(); } }; 作者:力扣官方题解 链接:https://leetcode.cn/problems/implement-queue-using-stacks/solutions/632369/yong-zhan-shi-xian-dui-lie-by-leetcode-s-xnb6/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
剑指offer59 - I: 滑动窗口最大值 Hard Pass
参考链接