【面试记录】0 平均分割线段

记一次之前一个游戏大厂引擎组的面试,由于面试时间有限没有答完这个题,在此做一个记录。

题目:给出多个 (x, y) 点组成的相连的线段(点的顺序决定连接顺序),以及一个数字 n。返回一组 (x,y)将之前的相连线段平均分成 n 份长度相等的线段。

思路:
(1)将所有的点集合所得到的总线段长度算出来。为了方便称呼叫输入的点组成的线叫原线。

(2)将总长度平分 n 份后算出每段平分的长度。

(3)然后按原线长度分段截取:若在原线线段上未超出原线两点长度则在该线段上计算新点,如超过了则从下一个原线上的点开始截取(去掉之前截取的部分)

// NetEaseEngineSecondInterview.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include <cmath>
#include <iostream>
#include <vector>

#define PI 3.1415926

using namespace std;

struct Point {
    float _x;
    float _y;
};

float CalculateSegmentLength(Point& p1, Point& p2) {
    return sqrt(pow((p2._y - p1._y), 2.0f) + pow((p2._x - p1._x), 2.0f));
}

// 利用三角函数算出新的点
// p1, p2: 原线上两点
// segmentLen: 原先线段上两点
// targetlen: 新线段需要达到的长度
Point CreateNewPoint(Point& p1, Point& p2, float targetLen) {
    float slope = (p2._y - p1._y) / (p2._x - p1._x);
    float angle = atan(slope);
    float sinAngle = sin(angle);
    float cosAngle = cos(angle);
    float yIncrement = targetLen * sinAngle;
    float xIncrement = targetLen * cosAngle;
    Point result = { p1._x + xIncrement, p1._y + yIncrement };
    return result;
}

vector<Point> SegmentSpliter(vector<Point>& input, int n) {
    vector<Point> result;

    // 将所有的点集合所得到的总线段长度算出来
    float length = 0.0f;
    vector<float> segmentsLength;
    for (int i = 1; i < input.size(); ++i) {
        float segmentLength = CalculateSegmentLength(input[i - 1], input[i]);
        length += segmentLength;
        segmentsLength.push_back(segmentLength);
    }

    // 平分长度
    float newSegmentLength = length / n;
    cout << "new segment length = " << newSegmentLength << endl;

    int originSegmentIndex = 0; // 原线段
    int originPointIndex = 0;   // 原线上点的索引

    // 使用 n-1 个点分割原线至 n 等分
    while (result.size() < n - 1) {
        float nextSegmentLength = newSegmentLength;
        // 若第一次生成新点(起点为原线的第一个点)
        if (result.empty()) {
            // 若在原线线段上超出原线两点长度,则更新线段长度,并前移起始点
            while (segmentsLength[originSegmentIndex] < nextSegmentLength) {
                nextSegmentLength -= segmentsLength[originSegmentIndex];
                originSegmentIndex++;
                originPointIndex++;
            }
            Point newPoint = CreateNewPoint(input[originPointIndex], input[originPointIndex + 1], nextSegmentLength);
            result.push_back(newPoint);
            // 这里进位原线点索引,因为下一次终点索引是这个指针开始的
            originPointIndex++;
        }
        // 若之前的新生成点集不为空的话,则以最后一个新生成的点索引作为起点
        else {
            Point start = result[result.size() - 1];
            Point end = input[originPointIndex];
            // 计算新点到下一个原线点的距离
            float remainLength = CalculateSegmentLength(start, end);
            while (remainLength < nextSegmentLength) {
                nextSegmentLength -= remainLength;
                start = input[originPointIndex];
                originPointIndex++;
                end = input[originPointIndex];
                remainLength = CalculateSegmentLength(start, end);
            }
            Point newPoint = CreateNewPoint(start, end, remainLength);
            result.push_back(newPoint);
        }
    }

    return result;
}

int main()
{
    // 每条线分割段数
    int n = 3;

    Point p1 = { 0.0f, 0.0f };
    Point p2 = { 1.0f, 0.0f };
    Point p3 = { 4.0f, 0.0f };
    Point p4 = { 6.0f, 0.0f };
    vector<Point> test1{ p1, p2, p3, p4 };
    

    Point p5 = { -1.0f, -1.0f };
    Point p6 = { 1.0f, 1.0f };
    Point p7 = { 3.0f, 1.0f };
    Point p8 = { 4.0f, -3.0f };
    Point p9 = { 2.0f, -5.0f };
    vector<Point> test2{ p5, p6, p7, p8, p9 };

    vector<Point> ans1 = SegmentSpliter(test1, n);
    for (Point p : ans1) {
        cout << p._x << "    " << p._y << endl;
    }
    vector<Point> ans2 = SegmentSpliter(test2, n);
    for (Point p : ans2) {
        cout << p._x << "    " << p._y << endl;
    }

    return 0;
}

由于第一个 testcase 比较简单,我又加了一个 testcase2。通过 Debug 模式证明是对的。

运行结果如下:

 欢迎各位提供新的 testcase 或提供更好的解法,不胜感激。

原文地址:https://www.cnblogs.com/thdt/p/13838814.html