LeetCode上第一题关于两数之和的问题,从而扩展到3数之和,4数之和等,其中关于解决重复集合的问题是关键以及多层遍历是解题的关键,以下归纳并总结其中在解题的过程中遇到的问题。
1.两数之和
https://leetcode.com/problems/two-sum/
这是leetcode的第一道题,也是入门简单等级。其中不包含重复数字也只有一组唯一解,相对来说只要从一个下标的数字开始并从当前的下一个开始到结束找到有符合的即为结果。
注意:这里的数字范围包含负数,目标值也可能为负数,因此不能采用传统双指针的方式去解决。
最简单的方式即采用map存储出现过的数字及对应下标,map的get为O(1)。
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer,Integer> map=new HashMap<>();
for(int i=0;i<nums.length;i++){
int toFindVal=target-nums[i];
if (map.containsKey(toFindVal)){
return new int[]{i,map.get(toFindVal)};
}
map.put(nums[i],i);
}
return new int[]{};
}
}
而适用于双指针的场景,左指针往后一定是结果变大,右指针往前一定是结果变小。
15.三数之和
https://leetcode.com/problems/3sum/
从两个数字扩展到三个数字,并且不包含重复方案,且元素下标也不相等,但目标和为0
。依然可以利用两数之和的方式对每个元素找与之对应的相反数是否存在,并跳过连续相等的元素。
同时对于多于两个数的情况,需要先对数组进行排序,这样才可以保证从左往右或从右往左根据数据大小进行范围的缩小。
注意:约束中,数值的范围代表用和的方式不会存在int的溢出,因此不需要考虑用long
- -10^5 <= nums[i] <= 10^5
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
if (nums == null || nums.length < 3) {
return result;
}
Arrays.sort(nums);
if(nums[0]>0){
return result;
}
for (int i = 0; i < nums.length - 2;i++) {
twoSum(nums,i,result);
while (i+1<nums.length&& nums[i]==nums[i+1]){
i++;
}
}
return result;
}
public static void twoSum(int[] nums, int i, List<List<Integer>> list) {
int left = i + 1, right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum > 0) {
right--;
} else if (sum < 0) {
left++;
} else {
list.add(Arrays.asList(nums[i],nums[left],nums[right]));
while (left+1<right && nums[left] == nums[left+1]){
left++;
}
while (left <right-1 && nums[right] == nums[right-1]){
right--;
}
left++;
right--;
}
}
}
}
16.三数最接近目标和
https://leetcode.com/problems/3sum-closest/
这个与前面三数之和的问题比较相近,唯一不同是条件变为与target相差最小(包括与target相等)的三数之和的值。
同理,超过两数需要先对数组进行排序。并以某个元素开始,从它的下一个与数组最后一个的范围开始搜索。
约束条件中依然表示不会存在溢出的情况,可以不用考虑
- -1000 <= nums[i] <= 1000
- -10^4 <= target <= 10^4
class Solution {
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int[] res = new int[]{target > 0 ? Integer.MIN_VALUE : Integer.MAX_VALUE};
for (int i = 0; i < nums.length-2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
twoSum(nums, i, target,res);
if(res[0] == target){
return target;
}
}
return res[0];
}
public static void twoSum(int[] nums, int index, int target,int[] result) {
int left = index + 1, right = nums.length - 1;
while (left < right) {
int sum = nums[index] + nums[left] + nums[right];
if (sum == target) {
result[0] = target;
return ;
}
if (Math.abs(sum - target) < Math.abs(result[0] - target)) {
result[0] = sum;
}
if (sum > target) {
right--;
} else {
left++;
}
}
}
}
18.四数之和
https://leetcode.com/problems/4sum/
关键信息在于:四数、方案不重复、等于目标和、数值范围可能溢出等。在三数之和的基础上,我们如果解决溢出、叠加遍历并去重依然可以用相似的方案。
- -10^9 <= nums[i] <= 10^9
- -10^9 <= target <= 10^9
固定第一个元素,在依次选择下一个作为第二元素,并求解往后剩下的范围内与前两数组成的满足条件的方案。
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> list=new ArrayList<>();
Arrays.sort(nums);
for(int i=0;i<nums.length-3;i++){
if(i>0 && nums[i]==nums[i-1]){
continue;
}
for(int j=i+1;j<nums.length-2;j++){
if(j>i+1 && nums[j]==nums[j-1]){
continue;
}
int left = j+1,right=nums.length-1;
long remain= (long)target -nums[i]-nums[j];
while(left<right){
long curSum = nums[left]+nums[right];
if(curSum==remain){
list.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right]));
while(left+1<right && nums[left]==nums[left+1]){
left++;
}
left++;
}else if(curSum<remain){
left++;
}else{
right--;
}
}
}
}
return list;
}
}
到四数后,我们发现需要叠加的遍历层数开始变的多了起来,需要O(n^3)。那如果还要更多的K数该怎么做呢?
K数之和
从四数之和的解法来看,需要对更多个数字的和这类问题有较好的回溯方式,否则只能无限的叠加遍历。
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> list=new ArrayList<>();
if(nums.length<4){
return list;
}
Arrays.sort(nums);
list =kSum(nums,target,4,0,nums.length);
return list;
}
public List<List<Integer>> kSum(int[] nums, long target,int k,int begin,int end) {
List<List<Integer>> list=new ArrayList<>();
if(begin>= end){
return list;
}
if(k==2){
return twoSum(nums,target,begin,end);
}
for (int i = begin; i <= end-k; ++i) {
if (i > begin && nums[i - 1] == nums[i]) {
continue;
}
List<List<Integer>> temp = kSum(nums, target - nums[i], k - 1, i + 1, end);
for (List<Integer> it : temp) {
List<Integer> newList = new ArrayList<>(Arrays.asList(nums[i]));
newList.addAll(it);
list.add(newList);
}
}
return list;
}
public List<List<Integer>> twoSum(int[] nums,long target,int start,int end){
List<List<Integer>> res = new ArrayList<>();
int lo = start, hi = end - 1;
while (lo < hi) {
long currSum = nums[lo] + nums[hi];
if (currSum < target || lo > start && nums[lo] == nums[lo - 1]) {
++lo;
} else if (currSum > target || hi < end - 1 && nums[hi] == nums[hi + 1]) {
--hi;
} else {
res.add(Arrays.asList(nums[lo], nums[hi]));
lo++;
hi--;
}
}
return res;
}
}