专栏名称: 吴师兄学算法
和程序员小吴一起从初学者的角度学习算法,以动画的形式呈现解题的思路。每周四篇原创文章,期待你的鉴赏!
目录
相关文章推荐
英国大家谈  ·  视频号平台开放广告投放啦! ·  4 小时前  
英国那些事儿  ·  少女自称“世界头号失踪女童”,被拐18年想回 ... ·  2 天前  
最英国  ·  唐顿移民问答| ... ·  19 小时前  
英国报姐  ·  让我放弃lulu的瑜伽服,不止是便宜…… ·  3 天前  
51好读  ›  专栏  ›  吴师兄学算法

华为今年的校招薪资。。。

吴师兄学算法  · 公众号  ·  · 2024-09-29 16:12

正文

大家好,我是吴师兄。

最近华为的校招笔试场次挺多的,大概看了一下, 最近半个月就有四场考试,但难度却各不相同, 比如 0925 的笔试难度就属于中等偏简单,让很多人从 0919 那场地狱难度的阴影中走出来,但没想到,0927 的笔试题的难度却又提升上去,着实摸不透出题者的心里状态

说起今年的华为校招,大家普遍关注的不仅仅是笔试的难度,还有华为给出的丰厚薪资待遇,在网上看到一张华为校招薪资介绍的图片,不得不说,确实挺诱人的,难怪每一场的笔试,都有很多人参与。

这个月辅导了不少同学都拿到了 offer,其中一半以上的都是去华为,华子真香!

...

回归我们公众号的主题。

继续来一道和「大厂秋招」相关的算法原题,来看看华为 0925 的题目如何简单。

2024 年的大厂真题可以在我的网站上进行练习。

网站地址: https://oj.algomooc.com/

关注吴师兄,算法学习好轻松

社交网络用户影响力计算

题目描述

社交网络拓扑图中的节点表示社交网络中的用户,边表示两个用户之间的社交连接,边是无向的,两个用户最多只有一条直接相连的边。

用户的影响力定义为:从某个社交网络用户开始,找出所有可以在 k 跳(直接或间接关系)内接触到的其他用户的总个数。

请实现一个程序,计算给定社交网络中某个用户在 k 跳范围内的影响力。

输入描述

  • 第一行输入 N M K (三个空格分隔的正整数): N 代表社交网络连接总数, M 代表需要计算影响力的用户编号, K 代表计算影响力的范围。 1<=N,K<=1000 0<=M<1000
  • 接下来的 N 行,每行两个整数 X Y(0<=X,Y<=1000) ,代表社交网络中一条直接连接的边,如 1 2 代表 1 号与 2 号用户互相直接连接。
  • 用例确保输入有效,无需进行校验

输出描述

输出 M 用户在 k 跳范围内的影响力

示例一

输入

5 0 2
0 1
1 2
2 3
3 4
4 0

输出

4

示例二

输入

8 0 3
0 1
0 2
0 3
3 4
2 5
5 4
2 3
1 5

输出

5

时空限制

时间限制: C/C++ 1000ms, 其他语言:2000ms

内存限制: C/C++ 256MB, 其他语言:512MB

解题思路

本题是一个非常典型的图搜索问题,可以直接将输入转化为邻接表来完成。

类似的题目有LC841. 钥匙和房间

其问题转变为, 从原点 M 出发,距离为 k 的点一共有多少个

由于规定了距离 k ,因此应该着重考虑使用BFS来解决问题,而不太适合使用DFS来解决了。

换个表达就是,从原点 M 出发,向外搜索 k 层,一共能够搜索到多少个点?

所以直接套上BFS代码的模板就可以完成。

在每一层的搜索中, k 都会递减 1 ,直到 k 小于 0 停止搜索,完成了 k 层搜索。

有很多人写了DFS版本的代码,这是一种错误的做法。

感兴趣的同学也可以尝试DFS的写法,很容易漏计或者超时。

代码

Python

from collections import defaultdict, deque

# 输入连接数n,起始节点m,步数k
n, m, k = map(int, input().split())

# 初始化邻接表
neighbor_dict = defaultdict(list)

# 输入边的情况
for _ in range(n):
    # 获得某一条无向边的两个节点,a和b
    a, b = map(int, input().split())
    # a的近邻节点包含b,b的近邻节点包含a
    neighbor_dict[a].append(b)
    neighbor_dict[b].append(a)


# 答案变量,记录范围k内的个数
ans = 0
# 初始化用于bfs过程的队列q
q = deque()
q.append(m)

# 用于检查元素是否重复的集合check_set
check_set = set()
# check_set中初始包含起始节点m
check_set.add(m)

