楼主: Garhom
跳转到指定楼层
上一主题 下一主题
收起左侧

LeetCode刷题日常记录

🔗
 楼主| Garhom 2019-4-8 10:06:36 | 只看该作者
全局:
04/07


289. Game of Life

两种做法。
解法一、copy一个二维数组,然后in-place改变原二维数组。
1)nested loop扫描每一个cell
2)对于每个cell,用一个helper method来实现update
3)在helper method中,扫描当前cell的8个neighbor,维护一个count来数live neighbor cells。当count == 3 || (count == 2 && current cell is live)时,在原二维数组中将当前cell标记为1
4)时间复杂度:O(m*n);空间复杂度:O(m*n)

解法二、利用bit的最后两位记录状态。最后一位是current state,倒数第二位是next state。00: dead <- dead; 01: dead <- live; 10: live <- dead; 11: live <- live
1)2)同上
3)用board[row][col] & 1,得到最后一位bit表示的current state,进而count live cells。update过后,所有元素从0,1变为2,3
4)再次遍历所有元素,右移一位,得到新的状态
5)时间复杂度:O(m*n);空间复杂度:O(1)


73. Set Matrix Zeroes
遍历两次。第一次记录哪些行列需要变为0,第二次将这些行列变为0。
1)如果遇到0,那么将这一行和这一列的第一个元素都设为0作为标记。因此会产生一个问题,那就是无法分辨第一行和第一列中出现的0到底是因为原本在第一行/列就有0,需要变为0,还是来自于标记的0。因此,维护两个boolean值来记录是否在第一行和第一列原本就有0
2)第二次遍历时,从row = 1和col = 1开始遍历,并将对应行列变为0
3)第一行和第一列单独遍历,根据boolean值来决定是否变为0
4)时间复杂度:O(m*n);空间复杂度:O(1)



287. Find the Duplicate Number

由于需要满足题目的三个条件:不能改变原array,额外空间O(1),时间复杂度小于 O(n2),因此不能直接sort。正确解法是用一快一慢两个指针
1)fast和slow都初始化为nums[0]。数学上可以证明在长度为n+1的数组里存1...n一定会有重复,从0出发一定不会再回到nums[0]
2)用do while循环(重点注意,否则下一步会变成死循环),fast走两步,即fast = nums[nums[fast]];slow走一步,即nums[slow],直至fast == slow
3)再用一个指针search = nums[0],每次走一步,直至和slow相等
4)时间复杂度:O(n);空间复杂度:O(1)



268. Missing Number

解法一:sort找第一个index和value不对应的值
解法二:把数组所有value求和得到actualSum,然后再把所有index求和再加nums.length得到expectedSum,差值为所求
解法三:用bit manipulation,将数组的value和index做XOR (^),再^nums.length,因为缺失的数只有其对应的index,所以XOR的结果即为所求


283. Move Zeroes

1)维护left和right两个pointer。其中left指向左边第一个0,right指向第一个未被扫描的数,left和right之间都是扫描过的0,left左边是所有扫描过的被交换过去的非0数。
2)如果right是0,继续向前
3)如果right非0,交换left和right,然后left和right都向前一步
4)时间复杂度:O(n);空间复杂度:O(1)



238. Product of Array Except Self
1)维护数组products,最后返回
2)先从左向右扫描。维护一个变量left,记录当前元素的左边所有元素的乘积,赋值给products[i]。边扫描边更新left和products[i]。扫描结束后,products[i]即为当前元素的左边所有元素的乘积
3)然后从右向左扫描。维护一个变量right,记录当前元素的右边所有元素的乘积,然后products[i]乘上right为新的products[i]。边扫描边更新right和products[i]。扫描结束后,products数组即为所求
4)时间复杂度:O(n);空间复杂度:O(1)



169. Majority Element

解法一:sort,取nums[nums.length / 2]。题目保证了majority element个数一定大于nums.length / 2,所以无论数组元素个数是奇数还是偶数,sort后的中间元素一定就是所求数
解法二:Boyer-Moore Voting Algorithm。
1)维护一个变量candidate,初始化为nums[0];同时维护其对应的count,初始化为1
2)遍历数组,如果count == 0,那么candidate为空,所以将当前nums[i]赋值为candidate,同时count = 1
3)如果遇到相同元素,count++;
4)如果遇到不同元素,candidate数量被抵消,count--。最后剩下的一定是有多余的未被抵消的candidate
5)时间复杂度:O(n);空间复杂度:O(1)



229. Majority Element II

169的follow-up,同样是用Boyer-Moore Voting Algorithm
1)维护两个变量candidate1和candidate2,都初始化为Integer.MIN_VALUE;同时维护其对应的count1和count2,初始化为0
2)如果count1 == 0,那么candidate1为空,所以将当前nums[i]赋值为candidate1,同时count1 = 1
3)count2 == 0,对应赋值candidate2,同时count2 = 1
4)遇到与candidate1或candidate2,count1++或count2++
5)遇到第三种数,count1和count2同时自减1(被抵消了)
6)得到两个candidate后,还需再进行一次循环,只有满足个数大于n / 3才符合要求
5)时间复杂度:O(n);空间复杂度:O(1)

回复

使用道具 举报

🔗
 楼主| Garhom 2019-4-10 01:53:34 | 只看该作者
全局:
04/08

