leetcode 122双周赛 解题思路+代码

文章介绍了如何解决三个编程题目,包括将数组划分为代价最小的子数组、判断数组通过元素交换能否变为有序,以及通过操作使数组长度达到最小。解题策略涉及动态规划、并查集和二进制操作的应用。
摘要由CSDN通过智能技术生成

本人水平有限,只做出3道,最后1道放弃。

一.将数组分成最小总代价的子数组 I

给你一个长度为 n 的整数数组 nums 。

一个数组的 代价 是它的 第一个 元素。比方说,[1,2,3] 的代价是 1 ,[3,4,1] 的代价是 3 。

你需要将 nums 分成 3 个 连续且没有交集 的子数组。

请你返回这些子数组的 最小 代价 总和 。

示例 1:

输入:nums = [1,2,3,12]
输出:6
解释:最佳分割成 3 个子数组的方案是:[1] ,[2] 和 [3,12] ,总代价为 1 + 2 + 3 = 6 。
其他得到 3 个子数组的方案是:

  • [1] ,[2,3] 和 [12] ,总代价是 1 + 2 + 12 = 15 。
  • [1,2] ,[3] 和 [12] ,总代价是 1 + 3 + 12 = 16 。
    示例 2:

输入:nums = [5,4,3]
输出:12
解释:最佳分割成 3 个子数组的方案是:[5] ,[4] 和 [3] ,总代价为 5 + 4 + 3 = 12 。
12 是所有分割方案里的最小总代价。
示例 3:

输入:nums = [10,3,1,1]
输出:12
解释:最佳分割成 3 个子数组的方案是:[10,3] ,[1] 和 [1] ,总代价为 10 + 1 + 1 = 12 。
12 是所有分割方案里的最小总代价。

解题思路

题目要求将原数组分割为三个子数组,每个子数组的第一个元素为该子数组的代价,求分割方案中三个子数组的最小总代价

设定两个指针i和j作为边界分割,三个子数组的区间范围为[0,i-1],[i,j-1],[j,m]
花销的表达式为nums[0]+nums[i]+nums[j]

代码
class Solution {
    public int minimumCost(int[] nums) {
        int n = nums.length;
        int minCost = Integer.MAX_VALUE;
        for (int i = 1; i < n-1; i++) {
            for (int j = i+1; j < n; j++) {
                minCost = Math.min(minCost, nums[0] + nums[i] + nums[j]);
            }
        }
        return minCost;
    }
}

二、判断一个数组是否可以变为有序

题目

给你一个下标从 0 开始且全是 正 整数的数组 nums 。

一次 操作 中,如果两个 相邻 元素在二进制下数位为 1 的数目 相同 ,那么你可以将这两个元素交换。你可以执行这个操作 任意次 (也可以 0 次)。

如果你可以使数组变有序,请你返回 true ,否则返回 false 。

示例 1:

输入:nums = [8,4,2,30,15]
输出:true
解释:我们先观察每个元素的二进制表示。 2 ,4 和 8 分别都只有一个数位为 1 ,分别为 “10” ,“100” 和 “1000” 。15 和 30 分别有 4 个数位为 1 :“1111” 和 “11110” 。
我们可以通过 4 个操作使数组有序:

  • 交换 nums[0] 和 nums[1] 。8 和 4 分别只有 1 个数位为 1 。数组变为 [4,8,2,30,15] 。
  • 交换 nums[1] 和 nums[2] 。8 和 2 分别只有 1 个数位为 1 。数组变为 [4,2,8,30,15] 。
  • 交换 nums[0] 和 nums[1] 。4 和 2 分别只有 1 个数位为 1 。数组变为 [2,4,8,30,15] 。
  • 交换 nums[3] 和 nums[4] 。30 和 15 分别有 4 个数位为 1 ,数组变为 [2,4,8,15,30] 。
    数组变成有序的,所以我们返回 true 。
    注意我们还可以通过其他的操作序列使数组变得有序。
    示例 2:

输入:nums = [1,2,3,4,5]
输出:true
解释:数组已经是有序的,所以我们返回 true 。
示例 3:

