蓝桥杯刷题4——矩形面积交

这道题真的花了我超多的时间,想过不少思路,也有很多弯路,以及各种逻辑的错误。改了好久的BUG终于是把他做出来了。最后写出来的代码不难,挺容易理解的,就是有时候可能想的不会那么周到。每一次的思路都会漏掉一些东西,花了很多时间完善和改BUG。
而且一道基础题写了这么多代码,应该也是一个不怎么好的算法。比赛现场花这么多时间做成这样的话觉得很不划算。
等我做完之后我去搜了搜别人的代码是我太菜了。

1 题目

题目:矩形面积交
问题描述
  平面上有两个矩形,它们的边平行于直角坐标系的X轴或Y轴。对于每个矩形,我们给出它的一对相对顶点的坐标,请你编程算出两个矩形的交的面积。

输入格式
  输入仅包含两行,每行描述一个矩形。
  在每行中,给出矩形的一对相对顶点的坐标,每个点的坐标都用两个绝对值不超过10^7的实数表示。

输出格式
  输出仅包含一个实数,为交的面积,保留到小数后两位。

样例输入
1 1 3 3
2 2 4 4

样例输出
1.00


2 解题过程

2.1 分析

输入的两个顶点分别是对角的两个点的xy坐标,这种表示法让我想起了游戏引擎碰撞检测里面的轴对齐包围盒。

本来想一下觉得其实很简单,就是算一个小矩形的面积。但是仔细想想发现相交的情况至有很多种。用相交的点的数量来分可以分为0个、1个、2个、4个四种类型。 (我这里相交只算两条垂直线段交点)

  1. 没有交点:
    在这里插入图片描述
  2. 一个交点
    在这里插入图片描述
  3. 两个交点
    在这里插入图片描述
  4. 四个交点
    ⑨是重合的意思。
    在这里插入图片描述
    同时以左下角为0点,逆时针顺序标记的方式作为四个顶点表示矩形的方式。
    在这里插入图片描述
    顶点在边上算作交点,不算作在矩形内。

可以想到,只要知道黄色矩形对角的两个顶点坐标即可计算面积。而且四个点都知道的时候,即便不知道哪两个点是对角的,只要任意的组合两个找到一个不为零的解即可。因为矩形四个点中任意两个点当作对角点计算,只有0和面积两种结果。

所以我打算先找出相交的所有点,若是四个交点时,从这四个交点中找出面积。若是两个交点时,判断是不是④情况,如果是的话因为只知道两个相邻的点无法计算面积,就需要找到另外两个矩形内的点结合交点计算面积,⑤⑥直接计算得出多少就是多少就好了。如果是一个交点就直接计算,或者可以直接返回0。
如果没有交点,就判断是不是①情况,是就直接通过矩形内的四个点计算面积,不是就是没有相交,面积为0;

2.2 顶点类

两个对角的点就足以表示一个矩形,但是为了方便起见我还是把另外两个顶点也求出来。因为我打算使用一个数组存储一个矩形,所以上面画图我也是从0开始的索引。顺便定义一个Point类方便后续的实现。

Point类
现在判断是否在矩形内的方法只适用于按左下角为0点,逆时针旋转记录顶点位置的方式。 大部分的函数都在这里实现,比如判断在矩形内,判断相交直线的交点,计算面积以及其他辅助性的函数。这里分开来说明好了。

关键的内容,一个x坐标一个y坐标。是double类型,float类型好像会不够用。

class Point{
	public double x;
	public double y;
}

