高频算法和数据结构(四)

题目一

数组为{3, 2, 2, 3, 1},查询为(0, 3, 2)

意思是在数组里下标0~3这个范围上,有几个2?答案返回2

假设给你一个数组arr

对这个数组的查询非常频繁,都给出来

请返回所有查询的结果

解题: 

预处理结构,生成如下一张表,这样要查1~4中有几个1,在这个表中用二分查就可以

public static class QueryBox2 {
		private HashMap<Integer, ArrayList<Integer>> map;

		public QueryBox2(int[] arr) {
			map = new HashMap<>();
			for (int i = 0; i < arr.length; i++) {
				if (!map.containsKey(arr[i])) {
					map.put(arr[i], new ArrayList<>());
				}
				map.get(arr[i]).add(i);
			}
		}

		public int query(int L, int R, int value) {
			if (!map.containsKey(value)) {
				return 0;
			}
			ArrayList<Integer> indexArr = map.get(value);
			// 查询 < L 的下标有几个
			int a = countLess(indexArr, L);
			// 查询 < R+1 的下标有几个
			int b = countLess(indexArr, R + 1);
			return b - a;
		}

		// 在有序数组arr中,用二分的方法数出<limit的数有几个
		// 也就是用二分法,找到<limit的数中最右的位置
		private int countLess(ArrayList<Integer> arr, int limit) {
			int L = 0;
			int R = arr.size() - 1;
			int mostRight = -1;
			while (L <= R) {
				int mid = L + ((R - L) >> 1);
				if (arr.get(mid) < limit) {
					mostRight = mid;
					L = mid + 1;
				} else {
					R = mid - 1;
				}
			}
			return mostRight + 1;
		}

	}

题目二

https://leetcode.com/problems/maximum-subarray/

返回一个数组中,子数组最大累加和

解题: 

子数组问题

如果必须以0结尾的子数组,它可以扩展到什么程度,能让累加和最大

如果必须以1结尾的子数组,它可以扩展到什么程度,能让累加和最大..........

public static int maxSubArray(int[] arr) {
		if (arr == null || arr.length == 0) {
			return 0;
		}
		int max = Integer.MIN_VALUE;
		int cur = 0;
		for (int i = 0; i < arr.length; i++) {
			cur += arr[i];
			max = Math.max(max, cur);
			cur = cur < 0 ? 0 : cur;
		}
		return max;
	}

	public static int maxSubArray2(int[] arr) {
		if (arr == null || arr.length == 0) {
			return 0;
		}
		// 上一步,dp的值
		// dp[0]
		int pre = arr[0];
		int max = arr[0];
		for (int i = 1; i < arr.length; i++) {
			pre = Math.max(arr[i], arr[i] + pre);
			max =  Math.max(max, pre);
		}
		return max;
	}

题目三

 返回一个二维数组中,子矩阵最大累加和

https://leetcode-cn.com/problems/max-submatrix-lcci/

解题:可以将矩阵转化为如题目二的一维数组来求解

先求0行数据,最大累加和多少?

求0,1行最大累加和数据是多少?..... 

public static int maxSum(int[][] m) {
		if (m == null || m.length == 0 || m[0].length == 0) {
			return 0;
		}
		// O(N^2 * M)
		int N = m.length;
		int M = m[0].length;
		int max = Integer.MIN_VALUE;
		for (int i = 0; i < N; i++) {
			// i~j
			int[] s = new int[M];
			for (int j = i; j < N; j++) {
				for (int k = 0; k < M; k++) {
					s[k] += m[j][k];
				}
				max = Math.max(max, maxSubArray(s));
			}
		}
		return max;
	}
	
	public static int maxSubArray(int[] arr) {
		if (arr == null || arr.length == 0) {
			return 0;
		}
		int max = Integer.MIN_VALUE;
		int cur = 0;
		for (int i = 0; i < arr.length; i++) {
			cur += arr[i];
			max = Math.max(max, cur);
			cur = cur < 0 ? 0 : cur;
		}
		return max;
	}

题目四

 返回一个数组中,选择的数字不能相邻的情况下,

