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)。