C++算法:最少翻转操作数

简介: C++算法:最少翻转操作数

题目

给你一个整数 n 和一个在范围 [0, n - 1] 以内的整数 p ,它们表示一个长度为 n 且下标从 0 开始的数组 arr ,数组中除了下标为 p 处是 1 以外,其他所有数都是 0 。

同时给你一个整数数组 banned ,它包含数组中的一些位置。banned 中第 i 个位置表示 arr[banned[i]] = 0 ,题目保证 banned[i] != p 。

你可以对 arr 进行 若干次 操作。一次操作中,你选择大小为 k 的一个 子数组 ,并将它 翻转 。在任何一次翻转操作后,你都需要确保 arr 中唯一的 1 不会到达任何 banned 中的位置。换句话说,arr[banned[i]] 始终 保持 0 。

请你返回一个数组 ans ,对于 [0, n - 1] 之间的任意下标 i ,ans[i] 是将 1 放到位置 i 处的 最少 翻转操作次数,如果无法放到位置 i 处,此数为 -1 。

子数组 指的是一个数组里一段连续 非空 的元素序列。

对于所有的 i ,ans[i] 相互之间独立计算。

将一个数组中的元素 翻转 指的是将数组中的值变成 相反顺序 。

1 <= n <= 105

0 <= p <= n - 1

0 <= banned.length <= n - 1

0 <= banned[i] <= n - 1

1 <= k <= n

banned[i] != p

banned 中的值 互不相同

用例分析

不考虑banned。假定1在i出,翻转[i,i+k),则1到了i+k-1处。翻转[j,j+k-1],翻转之前,i距离子数组的开始i-j,那么翻转后,1的距离子数组的结束i-j,即:j+k-1-(i-j)= k+2*j-i-1=k-i-1+2*j

j的取值范围为:[i-k+1,i],同时[0,n-k]

n=5,k=4

n=5,k=4

10000

00010

k-i-1=3新位置为:3+0=3

01000

00100 00001

k-i-1=2新位置为:2+0=2 2+2=4

00100

01000 00010

k-i-1=1新位置为:1+0=1 1+2=3

00010

10000 00100

k-i-1=0新位置为:0+0=0 0+2=2

00001

01000

k-i-1=-1新位置为:  -1+2=1

注意

p已经处理。

核心代码

class Solution {
public:
  vector<int> minReverseOperations(int n, int p, vector<int>& banned, int k) {
    vector<int> vRet(n, -1);
    vRet[p] = 0;
    queue<int> que;
    que.emplace(p);
    set<int> set0, set1;
    for (int i = 0; i < n; i++)
    {
      if (i & 1)
      {
        set1.emplace(i);
      }
      else
      {
        set0.emplace(i);
      }
    }
    for (const auto& b : banned)
    {
      set0.erase(b);
      set1.erase(b);
    }
    set0.erase(p);
    set1.erase(p);
    while (que.size())
    {
      const auto cur = que.front();
      que.pop();
      const int j0 = max(0, cur - k + 1);
      const int j1 = min(cur, n - k);
      const int next0 = k - cur - 1 + 2 * j0;
      const int next1 = k - cur - 1 + 2 * j1;
      auto& setCur = (next0 & 1) ? set1 : set0;
      auto it0 = setCur.lower_bound(next0);
      auto it1 = setCur.upper_bound(next1 + 1);
      for (auto it = it0; it != it1; ++it)
      {
        vRet[*it] = vRet[cur] + 1;
        que.emplace(*it);
      }
      setCur.erase(it0, it1);
    }
    return vRet;
  }
};

时间复杂度

广度优先实现,入队n次。每次出队时间复杂度O(k),总时间复杂度O(kn)。出队的时间复杂度可以优化。每次出队是连续的奇数或偶数,我们可以用两个std::set分别纪录未处理的奇数和偶数。未处理分两种情况:已经处理,被禁止。每次出队只需要查询两次,时间复杂度O(logn)。故总时间复杂度为O(nlogn)。

并集查找

如果i是偶数,vDo[i]记录需要处理的偶数中,大于等于i,且最小的数。奇数类似。以n等于8为例,只分析偶数,奇数类似。初始{0,2,4,6}

{0,2,4,6}

处理0{2,2,4,6} 处理2{0,4,4,6} 处理4{0,2,6,6} 处理6{0,2,4,MAX}

2,2,4,6

处理0{2,4,4,6}处理2{2,4,4,6} 处理4{2,2,6,6}处理6{2,2,6,MAX}

{2,4,4,6}

处理0{4,4,6,6},处理2{2,4,6,6}处理4{2,4,6,6}处理6{2,4,4,MAX}

非常类似并集查找,由于i一定小于vDo[i],所以不会有环。可以直接使用封装好的有向图的并集查找。

代码

 

