前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >百度2023秋招面试算法真题解析

百度2023秋招面试算法真题解析

作者头像
五分钟学算法
发布2023-09-20 08:59:30
2350
发布2023-09-20 08:59:30
举报
文章被收录于专栏:五分钟学算法

题目描述

小红有一个长度为n的排列,她可以选择两个位置,然后交换两个位置的数。

她想知道能否通过最多一次交换,使得存在一个连续子段,是长度为k的排列。

排列是指一个长度为 len 的整数数组,数组中包含1len的每个数,且每个数只出现一次。

输入描述

第一行两个整数n,k,表示排列长度和连续子段长度。

第二行n个整数a1, a2, ..., an,表示排列。

代码语言:javascript
复制
1 <= k <= n <= 10^5

输出描述

如果能够通过最多一次交换,存在一个连续子段是排列,输出YES,并输出交换的位置:先输出一个整数x (0 <= x <= 1),然后输出x行,每行两个整数u, v,表示交换位置u, v (u < v)

否则输出NO

示例

输入
代码语言:javascript
复制
5 3
1 2 3 4 5
输出
代码语言:javascript
复制
YES
0

解题思路

本题看似很复杂,实际上由于我们要找的是一个固定长度为k的滑动窗口,因此可以直接使用固定滑窗的方法来解答。

一个长度为k的排列,其中一定包含的是1k一共k个数,由于最多可以交换一次,我们可以允许固定滑窗中包含至多一个大于k的数。故我们可以构建一个哈希表dic,用于储存滑窗中所有大于k的数以及其下标,如果在滑动过程中,发现dic的长度小于等于1,则说明此时固定滑窗只包含至多一个大于k的数,这个数可以通过与其他的某个数进行交换,来使得该滑窗变成一个长度为k的排列。

滑窗三问

Q1:对于每一个右指针right所指的元素right_num,做什么操作?

Q2:什么时候要令左指针left右移?对于left所指的元素left_num,要做什么操作?

Q3:什么时候进行ans的更新?如何更新?

滑窗三答

A1:right_num大于k,则将其下标right计入哈希表dic中,即dic[right_num] = right

A2:在固定滑窗中,left始终为right-N。若left_num大于k,则需要将其在dic中所储存键值对删除,即del dic[left]

A3:当发现len(dic) <= 1时,说明此时此时固定滑窗可以至多一次交换,使得该滑窗变成一个长度为k的排列。此时退出循环,寻找窗口中缺失的那个数的下标。

注意在更新答案时,存在一种极为特殊的情况需要判断:

len(dic) == 1,且left恰好指向的是窗口中大于k的数,right+1恰好指向的是需要交换的数,那么窗口[left+1,right+1]是可以不通过交换就构建长度为k的排列的,所以此时应该输出交换次数为0

代码

代码语言:javascript
复制
# 根据dic的情况,获取答案的函数
def get_ans(nums, dic, right, k):
    x, first, second = 0, 0, 0
    # 若dic长度为0,说明此时固定滑窗就是一个长度为k的排列
    # 交换次数x为0,无需做任何修改。
    # 若dic长度为1,需要做以下判断
    if len(dic) == 1:
        # 第一个数字的位置,为dic中唯一一个键值对的value
        first = list(dic.values())[0]
        # 长度为k的排列的和可以用等差数列求和公式获得,记为A
        # 固定窗口的和可以直接计算,记为B
        # 窗口中多出来的数字,记为C
        # 窗口中缺失的数字num_miss,为A-(B-C)
        num_miss = (1+k)*k//2 - sum(nums[right-k+1:right+1]) + list(dic.keys())[0]

        # 找到num_miss在nums中的下标
        second = nums.index(num_miss)
        # 特殊情况判断:
        # 若此时second的位置在窗口右边界right的下一个位置right+1
        # 同时first的位置在窗口左边界right-k+1的位置
        # 说明下一个窗口可以无需进行交换,就可以获得长度为k的排列
        if second == right+1 and first == right-k+1:
            x = 0
        else:
            x = 1
    return x, first, second


n, k = map(int, input().split())
nums = list(map(int, input().split()))

# 初始化交换次数为任意一个大于1的数
x = 2

# 构建哈希表,储存固定滑窗中,所有大于k的元素的下标
dic = {num: i for i, num in enumerate(nums[:k]) if num > k}
# 若第一个固定滑窗就满足题意,则直接获得答案
if len(dic) <= 1:
    # 第一个窗口的有边界right = k-1
    x, first, second = get_ans(nums, dic, k-1, k)
else:
    for right, right_num in enumerate(nums[k:], k):
        # A1
        if right_num > k:
            dic[right_num] = right
        # A2
        left = right - k
        left_num = nums[left]
        if left_num > k:
            del dic[left_num]
        # A3
        if len(dic) <= 1:
            x, first, second = get_ans(nums, dic, right, k)
            break

# 根据最终得到的x的结果,输出答案
if x <= 1:
    print("YES")
    if x == 1:
        print(1)
        # 先输出小的位置,再输出大的位置
        if first > second:
            first, second = second, first
        print("{} {}".format(first, second))
    else:
        print(0)
else:
    print("NO")
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-09-17 20:41,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 五分钟学算法 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 输入描述
  • 输出描述
  • 示例
    • 输入
      • 输出
      • 解题思路
        • 滑窗三问
          • 滑窗三答
          • 代码
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档