题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
前置题目及其题解——1.两数之和
前置题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
前置题目分析:数组nums种不同位置的两个数之和等于target,两数可以相等。
前置题目题解1:len为数组长度,从0到len-2(倒数第二个元素)位置,依次作为第一个加数,选取后续元素为第二个加数,和为target则返回
class Solution {
public int[] twoSum(int[] nums, int target) {
int len = nums.length;
for(int i=0;i<len-1;i++){
for(int j=i+1;j<len;j++){
if(target == nums[i] + nums[j]){
return new int[]{i, j};
}
}
}
return new int[0];
}
}
前置题目题解2:
遍历整个数组,对于每次取到的数nums[i],如果HashMap中包含target-nums[i],返回i与target-nums[i]对应的下标(因此下标需要保存,这里我们可以放到map的value中)。不包含则将nums[i]放入map中。
考虑到map的key不能重复,否则会覆盖掉原本的value,而当出现相等两数和为target时,第二个数还未放入map就返回了,因此不冲突。
此外,HashMap的内部数据结构为Hash+链表/红黑树,在常数时间可以找到bucket,然后很短时间内找到对应的Entry。很短时间为>8次比较(链表长度大于8裂变)或log2(N)次比较,N为红黑树节点数。
class Solution {
public int[] twoSum(int[] nums, int target) {
int len = nums.length;
Map<Integer,Integer> map = new HashMap<>();
for(int i=0;i<len;i++){
if(map.containsKey(target-nums[i])){
return new int[]{map.get(target-nums[i]), i};
} else {
map.put(nums[i], i);
}
}
return new int[0];
}
}
题解——15. 三数之和
题目分析:
存在特殊情况,nums长度小于3返回为空;长度等于3且三数之和为0则返回这3个数,否则也返回空。
“不重复的三元组”的含义:对于三元组(a,b,c),不能再有元素种类和对应个数相同但顺序不同的三元组如(b,c,a)或(c,b,a)等。
如何保证不重复:
1.先使得数组整体有序,保证从前到后依次不放回取出的3个元素的大小顺序关系是唯一的。
2.对于三元组的每一位,不取和上一次相等的数据,不论=0成立与否。
对于三重循环的优化:在二重循环里边,直接将第三位的元素从最后一个开始取,当第三位元素的位置>第二位元素的位置时,如果三个数的和大于0,则第三位元素的位置持续左移。左移最终会出现三种情况:
①第三位元素和第二位元素重合。此时无论大于、小于还是等于0,均是使用了重复元素,说明在前两位确定的情况下,第三个数只能使三数之和大于0。
②第三位元素仍然大于第二位元素的位置,但和小于0了。说明前两位确定的情况下,第三个数只能使三数之和大于或小于0,再减小也只会使得和更小,左移第三位元素无济于事。
③第三位元素仍然大于第二位元素的位置,且和等于0。此时就可以将这三个元素保存并使第二位元素继续循环了。继续左移第三位元素,要么仍为0,元素重复,要么小于0,均没有意义。
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
int len = nums.length;
if(len < 3){
return res;
}
int sum = 0;
if(len == 3){
for (int i=0;i<len;i++){
sum += nums[i];
}
if (sum == 0){
List<Integer> list = new ArrayList<Integer>();
list.add(nums[0]);
list.add(nums[1]);
list.add(nums[2]);
res.add(list);
}
return res;
}
Arrays.sort(nums);
for(int first=0;first<len-2;first++){
if(first>0&& nums[first]==nums[first-1]){
continue;
}
int third = len-1;
for(int second = first+1;second<len-1;second++){
if(second > first+1 && nums[second]==nums[second-1]){
continue;
}
while (third > second && nums[first] + nums[second] + nums[third] > 0){
third--;
}
if(third == second){
break;
}
if(nums[first] + nums[second] + nums[third] == 0){
List<Integer> list = new ArrayList<Integer>();
list.add(nums[first]);
list.add(nums[second]);
list.add(nums[third]);
res.add(list);
}
}
}
return res;
}
}
引申题目之一——16. 最接近的三数之和
引申题目之一链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
引申题目之一分析:本题目不关心数组是否重复,而是关心三数之和与target的接近程度,说明重复的数组对结果无影响,为了简洁,不进行无意义的计算,仍然可以延续15题不重复的操作理念。知识在目标上变为接近target,那么有几点是不同的:
①当发生second=third的情况时,third+1必然不越界并且必然是>target的,此时的三数之和与target有比较的价值,看接近程度与之前保存的接近程度哪个更接近。
②存在和=target时,是最接近的,直接返回target即可,他也就是三数之和;
③当和<target时,也有比较的价值。
class Solution {
public int threeSumClosest(int[] nums, int target) {
int distance = Integer.MAX_VALUE, newDistance = Integer.MAX_VALUE;
int res = 0;
int len = nums.length;
if(len == 3){
return nums[0] + nums[1] + nums[2];
}
Arrays.sort(nums);
for(int first=0;first<len-2;first++){
if(first>0&& nums[first]==nums[first-1]){
continue;
}
int third = len-1;
for(int second = first+1;second<len-1;second++){
if(second > first+1 && nums[second]==nums[second-1]){
continue;
}
while (third > second && nums[first] + nums[second] + nums[third] > target){
third--;
}
if(third == second){
newDistance = Math.abs(nums[first] + nums[second] + nums[third+1] - target);
// 无效代码
// if (newDistance == 0){
// return target;
// }
if(newDistance < distance){
distance = newDistance;
res = nums[first] + nums[second] + nums[third+1];
}
break;
}
if(nums[first] + nums[second] + nums[third] == target){
return target;
}
// 小于
newDistance = Math.abs(nums[first] + nums[second] + nums[third] - target);
if(newDistance < distance){
distance = newDistance;
res = nums[first] + nums[second] + nums[third];
}
}
}
return res;
}
}
引申题目之二——18. 四数之和
引申题目之二链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
引申题目之二分析:
1.到了四数之和,理论上需要四重循环才能完成。
2.考虑到时间复杂度,可以在双重循环确定第一元素nums[first]和第二位元素nums[second]后,第三位初始值为nums[second+1],第四位初始值元素为nums[len-1],当和大于target时,第四位元素左移,和小于target时,第三位元素右移,当等于target时,记录数据,第三和第四位分别右移、左移至与当前数不同的位置,时刻注意first<second<third<fourth<=len-1。
3.在第一位元素确定后,与紧邻的后三位元素之和大于target,说明此情况下无解,直接跳出;与最后三位之和小于target时,说明第一位元素太小,需要右移。第二位元素同理,可以通过提前的边界条件判定减少后续处理。
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> res = new ArrayList<>();
int len = nums.length;
if(len < 4){
return res;
}
int onlyFourSum = 0;
if(len == 4){
for (int i=0;i<len;i++){
onlyFourSum += nums[i];
}
if (onlyFourSum == target){
List<Integer> list = new ArrayList<Integer>();
list.add(nums[0]);
list.add(nums[1]);
list.add(nums[2]);
list.add(nums[3]);
res.add(list);
}
return res;
}
Arrays.sort(nums);
for(int first=0;first<len-3;first++){
if(first>0&& nums[first]==nums[first-1]){
continue;
}
if(nums[first] + nums[first+1] + nums[first+2] + nums[first+3] > target){
break;
}
if(nums[first] + nums[len-1] + nums[len-2] + nums[len-3] < target){
continue;
}
for(int second = first+1;second<len-2;second++){
if(second > first+1 && nums[second]==nums[second-1]){
continue;
}
if(nums[first] + nums[second+1] + nums[second+2] + nums[second] > target){
break;
}
if(nums[first] + nums[len-1] + nums[len-2] + nums[second] < target){
continue;
}
int third = second+1, fourth = len-1;
while(third < fourth){
int sum = nums[first] + nums[second] + nums[third] + nums[fourth];
if(sum > target){
fourth--;
} else if (sum <target){
third++;
} else {
// 优化①相关代码
// List<Integer> list = new ArrayList<Integer>();
// list.add(nums[first]);
// list.add(nums[second]);
// list.add(nums[third]);
// list.add(nums[fourth]);
res.add(Arrays.asList(nums[first], nums[second], nums[third], nums[fourth]));
while(third < fourth && nums[third] == nums[third+1]){
third++;
}
third++;
while(third < fourth && nums[fourth] == nums[fourth-1]){
fourth--;
}
fourth--;
}
}
}
}
return res;
}
}
引申题目之二优化:
①new ArrayList<Integer>();会占用较多空间,因此使用Arrays.asList。
②nums=[0,0,0,1000000000,1000000000,1000000000,1000000000],target=1000000000.输出
[]而预期是[[0,0,0,1000000000]]。int类型范围是-2,147,483,648~2,147,483,647。巧妙地调整计算顺序可以使数据始终保持在int的范围内,但时间紧急的话还是建议直接使用(long)向上转型。