mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1894 字
5 分钟
面试算法学习1
2023-08-11

蛇形矩阵#

微软面试题

题目描述#

输入两个整数 $n$ 和 $m$,输出一个 $n$ 行 $m$ 列的矩阵,将数字 $1$ 到 $n \times m$ 按照回字蛇形填充至矩阵中。 具体矩阵形式可参考样例。

输入格式#

输入共一行,包含两个整数 $n$ 和 $m$。

输出格式#

输出满足要求的矩阵。 矩阵占 $n$ 行,每行包含 $m$ 个空格隔开的整数。

数据范围#

$1 \le n,m \le 100$

解法#

模拟法:#

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
int n,m,a[N][N];
int main(){
cin>>n>>m;
int l = 0,r = m-1, t = 0,d = n-1,cnt=1;
while(l<=r || t <= d){
for(int i=l;i<=r && t<=d;i++) a[t][i] = cnt++;t++;
for(int i=t;i<=d && l<=r;i++) a[i][r] = cnt++;r--;
for(int i=r;i>=l && t<=d;i--) a[d][i] = cnt++;d--;
for(int i=d;i>=t && l<=r;i--) a[i][l] = cnt++;l++;
}
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
cout<<a[i][j]<<" \\n"[j==m-1];
return 0;
}

触底模拟:#

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
int n,m,a[N][N],dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};
int main(){
cin>>n>>m;int x=0,y=0;
for(int i=1,u=0;i<=n*m;i++){
a[x][y] = i;
x += dx[u];y += dy[u];
if(a[x][y] || x<0 || y<0 || x>=n || y>=m)
x-=dx[u],y-=dy[u],u = (u+1)%4,
x += dx[u],y += dy[u];
}
for(int i=0;i<n;i++) for(int j=0;j<m;j++)
cout<<a[i][j]<<" \\n"[j==m-1];
return 0;
}

单链表快速排序#

旷视面试题

题目描述#

给定一个单链表,请使用快速排序算法对其排序。 要求:期望平均时间复杂度为 $O(nlogn)$,期望额外空间复杂度为 $O(logn)$。 思考题: 如果只能改变链表结构,不能修改每个节点的val值该如何做呢?

数据范围#

链表中的所有数大小均在 $int$ 范围内,链表长度在 $[0, 10000]$。 本题数据完全随机生成。

解法#

思路与普通的快排基本一致,将链表根据一个val分成小于val、等于val、大于val三段,再对前后两段递归进行快排,对于排序完的三段从前到后进行拼接即可完成。

/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* quickSortList(ListNode* head) {
if(!head || !head->next) return head;
auto left = new ListNode(-1),mid=new ListNode(-1),right=new ListNode(-1),
ltail = left,mtail = mid,rtail = right;
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 = NULL;
left->next = quickSortList(left->next);
right->next = quickSortList(right->next);
get_tail(left)->next = mid->next;
get_tail(left)->next = right->next;
auto p = left->next;
delete left;delete mid;delete right;
return p;
}
ListNode* get_tail(ListNode* head) {
while (head->next) head = head->next;
return head;
}
};

寻找峰值#

峰值元素是指其值严格大于左右相邻值的元素。 给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。 你可以假设 nums[-1] = nums[n] = -∞ 。 你必须实现时间复杂度为 O(log n) 的算法来解决此问题。 提示:

  • 1 <= nums.length <= 1000
  • 231 <= nums[i] <= 231 - 1
  • 对于所有有效的 i 都有 nums[i] != nums[i + 1]

解法#

可以发现当存在斜坡时,顺着高点的方向就可以找到答案。

class Solution {
public:
int findPeakElement(vector<int>& nums) {
int l=0,r = nums.size()-1;
while(l<r){
int mid = (l+r) >> 1;
long long lm = mid-1,rm = mid+1;
if(lm<0) lm = INT_MIN-1ll;
else lm = nums[lm];
if(rm>=nums.size()) rm = INT_MIN-1ll;
else rm = nums[rm];
long long key = nums[mid];
if(key>lm && key>rm)
return mid;
else if(key>lm &&rm>key)
l = mid+1;
else
r = mid-1;
}return l;
}
};

