题目 :
用一个下标从 0 开始的二维整数数组 rectangles 来表示 n 个矩形,其中 rectangles[i] = [widthi, heighti] 表示第 i 个矩形的宽度和高度。
如果两个矩形 i 和 j(i < j)的宽高比相同,则认为这两个矩形 可互换 。更规范的说法是,两个矩形满足 widthi/heighti == widthj/heightj(使用实数除法而非整数除法),则认为这两个矩形可互换 。
计算并返回 rectangles 中有多少对 可互换 矩形。
示例 1:
输入:rectangles = [[4,8],[3,6],[10,20],[15,30]]
输出:6
解释:下面按下标(从 0 开始)列出可互换矩形的配对情况:
- 矩形 0 和矩形 1 :4/8 == 3/6
- 矩形 0 和矩形 2 :4/8 == 10/20
- 矩形 0 和矩形 3 :4/8 == 15/30
- 矩形 1 和矩形 2 :3/6 == 10/20
- 矩形 1 和矩形 3 :3/6 == 15/30
- 矩形 2 和矩形 3 :10/20 == 15/30
示例 2:
输入:rectangles = [[4,5],[7,8]]
输出:0
解释:不存在成对的可互换矩形。
分析题目 :
leetcode 提示 :
n == rectangles.length 1 <= n <= 105 rectangles[i].length == 2 1 <= width, height <= 10^5 |
方法一 : 可以先遍历二维数组将他们的的 width/height 预存到一个一维数组中, 然后进行判断
class Solution01 {
public long interchangeableRectangles(int[][] rectangles) {
// 判断有 len 个矩形
int len = rectangles.length;
// 定义一维数组
double [] a = new double[len];
// 将数据预存到一维数组中
for (int temp=0;temp<len;temp++){
a[temp] = (double) rectangles[temp][0]/(double)rectangles[temp][1];
}
// 定义结果
long ans=0;
// 对一维数组进行遍历判断
for (int i=0; i<len;i++){
for (int j=i+1; j<len; j++){
if (a[i]==a[j]){
ans++;
}
}
}
return ans;
}
}
结果 : 超时
起初以为 因为遍历三次, 所以减少一次遍历再次提交
方法二 : 不预存数据, 直接遍历 rectangles 进行判断
class Solution01 {
public long interchangeableRectangles(int[][] rectangles) {
int len = rectangles.length;
int ans=0;
for (int i=0; i<len;i++){
for (int j=i+1; j<len; j++){
if ((double)rectangles[i][0]/(double)rectangles[i][1]==(double)rectangles[j][0]/(double)rectangles[j][1]){
ans++;
}
}
}
return ans;
}
}
结果 : 超时
去网上找答案 :
首先了解一下 gcd 最大公约数 :
最大公因数,也称最大公约数、最大公因子,指两个或多个整数共有约数中最大的一个。求最大公约数有多种方法,常见的有质因数分解法、短除法、辗转相除法、更相减损法。与最大公约数相对应的概念是最小公倍数
1. 辗转相除法:也叫欧几里德算法。 求 319 和 377 的最大公约数
∵ 319÷377=0(余319)
∴(319,377)=(377,319);
∵ 377÷319=1(余58)
∴(377,319)=(319,58);
∵ 319÷58=5(余29)
∴ (319,58)=(58,29);
∵ 58÷29=2(余0)
∴ (58,29)= 29;
∴ (319,377)=29。
循环相除 :
int num1 = 319;
int num2 = 377;
//辗转相除法
while(num2 != 0){
int temp = num2;
num2 = num1 % num2;
num1 = temp;
}
System.out.println("最大公约数为:" + num1);
递归调用 :
private static int gcd(int a, int b) {
if (b == 0) {
return a;
}
return gcd(b, a % b);
}
2. 更相减损术 : 求98与63的最大公约数。
第一步:任意给定两个正整数;判断它们是否都是偶数。若是,则用2约简;若不是则执行第二步。
第二步:以较大的数减较小的数,接着把所得的差与较小的数比较,并以大数减小数。继续这个操作,直到所得的减数和差相等为止。
解:由于63不是偶数,把98和63以大数减小数,并辗转相减:
98-63=35
63-35=28
35-28=7
28-7=21
21-7=14
14-7=7
所以,98和63的最大公约数等于7。
循环实现 :
int num3 = 98;
int num4 = 63;
//更相减损法
while(num3 != num4){
int temp = num4;
if(num4 > num3){
temp = num4;
num4 = num3;
num3 = temp;
}
num4 = num3 - num4;
num3 = temp;
}
System.out.println("大公约数为:" + num3);
递归实现 :
int gcd(int a, int b)
{
if(a == b)
return a;
if(a > b)
return gcd(a-b, b);
if(a < b)
return gcd(a, b-a);
}
比较辗转相除法与更相减损术的区别
(1)都是求最大公因数的方法,计算上辗转相除法以除法为主,更相减损术以减法为主,计算次数上辗转相除法计算次数相对较少,特别当两个数字大小区别较大时计算次数的区别较明显。
(2)从结果体现形式来看,辗转相除法体现结果是以相除余数为0则得到,而更相减损术则以减数与差相等而得到。
辗转相除法,在数值大的时候,需要进行大数除法,性能较低。
更相减损法:需要进行较多的循环,极端情况,a=1000,b=1,则需要循环999次
这里提出一种结合方法( 转载 : )
如果a,b都是偶数,则gcd(a,b) = 2 · gcd(a/2,b/2)
如果a是奇数,b是偶数,则gcd(a,b) = 2 · gcd(a,b/2) 因为其中一个为奇数,那么公约数的因素一定都不包含2
如果a是偶数,b是奇数,则gcd(a,b) = 2 · gcd(a/2,b)
如果a是奇数,b是奇数,则gdc(a,b) = gcd(b,a-b) 奇数相减,一定会出现偶数
int tt1 = 120;
int tt2 = 92;
//辗转相除法和更相减损法的结合
//辗转相除法,大数相除性能较低
//更相减损法,循环运算次数过多
int base = 1;
while(tt1 != tt2){
//保证tt1 是较大的数
if(tt1 < tt2){
int temp = tt1;
tt1 = tt2;
tt2 = temp;
}
//双偶
if(tt1 % 2 == 0 && tt2 % 2 == 0){
tt1 = tt1>>1;
tt2 = tt2>>1;
base = base * 2;
//奇偶
}else if(tt1 % 2 == 0 && tt2 % 2 != 0){
tt1 = tt1>>1;
//偶奇
}else if(tt1 % 2 != 0 && tt2 % 2 == 0){
tt2 = tt2>>1;
//双奇
}else if(tt1 % 2 != 0 && tt2 % 2 != 0){
int temp = tt2;
tt2 = tt1 - tt2;
tt1 = temp;
}
}
System.out.println("最大公约数为:" + tt2 * base);
了解一下HashMap:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
public class HashMapTest {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
map.put("zhang", "31");//存放键值对
System.out.println(map.containsKey("zhang"));//键中是否包含这个数据
System.out.println(map.containsKey("daniu"));
System.out.println("=========================");
System.out.println(map.get("zhang"));//通过键拿值
System.out.println(map.get("daniu"));
System.out.println("=========================");
System.out.println(map.isEmpty());//判空
System.out.println(map.size());
System.out.println("=========================");
System.out.println(map.remove("zhang"));//从键值中删除
System.out.println(map.containsKey("zhang"));
System.out.println(map.get("zhang"));//获取值
System.out.println(map.isEmpty());
System.out.println(map.size());
System.out.println("=========================");
map.put("zhang", "31");
System.out.println(map.get("zhang"));
map.put("zhang", "32");
System.out.println(map.get("zhang"));
System.out.println("=========================");
map.put("zhang", "31");
map.put("cheng", "32");
map.put("yun", "33");
for (String key : map.keySet()) {
System.out.println(key);
}
System.out.println("=========================");
for (String values : map.values()) {
System.out.println(values);
}
System.out.println("=========================");
map.clear();
map.put("A", "1");
map.put("B", "2");
map.put("C", "3");
map.put("D", "1");
map.put("E", "2");
map.put("F", "3");
map.put("G", "1");
map.put("H", "2");
map.put("I", "3");
for (Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "," + value);
}
System.out.println("=========================");
// you can not remove item in map when you use the iterator of map
// for(Entry<String,String> entry : map.entrySet()){
// if(!entry.getValue().equals("1")){
// map.remove(entry.getKey());
// }
// }
// if you want to remove items, collect them first, then remove them by
// this way.
List<String> removeKeys = new ArrayList<String>();
for (Entry<String, String> entry : map.entrySet()) {
if (!entry.getValue().equals("1")) {
removeKeys.add(entry.getKey());
}
}
for (String removeKey : removeKeys) {
map.remove(removeKey);
}
for (Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "," + value);
}
System.out.println("=========================");
}
}
解题思路1 :
两个矩形满足 widthi/heighti == widthj/heightj,那么左右两式约分后的分式肯定有分子i=分子j,分母i=分母j。
思路:
1. 使用HashMap记录每一种矩形出现次数并统计
2. key:分子/分母
value:出现次数
这个key涉及两个数据,java中可以自己定义个类重新equals和hashCode方法,太复杂了,看到数据范围1 <= widthi, heighti <= 10^5
所以widthi*10^5+heighti能保证这种类型的矩形唯一
class Solution {
public long interchangeableRectangles(int[][] rectangles) {
Map<Long,Long> map=new HashMap<>();
long res=0;
for(int[] arr:rectangles){
int gcd=gcd(arr[0],arr[1]);
long temp=arr[0]/gcd*100000+arr[1]/gcd;
res+=map.getOrDefault(temp,0L);
map.put(temp,map.getOrDefault(temp,0L)+1);
}
return res;
}
int gcd(int a,int b){
return b==0?a:gcd(b,a%b);
}
}
解题思路2 :
1. 通过哈希统计宽高比后相同的次数
2. 在通过公式n(n+1)/2对次数进行运算(比如宽高比相同的次数有4个,那么3*(3+1)/2)=6,说明这个键可互换矩形有6个。
class Solution {
public long interchangeableRectangles(int[][] arr) {
long sum=0;
double[] res=new double[rectangles.length];
// 通过哈希统计相同字符的次数,再通过公式n(n+1)/2进行统计
HashMap<Double,Long> map=new HashMap<>();
int j=0;
// for循环统计,然后将的宽高比存入数组中,为接下来取出哈希中的值
for(int i=0;i<rectangles.length;i++) {
double s=(double)rectangles[i][0]/rectangles[i][1];
// 去重,免得多余的数存入数组
if (!map.containsKey(s)) {
res[j]=s;
j++;
}
// 每存入一个相同数就加一
map.put(s, map.getOrDefault(s, (long)0)+1);
}
int i=0;
while(!map.isEmpty()) {
// System.out.println(res[i]);
// 如果指定的数组存在与哈希中
if (map.containsKey(res[i]) ){
// 获取值
long s=map.get(res[i])-1;
// 如果相同的宽高比大于0就说明至少有1次可互换矩形
if (s>0) {
// 在通过公式获取可互换矩形数
sum+=s*(s+1)/2;
}
// 将对应的键去除
map.remove(res[i]);
}
i++;
}
return sum;
}
}