15. 3Sum
1)由于不考虑index的影响,所以可以先sort
2)先定三个数中的第一个数,剩下的两个数reduce为类2sum的问题。除了index == 0,第一个数的选取不能有重复
3)类2sum问题中,维护left和right两个pointer初始化为start + 1和nums.length - 1
4)比较target和nums[left] + nums[right]
5)left向右移动,right向左移动,注意避免搜索重复的数
6)Time complexity: O(n2);Space complexity: O(1)
  1. class Solution {
  2.     public List<List<Integer>> threeSum(int[] nums) {
  3.         List<List<Integer>> resultList = new ArrayList<>();
  4.         if (nums == null || nums.length < 3) return resultList;
  5.         Arrays.sort(nums);
  6.         for (int i = 0; i < nums.length - 2; i++) {
  7.             if (nums[i] > 0) break;
  8.             if (i == 0 || nums[i] != nums[i - 1]) {
  9.                 int target = 0 - nums[i], left = i + 1, right = nums.length - 1;
  10.                 while (left < right) {
  11.                     if (target > nums[left] + nums[right]) left++;
  12.                     else if (target < nums[left] + nums[right]) right--;
  13.                     else {
  14.                         resultList.add(new ArrayList<Integer>(Arrays.asList(nums[i], nums[left], nums[right])));
  15.                         do left++; while (left < right && nums[left] == nums[left - 1]);
  16.                         do right--; while (left < right && nums[right] == nums[right + 1]);
  17.                     }
  18.                 }
  19.             }
  20.         }
  21.         return resultList;
  22.     }
  23. }
复制代码



16. 3Sum Closest

可以作为3Sum的follow-up。区别在于这题不需要找出相应的组合,只需找到closest sum。
1)由于不考虑index的影响,所以可以先sort

2)维护两个变量:closestSum,表示最终返回的结果;minDiff,用来找到closestSum
3)循环同上
4)在类2sum问题中,,维护一个临时变量sum表示当前三个数的和
5)如果target == sum,那么不可能有比它更接近target的数了,直接返回
6)比较target和nums[left] + nums[right],left向右移动,right向左移动。此处可以不用避免搜索重复的数。同时更新closestSum和minDiff
7)Time complexity: O(n2);Space complexity: O(1)

  1. class Solution {
  2.     public int threeSumClosest(int[] nums, int target) {
  3.         if (nums == null || nums.length < 3) return 0;
  4.         Arrays.sort(nums);
  5.         int closest = 0, minDiff = Integer.MAX_VALUE;
  6.         for (int i = 0; i < nums.length - 2; i++) {
  7.             if (i == 0 || nums[i] != nums[i - 1]) {
  8.                 int left = i + 1, right = nums.length - 1;
  9.                 while (left < right) {
  10.                     int sum = nums[i] + nums[left] + nums[right];
  11.                     if (Math.abs(target - sum) < minDiff) {
  12.                         closest = sum;
  13.                         minDiff = Math.abs(target - sum);
  14.                     }
  15.                     
  16.                     if (target == sum) return sum;
  17.                     else if (target > sum) left++;
  18.                     else right--;
  19.                 }
  20.             }
  21.         }
  22.         return closest;
  23.     }
  24. }
复制代码




18. 4Sum

可以作为3Sum的另一个follow-up。实际上,在kSum问题中,k >= 3时,可以利用recursion,写出一般形式的generalized代码,将k代入相应的具体数值即可。
1)由于不考虑index的影响,所以可以先sort
2)用helper method,用recursion将k = 4代入
3)helper method中,最关键的是维护一个临时的List<Integer> list = new ArrayList<>();
4)当k > 2时,临时list加入当前的数,用recursion计算k - 1,然后将当前数从list移除(类似backtracking)。遍历至nums.length - k + 1个数,注意避免搜索重复的数
5)当k == 2时,reduce成类2sum问题。当target == nums[left] + nums[right]时,加入临时list,再把这个list加入resultList里:resultList.add(new ArrayList(list)); 然后将nums[left]和nums[right]从list移除。注意避免搜索重复的数
6)Time complexity: O(n2)

  1. class Solution {
  2.     List<List<Integer>> resultList = new ArrayList<>();
  3.     public List<List<Integer>> fourSum(int[] nums, int target) {
  4.         if (nums == null || nums.length < 4) return resultList;
  5.         Arrays.sort(nums);
  6.         sum(4, nums, target, 0, new ArrayList<Integer>());
  7.         return resultList;
  8.     }
  9.    
  10.     private void sum(int k, int[] nums, int target, int start, List<Integer> list) {
  11.         if (k == 2){
  12.             int left = start, right = nums.length - 1;
  13.             while (left < right) {
  14.                 if (target > nums[left] + nums[right]) left++;
  15.                 else if (target < nums[left] + nums[right]) right--;
  16.                 else {
  17.                     list.add(nums[left]);
  18.                     list.add(nums[right]);
  19.                     resultList.add(new ArrayList<Integer>(list));
  20.                     list.remove(list.size() - 1);
  21.                     list.remove(list.size() - 1);
  22.                     do left++; while (left < right && nums[left] == nums[left - 1]);
  23.                     do right--; while (left < right && nums[right] == nums[right + 1]);
  24.                 }
  25.             }
  26.         } else if (k > 2) {
  27.             for (int i = start; i < nums.length - k + 1; i++) {
  28.                 if (i == start || nums[i] != nums[i - 1]) {
  29.                     list.add(nums[i]);
  30.                     sum(k - 1, nums, target - nums[i], i + 1, list);
  31.                     list.remove(list.size() - 1);
  32.                 }
  33.             }
  34.         } else return;
  35.     }
  36. }
