【秋招笔试】7月26日柠檬微趣秋招笔试改编题

✅ 秋招备战指南 ✅

💡 学习建议:

  • 先尝试独立解题
  • 对照解析查漏补缺

🧸 题面描述背景等均已深度改编,做法和题目本质基本保持一致。

🍹 感谢各位朋友们的订阅,你们的支持是我们创作的最大动力

🌸 目前本专栏已经上线100+套真题改编解析,后续会持续更新的

春秋招笔试机考招合集 -> 互联网必备刷题宝典🔗

题目一:两两交换链表节点

1️⃣:使用虚拟头节点简化边界处理

2️⃣:维护前驱指针,对每一对节点进行三步指针调整

3️⃣:遍历一次完成所有交换,时间复杂度 O(n)

难度:中等

这道题目的关键在于理解链表指针操作,通过虚拟头节点技巧可以统一处理所有节点对的交换。掌握了基本的三步指针调整方法后,就能轻松解决类似的链表重排问题。

题目二:有效字符串判断

1️⃣:理解题目本质是通过移除 "abc" 子串回到空字符串

2️⃣:使用栈模拟插入和移除过程

3️⃣:每当栈顶形成 "abc" 时立即移除,最终检查栈是否为空

难度:中等

这道题目结合了字符串处理和栈数据结构,需要理解递归定义的逆向思维。通过栈来模拟 "abc" 的动态移除过程,是处理嵌套结构问题的经典方法。

题目三:简单正则表达式匹配

1️⃣:使用二维动态规划,dp[i][j] 表示字符串前 i 个字符与模式前 j 个字符的匹配情况

2️⃣:分别处理普通字符、'*'(零次或多次)、'?'(一次或多次)三种情况

3️⃣:正确初始化边界条件,特别是处理 '*' 匹配零次的情况

难度:中等偏难

这道题目考查动态规划的状态设计和转移方程推导。虽然是简化版的正则匹配,但涵盖了动态规划中状态分类讨论的核心思想,对提升算法思维很有帮助。

题目四:下一个更大的排列

1️⃣:从右向左找第一个递减位置,确定需要增大的数位

2️⃣:从右向左找第一个大于该位的数字进行交换

3️⃣:将交换位置后面的数字反转,确保得到最小的后续排列

难度:中等

这道题目展示了经典的下一个排列算法,通过贪心思想找到刚好比当前数字大一点点的排列。掌握这个算法对理解排列生成和字典序比较很有价值。

01. 两两交换链表节点

问题描述

给定一个单链表,请两两交换其中相邻的节点,并返回交换后链表的头节点。

要求:

  • 不允许使用递归。
  • 只允许更改节点的 next 指针,不允许修改节点的值 value
  • 笔试者需要根据输入手动创建链表,并正确管理内存。

输入格式

一行包含多个整数,以空格分隔,代表链表的节点值。输入以 作为结束标志。

输出格式

输出交换后链表的所有节点值,以空格分隔。

样例输入

1 2 3 4 -1
5 6 7 -1

样例输出

2 1 4 3
6 5 7

数据范围

  • 节点数量
  • 节点值
样例 解释说明
样例1 原链表:1→2→3→4,交换后:2→1→4→3
样例2 原链表:5→6→7,交换后:6→5→7(最后一个节点保持不变)

题解

这道题目要求我们对链表中的节点进行两两交换。听起来挺复杂,但其实思路很清晰。

关键思路是使用一个虚拟头节点(dummy node)来简化边界处理。想象一下,如果我们直接操作原链表的头节点,需要特殊处理第一对节点的交换,但有了虚拟头节点,所有节点对的处理就变得统一了。

具体算法步骤:

  1. 创建虚拟头节点 dummy,让它指向原链表头部
  2. 用指针 prev 指向每一对待交换节点的前一个节点
  3. 对于每一对相邻节点 ab,进行三步指针调整:
    • a.next = b.next(让a指向b的下一个节点)
    • b.next = a(让b指向a)
    • prev.next = b(让前驱节点指向b)
  4. prev 移动到下一对节点的前驱位置,继续处理

