LeetCode
1. 两数之和 经典题
如果是直接返回数本身,可以先排序,然后再用双指针来做。
但题目要求返回的是序号,所以用map来存储每个数的下标,时间复杂度为O(n),空间复杂度为O(n)。
public int[] twoSum(int[] nums, int target) {if (nums == null) return null;Map<Integer, Integer> count = new HashMap();for (int i = 0; i < nums.length; i++) {if (count.get(target - nums[i]) != null) {return new int[] {count.get(target - nums[i]), i};}count.put(nums[i], i);}return null;}
18. 四数之和
首先想到的DFS,考虑到超时问题,一般需要剪枝处理。
先排好序,分三种情况,第一种是剩下的元素已经不足4个;第二种是剩下元素一定比target大,第三种是剩下元素一定比target小。
List<List<Integer>> res = new ArrayList();public List<List<Integer>> fourSum(int[] nums, int target) {if (nums == null) return null;Arrays.sort(nums);fourSum(nums, target, new ArrayList<Integer>(), 0);return res;}public void fourSum(int[] nums, int target, List<Integer> curList, int curIdx) {if (curList.size() == 4) {if (target == 0) {res.add(new ArrayList<Integer>(curList));}return ;}//剪枝if (nums.length - curIdx + 1 + curList.size() < 4) return ;if (curIdx < nums.length && (4 - curList.size()) * nums[curIdx] > target) return ;if ((4 - curList.size()) * nums[nums.length-1] < target) return ;Set<Integer> set = new HashSet();for (int i = curIdx; i < nums.length; i++) {if (set.contains(nums[i])) continue;set.add(nums[i]);curList.add(nums[i]);fourSum(nums, target-nums[i], curList, i+1);curList.remove(curList.size()-1);}}
官方答案思路:暴力四重循环优化,先使用两重循环枚举前两个数,然后在两重循环枚举到的数之后使用双指针枚举剩下的两个数。时间复杂度为O(n),空间复杂度为O(n)(排序可能需要额外数组)。
去重和剪枝的代码稍微麻烦点,容易出错,不建议写这种。
public List<List<Integer>> fourSum(int[] nums, int target) {List<List<Integer>> res = new ArrayList();if (nums == null) return null;Arrays.sort(nums);int length = nums.length;//第一重循环for (int i = 0; i < length - 3; i++) {//去重if (i > 0 && nums[i] == nums[i-1]) continue;//剪枝处理if (nums[i] + nums[i+1] + nums[i+2] + nums[i+3] > target) break;if (nums[i] + nums[length-3] + nums[length-2] + nums[length-1] < target) continue;//第二重循环for (int j = i+1; j < length - 2; j++) {//去重if (j > i + 1 && nums[j] == nums[j-1]) continue;//剪枝处理if (nums[i] + nums[j] + nums[j+1] + nums[j+2] > target) break;if (nums[i] + nums[j] + nums[length-2] + nums[length-1] < target) continue;//双指针int left = j + 1, right = length-1;while (left < right) {int sum = nums[i] + nums[j] + nums[left] + nums[right];if (sum == target) {res.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));while (left < right && nums[left] == nums[left+1]) {left++;}left++;} else if (sum < target) {left++;} else if (sum > target) {right--;}}}}return res;}
57. 和为s的两个数字(剑指 Offer )
题目已经排好序了,直接套模板就行。
public int[] twoSum(int[] nums, int target) {if (nums == null) return null;int l = 0, r = nums.length-1;while (l < r) {int sum = nums[l] + nums[r];if (sum == target) break;else if (sum < target) l++;else if (sum > target) r--;}return l == r ? null : (new int[] {nums[l], nums[r]});}
75. 颜色分类
将数组排列成红蓝白的顺序,假设0代表红,1代表蓝,2代表白。
比较容易想到的解法是计数排序,先一遍扫描出0,1,2的个数,然后在更改,但这样需要两遍扫描
现在要求只扫描一遍,且使用常数空间。
解法一:两个指针pt0,pt1分别指向已经排好序0的位置和1的位置(注意初始值都设为-1,因此是++pt)。根据当前遍历的值,可以分三种情况:
(1)如果当前值是0;这里比较特殊了。
如果pt0==pt1,意味着到现在为止还没有做过值是1的处理,交换指针i和指针pt0的值,一定是交换值2和0。因此,不仅需要移动pt0,而且还要移动pt1。此时由于nums[i] == 2,相当于不会走后面的语句了。
如果pt0<pt1,那么交换指针i和指针pt0的值,一定是交换值1和0。因此,不能直接i++继续遍历,而是还要继续走第2种情况,这就是为什么不写else if,而是直接写if。
(2)如果当前值是1;交换指针i和指针pt1的值,一定是交换值2和1。
(3)如果当前值是2;不需要做任何处理,只需要继续向后遍历,即i++。
public void sortColors(int[] nums) {if (nums == null) return ;int pt0 = -1, pt1 = -1;for (int i = 0; i < nums.length; i++) {if (nums[i] == 0) {if (pt0 == pt1) pt1++;swap(nums, ++pt0, i);}if (nums[i] == 1) {swap(nums, ++pt1, i);}}}public void swap(int[] nums, int i, int j) {int temp = nums[i];nums[i] = nums[j];nums[j] = temp;}
解法二:两个指针指向的是排好序0和2的位置,0放到最前面,2放到最后面。简单认为,pt0之前的都为0,pt2之后的都为2。
public void sortColors(int[] nums) {if (nums == null) return ;int pt0 = -1, pt2 = nums.length;for (int i = 0; i < pt2;i++) {while (i < pt2 && nums[i] == 2) {swap(nums, --pt2, i);}if (nums[i] == 0) {swap(nums, ++pt0, i);}}}
763. 划分字母区间
字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一个字母只会出现在其中的一个片段。返回一个表示每个字符串片段的长度的列表。
public List<Integer> partitionLabels(String S) {List<Integer> res = new ArrayList();int[] lastIndex = new int[26];for (int i = 0; i < S.length(); i++) {lastIndex[S.charAt(i)-'a'] = i;}int start = 0, end = 0;for (int i = 0; i < S.length(); i++) {int ch = S.charAt(i) - 'a';end = Math.max(end, lastIndex[ch]);if (end == i) {res.add(end - start + 1);start = ++end;}}return res;}
贪心+双指针:从左到右遍历字符串,同时维护当前片段的开始下标start和结束下标end,对于访问到的当前字符,如果字符的最后下标大于end,那么需要延长当前片段的结束下标end。
时间复杂度为O(n),空间复杂度为O(Σ),其中Σ=26。
925. 长按键入
public boolean isLongPressedName(String name, String typed) {if (name == null || typed == null) return true;int i = 0, j = 0;// char lastChar1 = name.charAt(i), lastChar2 = type.charAt(j);while (j < typed.length()) {if (i < name.length() && name.charAt(i) == typed.charAt(j)) {i++; j++;} else if (j > 0 && typed.charAt(j-1) == typed.charAt(j)) {j++;} else {return false;}}return i == name.length() && j == typed.length();}
这个题的边界容易出错,怎么写好这个循环还有点东西的。主要是找出正确的情况,其他情况全部返回false就行。
977. 有序数组的平方
非递减顺序排列的整数数组A,返回每个数字平方组成的新的非递减数组
一种解法是归并排序,先找到正负数的分界线,然后利用两边都是有序的性质,进行归并排序。
另一种解法是双指针,不需要判断越界等边界问题,代码更加优雅。
public int[] sortedSquares(int[] A) {if (A == null) return A;int n = A.length;int[] res = new int[n];int i = 0;for (; i < n; i++) {if (A[i] >= 0) break;res[n - i -1] = A[i] * A[i];}int i1 = n-i, i2 = i;for (int j = 0; j < n; j++) {if (i1 == n) {res[j] = A[i2] * A[i2++];continue;}if (i2 == n) break;res[j] = res[i1] < A[i2]*A[i2] ? res[i1++] : A[i2]*A[i2++];}return res;}public int[] sortedSquares(int[] A) {if (A == null) return A;int n = A.length;int[] res = new int[n];int i1 = 0, i2 = n-1;for (int j = n-1; j >= 0; j--) {int v1 = A[i1] * A[i1];int v2 = A[i2] * A[i2];if (v1 > v2) {res[j] = v1;i1++;} else {res[j] = v2;i2--;}}return res;}
时间复杂度都为O(n),空间复杂度都为O(n)。
