宇宙厂治理"加班乱象":「周末加班,周一请假」的套利时代即将过去

230 阅读5分钟

宇宙厂

据悉,宇宙厂将在年后对"不合理加班情况"进行治理,重点整理对象是「财经业务部门」。

这个"不合理加班情况"具体是指:财经业务部目前是极少数的可以提交周末双薪加班申请(且通过率很高)的部门,于是经常会有同事通过「周末申请加班,周一请假」来钻规则漏洞,赚取加班费。

这一神奇操作,基本上只被少数部门知晓,宇宙厂的其他业务部门首次听到该操作,反应和外人一样:大吃一鲸 🐳

不得不感叹,一些在黄金年代后半期迅速发展起来的公司,即使已经是以「高效、扁平化」为主节奏来运作,但背后仍然会有边边角角的人(或部门)游离在外,损害整体利益。

对此,你怎么看?除了「周末加班,周内休息」这样的"套利"操作,你还知晓什么"神奇"操作?欢迎评论区交流。

...

回归主题。

来一道和「社招」相关的算法题。

题目描述

平台:LeetCode

题号:209

给定一个含有 n 个正整数的数组和一个正整数 target

找出该数组中满足其和 ≥ target 的长度最小的连续子数组 [numsl,numsl+1,...,numsr1,numsr][nums_l, nums_{l+1}, ..., nums_{r-1}, nums_r] ,并返回其长度。如果不存在符合条件的子数组,返回 00

示例 1:

输入:target = 7, nums = [2,3,1,2,4,3]

输出:2

解释:子数组 [4,3] 是该条件下的长度最小的子数组。

示例 2:

输入:target = 4, nums = [1,4,4]

输出:1

示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]

输出:0

提示:

  • 1<=target<=1091 <= target <= 10^9
  • 1<=nums.length<=1051 <= nums.length <= 10^5
  • 1<=nums[i]<=1051 <= nums[i] <= 10^5

前缀和 + 二分

利用 nums[i]nums[i] 的数据范围为 [1,105][1, 10^5],可知前缀和数组满足「单调递增」。

我们先预处理出前缀和数组 sum(前缀和数组下标默认从 11 开始),对于每个 nums[i]nums[i] 而言,假设其对应的前缀和值为 s=sum[i+1]s = sum[i + 1],我们将 nums[i]nums[i] 视为子数组的右端点,问题转换为:在前缀和数组下标 [0,i][0, i] 范围内找到满足「值小于等于 sts - t」的最大下标,充当子数组左端点的前一个值。

利用前缀和数组的「单调递增」(即具有二段性),该操作可使用「二分」来做。

Java 代码:

class Solution {
    public int minSubArrayLen(int t, int[] nums) {
        int n = nums.length, ans = n + 10;
        int[] sum = new int[n + 10];
        for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + nums[i - 1];
        for (int i = 1; i <= n; i++) {
            int d = sum[i] - t;
            int l = 0, r = i;
            while (l < r) {
                int mid = l + r + 1 >> 1;
                if (sum[mid] <= d) l = mid;
                else r = mid - 1;
            }
            if (sum[r] <= d) ans = Math.min(ans, i - r);
        }
        return ans == n + 10 ? 0 : ans;
    }
}

C++ 代码:

class Solution {
public:
    int minSubArrayLen(int t, vector<int>& nums) {
        int n = nums.size(), ans = n + 10;
        vector<int> sum(n + 10, 0);
        for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + nums[i - 1];      
        for (int i = 1; i <= n; i++) {
            int d = sum[i] - t;
            int l = 0, r = i;
            while (l < r) {
                int mid = (l + r + 1) >> 1;
                if (sum[mid] <= d) l = mid;
                else r = mid - 1;
            }
            if (sum[r] <= d) ans = min(ans, i - r);
        }
        return ans == n + 10 ? 0 : ans;
    }
};

Python 代码:

