2022 秋招资料
算法数据结构
螺旋矩阵问题
螺旋矩阵I(将 1~n*m 个数按蛇形方向填入数组中)
记录蛇形矩阵偏移量方法,四个不同方向右下左上,判断什么情况下矩阵遍历完,1.要么出界 2.要么走完
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
int n = matrix.size(), m = matrix[0].size();
int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
vector<int> res;
vector<vector<bool>> vis(n, vector<bool>(m));
for (int i = 0, x = 0, y = 0, d = 0; i < n * m; i ++) {
res.push_back(matrix[x][y]);
vis[x][y] = true;
int nx = x + dir[d][0], ny = y + dir[d][1];
if (nx < 0 || nx >= n || ny < 0 || ny >= m || vis[nx][ny]) {
d = (d + 1) % 4;
nx = x + dir[d][0], ny = y + dir[d][1];
}
x = nx, y = ny;
}
return res;
}
};
螺旋矩阵 II
desc: 给定 n 生成一个包含一到 n^2的矩阵, 按照螺旋矩阵的方式存储
解法:和螺旋矩阵 I 类似,通过偏移量构造
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n, vector<int>(n, 0));
int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
for (int x = 0, y = 0, cnt = 1, d = 0; cnt <= n * n; cnt ++) {
res[x][y] = cnt;
int nx = x + dir[d][0], ny = y + dir[d][1];
if (nx < 0 || nx >= n || ny < 0 || ny >= n || res[nx][ny]) {
d = (d + 1) % 4;
nx = x + dir[d][0], ny = y + dir[d][1];
}
x = nx, y = ny;
}
return res;
}
};
螺旋矩阵 III
desc:给定一个nxm的矩阵,和一个开始走的起点坐标,求螺旋方向走的坐标,并且可以出界, 如下图所示
class Solution {
public:
vector<vector<int>> spiralMatrixIII(int n, int m, int x, int y) {
vector<vector<int>> res;
int tot = n * m;
res.push_back({x, y});
int d = 0;
int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
for (int k = 1; res.size() < tot; k ++) {
for (int i = 0; i < 2 && res.size() < tot; i ++) {
for (int j = 0; j < k && res.size() < tot; j ++) {
int nx = x + dir[d][0], ny = y + dir[d][1];
if (nx >= 0 && nx < n && ny >= 0 && ny < m)
res.push_back({nx, ny});
x = nx, y = ny;
}
d = (d + 1) % 4;
}
}
return res;
}
};
螺旋矩阵 IV
desc: 给定链表从链表中构建一个蛇形矩阵,和 I、II 的构建方式一样
class Solution {
public:
vector<vector<int>> spiralMatrix(int n, int m, ListNode* head) {
vector<vector<int>> res(n, vector<int>(m, -1));
int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
int x = 0, y = 0, d = 0;
for (auto p = head; p != nullptr; p=p->next) {
res[x][y] = p->val;
int nx = x + dir[d][0], ny = y + dir[d][1];
if (nx < 0 || nx >= n || ny < 0 || ny >= m || res[nx][ny] != -1) {
d = (d + 1) % 4;
nx = x + dir[d][0], ny = y + dir[d][1];
}
x = nx, y = ny;
}
return res;
}
};
单链表排序问题
单链表快速排序(时间O(nlogn), 空间(O(logn)))
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oay7AL1b-1662036839548)(/Users/liyan/Library/Application Support/typora-user-images/image-20220831102437989.png)]
链表快排基本思路
- 选定链表头基准元素
- 建立三个子链表left,mid,right
- left存储比基准元素小的节点
- mid 存储和基准元素相等的节点
- right 存储比基准元素大的节点
- 递归的去排序第二个过程
- 最后将 left 拼接 mid 拼接 right
class Solution {
public:
ListNode* getTail(ListNode* head) {
while (head->next) head=head->next;
return head;
}
ListNode* sortList(ListNode* head) {
if (!head || !head->next) return head;
auto left = new ListNode(-1), mid = new ListNode(-1), right = new ListNode(-1);
auto ltail = left, rtail = right, mtail = mid;
int val = head->val;
for (auto p = head; p; p=p->next) {
if (p->val < val) ltail = ltail->next = p;
else if (p->val == val) mtail = mtail->next = p;
else rtail = rtail->next = p;
}
ltail->next = mtail->next = rtail->next = nullptr;
left->next = sortList(left->next);
right->next = sortList(right->next);
getTail(left)->next = mid->next;
getTail(left)->next = right->next;
auto p = left->next;
delete left;
delete mid;
delete right;
return p;
}
};
时间复杂度O(nlogn)、空间复杂度O(logn), 疑问:为什么过不了leetcode 原题
可以将基准值随机选取而不是选取头尾节点
单链表排序(归并)(时间:O(nlogn) 空间:常数,迭代)
迭代写法
想法:
- 先求出要排序的链表结点数 n
- i 循环控制排序每一个子区间,子区间长度为 1,2,4,8…2^n
- 定义一个 dummy 节点为初始节点
- j循环控制合并每个子区间, j + i <= n, j += 2 * i;
- p 指向合并子区间第一个节点,q 指向合并子区间第二个节点, 通过 while 循环控制
class Solution {
public:
ListNode* sortList(ListNode* head) {
int n = 0;
for (auto p = head; p; p = p->next) n ++;
auto dummy = new ListNode(-1);
dummy->next = head;
for (int i = 1; i < n; i *= 2) {
auto cur = dummy;
for (int j = 1; j + i <= n; j += 2 * i) {
auto p = cur->next, q = p;
for (int k = 0; k < i; k ++) q = q->next;
int l = 0, r = 0;
while (l < i && r < i && p && q) {
if (p->val <= q->val) cur = cur->next=p, p = p->next, l ++;
else cur = cur->next=q, q = q->next, r ++;
}
while (l < i && p) cur = cur->next = p, p = p->next, l ++;
while (r < i && q) cur = cur->next = q, q = q->next, r ++;
cur->next = q;
}
}
return dummy->next;
}
};
递归写法O(nlogn) + 空间O(logn)
通过递归实现链表归并排序,有以下两个环节:
-
分割 cut 环节: 找到当前链表中点,并从中点将链表断开(以便在下次递归 cut 时,链表片段拥有正确边界);
- 我们使用 fast,slow 快慢双指针法,奇数个节点找到中点,偶数个节点找到中心左边的节点。
- 找到中点 slow 后,执行 slow.next = None 将链表切断。
- 递归分割时,输入当前链表左端点 head 和中心节点 slow 的下一个节点 tmp(因为链表是从 slow 切断的)。
- cut 递归终止条件: 当head.next == None时,说明只有一个节点了,直接返回此节点。
-
合并 merge 环节: 将两个排序链表合并,转化为一个排序链表。
- 双指针法合并,建立辅助ListNode h 作为头部。
- 设置两指针 left, right 分别指向两链表头部,比较两指针处节点值大小,由小到大加入合并链表头部,指针交替前进,直至添加完两个链表。
- 返回辅助ListNode h 作为头部的下个节点 h.next。
- 时间复杂度 O(l + r),l, r 分别代表两个链表长度。
ListNode* merge(ListNode* l, ListNode* r) {
ListNode *dummy = new ListNode(-1);
auto cur = dummy;
while (l && r) {
if (l->val <= r->val) cur = cur->next = l, l = l->next;
else cur = cur->next = r, r=r->next;
}
cur->next = (l ? l : r);
return dummy->next;
}
ListNode* sortList(ListNode* head) {
if (!head || !head->next) return head;
ListNode* fast = head, *slow = head;
while (fast->next && fast->next->next) {
fast = fast->next->next;
slow = slow->next;
}
fast = slow;
slow = slow->next;
fast->next = nullptr;
ListNode* l = sortList(head), *r = sortList(slow);
return merge(l, r);
}
单链表排序插入
插入排序 算法的步骤:
- 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
- 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
- 重复直到所有输入数据插入完为止。
为了方便处理边界情况,我们建立虚拟头结点,指向原链表头部。
然后扫描原链表,对于每个节点 vv,从前往后扫描结果链表,找到第一个比 vv 大的节点 uu,将 vv 插入到 uu 之前。
时间复杂度分析:一共遍历 nn 个节点,对于每个节点找合适位置时,最多需要遍历 O(n)O(n) 次,所以总时间复杂度是 O(n2)O(n2)。
class Solution {
public:
ListNode* insertionSortList(ListNode* head) {
ListNode* dummy = new ListNode(-1);
while (head) {
ListNode* next = head->next;
ListNode* p = dummy;
while (p->next && p->next->val <= head->val) p = p->next;
head->next = p->next;
p->next = head;
head = next;
}
return dummy->next;
}
};
单链表排序选择
选择排序过程
单链表排序冒泡
单链表排序堆排序
C++语言特性
C++ 中什么时候把析构函数写为虚函数 ?
当一个类为基类,通常其析构函数被声明为虚函数
why ?
因为如果定义基类指针,根据赋值兼容性问题,基类指针可以指向动态生成子类对象的地址,此时子类对象已经被充当基类使用
而在 delete 基类对象时,如果不定义析构函数为虚函数,则不会调用派生类的析构函数,这样就造成内存泄漏了.
C++中可以把构造函数声明为虚函数 ?
不可以。因为虚函数的调用依靠于虚函数表。然而在构造函数执行完时,虚函数表指针才正确初始化
构造函数不可以声明为虚函数,那么可以实现多态 ?
不可以,因为析构函数一旦被调用,虚函数表指针就会被销毁。在构造函数中实现体重也不可能发生多态行为,因为在构造函数执行时,虚函数表指针还没被正确初始化。
计算机网络
Web Server 项目
数据库
操作系统
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)