校招算法笔面试 | 校招笔面试真题-堆棋子_1

题目## 题目

题目链接

#题解
##难度:中等
##知识点:暴力求解

棋子只能上下左右移动,所以移动的步数即操作次数为两个位置的曼哈顿距离
坐标(x1,y1)的i点与坐标(x2,y2)的j点的曼哈顿距离为d(i,j)=|x1-x2|+|y1-y2|

本题是要求解棋盘上出现一个格子中至少k个棋子的最少操作。那这k个棋子出现在那个格子呢,最少的操作数可能出现在棋盘上的任何一个格子么?
先来看个例子:
先来看看一维的情况,如有四颗棋子x=[1,2,4,9],要将这4颗棋子移到一个格子上,格子坐标范围为[1:10],结果如下

四颗棋子都移到的格子总步数
x=10+1+3+8=12
x=21+0+2+7=10
x=32+1+1+6=10
x=43+2+0+5=10
x=54+3+1+4=12
x=65+4+2+3=14
x=76+5+3+2=16
x=87+6+4+1=18
x=98+7+5+0=20
x=109+8+6+1=24

可以发现规律:
1)当四颗棋子都要移到格子x=i时,相对于四颗棋子都要移到格子x=i-1的基础上,在格子x=i左边的棋子的步数都+1,在格子x=i上及右边的棋子步数都-1
2)由规律1)可以总结到对于四颗棋子都移到格子x=i的情况来说

  • 如果格子上及左边的棋子数<格子右边的棋子数,那么x再向右移动,总步数会增加 - 如果格子上及左边的棋子数>格子右边的棋子数,那么x再向右移动,总步数会减少
  • 如果格子上及左边的棋子数=格子右边的棋子数,那么x再向右移动,总步数不会变

3)从规律1)2)得到左右棋子数一样多的格子是总步数最少的,那么这些最小步数的格子是在一个区间内,并且区间的两端肯定是棋子所在的位置,才会造成区间外,格子左右棋子数不一致。

结论:在棋子所在位置得到的k个棋子移到到这个位置的最小操作数就是全局最小的,即聚合点的x坐标必然是给出棋子的x坐标之一。同理聚合点的y坐标必然是给出棋子的y坐标之一。所以只要在棋子所在位置找出聚合的最小操作数就是全局最小的操作数。

解题思路步骤:
1)求k个棋子移动到格子A的最小操作次数:先求所有棋子到格子A的曼哈顿距离,由于需要k个棋子,所以找到所有棋子中离格子A最近的k个棋子,它们的曼哈顿距离之和就是移k个棋子到格子A的最少操作次数。
2)循环所有棋子所占的格子,根据步骤1)求到移k个棋子到这个格子的最小操作次数,那么所有棋子所占格子中最小的操作数,就是题目所要求的最小操作数。

#include<iostream>
#include<algorithm>
 
using namespace std;
 
int n;
vector<int> x(55);
vector<int> y(55);
vector<int> ans(55);