复制代码




454. 4Sum II

跟之前做的2sum, 3sum, 4sum完全不是一回事,有另一套解题的思路。
1)将4个数组分为两组,每组2个数组
2)二重循环A, B两个数组,用HashMap,将A[i]+B[j]的sum作为key,对应出现的次数作为value,统计每一种sum出现了多少次
3)维护一个count变量作为最后的返回结果
4)二重循环C, D两个数组,因为target = 0 = A[i]+B[j] + C[x] + D[y],所以在HashMap中找是否存在0 - (C[x] + D[y])的key。如果存在,count += 对应的value,表示这四个数可以有count种方式得到target
5)Time complexity: O(n2);Space complexity: O(n)
  1. class Solution {
  2.     public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
  3.         Map<Integer, Integer> sumCountMapping = new HashMap<>();
  4.         for (int i = 0; i < A.length; i++) {
  5.             for (int j = 0; j < B.length; j++) {
  6.                 sumCountMapping.put(A[i] + B[j], sumCountMapping.getOrDefault(A[i] + B[j], 0) + 1);
  7.             }
  8.         }
  9.         int result = 0;
  10.         for (int i = 0; i < C.length; i++) {
  11.             for (int j = 0; j < D.length; j++) {
  12.                 result += sumCountMapping.getOrDefault(0 - C[i] - D[j], 0);
  13.             }
  14.         }
  15.         return result;
  16.     }
  17. }
复制代码




217. Contains Duplicate

解法一:Brute force,O(n2)时间,超时
解法二:sort,看是否有相邻两个数相等
解法三:HashSet


219. Contains Duplicate II

217的follow-up,增加条件nums[i] == nums[j]且j - i <= k。由于对index有要求,不能改变index,所以不能sort
1)维护一个HashMap,key为nums[i],value为index
2)如果HashMap存在nums[i]且j - i <= k,返回true
3)否则,put key and value
4)遍历完成,跳出循环,返回false
5)Time complexity: O(n);Space complexity: O(n)


220. Contains Duplicate III

219的follow-up,总结过了,打个卡


  1. class Solution {
  2.     public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
  3.         if (nums == null || nums.length == 0 || k <= 0 || t < 0) return false;
  4.         TreeSet<Integer> treeSet = new TreeSet<>();
  5.         for (int i = 0; i < nums.length; i++) {
  6.             Integer floor = treeSet.floor(nums[i]);
  7.             if (floor != null && ((long) nums[i] - floor) <= t) return true;
  8.             Integer ceiling = treeSet.ceiling(nums[i]);
  9.             if (ceiling != null && ((long) ceiling - nums[i]) <= t) return true;
  10.             treeSet.add(nums[i]);
  11.             if (treeSet.size() > k) treeSet.remove(nums[i - k]);
  12.         }
  13.         return false;
  14.     }
  15. }
复制代码


回复

使用道具 举报

🔗
 楼主| Garhom 2019-4-10 08:48:53 | 只看该作者
全局:
04/09


228. Summary Ranges

1)维护两个变量:start和end,指向处于同一range的起止
2)遍历数组,当发现相邻两数不连续时,将目前的range加入list
3)start和end相等和不相等分开处理
4)循环结束,再将最后得到的range加入list
  1. class Solution {
  2.     public List<String> summaryRanges(int[] nums) {
  3.         List<String> list = new ArrayList<>();
  4.         if (nums == null || nums.length == 0) return list;
  5.         StringBuilder sb = new StringBuilder();
  6.         String start = String.valueOf(nums[0]), end = String.valueOf(nums[0]);
  7.         for (int i = 1; i < nums.length; i++) {
  8.             if (nums[i] == nums[i - 1] + 1) end = String.valueOf(nums[i]);
  9.             else {
  10.                 sb.append(start);
  11.                 if (!start.equals(end)) sb.append("->").append(end);
  12.                 list.add(sb.toString());
  13.                 sb = new StringBuilder();
  14.                 start = String.valueOf(nums[i]);
  15.                 end = String.valueOf(nums[i]);
  16.             }
  17.         }
  18.         sb.append(start);
  19.         if (!start.equals(end)) sb.append("->").append(end);
  20.         list.add(sb.toString());
  21.         return list;
  22.     }
  23. }
复制代码




39. Combination Sum

属于backtracking问题。
1)choices:candidates数组里的数(无重复);goal:target;constraints:不能走回头路
2)因为candidates中没有重复的数,所以不用sort
3)每层遍历从当前的元素开始(而不是从candidates[0]开始)
  1. class Solution {
  2.     List<List<Integer>> resultList = new ArrayList<>();
  3.     public List<List<Integer>> combinationSum(int[] candidates, int target) {
  4.         //if (candidates == null || candidates.length == 0) return resultList;
  5.         find(candidates, target, new ArrayList<Integer>(), 0);
  6.         return resultList;
  7.     }
  8.     private void find(int[] candidates, int target, List<Integer> list, int start) {
  9.         if (target < 0) return;
  10.         if (target == 0) {
  11.             resultList.add(new ArrayList<Integer>(list));
  12.             return;
  13.         }
  14.         for (int i = start; i < candidates.length; i++) {
  15.             list.add(candidates[i]);
  16.             find(candidates, target - candidates[i], list, i);
  17.             list.remove(list.size() - 1);
  18.         }
  19.     }
  20. }
