记一次之前一个游戏大厂引擎组的面试,由于面试时间有限没有答完这个题,在此做一个记录。
题目:给出多个 (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 或提供更好的解法,不胜感激。