寻找矩阵的极小值#

微软面试题

题目描述#

给定一个 $n \times n$ 的矩阵,矩阵中包含 $n \times n$ 个 互不相同 的整数。 定义极小值:如果一个数的值比与它相邻的所有数字的值都小,则这个数值就被称为极小值。 一个数的相邻数字是指其上下左右四个方向相邻的四个数字,另外注意,处于边界或角落的数的相邻数字可能少于四个。 要求在 $O(nlogn)$ 的时间复杂度之内找出任意一个极小值的位置,并输出它在第几行第几列。 本题中矩阵是隐藏的,你可以通过我们预设的 $int$ 函数 $query$ 来获得矩阵中某个位置的数值是多少。 例如,$query(a,b)$ 即可获得矩阵中第 $a$ 行第 $b$ 列的位置的数值。 注意:

  1. 矩阵的行和列均从 $0$ 开始编号。
  2. query()函数的调用次数不能超过 $(n + 2) \times \lceil log_2n \rceil + n$。
  3. 答案不唯一,输出任意一个极小值的位置即可。

数据范围#

$1 \le n \le 300$,矩阵中的整数在int范围内。

解法#

与上题类似,同时通过调用次数可以获取提示:我们可以对$log_2n$列遍历其中的n个数。具体做法是: 通过二分确定包含极小值的列,对该列进行遍历即可得到答案,其中二分的条件是一列的最小值与其所在行左右值的大小比较。

// Forward declaration of queryAPI.
// int query(int x, int y);
// return int means matrix[x][y].
class Solution {
public:
vector<int> getMinimumValue(int n) {
typedef long long ll;
ll INF = 1e15;
int l,r;l=0;r = n-1;
while(l<r){
int mid = l+r>>1;
ll val = INF;
int p=0;
for(int i=0;i<n;i++){
int t = query(i,mid);
if(t < val)
val = t,p = i;
}
ll lt = mid ? query(p,mid-1):INF;
ll rt = mid+1<n ? query(p,mid+1):INF;
if(val<lt && val<rt)
return {p,mid};
if(lt<val)
r = mid - 1;
else
l = mid + 1;
}
ll val = INF;int p=0;
for(int i=0;i<n;i++){
int t = query(i,r);
if(t<val)
val = t,p = i;
}
return {p,r};
}
};

鸡蛋的硬度#

google面试题

输入格式#

输入包括多组数据,每组数据一行,包含两个正整数 $n$ 和 $m$,其中 $n$ 表示楼的高度,$m$ 表示你现在拥有的鸡蛋个数,这些鸡蛋硬度相同(即它们从同样高的地方掉下来要么都摔碎要么都不碎),并且小于等于 $n$。 你可以假定硬度为 $x$ 的鸡蛋从高度小于等于 $x$ 的地方摔无论如何都不会碎(没摔碎的鸡蛋可以继续使用),而只要从比 $x$ 高的地方扔必然会碎。 对每组输入数据,你可以假定鸡蛋的硬度在 $0$ 至 $n$ 之间,即在 $n+1$ 层扔鸡蛋一定会碎。

输出格式#

对于每一组输入,输出一个整数,表示使用最优策略在最坏情况下所需要的扔鸡蛋次数。

数据范围#

$1 \le n \le 100$,
$1 \le m \le 10$

样例解释#

最优策略指在最坏情况下所需要的扔鸡蛋次数最少的策略。 如果只有一个鸡蛋,你只能从第一层开始扔,在最坏的情况下,鸡蛋的硬度是100,所以需要扔100次。如果采用其他策略,你可能无法测出鸡蛋的硬度(比如你第一次在第二层的地方扔,结果碎了,这时你不能确定硬度是0还是1),即在最坏情况下你需要扔无限次,所以第一组数据的答案是100。