复制代码




40. Combination Sum II

39的follow-up。
1)choices:candidates数组里的数(有重复);goal:target;constraints:不能走回头路
2)因为candidates有重复的数,所以需要sort
3)每层遍历从当前的元素开始(而不是从candidates[0]开始),遍历时需要找到下一个非重复的数

  1. class Solution {
  2.     List<List<Integer>> resultList = new ArrayList<>();
  3.     public List<List<Integer>> combinationSum2(int[] candidates, int target) {
  4.         Arrays.sort(candidates);
  5.         find(candidates, target, new ArrayList<Integer>(), 0);
  6.         return resultList;
  7.     }
  8.     private void find(int[] candidates, int target, List<Integer> list, int start) {
  9.         if (target < 0) return;
  10.         if (target == 0) {
  11.             resultList.add(new ArrayList<Integer>(list));
  12.             return;
  13.         }
  14.         for (int i = start; i < candidates.length; i++) {
  15.             if (i == start || candidates[i] != candidates[i - 1]) {
  16.                 list.add(candidates[i]);
  17.                 find(candidates, target - candidates[i], list, i + 1);
  18.                 list.remove(list.size() - 1);
  19.             }
  20.         }
  21.     }
  22. }
复制代码




216. Combination Sum III

40的follow-up。
1)choices:1到9(无重复);goal:取k个数,使和为n;constraints:不能走回头路

2)当k < 0或n < 0时返回;当k == 0 && n == 0时加入resultList
3)下一层遍历从当前元素的下一个元素开始
  1. class Solution {
  2.     List<List<Integer>> resultList = new ArrayList<>();
  3.     public List<List<Integer>> combinationSum3(int k, int n) {
  4.         if (k > 9) return resultList;
  5.         find(k, n, new ArrayList<Integer>(), 1);
  6.         return resultList;
  7.     }
  8.     private void find(int k, int n, List<Integer> list, int start) {
  9.         if (n < 0 || k < 0) return;
  10.         if (n == 0 && k == 0) {
  11.             resultList.add(new ArrayList<Integer>(list));
  12.             return;
  13.         }
  14.         for (int i = start; i < 10; i++) {
  15.                 list.add(i);
  16.                 find(k - 1, n - i, list, i + 1);
  17.                 list.remove(list.size() - 1);
  18.         }
  19.     }
  20. }
复制代码




209. Minimum Size Subarray Sum

O(n)的解法:sliding window
1)维护left和right两个pointer形成sliding window,其中left指向当前range的最左边元素,初始化为0;right指向range右边还没扫描的第一个元素,初始化为0
2)维护临时变量sum用来和s比较,维护变量minLen作为返回结果,初始化为一个大的数
3)right依次扫描并加到sum里面
4)当sum >= s时,开始不断减去left的元素,同时移动left,直至sum < s,并更新minLen
5)如果minLen为初始化的数,未被改变,说明没有比s大的sum,返回0;否则返回minLen
  1. class Solution {
  2.     public int minSubArrayLen(int s, int[] nums) {
  3.         if (nums == null || nums.length == 0) return 0;
  4.         int left = 0, right = 0;
  5.         int sum = 0, minLen = nums.length + 10;
  6.         while (right < nums.length) {
  7.             sum += nums[right++];
  8.             if (sum >= s) {
  9.                 while (left < right && sum >= s) {
  10.                     minLen = Math.min(right - left, minLen);
  11.                     sum -= nums[left++];
  12.                 }
  13.             }
  14.         }
  15.         return minLen > nums.length ? 0 : minLen;
  16.     }
  17. }
复制代码



回复

使用道具 举报

🔗
 楼主| Garhom 2019-4-12 09:02:46 | 只看该作者
全局:
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的长度)

  1. // 解法一
  2. class Solution {
  3.     public String minWindow(String s, String t) {
  4.         if (s == null || s.length() == 0 || t == null || t.length() == 0) return "";
  5.         String minWindow = "";
  6.         //int[] mapping = new int[128];
  7.         char[] sArray = s.toCharArray(), tArray = t.toCharArray();
  8.         Map<Character, Integer> map = new HashMap<>();
  9.         
  10.         for (int i = 0; i < tArray.length; i++) map.put(tArray[i], map.getOrDefault(tArray[i], 0) + 1);
  11.         int total = tArray.length;
  12.         
  13.         int left = 0, right = 0;
  14.         int minLen = sArray.length + 10, minLeft = -1, minRight = -1;
  15.         while (right < sArray.length) {
  16.             if (map.containsKey(sArray[right])) {
  17.                 if (map.get(sArray[right]) > 0) total--;
  18.                 map.put(sArray[right], map.get(sArray[right]) - 1);
  19.             }
  20.             right++;
  21.                
  22.             while (left < right && total == 0) {
  23.                 if (map.containsKey(sArray[left])) {
  24.                     map.put(sArray[left], map.get(sArray[left]) + 1);
  25.                     if (map.get(sArray[left]) > 0) total++;
  26.                 }
  27.                 left++;
  28.                     
  29.                 if (right - left + 1 < minLen) {
  30.                     minLen = right - left + 1;
  31.                     minLeft = left - 1;
  32.                     minRight = right;
  33.                     //System.out.println(minLeft + ", " + minRight + ", minLen: " + minLen);
  34.                 }
  35.             }
  36.         }
  37.         if (minLeft != -1 && minRight != -1) minWindow = s.substring(minLeft, minRight);
  38.         return minWindow;
  39.     }
  40. }

  41. // 解法二
  42. class Solution {
  43.     public String minWindow(String s, String t) {
  44.         if (s == null || s.length() == 0 || t == null || t.length() == 0) return "";
  45.         String minWindow = "";
  46.         int[] mapping = new int[128];
  47.         char[] sArray = s.toCharArray(), tArray = t.toCharArray();
  48.         //Map<Character, Integer> map = new HashMap<>();
  49.         
  50.         for (int i = 0; i < tArray.length; i++) mapping[tArray[i]]++;
  51.         int total = tArray.length;
  52.         
  53.         int left = 0, right = 0;
  54.         int minLen = sArray.length + 10, minLeft = -1, minRight = -1;
  55.         while (right < sArray.length) {
  56.             if (mapping[sArray[right]] > 0) total--;
  57.             mapping[sArray[right]]--;
  58.             right++;
  59.             
  60.             while (total == 0 && left < right) {
  61.                 mapping[sArray[left]]++;
  62.                 if (mapping[sArray[left]] > 0) total++;
  63.                 left++;
  64.                 if (right - left + 1 < minLen) {
  65.                     minLen = right - left + 1;
  66.                     minLeft = left - 1;
  67.                     minRight = right;
  68.                     //System.out.println(minLeft + ", " + minRight + ", minLen: " + minLen);
  69.                 }
  70.             }
  71.         }
  72.         if (minLeft != -1 && minRight != -1) minWindow = s.substring(minLeft, minRight);
  73.         return minWindow;
  74.     }
  75. }