# 进行bfs,由于只需要起始节点m考虑最近的k层节点
# 所以当k大于0的时候,持续进行bfs
# 在每层bfs中,k会减小
while k >= 0:
    # 搜索层数-1
    k -= 1
    # 当前层的遍历,获得当前队列长度qSize
    # 循环qSize次
    qSize = len(q)
    # 所有q中的元素,都是距离起始点m的k步内的元素
    # ans增加qSize
    ans += qSize
    for _ in range(qSize):
        # 弹出当前队头元素
        cur = q.popleft()
        # 遍历cur的所有近邻节点nxt
        for nxt in neighbor_dict[cur]:
            # 如果nxt尚未检查过,则可以加入队列
            # 同时标记nxt为已检查过
            if nxt not in check_set:
                q.append(nxt)
                check_set.add(nxt)


# 需要排除掉起始点m本身,所以最终答案需要-1
print(ans-1)

Java

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // 输入连接数n,起始节点m,步数k
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        int k = scanner.nextInt();

        // 初始化邻接表
        Map> neighborDict = new HashMap<>();
        
        // 输入边的情况
        for (int i = 0; i             int a = scanner.nextInt();
            int b = scanner.nextInt();
            
            // 将a的近邻节点b加入邻接表
            neighborDict.computeIfAbsent(a, key -> new ArrayList<>()).add(b);
            neighborDict.computeIfAbsent(b, key -> new ArrayList<>()).add(a);
        }
        
        // 答案变量,记录范围k内的个数
        int ans = 0;
        // 初始化用于bfs过程的队列q
        Queue q = new LinkedList<>();
        q.add(m);
        
        // 用于检查元素是否重复的集合checkSet
        Set checkSet = new HashSet<>();
        checkSet.add(m);
        
        // 进行bfs,搜索k层节点
        while (k >= 0) {
            k--;
            int qSize = q.size();
            ans += qSize;
            
            for (int i = 0; i                 int cur = q.poll();
                
                // 遍历cur的所有近邻节点nxt
                for (int nxt : neighborDict.getOrDefault(cur, new ArrayList<>())) {
                    if (!checkSet.contains(nxt)) {
                        q.add(nxt);
                        checkSet.add(nxt);
                    }
                }
            }
        }

        // 需要排除掉起始点m本身,所以最终答案需要-1
        System.out.println(ans - 1);
    }
}

C++

#include 
#include 
#include 
#include 
#include 

using namespace std;

int main() {
    // 输入连接数n,起始节点m,步数k
    int n, m, k;
    cin >> n >> m >> k;

    // 初始化邻接表
    unordered_map<intvector<int>> neighbor_dict;

    // 输入边的情况
    for (int i = 0; i         int a, b;
        cin >> a >> b;
        // a的近邻节点包含b,b的近邻节点包含a
        neighbor_dict[a].push_back(b);
        neighbor_dict[b].push_back(a);
    }

    // 答案变量,记录范围k内的个数
    int ans = 0;
    // 初始化用于bfs过程的队列q
    queue<int> q;
    q.push(m);

    // 用于检查元素是否重复的集合checkSet
    unordered_set<int> check_set;
    check_set.insert(m);

    // 进行bfs,搜索k层节点
    while (k >= 0) {
        k--;
        int qSize = q.size();
        ans += qSize;

        for (int i = 0; i             int cur = q.front();
            q.pop();

            // 遍历cur的所有近邻节点nxt
            for (int nxt : neighbor_dict[cur]) {
                if (check_set.find(nxt) == check_set.end()) {
                    q.push(nxt);
                    check_set.insert(nxt);
                }
            }
        }
    }

    // 需要排除掉起始点m本身,所以最终答案需要-1
    cout <1 
    return 0;
}

时空复杂度

时间复杂度: O(n) 。每一条边都可能会被遍历到。

空间复杂度: O(n)

俄罗斯方块

题目描述

在俄罗斯方块游戏中,只有 1 种大方块,由四个正方形小方块组成。

现在,请计算在给定网格大小的情况下,最多可以放置多少个大方块。

放置规则如下:

  • 1、网格为正方形网络
  • 2、方块不能重叠。
  • 3、方块不能超出网格的边界。
  • 4、网格中部分位置不能放置方块。

输入描述

n k
y1 x1
y2 x2
...

表示边长为 n 的正方形网格,有 k 个位置不能放置方块。

接下来 k 行坐标对, y 表示自上向下的第几行, x 表示自左向右的第几列(坐标从 0 开始编号,左上角为 0 0 )。

n`的范围:`[1,8]
k`的范围:`[0,64]
x、y`的范围:`[0,n)

输出描述

最多能放下多少大方块。

示例

输入

2 0

输出

1

时空限制

时间限制: C/C++ 500 MS,其他语言 1000 MS

内存 限制: C/C++ 256 MB,其他语言 512 MB

解题思路

本题数据规模较小,考虑 回溯枚举 所有的放置情况即可。

