• 算法之数组和问题


    算法题之数组和求解

    数组和问题

    ​ 加上给定一个数组和值x。设计一个算法使得如果数组中存在两个元素的和为x,则输出两个元素的值组成的数组(不区分先后),否则输出{-1, -1}。

    ​ 分析:

    1. 最简单的办法,就是依次求每个元素与其他元素的和。这个就是经典的握手问题,不难得出其最坏时间复杂度为: (Theta)((n^2)) 这种指数级别的时间复杂度必然不是我们想要的,直接PASS
    2. 先做排序然后再进行查找: 假设使用前面已知的最快的排序算法,最坏时间复杂度为: (Theta)(nlg(n))。之后可以使用二分查找法对每个针对每个元素查找 x - arr[i] 是否在数组中,此时时间最坏时间复杂度为: (Theta)(nlg(n))。该算法实现的代码如下:
    private static int[] findSum(int[] arr, int sum) {
    		// STEP1: 先对数组采用归并排序进行排序
    		mergeSort(arr, 0, arr.length);
    		
    		// STEP2: 遍历数组,用二分查找法查找是否存在sum-arr[i]的元素
    		for (int i=0; i<arr.length; i++) {
    			int j = binarySearch(arr, 0, arr.length, sum - arr[i]);
    			if (j != -1) {
    				if (j != i) {
    					return new int[] {arr[i], arr[j]};
    				} else {
    					// j = i的时候,则需要判断j的左侧和右侧的值是否和j相等,相等则证明存在两个元素
    					if (arr[j - 1] == arr[j]) {
    						return new int[] {arr[i], arr[j - 1]};
    					} else if (arr[j + 1] == arr[j]) {
    						return new int[] {arr[i], arr[j + 1]};
    					}
    				}
    			}
    		}
    		
    		return new int[]{-1, -1};
    	}
    

    注意其中的mergeSort和binarySearch调用的是前一章节所指的代码http://www.cnblogs.com/Kidezyq/p/8379267.html


    扩展

    • 其实对于求两个元素的和有一种时间复杂度为:(Theta)(n)的算法。该算法利用了桶排序的思想,借助Map的特殊数据结构。我们这里以arr[i]为key,i为value。要判断sum-arr[i]是否存在数组中,只要看map.get(sum-arr[i])是否为空即可。实现的代码如下:
    private static int[] findSumWithMap(int[] arr, int sum) {
    		Map<Integer, Integer> map = new HashMap<>();
    		// STEP1: 将数据入map
    		for (int i = 0; i < arr.length; i++) {
    			map.put(arr[i], i);
    		}
    
    		// STEP2: 开始判断sum-arr[i]是否在map中 
    		for (int i = 0; i < arr.length; i++) {
    			// 因为遍历顺序同放入map的顺序都是从前到后,所以如果存在多个同值元素,其最终会将后者的下标放入map,此时不影响判断逻辑
    			if (map.get(sum - arr[i]) != null && i != map.get(sum - arr[i])) {
    				return new int[]{arr[i], sum - arr[i]};
    			}
    		}
    		return new int[]{-1, -1};
    	}
    
    • 其实对于上面的分析方法2,还有一个优化的方法,可以对排序后的数组在时间:(Theta)(n)之内找到两个和为指定值的算法。方法的思想还是二分查找法。首先取两个下边lowIndex和upIndex,最开始的时候lowIndex指向数组首元素,upIndex指向数组末尾元素。然后比对arr[lowIndex] + arr[upIndex]与sum的关系。根据比对的结果分别对lowIndex和upIdex进行移动。此时对于后续的整个查找过程只需要(Theta)(n)时间即可。源代码如下:
    private static int[] findSumTwoSide(int[] arr, int sum) {
    		// STEP1: 使用归并排序对数组进行排序
    		mergeSort(arr, 0, arr.length);
    		
    		// STEP2: 进行两端查找
    		int lowIndex = 0;
    		int upIndex = arr.length - 1;
    		while (upIndex > lowIndex) {
    			if (arr[lowIndex] + arr[upIndex] == sum) {
    				// 相等直接返回
    				return new int[] {arr[lowIndex], arr[upIndex]};
    			} else if (arr[lowIndex] + arr[upIndex] < sum) {
    				// 小于左侧右移
    				lowIndex++;
    			} else {
    				// 大于右侧左移
    				upIndex--;
    			}
    		}
    		return new int[] {-1, -1};
    	}
    
    • 该题可以延伸至n个元素的和:

    其解决思想就是分治了。将n个元素的规模依次降低,最终降到2个元素的和。这里给出三个元素求和的例子,其他多维依次类推:

    private static int[] findSumOf3Digits(int[] arr, int sum) {
    		// STEP1:先调用归并排序算法进行排序
    		mergeSort(arr, 0, arr.length);
    		
    		// STEP2: 进行细化问题处理
    		// 先申请一个数组来存储排除一个元素后的数组元素组成的新的数组
    		int[] leftArr = new int[arr.length - 1];	
    		for (int i = 0; i < arr.length; i++) {
    			// 复制arr[i]左侧元素到数组起始位置
    			if (i > 0) {
    				System.arraycopy(arr, 0, leftArr, 0, i);
    			}
    			
    			// 复制arr[i]右侧元素到数组结尾位置
    			if (i < arr.length - 1) {
    				System.arraycopy(arr, i + 1, leftArr, i, arr.length - i - 1);
    			}
    			
    			// 如果剩下两个数的和满足,则返回三个元素
    			int[] leftIndexes = findSumTwoSide(leftArr, sum - arr[i]);
    			if (!Arrays.equals(leftIndexes, new int[] {-1, -1})) {
    				return new int[] {arr[i], leftIndexes[0], leftIndexes[1]};
    			}
    		}
    		
    		return new int[] {-1, -1, -1};
    	}
    
  • 相关阅读:
    42. Trapping Rain Water
    223. Rectangle Area
    645. Set Mismatch
    541. Reverse String II
    675. Cut Off Trees for Golf Event
    安装 VsCode 插件安装以及配置
    向上取整 向下取整 四舍五入 产生100以内随机数
    JS 判断是否为数字 数字型特殊值
    移动端初始配置,兼容不同浏览器的渲染内核
    Flex移动布局中单行和双行布局的区别以及使用
  • 原文地址:https://www.cnblogs.com/Kidezyq/p/8379270.html
Copyright © 2020-2023  润新知