找出两个矩形的交点。
PointsInCollided:矩形的每一条边与另一矩形的所有边作碰撞比较,把碰撞点都存起来,去除重复点,然后输出一个List。
LineCollided:计算两条线段的交点,s1、e1表示第一条线的起点和终点,s2、e2为第二条线的。因为这里的线段只有水平的和垂直的,所以还是比较好计算的。首先起点和终点的x相同代表线段垂直,y相同代表水平。然后判断两天线段是否平行,平行没有交点(我这里重合也不算交点)。然后垂直的直线必然有交点,交点的x坐标为垂直线段的x,y坐标为水平线段的y。最后判断直线的交点在不在线段中,不在说明线段没有交点,在的话就返回交点位置。

	// 找出两个矩形相交的所有点
	public static List<Point> PointsInCollided(Point[] rect1, Point[] rect2){
		List<Point> points = new ArrayList<Point>();
		Point temp;
		for(int i=0;i<4;i++) {
			for (int j=0;j<4;j++) {
				// 对4求余是在数到4号顶点时让他转换为实际的索引0
				temp = LineCollided(rect1[i], rect1[(i+1)%4], rect2[j], rect2[(j+1)%4]);
				if (temp != null) {
					points.add(temp);
				}	
			}
		}
		// 利用hashset去除重复元素
		HashSet<Point> set = new HashSet<Point>(points);
		points.clear();
		points.addAll(set);
		return points;
	}
	// 找出两条线段的碰撞点
	private static Point LineCollided(Point s1, Point e1, Point s2, Point e2) {
		Point p = new Point();
		// 直线平行
		if((s1.x == e1.x && s2.x == e2.x) || (s1.y == e1.y && s2.y == e2.y))
			return null;
		
		// 直线相交
		if(s1.y == e1.y) {
			// 1是水平 2是垂直
			p.x = s2.x;
			p.y = s1.y;
			// 判断交点是否在线段上		
			if (p.x < Math.min(s1.x, e1.x) || p.x > Math.max(s1.x, e1.x))
				return null;
			else if (p.y < Math.min(s2.y, e2.y) || p.y > Math.max(s2.y, e2.y))
				return null;
		}
		else {
			// 1是垂直 2是水平
			p.x = s1.x;
			p.y = s2.y;
			// 判断交点是否在线段上		
			if (p.x < Math.min(s2.x, e2.x) || p.x > Math.max(s2.x, e2.x))
				return null;
			else if (p.y < Math.min(s1.y, e1.y) || p.y > Math.max(s1.y, e1.y))
				return null;
		}
		return p;
	}

找到一个矩形所有在另一个矩形中的顶点(顶点在边上不算)。
PointsInRect:矩形中每一个点都判断一边在不在另一个矩形中,把结果存成list输出。
IsInRect:判断点是否在矩形中,简单的使用x、y的大小判断,但顶点数组的表示矩形的形式须满足按左下角为0点,逆时针旋转递增。

// 找出所有Rect1在Rect2中的点
	public static List<Point> PointsInRect(Point[] rect1, Point[] rect2) {
		List<Point> points = new ArrayList<Point>();
		for(int i=0;i<rect1.length;i++) {
			if(rect1[i].IsInRect(rect2)) {
				points.add(rect1[i]);
			}
		}
		return points;
	}
	// 判断是否在某个矩形内
	// 按左下角为0点,逆时针旋转
	public boolean IsInRect(Point[] rect) {
		if(x > rect[0].x && x < rect[1].x && y > rect[1].y && y< rect[2].y) {
			return true;	
		}else {
			return false;
		}
	}	

其他的一些功能。
CaculateArea:通过两个点计算面积(这里两个点当作是对角点)。

用于保证顶点数组表示矩形的形式。
isSmallerThan:判断是不是比另一个点小。
ExChange:交换两个顶点的内容。

用于HashSet的比较。
重写hashCode:生成哈希码
重写equals:比较是否相等

	// 根据对角两个点计算面积
	public static double CaculateArea(Point p1, Point p2) {
		return  Math.abs((p1.x - p2.x) * (p1.y - p2.y));
	}
	// 判断这个点是不是比自己小(越往左下方越小)
	public boolean isSmallerThan(Point p) {
		if(x < p.x && y <p.y) {
			return true;
		}
		return false;
	}
	// 交换变量
	public static void ExChange(Point p1,Point p2) {
		double tx,ty;
		tx = p1.x;
		ty = p1.y;
		p1.x = p2.x;
		p1.y = p2.y;
		p2.x = tx;
		p2.y = ty;
	}
	// ========== 用于hashSet ==========
	@Override
	public int hashCode() {
		return (int)(x *x*11 +y * 30);  
	}
	@Override
	public boolean equals(Object o) {
		Point p = (Point)o;
		if(p.x == x && p.y ==y)
			return true;
		else
			return false;
	}

2.3 主函数

2.3.1 获取数据