但本题并非常规的回溯写法,不好直接套模板,存在很多细节需要考量。

代码

Python

# 根据当前左上角的点(x,y),获得下一个可能的点的(nx,ny)的函数
# (x, y)尚未触碰到最右的边界n-1,则下一个点考虑这一行右边的点(x, y+1)
# (x, y)触碰到最右的边界n-1,则下一个点考虑下一行的第一个点(x+1, 0)
def get_next(x, y, n):
    if y+1         return x, y+1
    return x+10


# 检查以(x, y)为左上角的2*2正方形,能否填充在grid中的函数
# 如果四个点(x, y) (x+1, y) (x, y+1) (x+1, y+1)均为0
# 则可以填充返回True,否则不能填充
# 由于遍历时已经进行了范围的判断,所以不会出现越界
def check(grid, x, y):
    # 如果x或y等于n-1,则说明必然无法放置,退出
    if x == n-1 or y == n-1:
        return False
    # 否则考虑这4个点是否均为0,是则返回True说明可以填充
    return grid[x][y] == grid[x+1][y] == grid[x][y+1] == grid[x+1][y+1] == 0


# 将以(x, y)为左上角的2*2正方形,填充在grid中
# 更新grid状态的函数
def update(grid, x, y):
    grid[x][y] = 1
    grid[x+1][y] = 1
    grid[x][y+1] = 1
    grid[x+1][y+1] = 1
    return


# 将填充在grid中以(x, y)为左上角的2*2正方形进行撤销
# grid状态更新后,进行回滚的函数
def roll_back(grid, x, y):
    grid[x][y] = 0
    grid[x+1][y] = 0
    grid[x][y+1] = 0
    grid[x+1][y+1] = 0
    return


# 回溯函数
# (cx, cy)为当前点的坐标
# num为当前已经填充的方块个数
def dfs(grid, cx, cy, n, num):
    # 设置全局变量ans,对ans进行更新
    global ans
    ans = max(ans, num)
    # 如果已经到达最后一行,则必然无法填充任何新方块,直接退出
    if cx == n-1:
        return
    # 如果不填充当前点(cx, cy)
    # 获得下一个点(nx, ny)
    nx, ny = get_next(cx, cy, n)
    # 如果不填充,考虑下一个点的情况,num不变
    dfs(grid, nx, ny, n, num)

    # 如果以当前点(cx, cy)作为左上角填充2*2的正方形
    if check(grid, cx, cy):
        # grid的状态更新
        update(grid, cx, cy)
        # 考虑下一个点(nx, ny)的情况,进行回溯函数的调用,num递增1
        dfs(grid, nx, ny, n, num+1)
        # 状态回滚
        roll_back(grid, cx, cy)


# 输入网格大小n,障碍点个数k
n, k = map(int, input().split())
# 构建大小为n*n的网格,初始化其中位置均为0
grid = [[0] * n for _ in range(n)]
# 循环k次,输入每一个障碍点的坐标(x, y)
for _ in range(k):
    x, y = map(int, input().split())
    # 将障碍点修改为1
    grid[x][y] = 1

# 初始化答案
ans = 0
# 递归入口,起始点为(0, 0),填充方块个数num = 0
dfs(grid, 00, n, 0)

print(ans)

Java

import java.util.Scanner;

public class Main {
    
    // 根据当前左上角的点(x,y),获得下一个可能的点(nx,ny)的函数
    public static int[] getNext(int x, int y, int n) {
        if (y + 1             return new int[]{x, y + 1};
        }
        return new int[]{x + 10};
    }

    // 检查以(x, y)为左上角的2*2正方形,能否填充在grid中的函数
    public static boolean check(int[][] grid, int x, int y, int n) {
        if (x == n - 1 || y == n - 1) {
            return false;
        }
        return grid[x][y] == 0 && grid[x + 1][y] == 0 && grid[x][y + 1] == 0 && grid[x + 1][y + 1] == 0;
    }

    // 将以(x, y)为左上角的2*2正方形,填充在grid中
    public static void update(int[][] grid, int x, int y) {
        grid[x][y] = 1;
        grid[x + 1][y] = 1;
        grid[x][y + 1] = 1;
        grid[x + 1][y + 1] = 1;
    }

    // 将填充在grid中以(x, y)为左上角的2*2正方形进行撤销
    public static void rollBack(int[][] grid, int x, int y) {
        grid[x][y] = 0;
        grid[x + 1][y] = 0;
        grid[x][y + 1] = 0;
        grid[x + 1][y + 1] = 0;
    }

    static int ans = 0;

