小R的排序挑战 | 豆包MarsCode AI刷题
问题描述
小R有一个长度为 n 的排列,排列中的数字是 1 到 n 的整数。她每次操作可以选择两个数 a_i 和 a_j 进行交换,前提是这两个数的下标 i 和 j 的奇偶性相同(即同为奇数或同为偶数)。小R希望通过最少的操作使数组变成升序排列。
请你帮小R计算,最少需要多少次操作才能使得数组有序。如果不能通过这样的操作使数组有序,则输出 -1。
测试样例
样例1:
输入:
n = 5, a = [1, 4, 5, 2, 3]输出:2
样例2:
输入:
n = 4, a = [4, 3, 2, 1]输出:-1
样例3:
输入:
n = 6, a = [2, 4, 6, 1, 3, 5]输出:-1
样例4:
输入:
n = 1, a = [1]输出:0
解题思路
- 分组:首先,将数组分成两部分:奇数下标的元素和偶数下标的元素。
- 检查可排序性:分别检查这两部分是否可以通过交换操作变成有序的。如果其中任何一部分不能通过交换操作变成有序,则输出
-1。如果相邻两个数字是连续的,则输出-1。如果像 样例2 一样这种倒序的,可以考虑通过对比排序后的奇数列和排序后的偶数列的第一个元素,奇数列的大于偶数列的输出-1。 - 计算最小交换次数:如果两部分都可以通过交换操作变成有序,则分别计算将这两部分排序所需的最小交换次数,然后将这两个次数相加即为最终答案。
- 当数组长度为
1时,直接输出0。
代码详解
1.当数组长度等于1时,不用排序
if len(a)==1:
return 0
2.分组:
将数组分成奇数下标和偶数下标的两个子数组
odd_indices = [a[i] for i in range(0, n, 2)]
even_indices = [a[i] for i in range(1, n, 2)]
3.对分组后的两个子数组进行判断是否可以交换
将两个子数组进行排序,判断每个子数组里面是否有相邻的数字,如果有相邻的数字则不能通过交换使数组变成升序,则输出-1,如果没有相邻数字,则判断第一个子数组的第一个元素是否小于第二个子数组的第一个元素,是则可以交换,否则输出-1。
sorted_arr1 = sorted(arr1)
sorted_arr2 = sorted(arr2)
num=1
for i in range(len(sorted_arr1)-1):
if sorted_arr1[i]+1==sorted_arr1[i+1]:
num=0
for i in range(len(sorted_arr2)-1):
if sorted_arr2[i]+1==sorted_arr2[i+1]:
num=0
if num == 1:
if sorted_arr1[0]<sorted_arr2[0]:
return False
else:
return True
else:
return True
4.计算最小交换次数
思路
(1)构建位置映射:创建一个字典,将数组中的每个元素映射到当前位置。
(2)排序数组:对数组进行排序,得到排序后的数组。
(3)寻找循环:遍历数组,找到所有的循环(即元素之间的依赖关系)。
(4)计算交换次数:每个循环的长度减1就是该循环排序所需的最小交换次数。
详细步骤
- 构建位置映射:
使用字典将数组中的每个元素映射到当前位置。
pos = {val: idx for idx, val in enumerate(arr)}
- 排序数组:
对数组进行排序,得到排序后的数组。
sorted_arr = sorted(arr)
- 寻找循环:
遍历数组,找到所有的循环。
使用一个布尔数组visited来记录已经访问过的元素。
对于每个未访问的元素,找到其所在的循环,并计算循环的长度。
visited = [False] * len(arr)
swaps = 0
for i in range(len(arr)):
if visited[i] or pos[sorted_arr[i]] == i:
continue
cycle_size = 0
j = i
while not visited[j]:
visited[j] = True
j = pos[sorted_arr[j]]
cycle_size += 1
if cycle_size > 1:
swaps += (cycle_size - 1)
- 计算交换次数
每个循环长度减1就是将该循环排序所需的最小交换次数。
if cycle_size > 1:
swaps += (cycle_size - 1)
知识总结:
1.如何判断是否可以进行交换,数组排序问题。
2.计算交换次数,使用字典来寻找最少的交换次数
学习计划:
1.定期刷题:每天至少一道算法题,思考如何在第一步的基础上,如何优化算法,降低时间复杂度。
2.错题总结:总结一下这道题考察的知识点、切入的角度、同类型的题目等,还要思考有没有更优的办法,代码还能不能更加简洁一些。
工具运用:
豆包MarsCode AI提供了详细的解题思路和代码示例。
向豆包提出问题,可以获得思路启发,便于我们更好的理解和把握如何运用算法去解题。
针对代码中可能出现的语法错误,逻辑漏洞等问题,可以帮忙检查出并给出修改建议,提升代码的准确率和效率
在解完算法题后,可以帮你回顾题目中涉及到的算法知识,数据结构知识等,强化你对这些知识点的掌握程度。并且为你拓展相关的,更深入或者更广泛的算法知识内容,拓展知识面,更好的应对更多类型的算法题。
完整代码实现
def solution(n: int, a: list) -> int:
if len(a)==1:
return 0
# 将数组分成奇数下标和偶数下标的两个子数组
odd_indices = [a[i] for i in range(0, n, 2)]
even_indices = [a[i] for i in range(1, n, 2)]
# 检查奇偶数下标子数组是否可以通过交换变成有序
if is_sortable(odd_indices,even_indices):
return -1
# 计算奇数下标子数组的最小交换次数
odd_swaps = min_swaps_to_sort(odd_indices)
# 计算偶数下标子数组的最小交换次数
even_swaps = min_swaps_to_sort(even_indices)
# 返回总的最小交换次数
return odd_swaps + even_swaps
# 辅助函数:检查数组是否可以通过交换变成有序
def is_sortable(arr1: list,arr2:list) -> bool:
# 对数组进行排序
sorted_arr1 = sorted(arr1)
sorted_arr2 = sorted(arr2)
num=1
for i in range(len(sorted_arr1)-1):
if sorted_arr1[i]+1==sorted_arr1[i+1]:
num=0
for i in range(len(sorted_arr2)-1):
if sorted_arr2[i]+1==sorted_arr2[i+1]:
num=0
if num == 1:
if sorted_arr1[0]<sorted_arr2[0]:
return False
else:
return True
else:
return True
# 辅助函数:计算将数组排序所需的最小交换次数
def min_swaps_to_sort(arr: list) -> int:
# 构建位置映射
pos = {val: idx for idx, val in enumerate(arr)}
# 排序后的数组
sorted_arr = sorted(arr)
# 记录已经访问过的元素
visited = [False] * len(arr)
# 初始化交换次数
swaps = 0
# 遍历数组
for i in range(len(arr)):
# 如果元素已经在正确的位置或者已经被访问过,跳过
if visited[i] or pos[sorted_arr[i]] == i:
continue
# 寻找循环
cycle_size = 0
j = i
while not visited[j]:
# 标记为已访问
visited[j] = True
# 移动到下一个位置
j = pos[sorted_arr[j]]
cycle_size += 1
# 如果循环长度大于1,则需要交换
if cycle_size > 1:
swaps += (cycle_size - 1)
return swaps
if __name__ == '__main__':
print(solution(5, [1, 4, 5, 2, 3]) == 2)
print(solution(4, [4, 3, 2, 1]) == -1)
print(solution(6, [2, 4, 6, 1, 3, 5]) == -1)
print(solution(9, [4,3,1,7,2,5,6,8,9]) == -1)
print(solution(6, [3,5,6,4,1,2]) == -1)