解法#

dp1#

f[i][j]来表示在区间长度为i中通过j个鸡蛋得到的最优策略。 对于每个鸡蛋j,可以考虑有两种情况:没用鸡蛋j,即f[i][j]=f[i][j-1];使用了鸡蛋j,对于其1~i间有i种情况,令其中一种情况为k,此时会有两种情况:鸡蛋碎了(f[k-1][j-1]),鸡蛋没碎(f[i-k][j]),最坏情况即为两者取最大值,此时的最少策略为min(f[i][j],max(f[k-1][j-1],f[i-k][j])+1)

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N =110,M=11;
int n,m,f[N][M];
int main(){
while(cin>>n>>m){
for(int i=1;i<=n;i++) f[i][1] = i;
for(int i=1;i<=m;i++) f[1][i] = 1;
for(int i=2;i<=n;i++)
for(int j=2;j<=m;j++){
f[i][j] = f[i][j-1];
for(int k=1;k<=i;k++)
f[i][j] = min(f[i][j],max(f[k-1][j-1],f[i-k][j])+1);
}
cout<<f[n][m]<<endl;
}return 0;
}

dp2#

与上一种方法不同,用f[i][j]来表示的是用在i次测量中用j个鸡蛋能测得的最大长度。 假设测量位置k,会有两种情况:鸡蛋碎了(f[i-1][j-1],递归下半部分)或者不碎(f[i-1][j],递归上半部分)。 f[i][j] = f[i-1][j]+f[i-1][j-1]+1;

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N =110,M=11;
int n,m,f[N][M];
int main(){
while(cin>>n>>m){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
f[i][j] = f[i-1][j]+f[i-1][j-1]+1;
if(f[i][m] >= n){
cout<<i<<endl;
break;
}
}
}return 0;
}

包含min函数的栈#

hulu面试题

题目描述#

设计一个支持push,pop,top等操作并且可以在O(1)时间内检索出最小元素的堆栈。

  • push(x)–将元素x插入栈中
  • pop()–移除栈顶元素
  • top()–得到栈顶元素
  • getMin()–得到栈中最小元素

数据范围#

操作命令总数 $[0,100]$。

样例#

MStack minStack = new MStack();
minStack.push(-1);
minStack.push(3);
minStack.push(-4);
minStack.getM(); --> Returns -4.
minStack.pop();
minStack.top(); --> Returns 3.
minStack.getM(); --> Returns -1.

解法#

方法一#

直接通过一个数组来存存入数时当前位的最小值即可。

class MinStack {
public:
/** initialize your data structure here. */
int len;
int a[110],ck[110];
MinStack() {
len = a[0] = ck[0] = 0;
}
void push(int x) {
a[len] = x;
ck[len] = min(len?ck[len-1]:x,x);
len++;
}
void pop() {
len--;
}
int top() {
return a[len-1];
}
int getMin() {
return ck[len-1];
}
};

方法二#

通过一个单调栈来维护最小值。 通过ck.top() >= x来使存在ck中的为单调递减的最小值。当进行pop时,只要不与ck当前的最小值相等就不需要对ck进行更新。获取最小值时获取ck.top()即可。

class MinStack {
public:
/** initialize your data structure here. */
stack<int> a;
stack<int> ck;
MinStack() {
}
void push(int x) {
a.push(x);
if(ck.empty() || ck.top() >= x)
ck.push(x);
}
void pop() {
if(ck.top() == a.top())
ck.pop();
a.pop();
}
int top() {
return a.top();
}
int getMin() {
return ck.top();
}
};

链表中环的入口结点#

阿里面试题

题目描述#

给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null

数据范围#

节点 val 值取值范围 $[1,1000]$。
节点 val 值各不相同。
链表长度 $[0,500]$。

样例#