定义两个矩形,然后获取输入数据。因为测试过之后发现他给的数据不一定会把左下角当作第一个数据,有时候会右上角,所以先判断一下,必要的时候交换数据(这里没有考虑右下角和左上角)。
然后再计算出其他点的数据。

		Point a[] = new Point[4]; // 矩形A
		Point b[] = new Point[4]; // 矩形B
		
		// 初始化
		for (int i=0;i<a.length;i++)
			a[i] = new Point();
		for (int i=0;i<b.length;i++)
			b[i] = new Point();
		
		// 输入数据
		Scanner scanner = new Scanner(System.in);
		a[0].x = scanner.nextDouble();
		a[0].y = scanner.nextDouble();
		a[2].x = scanner.nextDouble();
		a[2].y = scanner.nextDouble();
		b[0].x = scanner.nextDouble();
		b[0].y = scanner.nextDouble();
		b[2].x = scanner.nextDouble();
		b[2].y = scanner.nextDouble();
		scanner.close();
		
		// 确认左下角才是0号数据
		if(a[2].isSmallerThan(a[0]))
			Point.ExChange(a[0], a[2]);
		if(b[2].isSmallerThan(b[0]))
			Point.ExChange(b[0], b[2]);
		
		// 计算余下的四个点的位置
		a[3].x = a[0].x;
		a[3].y = a[2].y;
		a[1].x = a[2].x;
		a[1].y = a[0].y;
		b[3].x = b[0].x;
		b[3].y = b[2].y;
		b[1].x = b[2].x;
		b[1].y = b[0].y;

2.3.2 判断情况计算

	// 计算A在B中的顶点
	List<Point> pointsInB = Point.PointsInRect(a, b);
	// 计算B在A中的顶点
	List<Point> pointsInA = Point.PointsInRect(b, a);
	
	// 结果
	double resultArea = 0;
	
	// ============= 判断情况类型计算 =============
	List<Point> pointsCol = Point.PointsInCollided(a, b);
	// 如果有交点
	if(pointsCol.size() > 0) {
		// 判断是不是情况四
		if(pointsInB.size() == 2) {
			for(int i=0;i<pointsCol.size();i++) {
				resultArea = Point.CaculateArea(pointsInB.get(0), pointsCol.get(i));
				if(resultArea != 0) break;
			}
		}else if(pointsInA.size() == 2) {
			for(int i=0;i<pointsCol.size();i++) {
				resultArea = Point.CaculateArea(pointsInA.get(0), pointsCol.get(i));
				if(resultArea != 0) break;
			}
		}
		// 其他的情况
		else
		{
			for(int i=1;i<pointsCol.size();i++) {
				resultArea = Point.CaculateArea(pointsCol.get(0), pointsCol.get(i));
				if(resultArea != 0) break;
			}
		}
	}
	// 判断是不是情况一
	else if(pointsInB.size() == 4) {
		resultArea = Point.CaculateArea(pointsInB.get(0), pointsInB.get(2));
	}else if(pointsInA.size() == 4) {
		resultArea = Point.CaculateArea(pointsInA.get(0), pointsInA.get(2));
	}
	
	// 输出结果
	System.out.println(String.format("%.2f", resultArea));

2.4 完整代码

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Scanner;

public class Main {
	public static void main(String[] args) { 
		
		Point a[] = new Point[4]; // 矩形A
		Point b[] = new Point[4]; // 矩形B
		
		// 初始化
		for (int i=0;i<a.length;i++)
			a[i] = new Point();
		for (int i=0;i<b.length;i++)
			b[i] = new Point();
		
		// 输入数据
		Scanner scanner = new Scanner(System.in);
		a[0].x = scanner.nextDouble();
		a[0].y = scanner.nextDouble();
		a[2].x = scanner.nextDouble();
		a[2].y = scanner.nextDouble();
		b[0].x = scanner.nextDouble();
		b[0].y = scanner.nextDouble();
		b[2].x = scanner.nextDouble();
		b[2].y = scanner.nextDouble();
		scanner.close();
		
		// 确认左下角才是0号数据
		if(a[2].isSmallerThan(a[0]))
			Point.ExChange(a[0], a[2]);
		if(b[2].isSmallerThan(b[0]))
			Point.ExChange(b[0], b[2]);
		
		// 计算余下的四个点的位置
		a[3].x = a[0].x;
		a[3].y = a[2].y;
		a[1].x = a[2].x;
		a[1].y = a[0].y;
		b[3].x = b[0].x;
		b[3].y = b[2].y;
		b[1].x = b[2].x;
		b[1].y = b[0].y;
		
		// 计算A在B中的顶点
		List<Point> pointsInB = Point.PointsInRect(a, b);
		// 计算B在A中的顶点
		List<Point> pointsInA = Point.PointsInRect(b, a);
		
		// 结果
		double resultArea = 0;
		
		// ============= 判断情况类型计算 =============
		List<Point> pointsCol = Point.PointsInCollided(a, b);
		// 如果有交点
		if(pointsCol.size() > 0) {
			// 判断是不是情况四
			if(pointsInB.size() == 2) {
				for(int i=0;i<pointsCol.size();i++) {
					resultArea = Point.CaculateArea(pointsInB.get(0), pointsCol.get(i));
					if(resultArea != 0) break;
				}
			}else if(pointsInA.size() == 2) {
				for(int i=0;i<pointsCol.size();i++) {
					resultArea = Point.CaculateArea(pointsInA.get(0), pointsCol.get(i));
					if(resultArea != 0) break;
				}
			}
			// 其他的情况
			else
			{
				for(int i=1;i<pointsCol.size();i++) {
					resultArea = Point.CaculateArea(pointsCol.get(0), pointsCol.get(i));
					if(resultArea != 0) break;
				}
			}
		}
		// 判断是不是情况一
		else if(pointsInB.size() == 4) {
			resultArea = Point.CaculateArea(pointsInB.get(0), pointsInB.get(2));
		}else if(pointsInA.size() == 4) {
			resultArea = Point.CaculateArea(pointsInA.get(0), pointsInA.get(2));
		}
		
		// 输出结果
		System.out.println(String.format("%.2f", resultArea));
	}
}

