程序员老鬼

准备给一个34岁员工降薪50%,结果发现他刚离婚没软肋了,就把矛头转向了另一个31岁,刚结婚准备要小孩的

刚看到个贴子,说公司本来想给一个34岁的员工降薪50%,结果发现人家刚离婚、没软肋了,转头就把矛头对准另一个31岁、刚结婚准备要孩子的。

Image

网友们的回复我看了看,有的骂领导坏,有的嘲讽“职场就是看谁更容易捏”。我部分认同,但我的看法是——这事其实反映了一个赤裸现实:有些老板不是看绩效,是看你“能不能被拿捏”。你一旦被认为有家庭压力、有贷款、有牵绊,就很容易被盯上。

换个角度想,企业要控制成本可以理解,但把成本压在最弱的那个人身上,这就不是管理,是欺负老实人。

说到底还是价值和边界的问题——你越让人觉得你离不开这份工作,对方越容易拿你开刀。

不过话说回来,我们普通人也别被吓得太悲观。能做的就是提升自己的选择权,明确底线,让别人知道你不是任捏的软柿子。

公司可以挑员工,员工也能挑公司。【备注:文末可领最新资料】

面试题:设计循环队列

想象一下,你在做一个“消息通知”功能,后端要暂存最近 100 条消息,用完就扔,新的来了就顶掉最早的那条。这个时候要是用最普通的数组队列,每次出队都把后面的元素整体往前挪一遍,时间一长 CPU 得被你气冒烟,对吧。这个时候循环队列就上场了。

先说下循环队列是个啥 你可以脑补一圈座位,固定就 5 个位置:0、1、2、3、4。新来的同学从 tail 坐下,走的同学从 head 离开。坐到 4 的时候再来人,就绕回 0 继续用,所以叫“循环”。

用数组实现的时候,一般有两种做法: 1)留一个空位,通过 head == tail 表示空, (tail + 1) % n == head 表示满。 2)再加一个 size 变量,直接用 size == 0 判断空,size == capacity 判断满。

我下面用第二种,逻辑好理解一点,也不容易绕晕。

大致设计长这样:

  • 用一个 int[] data 存元素
  • int head 指向队头元素下标
  • int tail 指向“下一个要放元素”的位置
  • int size 当前有多少元素
  • int capacity 队列容量(数组长度)

公式就记两个:

  • 入队之后:tail = (tail + 1) % capacity
  • 出队之后:head = (head + 1) % capacity

取模就是那个“绕圈”的关键。

我先把 Java 代码丢出来,你可以对着看:

publicclassMyCircularQueue{

privatefinalint[] data;
privatefinalint capacity;
privateint head;   // 指向队头元素
privateint tail;   // 指向下一个要写入的位置
privateint size;   // 当前元素个数

publicMyCircularQueue(int k){
if (k <= 0) {
thrownew IllegalArgumentException("容量必须 > 0");
        }
this.capacity = k;
this.data = newint[k];
this.head = 0;
this.tail = 0;
this.size = 0;
    }

// 入队:成功返回 true,满了返回 false
publicbooleanenQueue(int value){
if (isFull()) {
returnfalse;
        }
        data[tail] = value;
        tail = (tail + 1) % capacity;
        size++;
returntrue;
    }

// 出队:成功返回 true,空队列返回 false
publicbooleandeQueue(){
if (isEmpty()) {
returnfalse;
        }
// 实际上不用把 data[head] 清零,逻辑上它已经“无效”了
        head = (head + 1) % capacity;
        size--;
returntrue;
    }

// 取队头元素,空队列就返回 -1(也可以改成抛异常,看题目要求)
publicintFront(){
if (isEmpty()) {
return -1;
        }
return data[head];
    }

// 取队尾元素,注意 tail 指向的是“下一个要写的位置”
publicintRear(){
if (isEmpty()) {
return -1;
        }
int index = (tail - 1 + capacity) % capacity;
return data[index];
    }

publicbooleanisEmpty(){
return size == 0;
    }

publicbooleanisFull(){
return size == capacity;
    }
}

代码不长,但里面几个小坑,顺手说一下。

第一个容易绕的点:head 和 tail 的含义 我这里的约定是:

  • head 永远指向当前队头元素
  • tail 永远指向“下一个入队要写入的位置”

所以:

  • 入队的时候在 tail 写数据,然后 tail 往后挪
  • 出队的时候 head 往后挪,相当于把原来的队头“丢弃”了

这样好处是,data 里某个位置到底有没有值,其实不重要,我们只看 head、tail 和 size,逻辑会简单很多。

第二个点:为什么 Rear 要 (tail - 1 + capacity) % capacity因为 tail 指的是下一个要写的位置,比如容量是 5,当前已经塞了 3 个元素:

  • 假设依次入:10, 20, 30

    • 第一次入队:data[0] = 10,tail -> 1
    • 第二次入队:data[1] = 20,tail -> 2
    • 第三次入队:data[2] = 30,tail -> 3

你要取队尾,就应该拿 data[2],也就是 tail - 1。 但会有绕圈的情况,比如 tail == 0 的时候,再减一就是 -1,这会越界,所以要先加上 capacity 再取模,保证结果落在 [0, capacity-1] 之间。

第三个点:为啥需要 size 如果只看 head / tail,你会发现有个尴尬的情况:

  • 刚创建的时候:head = 0, tail = 0,这显然是“空队列”
  • 如果你正好把数组填满,继续绕了一圈,最后 head 也可能等于 tail

这时候只看 head == tail 完全分不清是“空”还是“满”。 常见的两个解决办法:

  • 要么强制保留一个空位,永远不把数组塞满
  • 要么像我这样,多维护一个 size

size 的判断就很直观:

  • size == 0:空
  • size == capacity:满

代价就是每次入队 / 出队要多 ++ / -- 一下,不过这点开销基本可以忽略。

再顺嘴说点使用场景 循环队列这种东西,在实际项目里挺常见的,尤其是那种容量固定、不断覆盖旧数据的场景,比如:

  • 日志系统里“只保留最近 N 条记录”的内存缓冲
  • 限流、滑动窗口统计最近 N 秒的请求数
  • 音视频、设备采集这类场景的环形缓冲区

这时候如果你还用普通数组每次挪一长串数据,时间一长性能肯定难看。

最后简单小结一下哈: 核心就三个点:

  1. 用数组当底层存储,空间连续、访问快。
  2. 用 head、tail、size 三个变量维护状态,通过取模实现“绕圈”。
  3. 注意 Rear 的下标和空 / 满的判定逻辑,不要被各种边界条件绊住。

你可以先把这段 Java 代码直接丢到 IDE 里跑两组简单的入队 / 出队测试,多打几个打印,看着指针怎么走,很快就完全熟了。

-END-

我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html

最后给大家分享一份不错的副业资料,点击下方公众号,回复关键字: 副业 领,也可以链接我微信:hls404