惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
G
Google Developers Blog
V
V2EX
美团技术团队
H
Help Net Security
月光博客
月光博客
爱范儿
爱范儿
Engineering at Meta
Engineering at Meta
The Cloudflare Blog
U
Unit 42
大猫的无限游戏
大猫的无限游戏
Recent Announcements
Recent Announcements
A
About on SuperTechFans
博客园 - Franky
The GitHub Blog
The GitHub Blog
N
Netflix TechBlog - Medium
人人都是产品经理
人人都是产品经理
博客园 - 司徒正美
MyScale Blog
MyScale Blog
B
Blog
雷峰网
雷峰网
Y
Y Combinator Blog
云风的 BLOG
云风的 BLOG
T
The Blog of Author Tim Ferriss

OhYee 博客

小鹏辅助驾驶测评|OhYee 博客 小鹏非支持手机开启自动解锁|OhYee 博客 使用函数计算实现 301 重定向|OhYee 博客 针对 HTML 内容使用 Ant Design 图片弹框|OhYee 博客 博客进程泄露及僵尸进程解决|OhYee 博客 蓝易云服务器体验|OhYee 博客 SSH 调起本地 VSCode|OhYee 博客 【2022 秋招内推】阿里云后端研发工程师|OhYee 博客 使用函数计算获取 IP 地址信息|OhYee 博客 正确获取客户端 IP/HTTP Header 也可能重复|OhYee 博客 评测 Oculus Quest2 及 BigScreen|OhYee 博客 NextJS 热重载保留状态|OhYee 博客 如何优雅地贴 gist 代码|OhYee 博客 Linux 精细化文件权限|OhYee 博客 VSCode 容器开发环境|OhYee 博客 Clash 的不兼容更新排查|OhYee 博客 Zeek 导出 PCAP|OhYee 博客 记一次 ssh 配置问题|OhYee 博客 Git Commit 规范化工具|OhYee 博客 谈谈《星之卡比-探索发现》|OhYee 博客 VSCode 快捷键绑定 Shell 命令|OhYee 博客 ASN.1 语法及 X.509 证书格式解析解析|OhYee 博客 腾讯企业邮箱忽略 MX 记录发信|OhYee 博客 Chrome/Edge 标签组插件|OhYee 博客 【应届内推】阿里云后端研发工程师|OhYee 博客 损坏的 Typecho 备份处理为 JSON|OhYee 博客 VS Code VIM 插件高效使用|OhYee 博客 SSH 正反向代理|OhYee 博客 Let's Encrypt 根证书过期引发的问题|OhYee 博客 OpenWRT 忽略内核依赖|OhYee 博客
LeetCode 927. 三等分|OhYee 博客
2020-12-29 · via OhYee 博客

LeetCode 927. 三等分

题目描述

给定一个由 0011 组成的数组 A,将数组分成 33 个非空的部分,使得所有这些部分表示相同的二进制值。

如果可以做到,请返回任何 [i,j][i, j],其中 i+1<ji+1 < j,这样一来:

A[0],A[1],...,A[i]A[0], A[1], ..., A[i] 组成第一部分;
A[i+1],A[i+2],...,A[j−1]A[i+1], A[i+2], ..., A[j-1] 作为第二部分;
A[j],A[j+1],...,A[A.length−1]A[j], A[j+1], ..., A[A.length - 1] 是第三部分。
这三个部分所表示的二进制值相等。
如果无法做到,就返回 [−1,−1][-1, -1]

注意,在考虑每个部分所表示的二进制时,应当将其看作一个整体。例如,[1,1,0][1,1,0] 表示十进制中的 66,而不会是 33。此外,前导零也是被允许的,所以 [0,1,1][0,1,1] 和 [1,1][1,1] 表示相同的值。

示例 1

输入:[1,0,1,0,1]
输出:[0,3]

示例 2

输出:[1,1,0,1,1]
输出:[-1,-1]

题解