这个解法的巧妙之处在于,每次交换只需要调整三个指针,而且所有的边界情况都被虚拟头节点优雅地处理了。

时间复杂度是 ,因为每个节点只被访问一次。空间复杂度是 ,只使用了常数个额外指针。

对于输入处理,我们需要先读取所有数字构建链表,遇到 就停止。输出时遍历交换后的链表即可。

参考代码

  • Python
import sys
input = lambda: sys.stdin.readline().strip()

class Node:
    def __init__(self, val=0, nxt=None):
        self.val = val
        self.nxt = nxt

def solve():
    # 读取输入并构建链表
    nums = list(map(int, input().split()))
    if nums[-1] == -1:
        nums.pop()
    
    if not nums:
        return
    
    # 创建链表
    head = Node(nums[0])
    cur = head
    for i in range(1, len(nums)):
        cur.nxt = Node(nums[i])
        cur = cur.nxt
    
    # 交换节点对
    fake = Node(0)  # 虚拟头节点
    fake.nxt = head
    pre = fake
    
    while pre.nxt and pre.nxt.nxt:
        # 获取待交换的两个节点
        first = pre.nxt
        sec = first.nxt
        
        # 执行交换
        first.nxt = sec.nxt
        sec.nxt = first
        pre.nxt = sec
        
        # 移动到下一对
        pre = first
    
    # 输出结果
    res = []
    cur = fake.nxt
    while cur:
        res.append(str(cur.val))
        cur = cur.nxt
    
    print(' '.join(res))

if __name__ == "__main__":
    solve()
  • Cpp
#include <iostream>
#include <vector>
using namespace std;

struct Node {
    int val;
    Node* nxt;
    Node(int x) : val(x), nxt(nullptr) {}
};

// 交换链表中的相邻节点对
Node* swapPairs(Node* head) {
    Node fake(0);  // 虚拟头节点
    fake.nxt = head;
    Node* pre = &fake;
    
    while (pre->nxt && pre->nxt->nxt) {
        // 获取要交换的两个节点
        Node* a = pre->nxt;
        Node* b = a->nxt;
        
        // 执行交换操作
        a->nxt = b->nxt;
        b->nxt = a;
        pre->nxt = b;
        
        // 移动到下一对
        pre = a;
    }
    return fake.nxt;
}

int main() {
    int x;
    Node* head = nullptr;
    Node* tail = nullptr;
    
    // 读取输入构建链表
    while (cin >> x && x != -1) {
        Node* node = new Node(x);
        if (!head) {
            head = tail = node;
        } else {
            tail->nxt = node;
            tail = node;
        }
    }
    
    // 执行交换
    head = swapPairs(head);
    
    // 输出结果并释放内存
    Node* cur = head;
    bool first = true;
    while (cur) {
        if (!first) cout << " ";
        cout << cur->val;
        first = false;
        
        Node* tmp = cur;
        cur = cur->nxt;
        delete tmp;  // 释放内存
    }
    cout << endl;
    
    return 0;
}
  • Java
import java.util.*;
import java.io.*;

class ListNode {
    int val;
    ListNode next;
    ListNode(int val) {
        this.val = val;
        this.next = null;
    }
}

public class Main {
    // 交换链表中相邻的节点对
    public static ListNode swapPairs(ListNode head) {
        ListNode dummy = new ListNode(0);  // 虚拟头节点
        dummy.next = head;
        ListNode prev = dummy;
        
        while (prev.next != null && prev.next.next != null) {
            // 获取要交换的两个节点
            ListNode first = prev.next;
            ListNode second = first.next;
            
            // 执行交换操作
            first.next = second.next;
            second.next = first;
            prev.next = second;
            
            // 移动到下一对
            prev = first;
        }
        return dummy.next;
    }
    
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] tokens = br.readLine().split(" ");
        
        ListNode head = null;
        ListNode tail = null;
        
        // 构建链表
        for (String token : tokens) {
            int val = Integer.parseInt(token);
            if (val == -1) break;
            
            ListNode node = new ListNode(val);
            if (head == null) {
                head = tail = node;
            } else {
                tail.next = node;
                tail = node;
            }
        }
        
        // 执行交换
        head = swapPairs(head);
        
        // 输出结果
        List<String> result = new ArrayList<>();
        ListNode cur = head;
        while (cur != null) {
            result.add(String.valueOf(cur.val));
            cur = cur.next;
        }
        
        System.out.println(String.join(" ", result));
    }
}

