Leetcode分类刷题答案&心得
Posted KINGHEY
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Leetcode分类刷题答案&心得相关的知识,希望对你有一定的参考价值。
Array
448.找出数组中所有消失的数
要求:整型数组取值为 1 ≤ a[i] ≤ n,n是数组大小,一些元素重复出现,找出[1,n]中没出现的数,实现时时间复杂度为O(n),并不占额外空间
思路1:(discuss)用数组下标标记未出现的数,如出现4就把a[3]的数变成负数,当查找时判断a的正负就能获取下标
tips:注意数组溢出
public List<Integer> findDisappearedNumbers(int[] nums) { List<Integer> disList = new ArrayList<Integer>(); //用数组下标来记录出现过的数 for (int i = 0; i < nums.length; i++) { int item = Math.abs(nums[i]) - 1; //判断是否已经出现过 if (nums[item] > 0) { nums[item] = -nums[item]; } } //把仍然是正数所对应的下标加入数组列表中 for (int i = 0; i < nums.length; i++) { if (nums[i] > 0) { disList.add(i + 1); } } return disList; }
思路2:(discuss)下标标记数,但把出现过的数用作下标,然后此下标对应的数+n
public List<Integer> findDisappearedNumbers(int[] nums) { List<Integer> disList = new ArrayList<Integer>(); int n = nums.length; //用数组下标来记录出现过的数 for (int i = 0; i < nums.length; i++) { nums[(nums[i] - 1) % n] += n; } //把a[i]小于n时的i+1加入数组列表中 for (int i = 0; i < nums.length; i++) { if (nums[i] <= n) { disList.add(i + 1); } } return disList; }
思路3:(discuss)整理数组,目的就是把数字与下标对应,整理后下标与数字不对应就证明此数消失,此方法称为数组原地实现(只改变顺序不改变值)
public List<Integer> findDisappearedNumbers(int[] nums) { for (int i = 0; i < nums.length; i++) { while (nums[i] != i + 1 && nums[i] != nums[nums[i] - 1]) { int tmp = nums[i]; nums[i] = nums[tmp - 1]; nums[tmp - 1] = tmp; } } List<Integer> res = new ArrayList<Integer>(); for (int i = 0; i < nums.length; i++) { if (nums[i] != i + 1) { res.add(i + 1); } } return res; }
2016-12-14
442.找出数组重复的数
要求与448类似
思路1:(自写一次AC)还是用下标标记,每出现一次,相应下标的数+n,检索时搜大于2n的就重复两次,还能检测出现次数大于两次的情况
public List<Integer> findDuplicates(int[] nums) { List<Integer> dupList = new ArrayList<Integer>(); if (nums == null || nums.length == 0) { return dupList; } int n = nums.length; //出现一次就把相应下标所指整数+n for (int i = 0; i < n; i++) { nums[(nums[i] - 1) % n] += n; } for (int i = 0; i < n; i++) { if (nums[i] > 2*n) { dupList.add(i + 1); } } return dupList; }
思路2:(discuss)用正负法,巧用if的位置,但只适合出现两次,注意Math.abs用了两次(另:再遍历一次+Math.abs可恢复原数据)
public List<Integer> findDuplicates(int[] nums) { List<Integer> dupList = new ArrayList<Integer>(); if (nums == null || nums.length == 0) { return dupList; } for (int i = 0; i < nums.length; i++) { //获得原下标 int item = Math.abs(nums[i]) - 1; //只有出现过一次并且item一样才会满足 if (nums[item] < 0) { dupList.add(Math.abs(item + 1)); } nums[item] = -nums[item]; } return dupList; }
2016-12-14
414.第三大的数
要求:给出非空整型数组,返回第三大的数;若不存在,则返回最大数。时间复杂度要为O(n)
思路1: (discuss)尝试多次才成功,定义前三大数,并用continue跳出循环排除重复的情况,用右移插入数,判断好是否到第三大
warn:想好如何定义前三大的数,若定义为Integer.MIN_VALUE,输入为-2147483648时出错,可以投机定义为long类型
public int thirdMax(int[] nums) { if (nums == null || nums.length == 0) { return 0; } //创建三个变量存储前三大的数 long max, mid, min; max = mid = min = Long.MIN_VALUE; //判断第三个数是否赋值 int count = 0; for (int x : nums) { //防止重复输入,continue跳出本次循环 if (x == max || x == mid) { continue; } //x大于max则三个变量都向右移 if (x > max) { min = mid; mid = max; max = x; count++; } else if (x > mid) { min = mid; mid = x; count++; } else if (x >= min) { min = x; count++; } } if (count >= 3) { return (int)min; } return (int)max; }
思路2: (discuss)使用TreeSet特性,有序且不能重复,当size()大于3时去掉第一位,小于3时输出最大值,时间复杂度为O(nlog3)
public int thirdMax(int[] nums) { TreeSet<Integer> set = new TreeSet<Integer>(); for (int num : nums) { set.add(num); //超过3个则去除 if (set.size() > 3) { set.remove(set.first()); } } //少于3个输出最大值 return set.size() < 3 ? set.last() : set.first();
2016-12-15
283.移动零
要求:把数组中的0都放在末尾,且保持其他数的相对位置
思路1: (自写一次AC)从末尾开始判断是否为0,是0就与旁边交换,直到旁边的不是0为止(24ms)
tips:注意超过数组长度
public void moveZeroes(int[] nums) { for (int i = nums.length - 1; i >= 0; i--) { if (nums[i] == 0) { int j = i; while (j < nums.length - 1 && nums[j + 1] != 0) { nums[j] = nums[j + 1]; nums[j + 1] = 0; j++; } } } }
思路2: (discuss)把非0数放在头,其余位置补齐0(0ms)
public void moveZeroes(int[] nums) { int nonzero = 0; for (int num : nums) { if (num != 0) { nums[nonzero] = num; nonzero++; } } //补全0 while (nonzero < nums.length) { nums[nonzero] = 0; nonzero++; } }
思路3: (discuss)一指针一直动判断是否为0,另一指针停留在非0头部的下一位处等待非0数的出现,从而进行交换(0ms)
public void moveZeroes(int[] nums) { //指向非0数的指针 int j = 0; for (int i = 0; i < nums.length; i++) { if (nums[i] != 0) { int temp = nums[j]; nums[j] = nums[i]; nums[i] = temp; j++; } } }
2016-12-15
268.消失的数
要求:数组包含n个数,从0...n取值,找出缺失的那个值,时间复杂度要线性,空间复杂度恒定
思路1: (九章答案+自己想)把下标和值对应起来,等于是排序,然后判断下标和值不同的就是缺失项(对应448的思路3)
warn:记得用while,直到下标和值能对应,由于没有n这个下标,所以要排除这种情况,前面的都没缺失就返回n
public int missingNumber(int[] nums) { int n = nums.length; for (int i = 0; i < n; i++) { //先排除n的情况,因为超出了长度 while (nums[i] != i && nums[i] < n) { int t = nums[i]; nums[i] = nums[t]; nums[t] = t; } } for (int i = 0; i < n; i++) { if (nums[i] != i) { return i; } } return n; }
思路2: (discuss)利用数学,比如求和,下标与值求异或(a ^ b ^ b = a,剩下单个的)
求和法:不缺失数组和(n+1项)减去缺失数组和(n项)
public int missingNumber(int[] nums) { int n = nums.length; int s = n * (n + 1) / 2; int sum = 0; for (int i = 0; i < n; i++) { sum += nums[i]; } return s - sum; }
异或法:先设xor为n,数组有n时与n抵消可以输出缺失那一项,无n时就代表缺少n
public int missingNumber(int[] nums) { //下标没有n项,所以把xor先设为n int xor = nums.length; for (int i = 0; i < nums.length; i++) { xor = xor ^ i ^ nums[i]; } //数组的值有n时与原始xor抵消,输出缺失的另一项,无n就输出n return xor; }
2016-12-15
238.排除自己后的乘积
要求:output数组上每个位置的值等于由原数组num除该位置外的所有数的乘积,不能使用除法,复杂度为O(n)
思路: (discuss)分成位置的两边相乘
tips:从左往右乘的时候,令output[0]为1,此后就可以把output[i - 1]作为是之前i - 1项的积,可重复利用;而从右往左时,第一个数已经是最终结果,而第二个数只需要乘一次...所以需要新的累积变量right
(其实也可以两个方向都用累积变量存储前面项的乘积,只不过左往右乘的时候可应用输出数组本身作为累积,更省空间)
public int[] productExceptSelf(int[] nums) { int n = nums.length; int[] output = new int[n]; output[0] = 1; //从i=1开始,先算nums[i]左边数的乘积 for (int i = 1; i < n; i++) { output[i] = output[i - 1] * nums[i - 1]; } //新的累加值,算右边的乘积 int right = 1; for (int i = n - 1; i >= 0; i--) { output[i] *= right; right *= nums[i]; } return output; }
2016-12-15
229.多数元素(众数)II
要求:在大小为n的数组中找出出现次数大于n / 3次的元素,用线性时间和O(1)空间
思路: (经典算法)“摩尔投票法”,这里的含义是先设置两个候选数,相同的时候就计1票,不同的时候就扣1票,当票数为0时,就换另一个候选数,并且计第1票;选好候选数之后,就真正计算各自的票数
tips:背下来思路...1、设候选数、计票器 2、确定候选数 3、真正计票 4、判断是否满足条件
warn:考虑空数组,数组长度为1,两个数相等的情况!!!
public List<Integer> majorityElement(int[] nums) { List<Integer> res = new ArrayList<Integer>(); int n = nums.length; if (nums == null || n == 0) { return res; } if (n == 1) { res.add(nums[0]); return res; } //设2个候选数,2个对应的计票器 int number1, number2, count1, count2; number1 = number2 = nums[0]; count1 = count2 = 0; //挑选候选数 for (int i = 0; i < n; i++) { if (nums[i] == number1) { count1++; } else if (nums[i] == number2) { count2++; } else if (count1 == 0) { //count减为0就换候选数 number1 = nums[i]; count1++; } else if (count2 == 0) { number2 = nums[i]; count2++; } else { count1--; count2--; } } //清空票数,计算两个候选数的真正票数 count1 = count2 = 0; for (int i = 0; i < n; i++) { if (nums[i] == number1) { count1++; } if (nums[i] == number2) { count2++; } } //判断票数是否达到n/3以上 if (count1 > n / 3) { res.add(number1); } //考虑两数相等的情况 if (count2 > n / 3 && number2 != number1) { res.add(number2); } return res; }
另:据说使用Map也行,但是空间复杂度比较大
2016-12-15
228.总结范围数
要求:数组不重复,出现连续的数列就用->表示,如0, 1, 2就变成了0->2,单个数不需改变
思路: (自写修改后AC)判断nums[i+1] == nums[i],若等于则while循环获得范围结尾处,不等于就直接输出当前。
warn:注意数组溢出,注意数组溢出,注意数组溢出!!!考虑数组长度0,1,以及n-1位置上的情况
public List<String> summaryRanges(int[] nums) { List<String> res = new ArrayList<String>(); int n = nums.length; if (nums == null || n == 0) { return res; } if (n == 1) { String s = String.valueOf(nums[0]); res.add(s); return res; } for (int i = 0; i < n - 1; i++) { if (nums[i + 1] == nums[i] + 1) { int j = i; while (i < n - 1 && nums[i + 1] == nums[i] + 1) { i++; } String s = nums[j] + "->" + nums[i]; res.add(s); } else { String s = String.valueOf(nums[i]); res.add(s); } } if (nums[n - 1] != nums[n - 2] + 1) { String s = String.valueOf(nums[n - 1]); res.add(s); } return res; }
2016-12-16
219.包含重复II
要求:给出数组和整数k,求是否在最大间隔j - i = k以内有重复数出现
思路1: (查HashSet用法后自写一次AC)根据HashSet特性,不能有重复数插入,让set长度为k + 1(头尾下标间隔为k,总共包含k + 1个数)
tips:超过长度删除数据时,虽set无序,但可根据输入数组的有序,来确定删除的是哪个数(输入nums[i]对应删除nums[i - k - 1])
public boolean containsNearbyDuplicate(int[] nums, int k) { if (nums == null || nums.length == 0 || k == 0) { return false; } Set<Integer> set = new HashSet<Integer>(); for (int i = 0; i <nums.length; i++) { //set长度超过k,则删除当前第1个 if (i > k) { //set无序,但可利用数组有序 set.remove(nums[i - 1 - k]); } //插入不了证明重复 if (!set.add(nums[i])) { return true; } } return false; }
思路2: (discuss)根据HashMap特性键不能重复,把数组的值当作map键,数组的下标当作map值,求长度时比较map值(对应数组下标)
tips:先判断是否已经存在再输入!!
public boolean containsNearbyDuplicate(int[] nums, int k) { if (nums == null || nums.length == 0 || k == 0) { return false; } Map<Integer, Integer> map = new HashMap(); for (int i = 0; i < nums.length; i++) { if (map.containsKey(nums[i])) { //get(nums[i])是找之前的 if (i - map.get(nums[i]) <= k) { return true; } } //调转键与值 map.put(nums[i], i); } return false; }
2016-12-16
217.包含重复
要求:找出数组是否有重复数
思路: (自写map和set的)与219一样,比较简单,只放了map代码
public boolean containsDuplicate(int[] nums) { if (nums == null || nums.length == 0) { return false; } Map<Integer, Integer> map = new HashMap<Integer, Integer>(); for (int i = 0; i < nums.length; i++) { if (map.containsKey(nums[i])) { return true; } map.put(nums[i], i); } return false; }
2016-12-16
216.组合和III
要求:用k个1-9的数,求所有加起来等于n的集合,每个集合中的元素不重复
思路: (经典算法)“BackTracking回溯算法”,满足条件就添加,不满足就继续递归
warn:背步骤...具体还没理解如何return,如何递归 backTracking:1、判断是否满足条件,是就添加并返回 2、循环:2.1、添加新循环元素 2.2、递归表达式 2.3、弹出最后一项
public List<List<Integer>> combinationSum3(int k, int n) { List<List<Integer>> ans = new ArrayList<>(); backTracking(ans, new ArrayList<Integer>(), k, 1, n); return ans; } private void backTracking(List<List<Integer>> ans, List<Integer> comb, int k, int start, int n) { //数组中有k个数并相加为n就返回 if (comb.size() == k && n == 0) { List<Integer> real = new ArrayList<Integer>(comb); ans.add(real); return; } for (int i = start; i <= 9; i++) { //添加新数 comb.add(i); //递归 backTracking(ans, comb, k, i+1, n-i); //弹出最后一位 comb.remove(comb.size() - 1); } }
2016-12-17
209.最小子数组之和
要求:给出含有n个正数的数组,用最短的子数组各项加起来能够大于等于给定的正数s,若找不到则返回0
思路1: (discuss)“Two Pointers”用两个指针和一个记录最短长度的min,首先第一个指针用来叠加,第二个指针用来大于s时减去前面的数,并且更新最短长度min,时间复杂度为O(n)(虽然用了两层while)
public int minSubArrayLen(int s, int[] nums) { if (nums == null || nums.length == 0) { return 0; } int i = 0, j = 0, sum = 0, min = Integer.MAX_VALUE; while (i < nums.length) { sum += nums[i++]; while (sum >= s) { //超过s就减去前面一位,保留最短长度 min = Math.min(min, i - j); sum -= nums[j++]; } } return min == Integer.MAX_VALUE ? 0 : min; }
思路2:(discuss)叠加和法
第一个循环先求出sums数组,其中sums[i] = sums[i - 1] + nums[i - 1],此数组i项代表前i项的累计和;
第二个循环用二分法查找sums[i]+s的位置end,即代表sums[end] - sums[i] = s,此时子数组长度是end-i,复杂度为O(nlogn)
public int minSubArrayLen(int s, int[] nums) { int[] sums = new int[nums.length + 1]; //求前i项和 for (int i = 1; i < sums.length; i++) { sums[i] = sums[i - 1] + nums[i - 1]; } int minLen = Integer.MAX_VALUE; for (int i = 0; i < sums.length; i++) { //二分法找累计和的差值为s的位置end int end = binarySearch(i + 1, sums.length - 1, sums[i] + s, sums); if (end == sums.length) break; if (end - i < minLen) minLen = end - i; } return minLen == Integer.MAX_VALUE ? 0 : minLen; } private int binarySearch(int lo, int hi, int key, int[] sums) { while (lo <= hi) { int mid = (lo + hi) / 2; if (sums[mid] >= key){ hi = mid - 1; } else { lo = mid + 1; } } return lo; }
2016-12-17
189.旋转数组
要求:旋转n长度的数组,位数为k,用三种以上方法,其中最好消耗O(1)额外空间(即原地)
思路1: (九章思路+自写)假如原数组为1234567,旋转度为3,变成5671234,就从第4、5位中间(nums.length - k处)分割,进行三次首尾反转,前面1234首尾反转为4321,后面变成765,再统一首尾反转一次
warn:注意k大于数组长度的情况,用k %= nums.length解决
public void rotate(int[] nums, int k) { if (nums == null || nums.length == 0) { return; } //防止k大于数组长度 k %= nums.length;以上是关于Leetcode分类刷题答案&心得的主要内容,如果未能解决你的问题,请参考以下文章