复制代码


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)

  1. class Solution {
  2.     Set<Integer> set = new HashSet<>();
  3.     public boolean findTarget(TreeNode root, int k) {
  4.         if (root == null) return false;
  5.         if (set.contains(k - root.val)) return true;
  6.         set.add(root.val);
  7.         return findTarget(root.left, k) || findTarget(root.right, k);
  8.     }
  9. }
复制代码


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)
  1. class Solution {
  2.     public int maxProduct(int[] nums) {
  3.         if (nums == null || nums.length == 0) return 0;
  4.         int currMax = nums[0], currMin = nums[0], max = nums[0];
  5.         for (int i = 1; i < nums.length; i++) {
  6.             int tempMax = currMax * nums[i], tempMin = currMin * nums[i];
  7.             currMax = Math.max(Math.max(tempMax, tempMin), nums[i]);
  8.             currMin = Math.min(Math.min(tempMax, tempMin), nums[i]);
  9.             max = Math.max(currMax, max);
  10.         }
  11.         return max;
  12.     }
  13. }
复制代码


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)

  1. // 解法一
  2. class Solution {
  3.     public int longestConsecutive(int[] nums) {
  4.         if (nums == null || nums.length == 0) return 0;
  5.         Set<Integer> set = new HashSet<>();
  6.         for (int i = 0; i < nums.length; i++) {
  7.             set.add(nums[i]);
  8.         }
  9.         
  10.         int maxLen = 0;
  11.         for (int num : nums) {
  12.             if (set.contains(num - 1)) continue;
  13.             int currLen = 0;
  14.             while (set.contains(num)) {
  15.                 currLen++;
  16.                 num++;
  17.             }
  18.             maxLen = Math.max(maxLen, currLen);
  19.         }
  20.         return maxLen;
  21.     }
  22. }

  23. // 解法二
  24. class Solution {
  25.     public int longestConsecutive(int[] nums) {
  26.         if (nums == null || nums.length == 0) return 0;
  27.         
  28.         Map<Integer, Integer> map = new HashMap<>();
  29.         int maxLen = 1;
  30.         
  31.         for (int i = 0; i < nums.length; i++) {
  32.             if (map.containsKey(nums[i])) continue;
  33.             int newLen = 1;
  34.             map.put(nums[i], 1);
  35.             if (map.containsKey(nums[i] - 1) && map.containsKey(nums[i] + 1)) {
  36.                 int leftBoundary = nums[i] - map.get(nums[i] - 1), rightBoundary = nums[i] + map.get(nums[i] + 1);
  37.                 newLen = map.get(nums[i] - 1) + map.get(nums[i] + 1) + 1;
  38.                 map.put(leftBoundary, newLen);
  39.                 map.put(rightBoundary, newLen);
  40.             } else if (map.containsKey(nums[i] - 1)) {
  41.                 int leftBoundary = nums[i] - map.get(nums[i] - 1);
  42.                 newLen = map.get(nums[i] - 1) + 1;
  43.                 map.put(leftBoundary, newLen);
  44.                 map.put(nums[i], newLen);
  45.             } else if (map.containsKey(nums[i] + 1)) {
  46.                 int rightBoundary = nums[i] + map.get(nums[i] + 1);
  47.                 newLen = map.get(nums[i] + 1) + 1;
  48.                 map.put(rightBoundary, newLen);
  49.                 map.put(nums[i], newLen);
  50.             }
  51.             maxLen = Math.max(newLen, maxLen);
  52.         }
  53.         
  54.         return maxLen;
  55.     }
  56. }
复制代码


回复

使用道具 举报

🔗
 楼主| Garhom 2019-4-13 10:07:23 | 只看该作者
全局:
04/12


121. Best Time to Buy and Sell Stock

简单的DP题。当价格变更小时更新buy,否则就求当前price到buy的差值,用以更新maxProfit。