class Point{
	public double x;
	public double y;
	// 找出所有Rect1在Rect2中的点
	public static List<Point> PointsInRect(Point[] rect1, Point[] rect2) {
		List<Point> points = new ArrayList<Point>();
		for(int i=0;i<rect1.length;i++) {
			if(rect1[i].IsInRect(rect2)) {
				points.add(rect1[i]);
			}
		}
		return points;
	}
	// 找出两个矩形相交的所有点
	public static List<Point> PointsInCollided(Point[] rect1, Point[] rect2){
		List<Point> points = new ArrayList<Point>();
		Point temp;
		for(int i=0;i<4;i++) {
			for (int j=0;j<4;j++) {
				temp = LineCollided(rect1[i], rect1[(i+1)%4], rect2[j], rect2[(j+1)%4]);
				if (temp != null) {
					points.add(temp);
				}	
			}
		}
		// 利用hashset去除重复元素
		HashSet<Point> set = new HashSet<Point>(points);
		points.clear();
		points.addAll(set);
		return points;
	}
	// 找出两条线段的碰撞点
	private static Point LineCollided(Point s1, Point e1, Point s2, Point e2) {
		Point p = new Point();
		// 直线平行
		if((s1.x == e1.x && s2.x == e2.x) || (s1.y == e1.y && s2.y == e2.y))
			return null;
		
		// 直线相交
		if(s1.y == e1.y) {
			// 1是水平 2是垂直
			p.x = s2.x;
			p.y = s1.y;
			// 判断交点是否在线段上		
			if (p.x < Math.min(s1.x, e1.x) || p.x > Math.max(s1.x, e1.x))
				return null;
			else if (p.y < Math.min(s2.y, e2.y) || p.y > Math.max(s2.y, e2.y))
				return null;
		}
		else {
			// 1是垂直 2是水平
			p.x = s1.x;
			p.y = s2.y;
			// 判断交点是否在线段上		
			if (p.x < Math.min(s2.x, e2.x) || p.x > Math.max(s2.x, e2.x))
				return null;
			else if (p.y < Math.min(s1.y, e1.y) || p.y > Math.max(s1.y, e1.y))
				return null;
		}
		return p;
	}
	// 判断是否在某个矩形内
	// 按左下角为0点,逆时针旋转
	public boolean IsInRect(Point[] rect) {
		if(x > rect[0].x && x < rect[1].x && y > rect[1].y && y< rect[2].y) {
			return true;	
		}else {
			return false;
		}
	}
	// 根据对角两个点计算面积
	public static double CaculateArea(Point p1, Point p2) {
		return  Math.abs((p1.x - p2.x) * (p1.y - p2.y));
	}
	// 判断这个点是不是比自己小(越往左下方越小)
	public boolean isSmallerThan(Point p) {
		if(x < p.x && y <p.y) {
			return true;
		}
		return false;
	}
	// 交换变量
	public static void ExChange(Point p1,Point p2) {
		double tx,ty;
		tx = p1.x;
		ty = p1.y;
		p1.x = p2.x;
		p1.y = p2.y;
		p2.x = tx;
		p2.y = ty;
	}
	// ========== 用于hashSet ==========
	@Override
	public int hashCode() {
		return (int)(x *x*11 +y * 30);  
	}
	@Override
	public boolean equals(Object o) {
		Point p = (Point)o;
		if(p.x == x && p.y ==y)
			return true;
		else
			return false;
	}
}

也还勉强吧。
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值