活跃农民
- 积分
- 765
- 大米
- 颗
- 鳄梨
- 个
- 水井
- 尺
- 蓝莓
- 颗
- 萝卜
- 根
- 小米
- 粒
- 学分
- 个
- 注册时间
- 2018-6-6
- 最后登录
- 1970-1-1
|
04/11
76. Minimum Window Substring
属于sliding window的经典题目。解法可分为两种:可以用HashMap储存T中出现的字符和次数,那么在S的遍历中可以不考虑其他字符;也可以用长度为128(或256)的数组,这时要注意除了T中出现的字符,其他字符的次数最大为0。
解法一:HashMap
1)遍历T,在HashMap中key为T的字符,value为字符出现的次数。维护一个total变量,表示T字符总数。total的意义:和HashMap中的value共同确定是不是找到了所有的所要求字符。如果不用total,只能不停遍历key来询问是不是所有value都<=0(即所有字符都出现了),效率很低。
2)维护left和right两个pointer,作为sliding window的边界;同时维护minLen来获得最短长度的信息,维护minLeft,minRight找到最短长度对应的substring起止位置
3)right右移,遇到T中的字符,对应的value--。如果这时value >= 0,说明还没找完(或刚找完)所有字符,那么total--
4)当total == 0时,说明在window当中已经包含了所有T的字符,那么开始通过移动left来尝试缩小window
5)进行while循环,left左移。遇到T中的字符,对应的value++。如果这时value >= 0,说明还没找完(或刚找完)所有字符,那么total++,这时候total将会大于0,window不满足要求,会跳出循环
6)比较当前找到的window和minLen
7)Time complexity: O(S的长度),因为每个元素被扫描了两遍; Space complexity: O(T的长度)
解法二:数组
1)基本过程和解法一相同。唯一不同点在于,没有查找S字符是否在T中,而是通过对应的数组value来判断。这是因为,只有T中出现的字符value才会大于0,其他字符的次数初始都为0。只有数组value > 0,才能对total进行++和--操作,原因还是在于total记录的是与T中的字符有关的信息。
2)Time complexity: O(S的长度),因为每个元素被扫描了两遍; Space complexity: O(T的长度)
- // 解法一
- class Solution {
- public String minWindow(String s, String t) {
- if (s == null || s.length() == 0 || t == null || t.length() == 0) return "";
- String minWindow = "";
- //int[] mapping = new int[128];
- char[] sArray = s.toCharArray(), tArray = t.toCharArray();
- Map<Character, Integer> map = new HashMap<>();
-
- for (int i = 0; i < tArray.length; i++) map.put(tArray[i], map.getOrDefault(tArray[i], 0) + 1);
- int total = tArray.length;
-
- int left = 0, right = 0;
- int minLen = sArray.length + 10, minLeft = -1, minRight = -1;
- while (right < sArray.length) {
- if (map.containsKey(sArray[right])) {
- if (map.get(sArray[right]) > 0) total--;
- map.put(sArray[right], map.get(sArray[right]) - 1);
- }
- right++;
-
- while (left < right && total == 0) {
- if (map.containsKey(sArray[left])) {
- map.put(sArray[left], map.get(sArray[left]) + 1);
- if (map.get(sArray[left]) > 0) total++;
- }
- left++;
-
- if (right - left + 1 < minLen) {
- minLen = right - left + 1;
- minLeft = left - 1;
- minRight = right;
- //System.out.println(minLeft + ", " + minRight + ", minLen: " + minLen);
- }
- }
- }
- if (minLeft != -1 && minRight != -1) minWindow = s.substring(minLeft, minRight);
- return minWindow;
- }
- }
- // 解法二
- class Solution {
- public String minWindow(String s, String t) {
- if (s == null || s.length() == 0 || t == null || t.length() == 0) return "";
- String minWindow = "";
- int[] mapping = new int[128];
- char[] sArray = s.toCharArray(), tArray = t.toCharArray();
- //Map<Character, Integer> map = new HashMap<>();
-
- for (int i = 0; i < tArray.length; i++) mapping[tArray[i]]++;
- int total = tArray.length;
-
- int left = 0, right = 0;
- int minLen = sArray.length + 10, minLeft = -1, minRight = -1;
- while (right < sArray.length) {
- if (mapping[sArray[right]] > 0) total--;
- mapping[sArray[right]]--;
- right++;
-
- while (total == 0 && left < right) {
- mapping[sArray[left]]++;
- if (mapping[sArray[left]] > 0) total++;
- left++;
- if (right - left + 1 < minLen) {
- minLen = right - left + 1;
- minLeft = left - 1;
- minRight = right;
- //System.out.println(minLeft + ", " + minRight + ", minLen: " + minLen);
- }
- }
- }
- if (minLeft != -1 && minRight != -1) minWindow = s.substring(minLeft, minRight);
- return minWindow;
- }
- }
复制代码
189. Rotate Array
题目要求三种解法,甚至需要用O(1)空间。
解法一:copy新数组,根据新index一个个赋值,用O(n)空间。
解法二:循环替代。每次循环中,把当前元素赋值给右边相隔k的位置,直至回到原点。
解法三:三次翻转。第一次,翻转整个数组;第二次,翻转前k个数;第三次,翻转后n - k个数。很精妙。
1. Two Sum
老朋友了。直接上最最优解HashMap。
167. Two Sum II - Input array is sorted
1的follow-up,也没什么难度,常规经典的two pointers从nums[0]和nums[length - 1]向中间缩小范围。
653. Two Sum IV - Input is a BST
将2sum的input变为了BST,做法有所改变。两种解法。
解法一:利用BST inorder traversal转化为sorted array的特性,转化为167题。
解法二:利用HashSet
1)HashSet储存两个加数当中已经被遍历的一个,那么只需当前node的value和HashSet里的某个数加和为target即可。
2)如果没有,那么把当前node的value加入HashSet备用,同时用recursion查找左右子树,直至null时返回false。
3)Time complexity: O(n); Space complexity: O(n)
- class Solution {
- Set<Integer> set = new HashSet<>();
- public boolean findTarget(TreeNode root, int k) {
- if (root == null) return false;
- if (set.contains(k - root.val)) return true;
- set.add(root.val);
- return findTarget(root.left, k) || findTarget(root.right, k);
- }
- }
复制代码
152. Maximum Product Subarray
属于dynamic programing,可以看作是53. Maximum Subarray的升级版。
1)维护currMax表示包含当前nums[i]元素在内的局部最大值(局部的定义是从最近一个0元素到当前nums[i]);维护currMin表示包含当前nums[i]元素在内的局部最小值;维护max表示全局最大值,最后返回之
2)在遍历array元素的过程中,不断更新currMax,currMin,和max。当nums[i] < 0时,currMax * num[i]会变为最小,currMin * num[i]会变为最大,所以有两种更新的方式:
(a)因为currMax要取currMax * num[i],currMin * num[i],和nums[i]三个数的最大,同理currMin要取currMax * num[i],currMin * num[i],和nums[i]三个数的最小,如果不做临时储存,那么更新了currMax必然会导致更新currMin时用以比较的数发生改变,导致错误。所以先tempMax = currMax,tempMin =currMin,然后根据tempMax * num[i],tempMin * num[i],和nums[i]来更新,避免currMax和currMin的纠缠产生的错误。
(b)当nums[i] < 0时,currMax和currMin先行交换,然后currMax取currMax * num[i]和nums[i]的最大,currMin取currMin * num[i]和nums[i]的最小,同样可以避免由于currMax和currMin的纠缠产生的错误。
3)遍历过程中更新max。
4)Time complexity: O(n); Space complexity: O(1)
- class Solution {
- public int maxProduct(int[] nums) {
- if (nums == null || nums.length == 0) return 0;
- int currMax = nums[0], currMin = nums[0], max = nums[0];
- for (int i = 1; i < nums.length; i++) {
- int tempMax = currMax * nums[i], tempMin = currMin * nums[i];
- currMax = Math.max(Math.max(tempMax, tempMin), nums[i]);
- currMin = Math.min(Math.min(tempMax, tempMin), nums[i]);
- max = Math.max(currMax, max);
- }
- return max;
- }
- }
复制代码
128. Longest Consecutive Sequence
挺有意思的一道题。最开始想用union find,然而发现当元素为负数时array-based union find不适用,只好放弃。
有online和offline两种解法。
解法一:offline的HashSet解法,实际上是利用了HashSet的特性将unsorted array转化成了类似sorted array的性质。
1)先遍历一遍,把所有元素加入HashSet
2)第二次遍历,寻找可能的consecutive sequence最左边的元素,并以之为起点寻找是否有+1的元素来组成consecutive sequence,记录length并更新maxLen
3)Time complexity: O(n); Space complexity: O(n)
解法二:online的HashMap解法,模拟已发现的consecutive sequence。其中重要的key为已发现的consecutive sequence的两个端点,重要的value为这两个端点之间的consecutive sequence的长度。
1)如果num[i]的-1和+1的数同时存在,说明nums[i]可以连接两个consecutive sequences,那么根据端点保存的长度信息找到连接后新sequence的左右端点,并更新为最新的长度
2)如果只有num[i]的-1的数存在,说明nums[i]是作为新的右端点,那么找到左端点,并更新长度信息
3)如果只有num[i]的+1的数存在,说明nums[i]是作为新的左端点,那么找到右端点,并更新长度信息
4)遍历的过程中更新maxLen
5)Time complexity: O(n); Space complexity: O(n)
- // 解法一
- class Solution {
- public int longestConsecutive(int[] nums) {
- if (nums == null || nums.length == 0) return 0;
- Set<Integer> set = new HashSet<>();
- for (int i = 0; i < nums.length; i++) {
- set.add(nums[i]);
- }
-
- int maxLen = 0;
- for (int num : nums) {
- if (set.contains(num - 1)) continue;
- int currLen = 0;
- while (set.contains(num)) {
- currLen++;
- num++;
- }
- maxLen = Math.max(maxLen, currLen);
- }
- return maxLen;
- }
- }
- // 解法二
- class Solution {
- public int longestConsecutive(int[] nums) {
- if (nums == null || nums.length == 0) return 0;
-
- Map<Integer, Integer> map = new HashMap<>();
- int maxLen = 1;
-
- for (int i = 0; i < nums.length; i++) {
- if (map.containsKey(nums[i])) continue;
- int newLen = 1;
- map.put(nums[i], 1);
- if (map.containsKey(nums[i] - 1) && map.containsKey(nums[i] + 1)) {
- int leftBoundary = nums[i] - map.get(nums[i] - 1), rightBoundary = nums[i] + map.get(nums[i] + 1);
- newLen = map.get(nums[i] - 1) + map.get(nums[i] + 1) + 1;
- map.put(leftBoundary, newLen);
- map.put(rightBoundary, newLen);
- } else if (map.containsKey(nums[i] - 1)) {
- int leftBoundary = nums[i] - map.get(nums[i] - 1);
- newLen = map.get(nums[i] - 1) + 1;
- map.put(leftBoundary, newLen);
- map.put(nums[i], newLen);
- } else if (map.containsKey(nums[i] + 1)) {
- int rightBoundary = nums[i] + map.get(nums[i] + 1);
- newLen = map.get(nums[i] + 1) + 1;
- map.put(rightBoundary, newLen);
- map.put(nums[i], newLen);
- }
- maxLen = Math.max(newLen, maxLen);
- }
-
- return maxLen;
- }
- }
复制代码
|
|