leetcode 2001. 可互换矩形的组数

题目 :

用一个下标从 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;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值