    // 回溯函数
    public static void dfs(int[][] grid, int cx, int cy, int n, int num) {
        ans = Math.max(ans, num);
        if (cx == n - 1) {
            return;
        }
        int[] next = getNext(cx, cy, n);
        int nx = next[0], ny = next[1];

        // 不填充当前点
        dfs(grid, nx, ny, n, num);

        // 如果可以填充当前点
        if (check(grid, cx, cy, n)) {
            update(grid, cx, cy);
            dfs(grid, nx, ny, n, num + 1);
            rollBack(grid, cx, cy);
        }
    }

    public static void main(String[] args)  {
        Scanner scanner = new Scanner(System.in);
        
        // 输入网格大小n,障碍点个数k
        int n = scanner.nextInt();
        int k = scanner.nextInt();

        // 构建大小为n*n的网格,初始化其中位置均为0
        int[][] grid = new int[n][n];
        for (int i = 0; i             int x = scanner.nextInt();
            int y = scanner.nextInt();
            grid[x][y] = 1;  // 将障碍点修改为1
        }

        // 初始化答案
        ans = 0;

        // 递归入口,起始点为(0, 0),填充方块个数num = 0
        dfs(grid, 00, n, 0);

        // 输出结果
        System.out.println(ans);
    }
}

C++

#include 
#include 
using namespace std;

// 根据当前左上角的点(x,y),获得下一个可能的点(nx,ny)的函数
pair<intintgetNext(int x, int y, int n) {
    if (y + 1         return {x, y + 1};
    }
    return {x + 10};
}

// 检查以(x, y)为左上角的2*2正方形,能否填充在grid中的函数
bool check(const vector<vector<int>>& grid, int x, int y, int n) {
    if (x == n - 1 || y == n - 1) {
        return false;
    }
    return grid[x][y] == 0 && grid[x + 1][y] == 0 && grid[x][y + 1] == 0 && grid[x + 1][y + 1] == 0;
}

// 将以(x, y)为左上角的2*2正方形,填充在grid中
void update(vector<vector<int>>& grid, int x, int y) {
    grid[x][y] = 1;
    grid[x + 1][y] = 1;
    grid[x][y + 1] = 1;
    grid[x + 1][y + 1] = 1;
}

// 将填充在grid中以(x, y)为左上角的2*2正方形进行撤销
void rollBack(vector<vector<int>>& grid, int x, int y) {
    grid[x][y] = 0;
    grid[x + 1][y] = 0;
    grid[x][y + 1] = 0;
    grid[x + 1][y + 1] = 0;
}

int ans = 0;

// 回溯函数
void dfs(vector<vector<int>>& grid, int cx, int cy, int n, int num) {
    ans = max(ans, num);
    if (cx == n - 1) {
        return;
    }
    pair<intint> next = getNext(cx, cy, n);
    int nx = next.first, ny = next.second;

    // 不填充当前点
    dfs(grid, nx, ny, n, num);

    // 如果可以填充当前点
    if (check(grid, cx, cy, n)) {
        update(grid, cx, cy);
        dfs(grid, nx, ny, n, num + 1);
        rollBack(grid, cx, cy);
    }
}

int main() {
    // 输入网格大小n,障碍点个数k
    int n, k;
    cin >> n >> k;

    // 构建大小为n*n的网格,初始化其中位置均为0
    vector<vector<int>> grid(n, vector<int>(n, 0));
    for (int i = 0; i         int x, y;
        cin >> x >> y;
        grid[x][y] = 1;  // 将障碍点修改为1
    }

    // 初始化答案
    ans = 0;

    // 递归入口,起始点为(0, 0),填充方块个数num = 0
    dfs(grid, 00, n, 0);

    // 输出结果
    cout 
    return 0;
}

时空复杂度

时间复杂度: O(2^((N//2)*(N//2))) 。此处考虑一个非常宽松的上界,一共存在 N*N 个点,最多填充 (N//2)*(N//2) 2*2 的正方形,每个位置都存在填充或者不填充 2 种可能性。

空间复杂度: O(1)

评估最大工作量

题目描述

其团队以来了一个大项目,该项目己知有 n 个需求,每个需求工作量分别需要有 t1、t2…tn 人天,由于该项目需求过多,负责人小梁决定先给出 1 人天预算完成部分需求。对于单个需求,每个任务要么不做,要么全部完成,必须耗时 ti 人天完成,现在小梁想知道 t 人天的预算最多能做多少人天的需求。

输入描述

输入共两行

首行是 2 个整数,以空格隔开,分别是 n T n 代表需求总教, T 代表工作量评估不超过 T 人天

次行有 n 个整数,以空格隔开,分别是 t1、t2…tn ,代表每个需求所需工作量,单位是人天

数据范围: 1<=n<=40; 1<=ti<=10^5;1<=T<=10^5

输出描述

一个整数 ans ,代表T人天的预算最多能做 ans 人天的需求

示例

输入







请到「今天看啥」查看全文