122. Best Time to Buy and Sell Stock II

121的follow-up。变化之处在于不限买卖次数。观察可知,每当price[i] > price[i - 1]时,都可以得到利润,并加到总利润中。


123. Best Time to Buy and Sell Stock III
122的follow-up。限定最多买卖2次。是比较难的一道DP题。有两种解法,其中第二种解法为一般的DP解法,放在188题中讨论。下面是解法一,代码简单,理解难。
1)维护2对(共4个)变量,分别对应第一次买、第一次卖、第二次买、第二次卖后所能得到的最大利润
2)遍历每一个price。更新profitBuy1,为自身和0 - prices[i]的较大值,表示第一次买入以后得到的最大利润。然后更新profitSell1,为自身和prices[i] + profitBuy1的较大值,表示第一次卖出后得到的最大利润

3)然后更新profitBuy2,为自身和profitSell1 - prices[i]的较大值,表示如果立刻进行第二次买入能得到的最大利润。然后更新profitSell2,为自身和prices[i] + profitBuy2,表示第二次卖出后得到的最大利润
4)最后profitBuy2即为所求
5)Time complexity: O(n); Space complexity: O(1)
  1. class Solution {
  2.     public int maxProfit(int[] prices) {
  3.         if (prices == null || prices.length == 0) return 0;
  4.         int profitBuy1 = Integer.MIN_VALUE, profitSell1 = 0, profitBuy2 = Integer.MIN_VALUE, profitSell2 = 0;
  5.         for (int i = 0; i < prices.length; i++) {
  6.             profitBuy1 = Math.max(profitBuy1, -prices[i]);
  7.             profitSell1 = Math.max(profitSell1, profitBuy1 + prices[i]);
  8.             profitBuy2 = Math.max(profitBuy2, profitSell1 - prices[i]);
  9.             profitSell2 = Math.max(profitSell2, profitBuy2 + prices[i]);
  10.         }
  11.         return profitSell2;
  12.     }
  13. }
复制代码



188. Best Time to Buy and Sell Stock IV
123的follow-up,将次数变为k次。同样有两种解法。


不管使用哪一种解法,都需要用一个优化来避免Memory exceed limit:当k > prices.length / 2,直接degenerate为不限次数的情况(即122题)。当然也可以用prices.length为判断条件,但用prices.length / 2更严格,原因是,只有当两次交易不在同一天时,才有可能获得最大的利润,否则一直在同一天买卖利润只能为0。


解法一:根据123题的启发,维护k对变量,分别表示第k次买卖后能获得的最大利润,注意第一次买入的特殊表达式。最后返回sell[k - 1]。


解法二:
1)维护(k + 1) * prices.length的二维dp数组,其中row表示第t次买卖,col表示第i天。dp[t][i]表示在第i天完成了t次买卖交易后得到的最大利润。
2)外层循环为交易次数,从t == 1开始,即意味着dp[0]的所有元素(表示每天都没有完成任何交易)都为0。内存循环为天数,从i == 1开始,即dp[t][0]的所有元素(表示第0天不管完成了多少次交易)都为0。
3)维护一个maxDiff,表示在前面某一次交易完成后,再在那之后的某一天花钱买入之后的最大利润,所以意味着还差一次卖出就会完成第t次交易。在内层循环前,maxDiff初始化为0 - prices[0],表示第一次交易未完成,花出去了prices[0]这么多钱。
4)dp[t][i]取的是两种情况下的较大值:一种是前一天没交易,即dp[t][i - 1],交易次数不变;另一种是,前面某天做了交易买入,第i天要卖出把第t次交易完成,即maxDiff + prices[i]。所以dp[t][i] = Math.max(dp[t][i - 1],  maxDiff + prices[i]);
5)然后更新maxDiff,为自身和第i天买入的较大值,即maxDiff = Math.max(maxDiff, dp[t - 1][i] - prices[i]);
  1. lass Solution {
  2.     public int maxProfit(int k, int[] prices) {
  3.         if (prices == null || prices.length == 0 || k == 0) return 0;
  4.         
  5.         if (k > prices.length / 2) return quickSolve(prices);
  6.         
  7.         int[][] dp = new int[k + 1][prices.length];
  8.         for (int t = 1; t <= k; t++) {
  9.             int maxDiff = -prices[0];
  10.             for (int i = 1; i < prices.length; i++) {
  11.                 dp[t][i] = Math.max(dp[t][i - 1],  maxDiff + prices[i]);
  12.                 maxDiff = Math.max(maxDiff, dp[t - 1][i] - prices[i]);
  13.             }
  14.             
  15.         }
  16.         return dp[k][prices.length - 1];
  17.     }
  18.    
  19.     private int quickSolve(int[] prices) {
  20.         int maxProfit = 0;
  21.         for (int i = 1; i < prices.length; i++) {
  22.             if (prices[i] > prices[i - 1]) maxProfit += prices[i] - prices[i - 1];
  23.         }
  24.         return maxProfit;
  25.     }
  26. }
复制代码


120. Triangle

DP题,需要转变思维,从底层往上走,这样就可以在每一层遍历的时候进行选择。
  1. class Solution {
  2.     public int minimumTotal(List<List<Integer>> triangle) {
  3.         int dimension = triangle.size();
  4.         int[] dp = new int[dimension + 1];
  5.         for (int row = dimension - 1; row >= 0; row--) {
  6.             for (int col = 0; col <= row; col++) {
  7.                 dp[col] = triangle.get(row).get(col) + Math.min(dp[col], dp[col + 1]);
  8.             }
  9.         }
  10.         return dp[0];
  11.     }
  12. }