void calculate(){
    for(int i=0;i<n;i++){ for(int j="0;j<n;j++){" int dis[n],tmp="0;" 计算曼哈顿距离 k="0;k<n;k++){" dis[k]="abs(x[i]-x[k])" + abs(y[j]-y[k]); sort(dis,dis+n); tmp+="dis[k];" ans[k]="ans[k]">tmp ? tmp : ans[k];
            }
        }
    }
}
int main(){
    cin&gt;&gt;n;
 
    for(int i=0;i<n;i++) cin>&gt;x[i];
    for(int i=0;i<n;i++) cin>&gt;y[i];
    for(int i=0;i</n;i++)></n;i++)></n;i++){></int></int></int></algorithm></iostream></格子右边的棋子数,那么x再向右移动,总步数会增加>

[题目链接](https://www.nowcoder.com/practice/27f3672f17f94a289f3de86b69f8a25b?tpId=182&tqId=112728&sourceUrl=/exam/oj&channelPut=wcsdn&fromPut=wcsdn)

## 解题思路

1. 这是一个最小曼哈顿距离的问题。对于每个目标位置 $(i,j)$,我们需要计算将 $k$ 个棋子移动到该位置所需的最小步数。
2. 对于每个目标位置,我们计算所有棋子到该位置的曼哈顿距离。
3. 将距离排序后,取前 $k$ 个距离之和,即为将 $k$ 个棋子移动到该位置所需的步数。
4. 遍历所有可能的目标位置,更新每个 $k$ 的最小步数。

---

## 代码

```cpp []
#include<iostream>
#include<algorithm>
using namespace std;

int n, x[55], y[55], ans[55]; 

void helper() {
    // 遍历所有可能的目标位置
    for(int i = 0; i < n; i++) {
        for(int j = 0; j < n; j++) {
            int dis[n], tmp = 0;
            // 计算所有棋子到当前位置的曼哈顿距离
            for(int k = 0; k < n; k++) 
                dis[k] = abs(x[i]-x[k]) + abs(y[j]-y[k]);
            // 排序距离数组
            sort(dis, dis+n);
            // 计算并更新k个棋子的最小移动步数
            for(int k = 0; k < n; k++) {
                tmp += dis[k];
                ans[k] = ans[k]>tmp ? tmp : ans[k];
            } 
        }
    }
}

int main() {
    cin >> n;
    for(int i = 0; i < n; i++) cin >> x[i];
    for(int i = 0; i < n; i++) cin >> y[i];
    // 初始化答案数组
    for(int i = 0; i < n; i++) ans[i] = 10000000000;
    
    helper();
    
    // 输出结果
    for(int i = 0; i < n; i++) {
        cout << ans[i];
        if(i < n-1) cout << " ";
    }
    return 0;
}
import java.util.*;

public class Main {
    static int n;
    static int[] x = new int[55];
    static int[] y = new int[55];
    static long[] ans = new long[55];
    
    public static void helper() {
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
                int[] dis = new int[n];
                long tmp = 0;
                for(int k = 0; k < n; k++) 
                    dis[k] = Math.abs(x[i]-x[k]) + Math.abs(y[j]-y[k]);
                Arrays.sort(dis);
                for(int k = 0; k < n; k++) {
                    tmp += dis[k];
                    ans[k] = ans[k]>tmp ? tmp : ans[k];
                }
            }
        }
    }
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        for(int i = 0; i < n; i++) x[i] = sc.nextInt();
        for(int i = 0; i < n; i++) y[i] = sc.nextInt();
        for(int i = 0; i < n; i++) ans[i] = 10000000000L;
        
        helper();
        
        for(int i = 0; i < n; i++) {
            System.out.print(ans[i]);
            if(i < n-1) System.out.print(" ");
        }
    }
}
def helper(n, x, y):
    ans = [10000000000] * n
    for i in range(n):
        for j in range(n):
            dis = [abs(x[i]-x[k]) + abs(y[j]-y[k]) for k in range(n)]
            dis.sort()
            tmp = 0
            for k in range(n):
                tmp += dis[k]
                ans[k] = tmp if tmp < ans[k] else ans[k]
    return ans

n = int(input())
x = list(map(int, input().split()))
y = list(map(int, input().split()))

result = helper(n, x, y)
print(' '.join(map(str, result)))

算法及复杂度

  • 算法:枚举 + 排序
  • 时间复杂度: O ( n 3 log ⁡ n ) \mathcal{O}(n^3\log n) O(n3logn)
    • 需要遍历 n 2 n^2 n2 个目标位置
    • 对于每个位置需要计算 n n n 个距离并排序 ( O ( n log ⁡ n ) ) (\mathcal{O}(n\log n)) (O(nlogn))
  • 空间复杂度: O ( n ) \mathcal{O}(n) O(n)
    • 需要存储距离数组和答案数组
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值