class Solution {
public:
  vector<int> minReverseOperations(int n, int p, vector<int>& banned, int k) {
    vector<int> vRet(n, -1);
    vRet[p] = 0;
    queue<int> que;
    que.emplace(p);
    vector<int> vNext(n);
    for (int i = 0; i < n; i++)
    {
      vNext[i] = i;
    }   
    const int iMax = 1000 * 1000;
    auto HasDo = [&](int b)
    {
      vNext[b] = (b + 2 < n) ? vNext[b + 2] : iMax;
    };
    banned.emplace_back(p);
    for (const auto& b : banned)
    {
      HasDo(b);
    }
    while (que.size())
    {
      const auto cur = que.front();
      que.pop();
      const int j0 = max(0, cur - k + 1);
      const int j1 = min(cur, n - k);
       int next0 = k - cur - 1 + 2 * j0;
      const int next1 = k - cur - 1 + 2 * j1;
      for (next0 = GetNext(vNext, next0, iMax); next0 <= next1;)
      {
        vRet[next0] = vRet[cur] + 1;
        HasDo(next0);
        que.emplace(next0);
        next0 = GetNext(vNext, next0, iMax);
      }
    }
    return vRet;
  }
  int GetNext(vector<int>& vNext,const int b,const int iMax)
  {
    if ((iMax == b) || (b == vNext[b]))
    {
      return b;
    };
    return vNext[b]= GetNext(vNext, vNext[b],iMax);
  };
};

2023年4月版本

class Solution {
public:
    vector<int> minReverseOperations(const int n, const int p, vector<int>& banned, int k) {
        std::set<int> setOdd, setEven;
        {
            vector<bool> vBanned(n);
            for (const auto& b : banned)
            {
                vBanned[b] = true;
            }
            for (int i = 0; i < n; i++)
            {
                if (p == i)
                {
                    continue;
                }
                if (vBanned[i])
                {
                    continue;
                }
                if (i & 1)
                {
                    setOdd.emplace(i);
                }
                else
                {
                    setEven.emplace(i);
                }
            }
        }
        std::queue<int> que;
        que.emplace(p);
        vector<int> vDis(n, -1);
        vDis[p] = 0;
        for (int i = 0; que.size(); i++)
        {
            std::queue<int> queNext;
            while (que.size())
            {
                const int iCur = que.front();
                que.pop();
                std::set<int>& setNoDo = ((iCur + k - 1) & 1) ? setOdd : setEven;
                const int iLower = max(iCur - k + 1, k - iCur - 1);
                const int iUpper = min(iCur + k - 1, 2 * n - k - iCur - 1);
                const auto it1 = setNoDo.lower_bound(iLower);
                const auto it2 = setNoDo.upper_bound(iUpper);
                for (auto tmp = it1; tmp != it2; ++tmp)
                {
                    queNext.emplace(*tmp);
                    vDis[*tmp] = i + 1;
                }
                setNoDo.erase(it1, it2);
            }
            que.swap(queNext);
        }
        return vDis;
    }
};

2023年8月

class Solution {
public:
    vector<int> minReverseOperations(int n, int p, vector<int>& banned, int k) {
        std::set<int> setNeedDo[2];
        for (int i = 0; i < n; i++)
        {
            setNeedDo[i % 2].emplace(i);
        }
        for (const auto& n : banned)
        {
            setNeedDo[n % 2].erase(n);
        }
        vector<int> vRet(n, -1);
        vRet[p] = 0;
        setNeedDo[p % 2].erase(p);
        queue<int> que;
        que.emplace(p);
        while (que.size())
        {
            const auto cur = que.front();
            que.pop();
            const int leftMin = max(cur - (k - 1), 0);
            const int rightMin = leftMin + (k - 1);
            const int leftMax = min(cur, n - 1 - (k - 1));
            const int iPosMin = rightMin - (cur - leftMin);//翻转后的位置,子数组右移一位,翻转后的位置移动2位
            const int index = iPosMin % 2;
            auto it = setNeedDo[index].lower_bound(iPosMin);
            auto ij = setNeedDo[index].upper_bound(iPosMin + 2 * (leftMax - leftMin));
            for (auto tmp = it; tmp != ij; ++tmp)
            {
                vRet[*tmp] = vRet[cur] + 1;
                que.emplace(*tmp);
            }
            setNeedDo[index].erase(it, ij);
        }
        return vRet;
    }
};

其它

视频课程

如果你觉得复杂,想从简单的算法开始,可以学习我的视频课程。

https://edu.csdn.net/course/detail/38771

我的其它课程

https://edu.csdn.net/lecturer/6176

测试环境

win7 VS2019 C++17

 相关下载

算法精讲《闻缺陷则喜算法册》doc版

https://download.csdn.net/download/he_zhidan/88348653