复制代码


118. Pascal's Triangle

简单数学关系。


119. Pascal's Triangle II

动态更新每一层的数,因为需要在相邻两个数进行加和,所以从右边开始的话可以避免更新一个数后对后续数的更新产生影响。


88. Merge Sorted Array

在in-place操作时,如果从nums1左边开始操作,势必会影响原来的数。所以应该从右边开始填入,先填入较大的数。


977. Squares of a Sorted Array

和88题类似的思想,先填入较大的数。
回复

使用道具 举报

🔗
 楼主| Garhom 2019-4-15 04:39:00 | 只看该作者
全局:
04/13

80. Remove Duplicates from Sorted Array II


26题的follow-up,典型的two pointers问题。
1)维护两个pointer:right指向第一个未被扫描的元素;left指向最后一个已去重的元素,而非第一个可被放入的位置(即最后一个去重的元素的下一位)
2)如果left != 0 且 nums[right] == nums[left] 且 nums[left] == nums[left - 1]这三个条件同时满足时,表示已经有两个相同的当前元素了,所以直接跳过当前元素,right++
3)否则,nums[++left] = nums[right++];
  1. class Solution {
  2.     public int removeDuplicates(int[] nums) {
  3.         if (nums == null || nums.length == 0) return 0;
  4.         int left = 0, right = 1;
  5.         
  6.         while (right < nums.length) {
  7.             if (nums[right] == nums[left] && left != 0 && nums[left] == nums[left - 1]) right++;
  8.             else nums[++left] = nums[right++];
  9.         }
  10.         return left + 1;
  11.     }
  12. }
复制代码


79. Word Search

典型的DFS问题,用recursion。
1)遍历每个board中的元素,利用helper method判断能否从当前元素开始找到word
2)helper method中,因为是recursion,所以需要先写明终止条件,如过界、遍历到了已经被遍历过的元素、当前元素不在word相应位置等,返回false
3)另一个终止条件是,如果遍历完了整个word,那么返回true表示找到了
4)判断从当前元素开始的四个邻居能否找到,如果有至少一个邻居能找到就能返回true
5)如果没有一个邻居能找到,返回false
  1. class Solution {
  2.     public boolean exist(char[][] board, String word) {
  3.         if (word == null || word.length() == 0) return false;
  4.         
  5.         int rowLen = board.length, colLen = rowLen == 0 ? 0 : board[0].length;
  6.         char[] wArray = word.toCharArray();
  7.         boolean[][] visited = new boolean[rowLen][colLen];
  8.         
  9.         for (int row = 0; row < rowLen; row++) {
  10.             for (int col = 0; col < colLen; col++) {
  11.                 if (find(board, row, col, wArray, 0, visited)) return true;
  12.             }
  13.         }
  14.         return false;
  15.     }
  16.    
  17.     private boolean find(char[][] board, int row, int col, char[] wArray, int pos, boolean[][] visited) {
  18.         if (row < 0 || row >= board.length || col < 0 || col >= board[0].length || board[row][col] != wArray[pos] || visited[row][col]) return false;
  19.         if (pos == wArray.length - 1) return true;
  20.         visited[row][col] = true;
  21.         if(find(board, row + 1, col, wArray, pos + 1, visited)
  22.             || find(board, row - 1, col, wArray, pos + 1, visited)
  23.             || find(board, row, col + 1, wArray, pos + 1, visited)
  24.             || find(board, row, col - 1, wArray, pos + 1, visited)) return true;
  25.         visited[row][col] = false;
  26.         return false;
  27.     }
  28. }
复制代码


212. Word Search II

79题的follow-up,难度增加是因为要搜索一个dictionary里存在的words而不是只看某一个word是否存在。做法是,将这些words以trie的形式表示,然后按每个character搜索。换言之单个word搜索是trie的特殊形式。
1)自己构建TrieNode,内部维护TrieNode array表示出现过的character。注意:和一般的trie不同的地方在于,平常的trie还会维护一个boolean来表示上一个字母是不是某个word的最后一个字母,但由于题目的要求,可以直接维护一个String来表示这个word。
2)将dictionary里的所有words填进trie里,返回head
3)遍历board里的所有元素,用helper method来判断从这个元素开始能否找到一个word
4)确定终止条件
5)如果当前的TrieNode的word变量存在,说明找到了这样一个word,加入list,同时注意要把word变为null,把这个word从trie里删除,来避免重复加入
6)把当前的board元素标记为#,避免走回头路。搜索完成后在恢复为原来的元素
  1. class Solution {
  2.     List<String> list = new ArrayList<>();
  3.     int[] dir = new int[]{0, 1, 0, -1, 0};
  4.     public List<String> findWords(char[][] board, String[] words) {
  5.         if (words == null || words.length == 0) return list;
  6.         int rowLen = board.length, colLen = rowLen == 0? 0 : board[0].length;
  7.         
  8.         TrieNode head = buildTrie(words);
  9.         for (int row = 0; row < rowLen; row++) {
  10.             for (int col = 0; col < colLen; col++) {
  11.                 dfs(board, row, col, head);
  12.             }
  13.         }
  14.         return list;
  15.     }
  16.    
  17.     private void dfs(char[][] board, int row, int col, TrieNode node) {
  18.         if (row < 0 || row >= board.length || col < 0 || col >= board[0].length || board[row][col] == '#' || node.next[board[row][col]] == null) return;
  19.         char c = board[row][col];
  20.         if (node.next[c].word != null) {
  21.             list.add(node.next[c].word);
  22.             node.next[c].word = null;
  23.         }
  24.         board[row][col] = '#';
  25.         for (int i = 0; i < 4; i++) {
  26.             dfs(board, row + dir[i], col + dir[i + 1], node.next[c]);
  27.         }
  28.         board[row][col] = c;
  29.     }
  30.    
  31.     private TrieNode buildTrie(String[] words) {
  32.         TrieNode head = new TrieNode();
  33.         for (String word: words) {
  34.             TrieNode node = head;
  35.             for (char c: word.toCharArray()) {
  36.                 if (node.next[c] == null) node.next[c] = new TrieNode();
  37.                 node = node.next[c];
  38.             }
  39.             node.word = word;
  40.         }
  41.         return head;
  42.     }
  43.    
  44.     class TrieNode {
  45.         TrieNode[] next;
  46.         String word;
  47.         TrieNode() {
  48.             next = new TrieNode[128];
  49.         }
  50.     }
  51. }
