001-ksum 求符合条件的 k 个数 1. Two Sum/15. 3Sum/18. 4Sum/
推荐阅读
001-01-ksum 求符合条件的 k 个数 1. Two Sum/15. 3Sum/18. 4Sum/
003-无重复字符的最长子串 Longest Substring Without Repeating Characters
005-最长回文子串 Longest Palindromic Substring
007-整数反转 reverse integer 整数的位运算汇总
008-Regular Expression Matching 正则表达式匹配 + 42.Wildcard Matching 通配符匹配
010-删除链表的倒数第 N 个结点 Remove Nth Node From End of List 双指针
011-21.合并多个有序的链表 merge k sorted lists
012-括号生成 generate-parentheses + 20. 有效的括号 valid parentheses + 32. 最长有效括号 Longest Valid Parentheses
013-K 个一组翻转链表 Reverse Nodes in k-Group + 24. 两两交换链表中的节点 swap nodes in pairs
015-串联所有单词的子串 Substring with Concatenation of All Words
018-34. 在排序数组中查找元素的第一个和最后一个位置 Find First and Last Position of Element in Sorted Array
019-36. 有效的数独 Valid Sudoku + 37. 解数独 sudoku solver

1. Two Sum 两数之和
题目
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
思路
最简单粗暴的一个双层循环:
(1)遍历第一层数组 nums[i]
(2)遍历第二层数组 nums[j],如果 nums[i] + nums[j] == t,符合。
基础解法
java 实现:
public int[] twoSumBasic(int[] nums, int target) {
for(int i = 0; i < nums.length; i++) {
for(int j = 0; j < nums.length; j++) {
// 每个元素只使用一次
if(i == j) {
continue;
}
if(nums[i] + nums[j] == target) {
return new int[]{i, j};
}
}
}
// 实际每个都有答案,不应该都到这里。
return null;
}
性能分析:
可谓惨不忍睹,为什么呢?
是否有比较好的优化思路?
Runtime: 167 ms, faster than 5.01% of Java online submissions for Two Sum.
Memory Usage: 41.3 MB, less than 10.92% of Java online submissions for Two Sum.
优化解法
优化思路:
借助 HashMap 数据结构,将原本 O(n) 的遍历,降低为 O(1) 的查询。
java 实现如下:
实现时注意 HashMap 的扩容问题,此处直接指定为和数组一样大。
public int[] twoSum(int[] nums, int target) {
int[] result = new int[2];
final int length = nums.length;
Map<Integer, Integer> map = new HashMap<>(length);
for(int i = 0; i < length; i++) {
int num = nums[i];
int targetKey = target - num;
if (map.containsKey(targetKey)) {
result[1] = i;
result[0] = map.get(targetKey);
return result;
}
map.put(num, i);
}
return result;
}
效果:
Runtime: 1 ms, faster than 99.93% of Java online submissions for Two Sum.
Memory Usage: 39.5 MB, less than 69.35% of Java online submissions for Two Sum.
15. 3Sum 三数之和
结束了第一道开胃菜之后,我们来看看第二道菜。
题目
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
粗暴解法
最简单的思路,我们直接来一个三层循环:
public List<List<Integer>> threeSum(int[] nums) {
if(nums.length < 3) {
return Collections.emptyList();
}
List<List<Integer>> results = new ArrayList<>();
Set<String> stringSet = new HashSet<>();
for(int i = 0; i < nums.length-2; i+=3) {
for(int j = i+1; j < nums.length-1; j++) {
for(int k = j+1; k < nums.length; k++) {
if((nums[i] + nums[j]+nums[k]) == 0) {
List<Integer> list = Arrays.asList(nums[i], nums[j], nums[k]);
// 排序保证结果不重复
Collections.sort(list);
String string = list.toString();
if(stringSet.contains(string)) {
continue;
}
stringSet.add(string);
results.add(list);
}
}
}
}
return results;
}
- 执行结果
很不幸,这个是不通过的。会执行超时,因为执行的比较慢。
优化解法
优化思路:
(1)对原始数组进行排序,保证可以使用双指针法
(2)固定 1 个元素。剩余的两个元素采用双指针的方式。初始化时,一个在最左边,一个在最右边。然后不断调整位置,直到符合条件为止。
(3)不可以包含重复的三元组,要同时跳过重复的信息。
java 实现:
public List<List<Integer>> threeSum(int[] nums) {
//1. 排序
Arrays.sort(nums);
List<List<Integer>> results = new ArrayList<>(nums.length);
//2. 双指针
for(int i = 0; i < nums.length; i++) {
int num = nums[i];
if(num > 0) {
return results;
}
if(i > 0 && nums[i] == nums[i-1]) {
continue;
}
int l = i+1;
int r = nums.length-1;
while (l < r) {
int sum = num + nums[l] + nums[r];
if(sum < 0) {
l++;
} else if(sum > 0) {
r--;
} else {
List<Integer> integers = new ArrayList<>(3);
integers.add(num);
integers.add(nums[l]);
integers.add(nums[r]);
results.add(integers);
// 跳过重复的元素
while(l < r && nums[l+1] == nums[l]) {
l++;
}
while (l < r && nums[r-1] == nums[r]) {
r--;
}
l++;
r--;
}
}
}
return results;
}
性能:
速度超过 99.87 的用户提交,还不错。
16. 最接近的三数之和
给你一个长度为 n 的整数数组 nums 和 一个目标值 target。
请你从 nums 中选出三个整数,使它们的和与 target 最接近。
返回这三个数的和。
假定每组输入只存在恰好一个解。
示例 1:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
示例 2:
输入:nums = [0,0,0], target = 1
输出:0
提示:
3 <= nums.length <= 1000
-1000 <= nums[i] <= 1000
-10^4 <= target <= 10^4
V1-双指针法
思路
针对多个数之和,对无序的数组进行一次排序,可以大大降低后续的时间复杂度。
我们通过两个指针,l r 分别计算每一次的差值,找到最小的差异。
当然,等于肯定最小,直接返回即可。
java 实现
/**
* 思路:
*
* 能否继续借助排序+双指针?
*
* 1. 如果相等,则直接返回
* 2. 否则需要保存最接近的一个值。
*
* 3. 如果差异越来越大,则直接停止。
*
* 使用 abs
*
* @param nums 数字
* @param target 目标值
* @return 结果
* @since v1
*/
public int threeSumClosest(int[] nums, int target) {
// 最小
if(nums.length == 3) {
return nums[0]+nums[1]+nums[2];
}
//1. 排序
Arrays.sort(nums);
//2. 双指针
int diff = Integer.MAX_VALUE;
for(int i = 0; i < nums.length; i++) {
int l = i+1;
int r = nums.length-1;
// 去重 i,l,r
while (l < r) {
// 此处可以直接返回
int sum = nums[i] + nums[l] + nums[r];
int loopDiff = sum-target;
if(sum == target) {
return target;
} else if(loopDiff < 0) {
// 偏小
l++;
if(Math.abs(loopDiff) < Math.abs(diff)) {
diff = loopDiff;
}
} else {
// 偏大
r--;
if(Math.abs(loopDiff) < Math.abs(diff)) {
diff = loopDiff;
}
}
}
}
return target+diff;
}
效果
Runtime: 5 ms, faster than 99.27% of Java online submissions for Container With Most Water.
Memory Usage: 39 MB, less than 100% of Java online submissions for Container With Most Water.
效果还是非常不错的。
V2-优化
思路
针对上面的算法进行优化。
能否继续借助排序+双指针?
最大值如果依然小于原有差异,跳过
最小值如果依然大于原有差异,跳过。
直接先把可能有结果的大概范围找到,然后再进一步细化,快速定位结果。
java 实现
public int threeSumClosest(int[] nums, int target) {
// 最小
int result = nums[0] + nums[1] + nums[2];
//1. 排序
Arrays.sort(nums);
//2. 双指针
for(int i = 0; i < nums.length-2; i++) {
int l = i+1;
int r = nums.length-1;
if (nums[i] + nums[i+1] + nums[i+2] - target >= Math.abs(target - result)) {
break; //Too big, can't get better result!
}
if (i < nums.length-3 && nums[i+1] + nums[nums.length-2] + nums[nums.length-1] < target) {
continue; //Too small, skip
}
while (l < r) {
// 此处可以直接返回
int sum = nums[i] + nums[l] + nums[r];
// 如果差异较小
if(Math.abs(sum-target) < Math.abs(result-target)) {
result = sum;
} else if(sum < target) {
// 偏小
l++;
} else if(sum > target) {
r--;
} else {
return sum;
}
}
}
return result;
}
效果
Runtime: 1 ms, faster than 100.00% of Java online submissions for 3Sum Closest.
Memory Usage: 38.9 MB, less than 85.21% of Java online submissions for 3Sum Closest.
18. 4Sum 四数之和
常言道,有二有三必须有四。
这道题当然还有四个数之和的版本。
题目
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:
答案中不可以包含重复的四元组。
示例:
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。
满足要求的四元组集合为:
[
[-1, 0, 0, 1],
[-2, -1, 1, 2],
[-2, 0, 0, 2]
]
解法
解题思路类似上述的 3 个数之和,区别是这次我们固定左边 2 个元素。
java 实现如下:
public List<List<Integer>> fourSum(int[] nums, int target) {
// 大小可以优化
List<List<Integer>> resultList = new ArrayList<>(nums.length);
//1. 排序
Arrays.sort(nums);
//2. 类似双指针,固定左边2个元素。
for(int i = 0; i < nums.length -3; i++) {
// 跳过 i 的重复元素
if(i > 0 && nums[i] == nums[i-1]) {
continue;
}
for(int j = i+1; j < nums.length-2; j++) {
// 确保跳过 j 的重复元素
if(j > i+1 && nums[j] == nums[j-1]) {
continue;
}
// 双指针法则
int l = j+1;
int r = nums.length-1;
while (l < r) {
int sum = nums[i]+nums[j]+nums[l]+nums[r];
// 遍历完所有符合的信息
if(sum < target) {
l++;
} else if(sum > target) {
r--;
} else {
List<Integer> result = Arrays.asList(nums[i], nums[j], nums[l], nums[r]);
resultList.add(result);
// 跳过重复的元素
while (l < r && nums[l] == nums[l+1]) {
l++;
}
while(l < r && nums[r] == nums[r-1]) {
r--;
}
l++;
r--;
}
}
}
}
return resultList;
}
经过 3sum 之后,你对自己的实现信心十足。
效果对比打败了 49.10% 的用户。WTF!
其实答案也很简单,因为大家和你一样已经学会了这种解法。
那么,我们还能优化吗?
优化方法
思路:
主要是可以快速返回的一些优化
(1)对于长度小于 4 的数组,直接返回
(2)如果在当前循环中 max 小于 target,或者 min 大于 target 其实也可以快速跳过。
java 实现:
public List<List<Integer>> fourSum(int[] nums, int target) {
//1.1 快速返回
if(nums.length < 4) {
return Collections.emptyList();
}
// 大小可以优化
List<List<Integer>> resultList = new ArrayList<>(nums.length);
//2. 排序
Arrays.sort(nums);
//1.2 范围判断
final int length = nums.length;
int min = nums[0] + nums[1] + nums[2] + nums[3];
int max = nums[length-1] + nums[length-2] + nums[length-3] + nums[length-4];
if(min > target || max < target) {
return resultList;
}
//3. 类似双指针,固定左边2个元素。
for(int i = 0; i < length -3; i++) {
// 跳过 i 的重复元素
if(i > 0 && nums[i] == nums[i-1]) {
continue;
}
for(int j = i+1; j < length-2; j++) {
// 确保跳过 j 的重复元素
if(j > i+1 && nums[j] == nums[j-1]) {
continue;
}
// 双指针法则
int l = j+1;
int r = length-1;
// 快速跳过
int minInner = nums[i] + nums[j] + nums[j+1] + nums[j+2];
int maxInner = nums[i] + nums[j] + nums[r-1] + nums[r];
if(minInner > target || maxInner < target) {
continue;
}
while (l < r) {
int sum = nums[i]+nums[j]+nums[l]+nums[r];
// 遍历完所有符合的信息
if(sum < target) {
l++;
} else if(sum > target) {
r--;
} else {
List<Integer> result = Arrays.asList(nums[i], nums[j], nums[l], nums[r]);
resultList.add(result);
// 跳过重复的元素
while (l < r && nums[l] == nums[l+1]) {
l++;
}
while(l < r && nums[r] == nums[r-1]) {
r--;
}
l++;
r--;
}
}
}
}
return resultList;
}
效果:
超过了 92.32% 的提交,勉强过关。
Runtime: 5 ms, faster than 92.21% of Java online submissions for 4Sum.
Memory Usage: 39.9 MB, less than 56.17% of Java online submissions for 4Sum.
ksum
题目
经过了上面的 2sum/3sum/4sum 的洗礼,我们现在将这道题做下推广。
如何求 ksum?
思路
其实所有的这种解法都可以转换为如下的两个问题:
(1)sum 问题
(2)将 k sum 问题转换为 k-1 sum 问题
示例代码
/**
* 对 k 个数进行求和
* @param nums 数组
* @param target 目标值
* @param k k
* @param index 下标
* @return 结果类表
* @since v1
*/
public List<List<Integer>> kSum(int[] nums, int target, int k, int index) {
int len = nums.length;
List<List<Integer>> resultList = new ArrayList<>();
if (index >= len) {
return resultList;
}
if (k == 2) {
int i = index, j = len - 1;
while (i < j) {
//find a pair
if (target - nums[i] == nums[j]) {
List<Integer> temp = new ArrayList<>();
temp.add(nums[i]);
temp.add(target - nums[i]);
resultList.add(temp);
//skip duplication
while (i < j && nums[i] == nums[i + 1]) {
i++;
}
while (i < j && nums[j - 1] == nums[j]) {
j--;
}
i++;
j--;
//move left bound
} else if (target - nums[i] > nums[j]) {
i++;
//move right bound
} else {
j--;
}
}
} else {
for (int i = index; i < len - k + 1; i++) {
//use current number to reduce ksum into k-1 sum
List<List<Integer>> temp = kSum(nums, target - nums[i], k - 1, i + 1);
if (temp != null) {
//add previous results
for (List<Integer> t : temp) {
t.add(0, nums[i]);
}
resultList.addAll(temp);
}
while (i < len - 1 && nums[i] == nums[i + 1]) {
//skip duplicated numbers
i++;
}
}
}
return resultList;
}
开源地址
为了便于大家学习,所有实现均已开源。欢迎 fork + star~
参考资料
https://leetcode-cn.com/problems/two-sum
https://leetcode.com/problems/two-sum/discuss/3/Accepted-Java-O(n)-Solution
https://leetcode-cn.com/problems/3sum
https://leetcode-cn.com/problems/4sum
https://leetcode.com/problems/4sum/discuss/8609/My-solution-generalized-for-kSums-in-JAVA
001-ksum 求符合条件的 k 个数 1. Two Sum/15. 3Sum/18. 4Sum/的更多相关文章
- hdu 3641 数论 二分求符合条件的最小值数学杂题
http://acm.hdu.edu.cn/showproblem.php?pid=3641 学到: 1.二分求符合条件的最小值 /*================================= ...
- 谷歌面试题:输入是两个整数数组,他们任意两个数的和又可以组成一个数组,求这个和中前k个数怎么做?
谷歌面试题:输入是两个整数数组,他们任意两个数的和又可以组成一个数组,求这个和中前k个数怎么做? 分析: "假设两个整数数组为A和B,各有N个元素,任意两个数的和组成的数组C有N^2个元素. ...
- 找到n中最小的k个数
题目:n个数中,求最小的前k个数. 这道题在各个地方都看到过,在国内出现的频率也非常高. 面完阿里回来听说这道题又被考了,所以还是决定回来写一写,对于这种高频题...顺便再吐槽一下阿里的面试,我竟然一 ...
- 寻找最小(最大)的k个数
题目描述:输入n个整数,输出其中最小的k个元素. 例如:输入1,2,3,4,5,6,7,8这8个数字,则最小的4个数字为1,2,3,4. 思路1:最容易想到的方法:先对这个序列从小到大排序,然后输出前 ...
- 每天一道算法题(14)——N个降序数组,找到最大的K个数
题目: 假定有20个有序数组,每个数组有500个数字,降序排列,数字类型32位uint数值,现在需要取出这10000个数字中最大的500个. 思路 (1).建立大顶堆,维度为数组的个数,这里为20( ...
- 找出一堆数中最小的前K个数
描写叙述: 给定一个整数数组.让你从该数组中找出最小的K个数 思路: 最简洁粗暴的方法就是将该数组进行排序,然后取最前面的K个数就可以. 可是,本题要求的仅仅是求出最小的k个数就可以,用排序能够但显然 ...
- 求一个数组中最小的K个数
方法1:先对数组进行排序,然后遍历前K个数,此时时间复杂度为O(nlgn); 方法2:维护一个容量为K的最大堆(<算法导论>第6章),然后从第K+1个元素开始遍历,和堆中的最大元素比较,如 ...
- 求n个数中的最大或最小k个数
//求n个数中的最小k个数 public static void TestMin(int k, int n) { Random rd = new Ra ...
- 求给定数据中最小的K个数
public class MinHeap { /* * * Top K个问题,求给定数据中最小的K个数 * * 最小堆解决:堆顶元素为堆中最大元素 * * * */ private int MAX_D ...
- Excel中用countif和countifs统计符合条件的个数 good
countif单条件统计个数 1 就以下表为例,统计总分大于(包含等于)400的人数. 2 在J2单元格输入公式=COUNTIF(I2:I22,">=400") 3 回车 ...
随机推荐
- SQL 用 in 大于 1000 问题解决
-- 今天生成环境数据突然多,系统异常 解决方案(必须用in 业务情况),也可以用其他函数解决 union all 或者 exists 等 1:截取list List<Integer> ...
- Java基础学习:2、Java数据类型及逻辑运算符
1.数据类型: 数据类型范围: byte:-2^7 ~ 2^7-1,即-128 ~ 127.1字节.Byte.末尾加B short:-2^15 ~ 2^15-1,即-32768 ~ 32767.2字节 ...
- 树莓派4B的Node-Red编程(一)
一.树莓派烧写 二.Node-Red 环境搭建 (一)安装Node.js (二)安装Node-Red (三)启动服务:win+R输入CMD:输入Node-red. (四)进入浏览器127.0.0.1: ...
- element-ui跨行
1 <template> 2 <el-table :data="scheduleList" :span-method="objectSpanMethod ...
- 通达OA实施的小总结
1.表格中带有复选框的单独一行单元格(一行两列 表头在第一列 复选框在第二列),在手机上进行显示时,这一行会把行表头屏蔽掉.2.日历控件不要用自定义格式,要不部分手机上无法使用.但是有些手机只能进行填 ...
- Spring系列之基于环境抽象-10
目录 Bean 定义配置文件 使用 `@Profile` XML Bean 定义配置文件 激活配置文件 默认配置文件 `PropertySource` 使用`@PropertySource` Envi ...
- django rest 自定义返回数据接口和异常处理
参考 Django rest framework自定义返回数据格式 一.简介 drf 默认返回的异常格式是这样的 1 { 2 "username": [ 3 "该字段是必 ...
- 源码安装RocketMQ4.x可视化控制台详细教程
下载源码 https://github.com/apache/rocketmq-externals 进入 roccketmq-console cd roccketmq-console 编译打包 mvn ...
- web后端之连接mysql
1建立java enterprise项目 2在WEB-INF目录下建立lib目录把jdbc用的mysql-connector-java.jar包复制过来 3添加依赖 4编写class 或在 ...
- PHP操作MySQL批量Update的写法,各框架通用防注入版
使用别人的扩展遇到了问题,发现没有做SQL注入的处理.我又写了个轮子,根据自己需求扩展了下,有需要的小伙伴可以直接取用. 这里就直接粘贴源码了,会用PHPD ,基本都会如何把它运用到各个框架里的. 本 ...