• C++前缀和算法的应用:得到连续 K 个 1 的最少相邻交换次数 原理源码测试用例


    本文涉及的基础知识点

    C++算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频
    C++算法:滑动窗口总结

    LeetCode1703得到连续 K 个 1 的最少相邻交换次数

    给你一个整数数组 nums 和一个整数 k 。 nums 仅包含 0 和 1 。每一次移动,你可以选择 相邻 两个数字并将它们交换。
    请你返回使 nums 中包含 k 个 连续 1 的 最少 交换次数。
    示例 1:
    输入:nums = [1,0,0,1,0,1], k = 2
    输出:1
    解释:在第一次操作时,nums 可以变成 [1,0,0,0,1,1] 得到连续两个 1 。
    示例 2:
    输入:nums = [1,0,0,0,0,0,1,1], k = 3
    输出:5
    解释:通过 5 次操作,最左边的 1 可以移到右边直到 nums 变为 [0,0,0,0,0,1,1,1] 。
    示例 3:

    输入:nums = [1,1,0,1], k = 2
    输出:0
    解释:nums 已经有连续 2 个 1 了。
    提示:
    1 <= nums.length <= 105
    nums[i] 要么是 0 ,要么是 1 。
    1 <= k <= sum(nums)

    分析

    假定

    nums[left]和nums[r]都是1,且nums[left,r]共有k个1。

    左移(右移)的顺序不影响结果

    换种思考方式,将0移出。假定left < m0 < m1 < r。先移m0,移动m1,需要m0-left,移动m1,需要m1-(left+1),共需要m0+m1-left2-1。先移m1,移动m0,需要m1-left,移动m0,需要m0-(left+1),共需要m0+m1-left2-1。
    公式:如果有n个数左移,则交换次数为:这些数距离left的和-n*(n-1)/2。

    如果m1左移更划算,那么m0左移也更划算

    m0相比与m1,左移消耗更少,右移消耗更多。显然左移更划算。右移类似。

    代码解释

    vOneIndex依次记录了nums[i]等于1的索引。m是[left,r]中第一个右移划算的0。
    [left,m)中的0 左移,[m,end)中的0右移。由于nums[end]是1,所以[m,end]中的0,就是[m,end)中的0。
    v0Dis[m] - v0Dis[left][left,m)中的0 全部移到索引0处需要的交换次数。
    left * iLeft0Num[left,m)中的0 全部从left移动0的交换次数。
    两种相减就是 [left,m)中的0 全部 移动到left,处需要的交换次数。
    r * iRight0Num是[m,r)中的0全部从r移动到0。
    v0Dis[r] - v0Dis[m ][m,r)中的0全部移动到0。
    两者相减就是[m,r)的0全部移到r需要的次数。

    时间复杂度

    O(n)。枚举i,时间复杂度O(n);枚举m,时间复杂度O(n)。注意m不是从头开始,所以枚举m的总时间复杂度是O(n),而不是每个i都是O(n)。

    代码

    核心代码

    class Solution {
    public:
    int minMoves(vector& nums, int k) {
    m_c = nums.size();
    vector vOneIndex;
    for (int i = 0; i < m_c ; i++)
    {
    if (1 == nums[i])
    {
    vOneIndex.emplace_back(i);
    }
    }
    vector v0Dis = { 0 };//记录nums[0,i)中,nums[i]等于0时 i之和,也就是将所有nums[i]移到0处
    for (int i = 0; i < m_c; i++)
    {
    long long llAdd = (0 == nums[i]) ? i : 0;
    v0Dis.emplace_back(llAdd+v0Dis.back());
    }
    vector v0Num = { 0 };//记录nums[0,i)中0的个数
    for (const auto& n : nums)
    {
    v0Num.emplace_back(v0Num.back() + (0==n));
    }
    long long llRet = INT_MAX;
    int m = 0;
    for (int i = 0; i + k - 1 < vOneIndex.size(); i++)
    {
    const int left = vOneIndex[i];
    const int r = vOneIndex[i + k - 1];
    if (m < left)
    {
    m = left + 1;
    }
    for (; m < r; m++)
    {
    if (1 == nums[m])
    {
    continue;
    }
    //[left,m)中的0 左移
    const int iLeft0Num = v0Num[m] - v0Num[left];
    //[m,end)中的0右移,由于nums[end]是1,所以[m,end]中的0,就是[m,end)中的0
    const int iRight0Num = v0Num[r] - v0Num[m];
    const int iLeftCur = m - left - iLeft0Num;
    const int iRightCur = r - m - (iRight0Num - 1);
    if (iRightCur <= iLeftCur)
    {
    break;
    }
    }
    //m 等于r,也符合下面的逻辑[left,r)和[r,r),右移为空
    const long long iLeft0Num = v0Num[m] - v0Num[left];
    const long long iRight0Num = v0Num[r] - v0Num[m];
    const long long llLeftMove = v0Dis[m] - v0Dis[left] - left * iLeft0Num - (iLeft0Num - 1) * iLeft0Num / 2;
    const long long llRightMove = r * iRight0Num - (v0Dis[r] - v0Dis[m]) - (iRight0Num - 1) * iRight0Num / 2;
    llRet = min(llRet, llLeftMove + llRightMove);
    }
    return llRet;
    }
    int m_c;
    };

    测试用例

    template
    void Assert(const vector& v1, const vector& v2)
    {
    if (v1.size() != v2.size())
    {
    assert(false);
    return;
    }
    for (int i = 0; i < v1.size(); i++)
    {
    assert(v1[i] == v2[i]);
    }
    }

    template
    void Assert(const T& t1, const T& t2)
    {
    assert(t1 == t2);
    }

    int main()
    {
    vector nums = { 1, 0, 0, 1, 0, 1 };
    int k = 2;
    auto res = Solution().minMoves(nums,k);
    Assert(1, res);
    nums = { 1, 0, 0, 0, 0, 0, 1, 1 };
    k = 3;
    res = Solution().minMoves(nums, k);
    Assert(5, res);
    nums = { 1,1,0,1 };
    k = 2;
    res = Solution().minMoves(nums, k);
    Assert(0, res);

    //CConsole::Out(res);
    
    • 1

    }

    扩展阅读

    视频课程

    有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
    https://edu.csdn.net/course/detail/38771

    如何你想快

    速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
    https://edu.csdn.net/lecturer/6176

    相关下载

    想高屋建瓴的学习算法,请下载《闻缺陷则喜算法册》doc版
    https://download.csdn.net/download/he_zhidan/88348653

    鄙人想对大家说的话
    闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
    墨家名称的来源:有所得以墨记之。
    如果程序是一条龙,那算法就是他的是睛

    测试环境

    操作系统:win7 开发环境: VS2019 C++17
    或者 操作系统:win10 开发环境:

    VS2022 C++17

  • 相关阅读:
    合合信息、上海大学、华南理工大学发布业内首个古彝文编码“大字典” ,为古文字打造“身份证”
    动态爱心-详细教程(小白也会)(HTML)
    【C++STL基础入门】stack栈的增删查等操作的使用
    SOA、分布式、微服务
    Lua系列文章(1)---Lua5.4参考手册学习总结
    工业级Netty网关,京东是如何架构的?
    114. 二叉树展开为链表
    LTE小区搜索过程及SCH/BCH设计
    Python酷库之旅-比翼双飞情侣库(17)
    《艾尔登法环 黄金树幽影》是什么?Mac电脑怎么玩《艾尔登法环》艾尔登法环下载
  • 原文地址:https://blog.csdn.net/he_zhidan/article/details/133963896