复制代码


75. Sort Colors

简化的quicksort,元素只有3个。
1)维护3个pointers,其中left指向最后一个0的下一个元素(即第一个1)表示新的0可插入的位置;right指向第一个2的前一个元素表示新的2可插入的位置;mid指向第一个未被扫描的元素(即最后一个1的下一个元素)。[mid, right] inclusively是未扫描的元素。
2)循环条件:mid <= right
3)nums[mid]为1,什么都不做,mid++
4)nums[mid]为0,和nums[left]交换元素,同时left++,mid++
5)nums[mid]为2,和nums[right]交换元素,同时right--
  1. class Solution {
  2.     public void sortColors(int[] nums) {
  3.         if (nums == null || nums.length < 2) return;
  4.         int left = 0, mid = 0, right = nums.length - 1;
  5.         while (mid <= right) {
  6.             if (nums[mid] == 1) mid++;
  7.             else if (nums[mid] == 0) swap(nums, left++, mid++);
  8.             else swap(nums, mid, right--);
  9.         }
  10.     }
  11.     private void swap(int[] nums, int i, int j) {
  12.         int temp = nums[i];
  13.         nums[i] = nums[j];
  14.         nums[j] = temp;
  15.     }
  16. }
复制代码


66. Plus One

简单数学的应用。从右向左,当carry不为零时,继续加,否则跳出循环。


64. Minimum Path Sum

典型的DP题。可以维护一个二维dp数组,但观察发现实际上dp[i]只和自身和dp[i - 1]有关,所以可以降维至一维,动态更新。
1)单独更新dp[0]
2)row == 0时,dp更新不需要经过选择,只和dp[i - 1]有关
3)除了第0行/列,dp[i]更新为自身和dp[i - 1]的较小值与grid[row][col]的和。
4)最后返回dp[n - 1]
  1. class Solution {
  2.     public int minPathSum(int[][] grid) {
  3.         int rowLen = grid.length, colLen = rowLen == 0? 0 : grid[0].length;
  4.         int[] dp = new int[colLen];
  5.         
  6.         for (int row = 0; row < rowLen; row++) {
  7.             dp[0] += grid[row][0];
  8.             for (int col = 1; col < colLen; col++) {
  9.                 if (row == 0) dp[col] = grid[row][col] + dp[col - 1];
  10.                 else dp[col] = grid[row][col] + Math.min(dp[col], dp[col - 1]);
  11.             }
  12.         }
  13.         return dp[colLen - 1];
  14.     }
  15. }
复制代码


回复

使用道具 举报

🔗
 楼主| Garhom 2019-4-16 00:45:39 | 只看该作者
全局:
04/14 跟警察叔叔打了场球,有点爽

54, 56, 57, 59, 174


从今天开始将所有总结放到github上:https://github.com/GarhomLee/LeetCode






回复

使用道具 举报

🔗
 楼主| Garhom 2019-4-16 08:55:41 | 只看该作者
全局:
04/15
打卡 53,55,45,48,42,407,268,448,287,41
回复

使用道具 举报

🔗
Alice1109 2019-4-16 09:47:43 | 只看该作者
全局:
加油 一起刷题

评分

参与人数 1大米 +1 收起 理由
Garhom + 1 加油

查看全部评分

回复

使用道具 举报

🔗
 楼主| Garhom 2019-4-18 06:09:28 | 只看该作者
全局:
04/17

开始做hashtable tag,打卡31,26,27,414,11,451,447,438,409
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册账号
隐私提醒:
  • ☑ 禁止发布广告,拉群,贴个人联系方式:找人请去🔗同学同事飞友,拉群请去🔗拉群结伴,广告请去🔗跳蚤市场,和 🔗租房广告|找室友
  • ☑ 论坛内容在发帖 30 分钟内可以编辑,过后则不能删帖。为防止被骚扰甚至人肉,不要公开留微信等联系方式,如有需求请以论坛私信方式发送。
  • ☑ 干货版块可免费使用 🔗超级匿名:面经(美国面经、中国面经、数科面经、PM面经),抖包袱(美国、中国)和录取汇报、定位选校版
  • ☑ 查阅全站 🔗各种匿名方法

本版积分规则

>
快速回复 返回顶部 返回列表