【每日一题Day60】LC1703得到连续 K 个 1 的最少相邻交换次数 | 中位数贪心

本文介绍了一种解决LeetCode 1703问题的方法,通过贪心策略计算将0-1数组转换为连续k个1所需的最少相邻交换次数。关键步骤包括构造p数组、计算前缀和并遍历可能的起始位置。优化版本简化了p数组的计算过程。

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

You are given an integer array, nums, and an integer k. nums comprises of only 0’s and 1’s. In one move, you can choose two adjacent indices and swap their values.

Return the minimum number of moves required so that nums has k consecutive 1’s.

太难了 终于想明白了

  • 思路:我们需要将数组中的1移动成连续的k的1,并计算最少的移动次数,每次移动只能移动相邻的两个元素。假设第 i i i个1的下标为 q i q_i qi,我们需要将第一个1 q 0 q_0 q0移动至位置 x x x,并移动第二个1 q 1 q_1 q1 x + 1 x+1 x+1,所需要的移动次数总和为
    ∑ i i + k − 1 ∣ q i − ( x + i ) ∣ = ∑ i i + k − 1 ∣ ( q i − i ) − x ∣ = ∑ i i + k − 1 ∣ p i − x ∣ \sum_{i}^{i+k-1}|q_i-(x+i)|=\sum_{i}^{i+k-1}|(q_i-i)-x|=\sum_{i}^{i+k-1}|p_i-x| ii+k1qi(x+i)=ii+k1(qii)x=ii+k1pix
    因此问题转变为计算所有 p i p_i pi x x x的距离和的最小值,贪心可得当 x x x p i p_i pi的中位数 p [ ( 2 i + k − 1 ) / 2 ] p[(2i+k-1)/2] p[(2i+k1)/2]时,距离之和最小。

    • 为什么?

      局部最优: x x x位于区间 [ p [ 0 ] , p [ k − 1 ] ] [p[0],p[k-1]] [p[0],p[k1]]内部时【无论位于哪个位置】,到 p [ 0 ] p[0] p[0] p [ k − 1 ] p[k-1] p[k1]的距离是定值 p [ k − 1 ] − p [ 0 ] p[k-1]-p[0] p[k1]p[0],因此应使 x x x位于所有区间的内部即中位数,使 x x x到所有区间的距离为定值,此时能够达到全局最优

      全局最优:所有 p i p_i pi x x x的距离和的最小值

    • 令中位数为 m i d = ( 2 i + k − 1 ) / 2 mid=(2i+k-1)/2 mid=(2i+k1)/2,那么此时的距离可计算得
      ∑ i k − 1 ∣ p i − x ∣ = ∑ j = i m i d − 1 ( p m i d − p j ) + ∑ j = m i d + 1 i + k − 1 ( p j − p m i d ) = ( m i d − i ) p m i d − ∑ j = i m i d − 1 p j + ∑ j = m i d + 1 i + k − 1 p j − − ( i + k − 1 − m i d ) p m i d = ( 2 ( m i d − i ) − k + 1 ) p m i d + ∑ j = m i d + 1 i + k − 1 p j − ∑ j = i m i d − 1 p j \sum_{i}^{k-1}|p_i-x|= \sum_{j=i}^{mid-1}(p_{mid}-p_j)+\sum_{j=mid+1}^{i+k-1}(p_j-p_{mid})\\ =(mid-i)p_{mid}-\sum_{j=i}^{mid-1}p_j+\sum_{j=mid+1}^{i+k-1}p_j--(i+k-1-mid)p_{mid}\\ =(2(mid-i)-k+1)p_{mid}+\sum_{j=mid+1}^{i+k-1}p_j-\sum_{j=i}^{mid-1}p_j ik1pix=j=imid1(pmidpj)+j=mid+1i+k1(pjpmid)=(midi)pmidj=imid1pj+j=mid+1i+k1pj(i+k1mid)pmid=(2(midi)k+1)pmid+j=mid+1i+k1pjj=imid1pj

    • 枚举第 i i i个1作为连续1的最左边那个1,求出此时的距离,返回最小距离即可

  • 实现

    • 找到所有1的位置存储在List类型的变量loc
    • 根据loc求出数组p
    • 求出p的前缀和数组pSum
    • 枚举最左边的1 求出最小值
    class Solution {
        public int minMoves(int[] nums, int k) {
            int n = nums.length;
            List<Integer> loc = new ArrayList<>();
            // 找到数组中1的位置
            for (int i = 0; i < n; i++){
                if (nums[i] == 1){
                    loc.add(i);
                }
            }
            // 求出p数组
            int m = loc.size();
            int[] p = new int[m];
            for (int i = 0; i < m; i++){
                p[i] = loc.get(i) - i;
            }
            // 前缀和数组
            int[] pSum = new int[m + 1];
            for (int i = 1; i <= m; i++){
                pSum[i] = pSum[i - 1] + p[i - 1];
            } 
            // 枚举左边的1 求出最小值      
            int ans = Integer.MAX_VALUE;
            for (int i = 0; i + k <= m; i++){
                int mid = (2 * i + k - 1) / 2;
                int temp = (2 * (mid - i) - k + 1) * p[mid] + pSum[i + k] - pSum[mid + 1] - (pSum[mid] - pSum[i]);
                ans = Math.min(ans, temp);
            }
            return ans;
        }
    }
    
    
    • 复杂度
      • 时间复杂度: O ( n + m ) O(n+m) O(n+m) n n n为数组长度, m m m为数组中1的个数
      • 空间复杂度: O ( n + m ) O(n+m) O(n+m)
  • 优化:直接求出数组p

    class Solution {
        public int minMoves(int[] nums, int k) {
            int n = nums.length;
            List<Integer> p = new ArrayList<>();
            // 求出p数组
            for (int i = 0; i < n; i++){
                if (nums[i] == 1){
                    p.add(i - p.size() );
                }
            }       
            int m = p.size();
            // 前缀和数组
            int[] pSum = new int[m + 1];
            for (int i = 1; i <= m; i++){
                pSum[i] = pSum[i - 1] + p.get(i - 1);
            } 
            // 枚举左边的1 求出最小值      
            int ans = Integer.MAX_VALUE;
            for (int i = 0; i + k <= m; i++){
                int mid = (2 * i + k - 1) / 2;
                int temp = (2 * (mid - i) - k + 1) * p.get(mid) + pSum[i + k] - pSum[mid + 1] - (pSum[mid] - pSum[i]);
                ans = Math.min(ans, temp);
            }
            return ans;
        }
    }
    
    
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值