相关文章
|
6月前
|
存储 监控 算法
基于 C++ 哈希表算法实现局域网监控电脑屏幕的数据加速机制研究
企业网络安全与办公管理需求日益复杂的学术语境下,局域网监控电脑屏幕作为保障信息安全、规范员工操作的重要手段,已然成为网络安全领域的关键研究对象。其作用类似网络空间中的 “电子眼”,实时捕获每台电脑屏幕上的操作动态。然而,面对海量监控数据,实现高效数据存储与快速检索,已成为提升监控系统性能的核心挑战。本文聚焦于 C++ 语言中的哈希表算法,深入探究其如何成为局域网监控电脑屏幕数据处理的 “加速引擎”,并通过详尽的代码示例,展现其强大功能与应用价值。
158 2
|
7月前
|
存储 算法 C++
Windows共享文件:探秘C++实现的B树索引算法奇境
在数字化时代,Windows共享文件的高效管理至关重要。B树算法以其自平衡多路搜索特性,在文件索引与存储优化中表现出色。本文探讨B树在Windows共享文件中的应用,通过C++实现具体代码,展示其构建文件索引、优化数据存储的能力,提升文件检索效率。B树通过减少磁盘I/O操作,确保查询高效,为企业和个人提供流畅的文件共享体验。
|
4月前
|
存储 监控 算法
基于跳表数据结构的企业局域网监控异常连接实时检测 C++ 算法研究
跳表(Skip List)是一种基于概率的数据结构,适用于企业局域网监控中海量连接记录的高效处理。其通过多层索引机制实现快速查找、插入和删除操作,时间复杂度为 $O(\log n)$,优于链表和平衡树。跳表在异常连接识别、黑名单管理和历史记录溯源等场景中表现出色,具备实现简单、支持范围查询等优势,是企业网络监控中动态数据管理的理想选择。
148 0
|
6月前
|
监控 算法 数据处理
基于 C++ 的 KD 树算法在监控局域网屏幕中的理论剖析与工程实践研究
本文探讨了KD树在局域网屏幕监控中的应用,通过C++实现其构建与查询功能,显著提升多维数据处理效率。KD树作为一种二叉空间划分结构,适用于屏幕图像特征匹配、异常画面检测及数据压缩传输优化等场景。相比传统方法,基于KD树的方案检索效率提升2-3个数量级,但高维数据退化和动态更新等问题仍需进一步研究。未来可通过融合其他数据结构、引入深度学习及开发增量式更新算法等方式优化性能。
181 17
|
5月前
|
存储 机器学习/深度学习 算法
基于 C++ 的局域网访问控制列表(ACL)实现及局域网限制上网软件算法研究
本文探讨局域网限制上网软件中访问控制列表(ACL)的应用,分析其通过规则匹配管理网络资源访问的核心机制。基于C++实现ACL算法原型,展示其灵活性与安全性。文中强调ACL在企业与教育场景下的重要作用,并提出性能优化及结合机器学习等未来研究方向。
151 4
|
5月前
|
机器学习/深度学习 存储 算法
基于 C++ 布隆过滤器算法的局域网上网行为控制:URL 访问过滤的高效实现研究
本文探讨了一种基于布隆过滤器的局域网上网行为控制方法,旨在解决传统黑白名单机制在处理海量URL数据时存储与查询效率低的问题。通过C++实现URL访问过滤功能,实验表明该方法可将内存占用降至传统方案的八分之一,查询速度提升约40%,假阳性率可控。研究为优化企业网络管理提供了新思路,并提出结合机器学习、改进哈希函数及分布式协同等未来优化方向。
160 0
|
7月前
|
存储 监控 算法
基于 C++ 哈希表算法的局域网如何监控电脑技术解析
当代数字化办公与生活环境中,局域网的广泛应用极大地提升了信息交互的效率与便捷性。然而,出于网络安全管理、资源合理分配以及合规性要求等多方面的考量,对局域网内计算机进行有效监控成为一项至关重要的任务。实现局域网内计算机监控,涉及多种数据结构与算法的运用。本文聚焦于 C++ 编程语言中的哈希表算法,深入探讨其在局域网计算机监控场景中的应用,并通过详尽的代码示例进行阐释。
161 4
|
8月前
|
存储 算法 安全
企业员工数据泄露防范策略:基于 C++ 语言的布隆过滤器算法剖析[如何防止员工泄密]
企业运营过程中,防范员工泄密是信息安全领域的核心议题。员工泄密可能致使企业核心数据、商业机密等关键资产的流失,进而给企业造成严重损失。为应对这一挑战,借助恰当的数据结构与算法成为强化信息防护的有效路径。本文专注于 C++ 语言中的布隆过滤器算法,深入探究其在防范员工泄密场景中的应用。
187 8
|
9月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
5月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
158 0

热门文章

最新文章