最大子序列累加和

解题:

动态规划:定义[i]表示0~i范围上在不相邻的情况下,怎么取最大累加和才能得到最好答案 

public static int subSqeMaxSumNoNext(int[] arr) {
		if (arr == null || arr.length == 0) {
			return 0;
		}
		if (arr.length == 1) {
			return arr[0];
		}
		int[] dp = new int[arr.length];
		// dp[i] : arr[0..i]挑选,满足不相邻设定的情况下,随意挑选,最大的累加和
		dp[0] = arr[0];
		dp[1] = Math.max(arr[0], arr[1]);
		for (int i = 2; i < arr.length; i++) {
			int p1 = dp[i - 1];
			int p2 = arr[i] + Math.max(dp[i - 2], 0);
			dp[i] = Math.max(p1, p2);
		}
		return dp[arr.length - 1];
	}

	// 给定一个数组arr,在不能取相邻数的情况下,返回所有组合中的最大累加和
	// 思路:
	// 定义dp[i] : 表示arr[0...i]范围上,在不能取相邻数的情况下,返回所有组合中的最大累加和
	// 在arr[0...i]范围上,在不能取相邻数的情况下,得到的最大累加和,可能性分类:
	// 可能性 1) 选出的组合,不包含arr[i]。那么dp[i] = dp[i-1]
	// 比如,arr[0...i] = {3,4,-4},最大累加和是不包含i位置数的时候
	//
	// 可能性 2) 选出的组合,只包含arr[i]。那么dp[i] = arr[i]
	// 比如,arr[0...i] = {-3,-4,4},最大累加和是只包含i位置数的时候
	//
	// 可能性 3) 选出的组合,包含arr[i], 且包含arr[0...i-2]范围上的累加和。那么dp[i] = arr[i] + dp[i-2]
	// 比如,arr[0...i] = {3,1,4},最大累加和是3和4组成的7,因为相邻不能选,所以i-1位置的数要跳过
	//
	// 综上所述:dp[i] = Max { dp[i-1], arr[i] , arr[i] + dp[i-2] }
	public static int maxSum(int[] arr) {
		if (arr == null || arr.length == 0) {
			return 0;
		}
		int N = arr.length;
		if (N == 1) {
			return arr[0];
		}
		if (N == 2) {
			return Math.max(arr[0], arr[1]);
		}
		int[] dp = new int[N];
		dp[0] = arr[0];
		dp[1] = Math.max(arr[0], arr[1]);
		for (int i = 2; i < N; i++) {
			dp[i] = Math.max(Math.max(dp[i - 1], arr[i]), arr[i] + dp[i - 2]);
		}
		return dp[N - 1];
	}

题目五

 原问题Loading...

进阶问题:在原问题的基础上,增加一个原则:

相邻的孩子间如果分数一样,分的糖果数必须一样

返回至少需要分多少糖

 解题:

预处理结构+贪心:

左边没东西就为1块糖,比左边大数字++,不再比左边大就返回1,left代表每一个点左边坡度

右边没有东西为1块糖,比右边大数字++,不再比右边大就返回1,right代表每一个点右边坡度

最后每个位置求left和right的最大值,便是最后答案

