题目一
数组为{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; }