02. 有效字符串判断

问题描述

给定一个字符串 ,判断 是否为有效字符串。有效的标准如下:

  1. 字符串 "abc" 是有效的。
  2. 如果 是一个有效字符串,可以表示为 (其中 为字符串连接符, 可以为空),那么 也是一个有效字符串。

换言之,一个字符串是有效的,当且仅当它能由一个空字符串通过在任意位置插入 "abc" 多次得到。

输入格式

输入一个字符串 ,其字符集为

输出格式

如果字符串有效,输出 true,否则输出 false

样例输入

aabcbc
abcabc

样例输出

true
true

数据范围

  • 字符串只包含字符 abc
样例 解释说明
样例1 aabcbc → a + abc + bc → abc + bc → abc + (空串插入abc后得到bc无效),实际上应该理解为通过不断移除abc得到空串
样例2 abcabc → abc + abc → (空串) + abc → abc → (空串)

题解

这道题目的关键在于理解什么是"有效字符串"。题目告诉我们,一个字符串有效当且仅当它可以通过在空字符串中不断插入 "abc" 得到。

换个角度思考:如果一个字符串是有效的,那么我们应该能够通过不断移除其中的 "abc" 子串,最终得到空字符串。

这就给了我们一个很自然的解法思路:使用栈来模拟这个过程。