// 这是原问题的优良解
	// 时间复杂度O(N),额外空间复杂度O(N)
	public static int candy1(int[] arr) {
		if (arr == null || arr.length == 0) {
			return 0;
		}
		int N = arr.length;
		int[] left = new int[N];
		for (int i = 1; i < N; i++) {
			if (arr[i - 1] < arr[i]) {
				left[i] = left[i - 1] + 1;
			}
		}
		int[] right = new int[N];
		for (int i = N - 2; i >= 0; i--) {
			if (arr[i] > arr[i + 1]) {
				right[i] = right[i + 1] + 1;
			}
		}
		int ans = 0;
		for (int i = 0; i < N; i++) {
			ans += Math.max(left[i], right[i]);
		}
		return ans + N;
	}

	// 这是原问题空间优化后的解
	// 时间复杂度O(N),额外空间复杂度O(1)
	public static int candy2(int[] arr) {
		if (arr == null || arr.length == 0) {
			return 0;
		}
		int index = nextMinIndex2(arr, 0);
		int res = rightCands(arr, 0, index++);
		int lbase = 1;
		int next = 0;
		int rcands = 0;
		int rbase = 0;
		while (index != arr.length) {
			if (arr[index] > arr[index - 1]) {
				res += ++lbase;
				index++;
			} else if (arr[index] < arr[index - 1]) {
				next = nextMinIndex2(arr, index - 1);
				rcands = rightCands(arr, index - 1, next++);
				rbase = next - index + 1;
				res += rcands + (rbase > lbase ? -lbase : -rbase);
				lbase = 1;
				index = next;
			} else {
				res += 1;
				lbase = 1;
				index++;
			}
		}
		return res;
	}

	public static int nextMinIndex2(int[] arr, int start) {
		for (int i = start; i != arr.length - 1; i++) {
			if (arr[i] <= arr[i + 1]) {
				return i;
			}
		}
		return arr.length - 1;
	}

	public static int rightCands(int[] arr, int left, int right) {
		int n = right - left + 1;
		return n + n * (n - 1) / 2;
	}
// 这是进阶问题的最优解
	// 时间复杂度O(N), 额外空间复杂度O(1)
	public static int candy3(int[] arr) {
		if (arr == null || arr.length == 0) {
			return 0;
		}
		int index = nextMinIndex3(arr, 0);
		int[] data = rightCandsAndBase(arr, 0, index++);
		int res = data[0];
		int lbase = 1;
		int same = 1;
		int next = 0;
		while (index != arr.length) {
			if (arr[index] > arr[index - 1]) {
				res += ++lbase;
				same = 1;
				index++;
			} else if (arr[index] < arr[index - 1]) {
				next = nextMinIndex3(arr, index - 1);
				data = rightCandsAndBase(arr, index - 1, next++);
				if (data[1] <= lbase) {
					res += data[0] - data[1];
				} else {
					res += -lbase * same + data[0] - data[1] + data[1] * same;
				}
				index = next;
				lbase = 1;
				same = 1;
			} else {
				res += lbase;
				same++;
				index++;
			}
		}
		return res;
	}

	public static int nextMinIndex3(int[] arr, int start) {
		for (int i = start; i != arr.length - 1; i++) {
			if (arr[i] < arr[i + 1]) {
				return i;
			}
		}
		return arr.length - 1;
	}

	public static int[] rightCandsAndBase(int[] arr, int left, int right) {
		int base = 1;
		int cands = 1;
		for (int i = right - 1; i >= left; i--) {
			if (arr[i] == arr[i + 1]) {
				cands += base;
			} else {
				cands += ++base;
			}
		}
		return new int[] { cands, base };
	}

题目六

 生成长度为size的达标数组,什么叫达标?

达标:对于任意的 i<k<j满足 [i] + [j] != [k] * 2

给定一个正数size,返回长度为size的达标数组

解题:

 将数据分为两部分

做变是个偶数域,右边是个奇数域,一个偶数+一个奇数不可能是某个数的两倍

分治思想,当你搞定一半规模的时候,你去想整合逻辑,怎么让它扩展出两倍的N都达标

public static int[] makeNo(int size) {
		if (size == 1) {
			return new int[] { 1 };
		}
		// size
		// 一半长达标来
		// 7 : 4
		// 8 : 4
		// [4个奇数] [3个偶]
		int halfSize = (size + 1) / 2;
		int[] base = makeNo(halfSize);
		// base -> 等长奇数达标来
		// base -> 等长偶数达标来
		int[] ans = new int[size];
		int index = 0;
		for (; index < halfSize; index++) {
			ans[index] = base[index] * 2 - 1;
		}
		for (int i = 0; index < size; index++, i++) {
			ans[index] = base[i] * 2;
		}
		return ans;
	}

题目七

 字符串交错组成

https://leetcode.com/problems/interleaving-string/

 解题:

样本对应模型动态规划

dp[i][j]:表示str1只拿i个字符,和str2只拿j个字符,能否组成str总的只拿i+j个字符?都是前缀,如果这张表可以填好,最右下角就是我们需要的答案。

