【大厂秋招高频算法】阿里秋招高频算法题汇总(中级篇)

欢迎关注公众号(通过文章导读关注:【11来了】),及时收到 AI 前沿项目工具及新技术的推送!

在我后台回复 「资料」 可领取编程高频电子书
在我后台回复「面试」可领取硬核面试笔记

文章导读地址:点击查看文章导读!

感谢你的关注!

阿里秋招高频算法题汇总(中级篇)

这里讲一下阿里秋招中的高频算法题,分为三个部分: 基础篇中级篇进阶篇

目的就是为了应对秋招中的算法题,其实过算法题的诀窍就在于 理解的基础上 + 背会

看到一个题目,首先要了解题目考察的算法是什么,这个算法要理解,至于具体实现的话,就靠背会了(多写、多练),没有什么捷径!

还有一点要注意的是,在大厂的比试中, 可能考察算法的方式是 ACM 模式 ,这一点和力扣上不同,ACM 模式需要我们自己去引入对应的包,以及自己写算法,力扣是将方法框架给定,只需要在方法内写代码就可以了,这一点要注意!

接下来开始阿里秋招算法的算法讲解,文章内的题目都在 LeetCode 上,因此这里只列出对应的题目序号、题目简介!

中级篇

image-20240315095138283

在中级篇中主要考察的算法更加偏向于 链表动态规划 这两个方面,包括:

  • 双链表的实现以及基于双链表实现 LRU
  • 动态规划
  • 链表操作

LC 146. LRU 缓存(中等)

题目简述:

请你设计并实现一个满足LRU (最近最少使用) 缓存约束的数据结构。

实现 LRUCache 类:

  • LRUCache(int capacity)正整数 作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

LRU 缓存淘汰策略还是比较常用的,并且实现起来不算复杂,同时考察了对基础数据结构的掌握,因此在面试或者笔试中出现的概率还是不小的,建议要好好掌握一下

实现的话,我们自己定义一个 Node 数据结构,并定义 prev 指针next 指针 ,来自己实现一个双向链表,常用的元素在链表头部,不常用的在尾部,我们向链表中插入一个 虚拟头节点 dummy 就可以在 O(1) 的时间复杂度内获取到头节点和尾节点

如果新插入元素的话,就放在头节点,如果查询一个元素,就将该元素移动到链表头,表示最近刚使用过,如下:

image-20240314135111125

class LRUCache {
    // 定义节点,实现双向链表
    private static class Node {
        int k, v;
        Node prev,next;
        Node (int k, int v) {
            this.k = k;
            this.v = v;
        }
    }

    // 虚拟头节点
    Node dummy = new Node(-1, -1);
    // 存储 key 对应的 Node 节点
    Map<Integer, Node> nodes = new HashMap<>();
    // LRU 缓存容量
    int capacity;
    // LRU 中元素数量
    int size;

    // 初始化
    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.size = 0;
        dummy.prev = dummy;
        dummy.next = dummy;
    }

    public int get(int key) {
        Node node = nodes.get(key);
        if (node == null) return -1;
        pushToFront(node);
        return node.v;
    }

    // 将 node 节点移动至链表头
    private void pushToFront(Node node) {
        // 将 node 从当前位置删除
        removeNode(node);
        addToFront(node);
    }

    // 将 node 放到链表头
    private void addToFront(Node node) {
        node.prev = dummy;
        node.next = dummy.next;
        dummy.next.prev = node;
        dummy.next = node;
    }

    public void put(int key, int value) {
        Node node = nodes.get(key);
        // 如果节点不为空,更新值,并放入头部
        if (node != null) {
            node.v = value;
            nodes.put(key, node);
            pushToFront(node);
        } else {
            // 如果节点为空,插入新的节点
            size ++;
            // 如果超过 LRU 容量,移除最长最久未使用的节点
            if (size > capacity) {
                Node tail = dummy.prev;
                // 移除最后一个不常使用的节点
                nodes.remove(tail.k);
                removeNode(tail);
                this.size --;
            }
            node = new Node(key, value);
            addToFront(node);
            nodes.put(key, node);
        }
    }

    // 移除指定节点
    private void removeNode(Node node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    // 打印 LRU 中存储的数据情况,方便看出哪些数据被淘汰
    public void printLRUCache() {
        Node node = dummy.next;
        while (node != dummy) {
            System.out.print("k=" + node.k + ":v=" + node.v + " ");
            node = node.next;
        }
        System.out.println();
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

LC 22. 括号生成(中等)

题目描述:

给定一个数字 n ,生成所有有效的括号组合

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

这里就是枚举出来所有有效的括号组合,必须保证 每一个左括号都有一个右括号与之对应

保证括号组合有效的话,我们可以通过剩余未使用的左括号和右括号的数量来快速判断:

  • 如果剩余的左括号的数量 大于 右括号的数量,那么说明会存在部分左括号找不到对应的右括号对应,因此肯定不合法

这里解题的话直接使用 dfs 枚举所有的情况,也就是对当前字符串,加左括号和加右括号两种情况都试一下,将不合法的情况给及时回溯掉就可以了

image-20240314141900724

class Solution {
    List<String> res = new ArrayList<>();
    public List<String> generateParenthesis(int n) {
        dfs("", n, n);
        return res;
    }
    // str 表示当前枚举的括号,l 表示剩余可用左括号、r 表示剩余可用右括号
    public void dfs(String str, int l, int r) {
        // 将错误情况排除掉
        if (l < 0 || l > r) return;
        // 如果括号用完了,就加入到结果集
        if (l == 0 && r == 0) {
            res.add(str);
            return;
        }

        // 接下来,要么加左括号,要么加右括号
        dfs(str + "(", l - 1, r);
        dfs (str + ")", l, r - 1);
    }
}

LC 206. 反转链表(简单)

题目描述:

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

这道题目代码不算太难,只是稍微有点绕,而且这一种链表的题目还是比较常见的

这里我们在 原地进行反转链表 ,不额外申请空间(额外申请空间的话,就比较简单了)

那么只需要定义一个 lastnextnow 节点,用来存储当前节点以及上一个和下一个节点,就可以进行翻转了,如下图:

image-20240314143154234

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode last = null;
        ListNode now = head;
        while (now != null) {
            ListNode next = now.next;
            now.next = last;
            last = now;
            now = next;
        }
        return last;
    }
}

LC 21. 合并两个有序链表(简单)

题目描述:

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

解题思路比较简单,同时遍历两个链表,挑一个数值比较小的加入到结果链表中就可以了!

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        // 定义结果链表的头节点
        ListNode head = new ListNode(-1);
        ListNode now = head;
        while (true) {
            // 找到数值较小的节点,加入到结果链表
            if (l1 != null && l2 != null) {
                if (l1.val < l2.val) {
                    now.next = new ListNode(l1.val);
                    now = now.next;
                    l1 = l1.next;
                }  else {
                    now.next = new ListNode(l2.val);
                    now = now.next;
                    l2 = l2.next;
                }
            } else if (l1 == null) {
                // 如果 l1 链表后边没元素了,就将 l2 链表后边的元素拼到结果链表后
                now.next = l2;
                break;
            } else if (l2 == null) {
                // 如果 l2 链表后边没元素了,就将 l1 链表后边的元素拼到结果链表后
                now.next = l1;
                break;
            }
        }
        // 返回头节点后的数据
        return head.next;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

11来了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值