class Solution:
    def minSubArrayLen(self, t: int, nums: List[int]) -> int:
        n, ans = len(nums), len(nums) + 10
        s = [0] * (n + 10)
        for i in range(1, n + 1):
            s[i] = s[i - 1] + nums[i - 1]
        for i in range(1, n + 1):
            d = s[i] - t
            l, r = 0, i
            while l < r:
                mid = (l + r + 1) // 2
                if s[mid] <= d:
                    l = mid
                else:
                    r = mid - 1
            if s[r] <= d: 
                ans = min(ans, i - r)
        return 0 if ans == n + 10 else ans

TypeScript 代码:

function minSubArrayLen(t: number, nums: number[]): number {
    let n = nums.length, ans = n + 10;
    const sum = new Array(n + 10).fill(0);
    for (let i = 1; i <= n; i++) sum[i] = sum[i - 1] + nums[i - 1];
    for (let i = 1; i <= n; i++) {
        const d = sum[i] - t;
        let l = 0, r = i;
        while (l < r) {
            const mid = l + r + 1 >> 1;
            if (sum[mid] <= d) l = mid;    
            else r = mid - 1;
        }
        if (sum[r] <= d) ans = Math.min(ans, i - r);
    }
    return ans == n + 10 ? 0 : ans;
};
  • 时间复杂度:预处理前缀和数组的复杂度为 O(n)O(n),遍历前缀和数组统计答案复杂度为 O(nlogn)O(n\log{n})。整体复杂度为 O(nlogn)O(n\log{n})
  • 空间复杂度:O(n)O(n)

滑动窗口

另外一个,复杂度比 O(nlogn)O(n\log{n}) 更低的做法,是滑动窗口。

在一次遍历过程中,使用 ji 分别代表窗口的左右端点,变量 c 用于记录窗口内的数值总和。

遍历过程其实就是右端点 i 不断右移的过程,每次将当前右端点 i 的值累加到 c 上,若累加后,左端点右移仍能满足「总和大于等于 t」的要求,那么我们则让左端点 j 右移。

如此一来,我们便得到了每个右端点 i 固定时,下标最大的合法左端点 j(若有)。所有合法窗口长度的最小值即是答案。

Java 代码:

class Solution {
    public int minSubArrayLen(int t, int[] nums) {
        int n = nums.length, ans = n + 10;
        for (int i = 0, j = 0, c = 0; i < n; i++) {
            c += nums[i];
            while (j < i && c - nums[j] >= t) c -= nums[j++];
            if (c >= t) ans = Math.min(ans, i - j + 1);
        }
        return ans > n ? 0 : ans;
    }
}

C++ 代码:

class Solution {
public:
    int minSubArrayLen(int t, vector<int>& nums) {
        int n = nums.size(), ans = n + 10;
        for (int i = 0, j = 0, c = 0; i < n; i++) {
            c += nums[i];
            while (j < i && c - nums[j] >= t) c -= nums[j++];
            if (c >= t) ans = min(ans, i - j + 1);
        }
        return ans > n ? 0 : ans;
    }
};

Python 代码:

class Solution:
    def minSubArrayLen(self, t: int, nums: List[int]) -> int:
        n, ans = len(nums), len(nums) + 10
        j, c = 0, 0
        for i in range(n):
            c += nums[i]
            while j < i and c - nums[j] >= t:
                c -= nums[j]
                j += 1
            if c >= t:
                ans = min(ans, i - j + 1)
        return 0 if ans > n else ans

TypeScript 代码:

function minSubArrayLen(t: number, nums: number[]): number {
    let n = nums.length, ans = n + 10;
    for (let i = 0, j = 0, c = 0; i < n; i++) {
        c += nums[i];
        while (j < i && c - nums[j] >= t) c -= nums[j++];
        if (c >= t) ans = Math.min(ans, i - j + 1);
    }
    return ans > n ? 0 : ans;
};
  • 时间复杂度:O(n)O(n)
  • 空间复杂度:O(1)O(1)