public static boolean isInterleave(String s1, String s2, String s3) {
		if (s1 == null || s2 == null || s3 == null) {
			return false;
		}
		char[] str1 = s1.toCharArray();
		char[] str2 = s2.toCharArray();
		char[] str3 = s3.toCharArray();
		if (str3.length != str1.length + str2.length) {
			return false;
		}
		boolean[][] dp = new boolean[str1.length + 1][str2.length + 1];
		dp[0][0] = true;
		for (int i = 1; i <= str1.length; i++) {
			if (str1[i - 1] != str3[i - 1]) {
				break;
			}
			dp[i][0] = true;
		}
		for (int j = 1; j <= str2.length; j++) {
			if (str2[j - 1] != str3[j - 1]) {
				break;
			}
			dp[0][j] = true;
		}
		for (int i = 1; i <= str1.length; i++) {
			for (int j = 1; j <= str2.length; j++) {
				if (
						(str1[i - 1] == str3[i + j - 1] && dp[i - 1][j])
						
						
						
						
						|| 
						
						
						
						(str2[j - 1] == str3[i + j - 1] && dp[i][j - 1])
						
						
						) {
					dp[i][j] = true;
				}
			}
		}
		return dp[str1.length][str2.length];
	}

 题目八

大楼轮廓线问题

https://leetcode.com/problems/the-skyline-problem/

 解题:

生成一个有序表map

key代表高度,按照有序组织,value代表这个高度加入的次数,加入之后,有序表中拿出最大的key,logN复杂度,我就可以知道最大高度的变化

1位置加了高度4,次数变成2,最大高度没有变化,还是4

2位置加了高度5,次数1,我就知道在2位置最大高度由4变成了5

public static class Node {
		public int x;
		public boolean isAdd;
		public int h;

		public Node(int x, boolean isAdd, int h) {
			this.x = x;
			this.isAdd = isAdd;
			this.h = h;
		}
	}

	public static class NodeComparator implements Comparator<Node> {
		@Override
		public int compare(Node o1, Node o2) {
			return o1.x - o2.x;
		}
	}

	public static List<List<Integer>> getSkyline(int[][] matrix) {
		Node[] nodes = new Node[matrix.length * 2];
		for (int i = 0; i < matrix.length; i++) {
			nodes[i * 2] = new Node(matrix[i][0], true, matrix[i][2]);
			nodes[i * 2 + 1] = new Node(matrix[i][1], false, matrix[i][2]);
		}
		Arrays.sort(nodes, new NodeComparator());
		// key  高度  value 次数
		TreeMap<Integer, Integer> mapHeightTimes = new TreeMap<>();
		TreeMap<Integer, Integer> mapXHeight = new TreeMap<>();
		for (int i = 0; i < nodes.length; i++) {
			if (nodes[i].isAdd) {
				if (!mapHeightTimes.containsKey(nodes[i].h)) {
					mapHeightTimes.put(nodes[i].h, 1);
				} else {
					mapHeightTimes.put(nodes[i].h, mapHeightTimes.get(nodes[i].h) + 1);
				}
			} else {
				if (mapHeightTimes.get(nodes[i].h) == 1) {
					mapHeightTimes.remove(nodes[i].h);
				} else {
					mapHeightTimes.put(nodes[i].h, mapHeightTimes.get(nodes[i].h) - 1);
				}
			}
			if (mapHeightTimes.isEmpty()) {
				mapXHeight.put(nodes[i].x, 0);
			} else {
				mapXHeight.put(nodes[i].x, mapHeightTimes.lastKey());
			}
		}
		List<List<Integer>> ans = new ArrayList<>();
		for (Entry<Integer, Integer> entry : mapXHeight.entrySet()) {
			int curX = entry.getKey();
			int curMaxHeight = entry.getValue();
			if (ans.isEmpty() || ans.get(ans.size() - 1).get(1) != curMaxHeight) {
				ans.add(new ArrayList<>(Arrays.asList(curX, curMaxHeight)));
			}
		}
		return ans;
	}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值