具体算法:

  1. 遍历输入字符串的每个字符
  2. 将当前字符压入栈中
  3. 检查栈顶的最后三个字符是否构成 "abc"
  4. 如果是,就将这三个字符弹出(相当于移除一个 "abc"
  5. 继续处理下一个字符
  6. 最后检查栈是否为空

为什么这个方法有效?因为每当我们看到 "abc" 时立即移除它,这等价于撤销了一次"插入abc"的操作。如果字符串确实是通过插入 "abc" 得到的,那么通过逆向的移除操作,最终一定能回到空字符串的状态。

举个例子来说明:

  • 对于 "aabcbc":处理到 "aabc" 时栈顶是 "abc",移除后剩下 "a";继续处理 "bc",最终栈内容是 "abc",再次移除后为空,所以有效。

时间复杂度 ,每个字符最多入栈出栈各一次。空间复杂度 ,最坏情况下所有字符都在栈中。

参考代码

  • Python
import sys
input = lambda: sys.stdin.readline().strip()

def solve():
    s = input()
    stk = []  # 使用列表模拟栈
    
    for ch in s:
        stk.append(ch)  # 将字符入栈
        # 检查栈顶是否为"abc"
        if len(stk) >= 3 and stk[-3:] == ['a', 'b', 'c']:
            # 弹出栈顶的三个字符
            stk.pop()
            stk.pop() 
            stk.pop()
    
    # 如果栈为空则字符串有效
    print("true" if not stk else "false")

if __name__ == "__main__":
    solve()
  • Cpp
#include <iostream>
#include <vector>
#include <string>
using namespace std;

int main() {
    string s;
    cin >> s;
    
    vector<char> stk;  // 使用vector模拟栈
    
    for

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

互联网刷题笔试宝典 文章被收录于专栏

互联网刷题笔试宝典,这里涵盖了市面上大部分的笔试题合集,希望助大家春秋招一臂之力

全部评论

相关推荐

会员标识
09-02 21:49
已编辑
电子科技大学 算法工程师
纯八股一点项目没问,也是挺少见的,柠檬是个好公司,有点想要痛哭流涕当时oc了最后没去,感觉怪不好意思的,秋招应该也不会再投他们家了,发出来攒攒好运柠檬一面+二面4.2投递,4.12笔,4.24一面,4..28二面,一面1.逻辑右移和算术右移的区别?2.一般有符号整数的编码方式?3.补码的规则是什么?4.为什么采用补码去记有符号整数--这个确实没想起来,说了可以首尾成环,可以连续表示什么的……(搜了下其实主要是解决原码和反码的问题(无法统一加减法,零的歧义,溢出不好判断等问题)5.有符号整数,正数和负数的值域不同,为什么会这样?--我说有正数这里会有0的存在,(实际更好的回答,是说原本原码和反码有双0的歧义,补码没有歧义,原本表示-0的10000000在补码中被定义为-128)6.浮点数在计算机里是怎么存的(1+8+23,怎么组合忘了)7.描述一下快速排序8.快排的nlogn是怎么推出来的9.最差的情况下会退化成?10.稳定还是不稳定?为什么不稳定11.STL,挑一些容器说一下内部实现原理(说了vector,list,map,unordered_map)12.Vector怎么扩容13.什么情况下,即使不有序也不会考虑使用哈希表存储数据?--一时半会想不到,说了哈希表可能存在严重哈希冲突导致退化成O(n),还有就是哈希表内存可能占用更多(还有吗?)14.哈希表怎么知道哪几个元素发生冲突了?15.Const&nbsp;Static&nbsp;Inline&nbsp;说一下使用场景16.C++多态如何实现?17.虚函数作用原理18.构造函数可以是虚的吗?析构呢?静态函数可以是虚的吗?手撕:获取二叉树最大深度的所有结点(用的层序遍历)二面:自我介绍略1.一上来直接让我定义单向链表的数据结构(结构体)2.创建一个ListNode,创建在哪个内存?说下开辟的内存大小3.关于这个内存对齐还有哪些方面能再具体讲一下吗?--主要补充了内存对齐优缺点,以及pragma_back调整内存对齐4.关于堆和栈的区别有哪些,能再讲一下吗?5.对于堆内存的管理手段,有哪些你知道的呢?说一下--除了new&nbsp;malloc这些,还说了两级分配器和内存池6.你刚说的这种做法有什么好处?--减少new/malloc调用开销,降低内存碎片7.你刚刚提到的内存碎片是怎么一回事?--说了内部碎片和外部碎片后面重点开始了8.用ListNode创建两个单向链表,两个单项链表有任意个公共节点&nbsp;(0~无穷),画出有哪些组合(看图2)一开始的储备只有1,2,3,4,5,6,网上不少文章也是这样的,但面试官提示,有9种。后面磕磕绊绊临场把7,8,9考虑出来了9.假设已知只有两个链表的头结点,怎么确定具体是其中哪一种。临场的解决思路是,(仅供参考)首先要看是否有环(快慢指针),然后仍然要算结点数量,长度,(如果有环的话,需要找到入环位置,确定有效的结点数量)无环情况比较好区分,就是长度差先后走的那一套有环情况:1)如果两表循环能回到自己的头结点,可以得出&nbsp;8。(8其实就是同一个环,不同头结点位置)2)然后5是2的变体,7是4的变体,9是1的变体,就看开始相交的位置和入环口的关系(9其实有两种情况,一种是两个都有环外部分,另一种是,一个为环,一个有环外部分)3)排除所有其他情况最后为6.10.中间顺便问了下怎么看是否有环,如何找入环位置等常规问题。前后这里口述扯了有20分钟,面试官有一定引导,也还算宽容手撕:给一个7x9的棋盘,选一个位置,围绕这个位置顺时针开始放数字,放30个数。其余置0。注意考虑边缘情况。我没找到原题,个人的思路大概就是螺旋数组II那道题的思路,大循环内4个小循环添数字。注意要加一点判断,如果超出了7x9的边界,那么就跳过,num就不会增加。(仅供参考)
查看30道真题和解析
点赞 评论 收藏
分享
评论
3
9
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务