-
算法高级学习1
一、大楼轮廓(有序表)
给定一个 N×3 的矩阵 matrix,对于每一个长度为 3 的小数组 arr,都表示一个大楼的三个数 据。
arr[0]表示大楼的左边界,arr[1]表示大楼的右边界,arr[2]表示大楼的高度(一定大于 0)。
每座大楼的地基都在 X 轴上,大楼之间可能会有重叠,请返回整体的轮廓线数组。
【举例】
matrix = { {2,5,6}, {1,7,4}, {4,6,7}, {3,6,5}, {10,13,2}, {9,11,3}, {12,14,4}, {10,12,5} }
返回:
{{1,2,4}, {2,4,6}, {4,6,7}, {6,7,4}, {9,10,3}, {10,12,5},{12,14,4}}
/**
* @Author: 郜宇博
*/
public class BuildingBorder {
public static void main(String[] args) {
int[][] matrix = new int[][]{
{2,7,3},
{3,5,5},
{4,6,4}
};
List<List<Integer>> buildingBorder = getBuildingBorder(matrix);
System.out.println(buildingBorder);
}
/**
* 用于记录高度变化
*/
public static class Node{
public int index;
public boolean isAdd;
public int height;
public Node(int index, boolean isAdd, int height) {
this.index = index;
this.isAdd = isAdd;
this.height = height;
}
}
public static List<List<Integer>> getBuildingBorder(int[][] matrix){
Node[] nodes = getHeightUpDown(matrix);
//构造两个有序表
//坐标-高度表
TreeMap<Integer,Integer> indexHeightMap = new TreeMap<>();
//高度-次数表
TreeMap<Integer,Integer> heightTimesMap = new TreeMap<>();
fillMap(indexHeightMap,heightTimesMap,nodes);
//返回结果
return getBorder(indexHeightMap);
}
public static List<List<Integer>> getBorder(TreeMap<Integer,Integer> indexHeightMap){
List<List<Integer>> res = new ArrayList<>();
int start = indexHeightMap.firstKey();
int preHeight = indexHeightMap.get(start);
for (Map.Entry<Integer,Integer> entry: indexHeightMap.entrySet()){
//如果高度发生变化
if (entry.getValue() != preHeight){
//添加(start,curKey,preHeight)
List<Integer> border= null;
if (preHeight != 0){
border = new ArrayList<>(Arrays.asList(start,entry.getKey(),preHeight));
res.add(border);
}
//新的list,起始点,高度都变了
start = entry.getKey();
preHeight = entry.getValue();
}
}
return res;
}
/**
* 填充map表
*/
public static void fillMap(TreeMap<Integer,Integer> indexHeightMap,TreeMap<Integer,Integer> heightTimesMap,Node[] nodes){
for (Node node: nodes){
//是否为添加
if (node.isAdd){
//1.处理heightTimesMap
//添加过,次数+1
if (heightTimesMap.containsKey(node.height)){
heightTimesMap.put(node.height,heightTimesMap.get(node.height)+1);
}else {
//没添加guo
heightTimesMap.put(node.height,1);
}
}else {
//下降
if (heightTimesMap.get(node.height)==1){
//删除后为0,则移除
heightTimesMap.remove(node.height);
}else {
heightTimesMap.put(node.height,heightTimesMap.get(node.height)-1);
}
}
//2.处理indexHeightMap
//没有高度了,到结尾了
//获取最大高度
Integer maxHeight = heightTimesMap.isEmpty()?0:heightTimesMap.lastKey();
indexHeightMap.put(node.index,maxHeight);
}
}
/**
* 获取高度变化的数组,返回排序后的结果
*/
public static Node[] getHeightUpDown(int[][] matrix){
Node[] resultNode = new Node[matrix.length * 2];
for (int i = 0; i < matrix.length; i++){
for (int j = 0; j < matrix[0].length ; j++){
resultNode[i * 2] = new Node(matrix[i][0],true,matrix[i][2]);
resultNode[i * 2+1] = new Node(matrix[i][1],false,matrix[i][2]);
}
}
//按照index大小排序,相同的话根据add在前,排序,防止出现线状楼
Arrays.sort(resultNode,(x,y)->{
if (x.index != y.index){
return x.index - y.index;
}
if (x.isAdd != y.isAdd){
return x.isAdd?-1:1;
}
return 0;
});
return resultNode;
}
}
二、最长子数组等于k(构造单调性、滑动窗口)
给定一个数组 arr,该数组无序,但每个值均为正数,再给定一个正数 k。
求 arr 的 所有子 数组中所有元素相加和为 k 的最长子数组长度。
例如,arr=[1,2,1,1,1],k=3。
累加和为 3 的最长子数组为[1,1,1],所以结果返回 3。
要求:时间复杂度O(N),额外空间复杂度O(1)
/**
* @Author: 郜宇博
* 给定一个数组 arr,该数组无序,但每个值均为正数,再给定一个正数 k。
* 求 arr 的 所有子 数组中所有元素相加和为 k 的最长子数组长度。
*/
public class LongestChildArraySumK {
public static void main(String[] args) {
int len = 30;
int k = 15;
int[] arr = generatePositiveArray(len);
System.out.println(getLongestCount(arr, k));
}
/**
* 构造单调性,因为数组都是正数,保持一个区间,
* 当区间内sum < k时,继续扩充区间
* 当区间sum = k 时,以i为起点,此区间最大了,更换区间,r++, l++
* 当sum > k时,如果l,r在同一位置,都向后移动,不在的话,left++,缩小区域
*/
public static int getLongestCount(int[] array, int target){
int left = 0;
int right = 0;
// sum = [left,...,right]
int sum = array[0];
int result = Integer.MIN_VALUE;
while (right != array.length){
//sum = array[right];
if (sum == target){
result = Math.max(result, right - left + 1);
if (right == array.length-1){
return Math.max(result, 0);
}
sum = sum - array[left++] + array[++right];
}
else if (sum < target){
if (right == array.length-1){
return Math.max(result, 0);
}
sum += array[++right];
}else {
if (left == right){
sum = array[++left];
right++;
}else {
sum -= array[left++];
}
}
}
return Math.max(result, 0);
}
public static int[] generatePositiveArray(int size) {
int[] result = new int[size];
for (int i = 0; i != size; i++) {
result[i] = (int) (Math.random() * 10) + 1;
}
return result;
}
}
三、拿硬币
给定一个非负数组,每一个值代表该位置上有几个铜板。
a和b玩游戏,a先手,b后手, 轮到某个人的时候,只能在一个位置上拿任意数量的铜板,但是不能不拿。
谁最先把铜板拿完谁赢。假设a和b都极度聪明,请返回获胜者的名字。
/**
* @Author: 郜宇博
* 给定一个非负数组,每一个值代表该位置上有几个铜板。
* a和b玩游戏,a先手,b后手, 轮到某个人的时候,只能在一个位置上拿任意数量的铜板,但是不能不拿。
* 谁最先把铜板拿完谁赢。假设a和b都极度聪明,请返回获胜者的名字。
*/
public class PickMoneyWin {
public static void main(String[] args) {
int[] array = new int[]{3,4,1};
System.out.println(getWinner(array));
}
/**
* 最后桌子上剩下1个硬币时,先手赢,否则后手赢
* 将所有数字求异或和,为0说明先手怎么都赢不了
*/
public static String getWinner(int[] array){
int eorResult = 0;
for (int num: array){
eorResult ^= num;
}
return eorResult==0?"后手":"先手";
}
}
四、最长子数组小于等于k(舍去部分解)
给定一个无序数组 arr,其中元素可正、可负、可 0,给定一个整数 k。
求 arr 所 有的子数组 中累加和小于或等于 k 的最长子数组长度。
例如:arr=[3,-2,-4,0,6],k=-2,相加和小于或等于-2 的最长子数组为{3,-2,-4,0},所以结果返回4。
/**
* @Author: 郜宇博
* 给定一个无序数组 arr,其中元素可正、可负、可0,给定一个整数k。
* 求arr 所有的子数组累加和小于或等于k的最长子数组长度。
*/
public class LongestChildArraySumLessK {
public static void main(String[] args) {
int[] array = new int[]{3,-2,-4,0,6};
System.out.println(getSumLessK(array,-2));
}
public static int getSumLessK(int[] array, int target) {
//准备两个数组
//1.以i开始,向后最小sum
//2.到达最小sum的边界坐标
int[] minSum = new int[array.length];
int[] minBorder = new int[array.length];
minSum[array.length - 1] = array[array.length - 1];
minBorder[array.length - 1] = array.length - 1;
for (int i = array.length - 2; i >= 0; i--) {
minSum[i] = array[i];
minBorder[i] = i;
//如果右边的最小和<0,那么应该向右拓展
if (minSum[i + 1] <= 0) {
minSum[i] += minSum[i + 1];
minBorder[i] = minBorder[i + 1];
}
}
int left = 0;
int right = 0;
int result = 0;
int curSum = 0;
for (; array.length - left > result; left++) {
//不越界,并且满足条件
while (right < array.length && minSum[right] + curSum <= target) {
//更新curSum
curSum += minSum[right];
right = minBorder[right] + 1;
}
//找到了以left为左边界最长满足条件的数组,更新
result = Math.max(result,right - left);
//边界整体向右移动(!重点)
if (left == right){
right++;
}else {
curSum -= array[left];
}
}
return result;
}
}