给定如上所示的链表:
[1, 2, 3, 4, 5, 6]
2
注意,这里的2表示编号是2的节点,节点编号从0开始。所以编号是2的节点就是val等于3的节点。
则输出环的入口节点3.

解法#

可以发现val值不同且范围只有1000,使用用个数组记录下用过的val值对应的节点就行了。当再访问到记录过的val时,就是发生了循环。

class Solution {
public:
ListNode *entryNodeOfLoop(ListNode *head) {
ListNode* ck[1010];
for(auto p=head;p;p=p->next){
int val = p->val;
if(ck[val])
return ck[val];
ck[val] = p;
}return NULL;
}
};
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

面试算法学习1
https://dreaife.tokyo/interview-algorithms/
作者
dreaife
发布于
2023-08-11
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

相关文章 智能推荐
1
心理记录1
psycho 意识到自己在抑制情感,反思过去的生活习惯导致对外界的恐惧和内心的静止。希望通过与世界的互动找到真实的自我,面对选择方向的责任感和可能性丢失的恐惧。真正的自我需要在探索中形成,而选择未来的方向意味着放弃其他可能性。
2
本人在x的一些关于市场认识的总结
market 从复杂系统与博弈论出发,梳理市场运行、情绪反身性与货币价值共识的内在逻辑,帮助读者穿透短期波动,建立对市场预期与通胀传导的深层认知。
3
关于EOA钱包在链上的基础操作
WEB3 快速掌握Web3钱包在链上的核心操作。内容涵盖EOA与HD钱包的创建原理、助记词生成与BIP派生流程,深入剖析SIWE和EIP-712链上验证,以及交易构造、签名、广播的完整周期,助你理清钱包底层逻辑。
4
关于一次EOA钱包的签名验证及其相关内容
WEB3 本篇文章深入解析了以太坊 EOA 钱包一次签名验证的完整流程与背后数学原理。首先回顾了 secp256k1 曲线的有限域 Fₚ、椭圆曲线点群 E(Fₚ) 以及基点 G 与其阶 n 的基本概念,详细说明了点加法、点倍加和标量乘法的模 p 与模 n 计算方式。随后,文章通过实际的 SIWE(Sign‑In with Ethereum)场景,逐步展示了钱包在收到签名请求后如何生成 r/s/v 三元组,包括哈希计算、随机数 k 的生成、R 点的求取以及 r、s、v 的具体公式。接着,服务端如何利用已知的 r、s、v、消息哈希 e 和基点 G 逆向求解公钥 Q 的公式 Q = r⁻¹(sR − eG) 进行验证,并通过 keccak‑256 取后 20 字节得到钱包地址,实现无私钥泄露的所有权确认。文章还指出了 p 与 n 的区别、椭圆曲线离散对数问题的计算难度(约 2¹²⁸)以及当前量子计算对该安全性的潜在影响。整体内容为开发者提供了从理论到实现的完整参考,适合作为博客 SEO 摘要,提升相关关键词(如 “EOA 钱包签名验证”“secp256k1”“ECDSA”“SIWE”)的搜索可见性。
5
一个对于EOA的EVM钱包登陆界面
WEB3 本篇博客详尽阐述了一个基于前端实现的 EO A(Externally Owned Account)钱包登录界面,包括整体流程、技术选型与实现细节。文章首先介绍了登录的双重交互:先获取用户钱包地址,再通过 SIWE(Sign‑In with Ethereum)协议发送带有 nonce、时间戳等信息的认证消息,要求用户在钱包中签名。随后,作者展示了使用 wagmi 库的关键代码片段——从 connect、useAccount 获取钱包信息,到 useSignMessage 发起签名请求,再通过后端 /api/auth/verify 验证签名的完整实现。文中还简要解释了 EOA 私钥、公钥、地址生成及 ECDSA(secp256k1)签名原理,帮助读者理解安全性背后的数学基础。最后,作者以个人学习体会收尾,强调该项目是其区块链开发的入门实践,为后续更复杂的链上交互奠定了基础。

目录