题目要求将一个 0-1 数组分割成不为空的三部分,要求每部分看作二进制串后,数值相等(忽略前导零)

要满足数值相等,需要有以下条件:

  • 每部分 1 的个数相同
  • 每部分后导零个数相同
  • 每部分除去前后导零外的部分相同

因此,需要操作的第一步是获取几个关键的 11 的位置:每部分的第一个和最后一个 11
将第 ii 部分的第一个和最后一个 11 分别记为 one[i][0]one[i][0]one[i][1]one[i][1]

对于有 count1count111 的数组,每部分应该有 count1/3count1 / 311,且第 ii 部分的第一个 11(i−1)×count1/3+1(i-1) \times count1 / 3 + 1,最后一个 11i×count1/3i \times count1 / 3。这样可以在 O(n)O(n) 的时间复杂度内,获得这些关键的位置。

前两部分的后导零可以通过划分成下一部分的前导零从而被“删去”,因此第三部分的后导零决定了后导零的个数。可以通过 n−one[3][1]−1n - one[3][1] - 1 获取后导零的个数

那么,可根据每部分最后一个 11 的位置加上后导零个数,获得分割点。
{i=one[1][1]+zeroj=one[2][1]+zero+1\begin{cases} i = one[1][1] + zero \\ j = one[2][1] + zero + 1 \end{cases}

接下来,分别判断 iijj 是否合法(是否在下一个区域的位置)。至此,前两个条件已满足,只需要判断最后一个条件:非前后导零部分是否相同,只需要判断各个分区关键位置的长度即内容即可。

时间复杂度为 O(n)O(n),空间复杂度为 O(1)O(1)

除去上述部分,还需要考虑一些特殊情况:

  • 数组为空(数组长度不足 33): 直接返回 [−1,−1][-1, -1]
  • 数组内容全为 00: 直接返回 [0,2][0, 2](前一步已经确保长度至少为 33

代码

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* threeEqualParts(int* A, int ASize, int* returnSize){
    int *res = malloc(sizeof(int)*2);
    res[0] = -1; res[1] = -1; *returnSize = 2;

    // 数组无法分成非空的 3 份
    if (ASize < 3) return res;

    int count = 0;
    for (int i=0; i<ASize; ++i)
        if (A[i] == 1)
            ++count;

    // 1 的个数无法整除
    if (count % 3 != 0) return res;
    // 全是 0
    if (count == 0) {res[0] = 0; res[1] = 2; return res;}

    // 获取关键的 1 的位置
    int p[6] = {0, 0, 0, 0, 0, 0};
    int n1 = count / 3; // 每组中 1 的长度
    int temp = 0;
    for (int i=0; i<ASize; ++i) {
        if (A[i] == 1) {
            ++temp;
            if (temp == 1) p[0] = i;            // 第一组第一个 1
            if (temp == n1) p[1] = i;           // 第一组的最后一个 1
            if (temp == n1 + 1) p[2] = i;       // 第二组的第一个 1
            if (temp == n1 * 2) p[3] = i;       // 第二组的最后一个 1
            if (temp == n1 * 2 + 1) p[4] = i;   // 第三组的第一个 1
            p[5] = i;                           // 第三组的最后一个 1
        }
    }

    // 后导零个数
    int zero = ASize - p[5] - 1;
    
    // 确定 i 和 j 的位置
    int i = p[1] + zero;
    int j = p[3] + zero + 1;
    if (i >= p[2] || j > p[4] || i+1 >= j)
        return res;
    
    // 判断该位置分割的区间是否合法
    if (p[1] - p[0] != p[3] - p[2] || p[1] - p[0] != p[5] - p[4])
        return res;
    for (int i=0; i< p[1]-p[0]; ++i)
        if (A[p[0] + i] != A[p[2] + i] || A[p[0] + i] != A[p[4] + i]) 
            return res;

    res[0] = i; res[1] = j;
    return res;
}