输入:nums = [3,16,8,4,2]
输出:false
解释:无法通过操作使数组变为有序。

解题思路

题目要求我们只能对在二进制下数位为1的数量相同的数字之间进行交换,判断数组是否可以变得有序。
初始思路考虑过,构建一个数组表示原始数组元素二进制形式数位为1的数量,构建另一个数组表示排序后的原始数组二进制形式数位为1的数量,并通过Arrays.equals()进行比较。但是,要考虑到特殊情况数位小的数字可能比数位大的数字要大,例如:4的数位为1,3的数位却为2.

最终考虑到题目要求允许交换二进制中1的数目相同的相邻元素,符合动态连通性的定义,考虑通过并查集解决问题。先克隆一个原始数组,并对其进行排序,以获取每个元素应该存放的位置,并将相邻元素间二进制数位为1数量相同的进行合并,合并后,对每个位置上的现有元素和应存放元素进行查找,判断这两个元素是否具有相同的关键元素,从而判断原始元素是否能够通过元素交换交换到正确的位置上。

代码
class Solution {
    public boolean canSortArray(int[] nums) {
        int n = nums.length;
        // nums1 是 nums 的一个副本,用于排序
        int[] nums1 = nums.clone();
        Arrays.sort(nums1);

        // 初始化并查集的数组
        int[] parent = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }

        // d 是一个映射,用于记录原数组中每个数字的位置
        Map<Integer, Integer> d = new HashMap<>();
        for (int i = 0; i < n; i++) {
            d.put(nums[i], i);
        }

        // 遍历数组,将二进制1的数量相同的相邻元素合并
        for (int i = 1; i < n; i++) {
            if (Integer.bitCount(nums[i]) == Integer.bitCount(nums[i - 1])) {
                union(parent, i, i - 1);
            }
        }

        // 检查排序后的数组是否可以通过交换特定的相邻元素变为原数组的顺序
        for (int i = 0; i < n; i++) {
            if (nums[i] != nums1[i] && find(parent, i) != find(parent, d.get(nums1[i]))) {
                return false;
            }
        }
        return true;
    }

    // find 函数用于查找元素所在集合的代表元素
    private int find(int[] parent, int x) {
        if (parent[x] != x) {
            parent[x] = find(parent, parent[x]);
        }
        return parent[x];
    }

    // union 函数用于合并两个元素所在的集合
    private void union(int[] parent, int x, int y) {
        int fx = find(parent, x);
        int fy = find(parent, y);
        if (fx != fy) {
            parent[fx] = fy;
        }
    }
}
并查集

适用场景:涉及多个元素分组和组间关系的场景。
动态连接问题

  1. 合并(Union):
    将两个符合相同规则的独立集合合并为一个集合。

  2. 查询(Find):
    找到这个集合的"根",便于我们快速检查两个元素是否属于一个集合。

代码模板:

...
// 初始化并查集数组
int[] parent = new int[n];
for(int i = 0;i < n;i++){
	parent[i] = i;// parent[i]表示元素`i`在并查集中的代表元素的位置
}

// find 函数用于查找元素所在集合的代表元素
private int find(int[] parent,int x){
	if(parent[x] != x){
		parent[x] = find(parent,parent[x]);
	}
	return parent[x];
}

// union 函数用于合并两个元素所在的集合
private void union(int[] parent,int x,int y){
	int fx = find(parent,x);
	int fy = find(parent,y);
	if(fx!=fy){
		parent[fx] = fy;
	}
}

三、通过操作使数组长度最小

题目

给你一个下标从 0 开始的整数数组 nums ,它只包含 正 整数。

你的任务是通过进行以下操作 任意次 (可以是 0 次) 最小化 nums 的长度:

在 nums 中选择 两个不同 的下标 i 和 j ,满足 nums[i] > 0 且 nums[j] > 0 。
将结果 nums[i] % nums[j] 插入 nums 的结尾。
将 nums 中下标为 i 和 j 的元素删除。
请你返回一个整数,它表示进行任意次操作以后 nums 的 最小长度 。

示例 1:

输入:nums = [1,4,3,1]
输出:1
解释:使数组长度最小的一种方法是:
操作 1 :选择下标 2 和 1 ,插入 nums[2] % nums[1] 到数组末尾,得到 [1,4,3,1,3] ,然后删除下标为 2 和 1 的元素。
nums 变为 [1,1,3] 。
操作 2 :选择下标 1 和 2 ,插入 nums[1] % nums[2] 到数组末尾,得到 [1,1,3,1] ,然后删除下标为 1 和 2 的元素。
nums 变为 [1,1] 。
操作 3 :选择下标 1 和 0 ,插入 nums[1] % nums[0] 到数组末尾,得到 [1,1,0] ,然后删除下标为 1 和 0 的元素。
nums 变为 [0] 。
nums 的长度无法进一步减小,所以答案为 1 。
1 是可以得到的最小长度。
示例 2:

输入:nums = [5,5,5,10,5]
输出:2
解释:使数组长度最小的一种方法是:
操作 1 :选择下标 0 和 3 ,插入 nums[0] % nums[3] 到数组末尾,得到 [5,5,5,10,5,5] ,然后删除下标为 0 和 3 的元素。
nums 变为 [5,5,5,5] 。
操作 2 :选择下标 2 和 3 ,插入 nums[2] % nums[3] 到数组末尾,得到 [5,5,5,5,0] ,然后删除下标为 2 和 3 的元素。
nums 变为 [5,5,0] 。
操作 3 :选择下标 0 和 1 ,插入 nums[0] % nums[1] 到数组末尾,得到 [5,5,0,0] ,然后删除下标为 0 和 1 的元素。
nums 变为 [0,0] 。
nums 的长度无法进一步减小,所以答案为 2 。
2 是可以得到的最小长度。
示例 3:

输入:nums = [2,3,4]
输出:1
解释:使数组长度最小的一种方法是:
操作 1 :选择下标 1 和 2 ,插入 nums[1] % nums[2] 到数组末尾,得到 [2,3,4,3] ,然后删除下标为 1 和 2 的元素。
nums 变为 [2,3] 。
操作 2 :选择下标 1 和 0 ,插入 nums[1] % nums[0] 到数组末尾,得到 [2,3,1] ,然后删除下标为 1 和 0 的元素。
nums 变为 [1] 。
nums 的长度无法进一步减小,所以答案为 1 。
1 是可以得到的最小长度。

提示:

1 <= nums.length <= 105
1 <= nums[i] <= 109

解题思路

题目要求我们对数组选取下标i,j(nums[i]>0 nums[j]>0),插入nums[i]%nums[j],并且删除nums[i],nums[j]。根据这个规则,求出数组的最小长度。
首先对nums[i]%nums[j]进行分类讨论
① nums[i]<nums[j]
nums[i]%nums[j]=nums[i],最终保留nums[i],剔除nums[j],理解为小数踢大数
② nums[i]>=nums[j]
A.非整除
显然nums[i]%nums[j]<nums[j]<=nums[i],得到一个更小数,这个更小数可以提走所有大数,结果为1(这也是最优情况)
B.整除
如果大数整除小数,生成0,会占位置,因为nums[i]=0不能参与规则,因此考虑小数整除大数。并且分类讨论奇数和偶数个最小值的情况,总结出规律(m+1)//2

对规律进行提炼优化,即考虑遍历所有数并判断是否能够整除最小数,如果不能,结果为1;如果能,再获取最小数的数量,并且利用(m+1)//2计算。

代码
class Solution {
    public int minimumArrayLength(int[] nums) {
        int m = findMin(nums);
        for (int num : nums) {
            if (num % m != 0) { // 可以产生新的最小值
                return 1;
            }
        }
        int length = count(nums, m);
        return (length + 1) / 2;  // 向上取整
    }

    // 寻找数组中的最小值
    private int findMin(int[] nums) {
        int minVal = nums[0];
        for (int num : nums) {
            if (num < minVal) {
                minVal = num;
            }
        }
        return minVal;
    }

    // 计算最小值在数组中出现的次数
    private int count(int[] nums, int m) {
        int count = 0;
        for (int num : nums) {
            if (num == m) {
                count++;
            }
        }
        return count;
    }
}
  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Byyyi耀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值