问题
| 序号 | 题目 | 完成 |
|---|---|---|
| 1. 两数之和 | ✅ | |
| 15. 三数之和 | ✅ | |
| 18. 四数之和 | ✅ | |
| 167. 两数之和 II - 输入有序数组 | ✅ | |
| 剑指 Offer II 007. 数组中和为 0 的三个数 | ✅ | |
| 剑指 Offer II 006. 排序数组中两个数字之和 | ✅ |
题解
解法
(1)利用hashmap缓存
正常的想法是把所有的元素都存到hashmap里,然后再遍历数组去但是又觉得这样做对重复元素处理的不对。不过后来又一想,如果有重复元素,正好后面的元素的位置覆盖了前面的。遍历到第一个的时候,拿到的target正好是第二个重复的。
(2)优化
看了官方题解,还是有点巧妙的,可以只遍历一次。
// 两次遍历
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> idxMap = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
idxMap.put(nums[i], i);
}
int[] ans = new int[2];
for (int i = 0; i < nums.length; i++) {
if (idxMap.containsKey(target - nums[i])) {
int idx = idxMap.get(target - nums[i]);
if (idx != i) {
return new int[]{i, idxMap.get(target - nums[i])};
}
}
}
return null;
}
}
// 一次循环解决问题,还可以解决自身重复的问题
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> idxMap = new HashMap<>();
int[] ans = new int[2];
for (int i = 0; i < nums.length; i++) {
int need = target - nums[i];
if (idxMap.containsKey(need)) {
return new int[]{i, idxMap.get(target - nums[i])};
} else {
idxMap.put(nums[i], i);
}
}
return null;
}
}
解法
有序数组就比较好解决了,第一时间想到用滑动指针。
class Solution {
public int[] twoSum(int[] numbers, int target) {
int left = 0;
int right = numbers.length - 1;
while (left < right) {
int sum = numbers[left] + numbers[right];
if (sum == target) {
return new int[]{left + 1, right + 1};
} else if (sum < target) {
left++;
} else {
right--;
}
}
return null;
}
}
解法
- a+b+c=0,其实就是a+b=-c,这就是一个两数之和问题。
- 但是增加了一个维度,复杂性也随之增加:
- 怎么选取target(从0开始),选择了target,left(target后一个元素开始)和right怎么选择(right就是右边界,可以确定就是数组的右边界)。
- 怎么去重:排序+相邻判断(
while(nums[left] == nums[left+1]))
时空复杂度
时间复杂度:O(n^2)
空间复杂度:O(n)
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> ans = new ArrayList<>();
int len = nums.length;
int left = 0;
Arrays.sort(nums);
while (left < len) {
List<List<Integer>> res = twoSum(nums, left + 1, -nums[left]);
ans.addAll(res);
// 对元素头去重
while ((left < len - 1) && (nums[left] == nums[left + 1])) {
left++;
}
// 继续下一个元素
left++;
}
return ans;
}
public List<List<Integer>> twoSum(int[] nums, int left, int target) {
List<List<Integer>> ans = new ArrayList<>();
int right = nums.length - 1;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == target) {
List<Integer> tmp = new ArrayList<>();
tmp.add(-target);
tmp.add(nums[left]);
tmp.add(nums[right]);
ans.add(tmp);
// 把相等的元素都去除
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
left++;
} else if (sum < target) {
// 把相等的元素都去除
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
left++;
} else {
// 把相等的元素都去除
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
right--;
}
}
return ans;
}
}
解法
- 四数之和可以转化为三数和为0的问题。
- 整数问题案例需要考虑越界的问题,int的范围:
-2147483648~2147483647
// 先自己写了一版,虽然通过了,但是时空复杂度有点高
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> ans = new ArrayList<>();
Arrays.sort(nums);
int len = nums.length;
int left = 0;
while (left < len) {
List<List<Integer>> res = threeSum(nums, left + 1, nums[left], target);
for (List<Integer> re : res) {
re.add(nums[left]);
}
ans.addAll(res);
// 对元素头去重
while ((left < len - 1) && (nums[left] == nums[left + 1])) {
left++;
}
// 继续下一个元素
left++;
}
return ans;
}
public List<List<Integer>> threeSum(int[] nums, int left, long parentNum, long target) {
List<List<Integer>> ans = new ArrayList<>();
int len = nums.length;
while (left < len) {
List<List<Integer>> res = twoSum(nums, left + 1, nums[left], target - parentNum);
ans.addAll(res);
// 对元素头去重
while ((left < len - 1) && (nums[left] == nums[left + 1])) {
left++;
}
// 继续下一个元素
left++;
}
return ans;
}
public List<List<Integer>> twoSum(int[] nums, int left, long parentNum, long target) {
List<List<Integer>> ans = new ArrayList<>();
int right = nums.length - 1;
while (left < right) {
long sum = nums[left] + nums[right] + parentNum;
if (sum == target) {
List<Integer> tmp = new ArrayList<>();
tmp.add((int) parentNum);
tmp.add(nums[left]);
tmp.add(nums[right]);
ans.add(tmp);
// 把相等的元素都去除
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
left++;
} else if (sum < target) {
// 把相等的元素都去除
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
left++;
} else {
// 把相等的元素都去除
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
right--;
}
}
return ans;
}
}
//labuladong的写法,用了递归,大体意思是一样的
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums);
return nSum(nums, 4, 0, target);
}
// target可能会溢出
public List<List<Integer>> nSum(int[] nums, int n, int left, long target) {
List<List<Integer>> ans = new ArrayList<>();
int len = nums.length;
if (n == 2) {
// 计算两数之和
int right = nums.length - 1;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == target) {
List<Integer> tmp = new ArrayList<>();
tmp.add(nums[left]);
tmp.add(nums[right]);
ans.add(tmp);
// 把相等的元素都去除
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
left++;
} else if (sum < target) {
// 把相等的元素都去除
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
left++;
} else {
// 把相等的元素都去除
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
right--;
}
}
return ans;
} else {
// 计算多数之和
while (left < len) {
List<List<Integer>> res = nSum(nums, n - 1, left + 1, target - nums[left]);
for (List<Integer> r : res) {
r.add(nums[left]);
ans.add(r);
}
// 对元素头去重
while ((left < len - 1) && (nums[left] == nums[left + 1])) {
left++;
}
// 继续下一个元素
left++;
}
}
return ans;
}
}
//leetcode submit region end(Prohibit modification and deletion)