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


    LeetCode2612题目

    给你一个整数 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已经处理。


    核心代码

    1. class Solution {
    2. public:
    3.   vector<int> minReverseOperations(int n, int p, vector<int>& banned, int k) {
    4.     vector<int> vRet(n, -1);
    5.     vRet[p] = 0;
    6.     queue<int> que;
    7.     que.emplace(p);
    8.     set<int> set0, set1;
    9.     for (int i = 0; i < n; i++)
    10.     {
    11.       if (i & 1)
    12.       {
    13.         set1.emplace(i);
    14.       }
    15.       else
    16.       {
    17.         set0.emplace(i);
    18.       }
    19.     }
    20.     for (const auto& b : banned)
    21.     {
    22.       set0.erase(b);
    23.       set1.erase(b);
    24.     }
    25.     set0.erase(p);
    26.     set1.erase(p);
    27.     while (que.size())
    28.     {
    29.       const auto cur = que.front();
    30.       que.pop();
    31.       const int j0 = max(0, cur - k + 1);
    32.       const int j1 = min(cur, n - k);
    33.       const int next0 = k - cur - 1 + 2 * j0;
    34.       const int next1 = k - cur - 1 + 2 * j1;
    35.       auto& setCur = (next0 & 1) ? set1 : set0;
    36.       auto it0 = setCur.lower_bound(next0);
    37.       auto it1 = setCur.upper_bound(next1 + 1);
    38.       for (auto it = it0; it != it1; ++it)
    39.       {
    40.         vRet[*it] = vRet[cur] + 1;
    41.         que.emplace(*it);
    42.       }
    43.       setCur.erase(it0, it1);
    44.     }
    45.     return vRet;
    46.   }
    47. };

    时间复杂度

    广度优先实现,入队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],所以不会有环。可以直接使用封装好的有向图的并集查找。

    代码


     

    1. class Solution {
    2. public:
    3.   vector<int> minReverseOperations(int n, int p, vector<int>& banned, int k) {
    4.     vector<int> vRet(n, -1);
    5.     vRet[p] = 0;
    6.     queue<int> que;
    7.     que.emplace(p);
    8.     vector<int> vNext(n);
    9.     for (int i = 0; i < n; i++)
    10.     {
    11.       vNext[i] = i;
    12.     }   
    13.     const int iMax = 1000 * 1000;
    14.     auto HasDo = [&](int b)
    15.     {
    16.       vNext[b] = (b + 2 < n) ? vNext[b + 2] : iMax;
    17.     };
    18.     
    19.     banned.emplace_back(p);
    20.     for (const auto& b : banned)
    21.     {
    22.       HasDo(b);
    23.     }
    24.     while (que.size())
    25.     {
    26.       const auto cur = que.front();
    27.       que.pop();
    28.       const int j0 = max(0, cur - k + 1);
    29.       const int j1 = min(cur, n - k);
    30.        int next0 = k - cur - 1 + 2 * j0;
    31.       const int next1 = k - cur - 1 + 2 * j1;
    32.       
    33.       for (next0 = GetNext(vNext, next0, iMax); next0 <= next1;)
    34.       {
    35.         vRet[next0] = vRet[cur] + 1;
    36.         HasDo(next0);
    37.         que.emplace(next0);
    38.         next0 = GetNext(vNext, next0, iMax);
    39.       }
    40.     }
    41.     return vRet;
    42.   }
    43.   int GetNext(vector<int>& vNext,const int b,const int iMax)
    44.   {
    45.     if ((iMax == b) || (b == vNext[b]))
    46.     {
    47.       return b;
    48.     };
    49.     return vNext[b]= GetNext(vNext, vNext[b],iMax);
    50.   };
    51. };

    2023年4月版本

    1. class Solution {
    2. public:
    3.     vector<int> minReverseOperations(const int n, const int p, vector<int>& banned, int k) {
    4.         std::set<int> setOdd, setEven;
    5.         {
    6.             vector<bool> vBanned(n);
    7.             for (const auto& b : banned)
    8.             {
    9.                 vBanned[b] = true;
    10.             }
    11.             for (int i = 0; i < n; i++)
    12.             {
    13.                 if (p == i)
    14.                 {
    15.                     continue;
    16.                 }
    17.                 if (vBanned[i])
    18.                 {
    19.                     continue;
    20.                 }
    21.                 if (i & 1)
    22.                 {
    23.                     setOdd.emplace(i);
    24.                 }
    25.                 else
    26.                 {
    27.                     setEven.emplace(i);
    28.                 }
    29.             }
    30.         }
    31.         std::queue<int> que;
    32.         que.emplace(p);
    33.         vector<int> vDis(n, -1);
    34.         vDis[p] = 0;
    35.         for (int i = 0; que.size(); i++)
    36.         {
    37.             std::queue<int> queNext;
    38.             while (que.size())
    39.             {
    40.                 const int iCur = que.front();
    41.                 que.pop();
    42.                 std::set<int>& setNoDo = ((iCur + k - 1) & 1) ? setOdd : setEven;
    43.                 const int iLower = max(iCur - k + 1, k - iCur - 1);
    44.                 const int iUpper = min(iCur + k - 1, 2 * n - k - iCur - 1);
    45.                 const auto it1 = setNoDo.lower_bound(iLower);
    46.                 const auto it2 = setNoDo.upper_bound(iUpper);
    47.                 for (auto tmp = it1; tmp != it2; ++tmp)
    48.                 {
    49.                     queNext.emplace(*tmp);
    50.                     vDis[*tmp] = i + 1;
    51.                 }
    52.                 setNoDo.erase(it1, it2);
    53.             }
    54.             que.swap(queNext);
    55.         }
    56.         return vDis;
    57.     }
    58. };

    2023年8月

    class Solution {
    public:
        vector minReverseOperations(int n, int p, vector& banned, int k) {
            std::set 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 vRet(n, -1);
            vRet[p] = 0;
            setNeedDo[p % 2].erase(p);
            queue 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
     

  • 相关阅读:
    拍摄视频,真的帧率越高越好吗?
    Jmeter 学习目录(0)
    [附源码]计算机毕业设计宁财二手物品交易网站Springboot程序
    Leetcode179. 最大数
    极简Emacs教程
    计算机毕业设计JavaWeb企业客户管理系统(源码+系统+mysql数据库+lw文档)
    设计模式学习(四):建造者模式
    Mac电脑上有什么好玩的格斗游戏 《真人快打1》可以在苹果电脑上玩吗
    OpenAI抓内鬼出奇招,奥特曼耍了所有人:GPT搜索鸽了!改升级GPT-4
    【MySQL架构篇】SQL执行流程与缓冲池
  • 原文地址:https://blog.csdn.net/he_zhidan/article/details/133776673