# Java Leetcode中KSum算法问题详解
今天写了下Leetcode里面Ksum算法题,一个算法用不同的方法实现,确实非常累,特别是写到最后一种Hash查找实现的方法,看代码逻辑都是debug一点一点的看,这里奉劝各位,写不出来,出去走走,回来在写,也许灵感就来了呢?废话不多说,看正文。
关于Leetcode K sum问题的解法目前有三种,刚开始做题我使用的是暴力解法,提交到Leetcode的时候由于时间复杂度太高,提交不过,查看关于KSum问题的更多解法的时候,发现大家都在说,化解为2sum问题,和使用hash算法实现,于是马上着手实现,从时间复杂度上看,肯定是前两种时间复杂度更低,最后也能submit过。
这里发一下题目的链接:
https://leetcode.com/problems/3sum/
三种实现方法
- 暴力法
- 最终化解为2sum问题
- hash实现法
暴力法
这种方法就是采用K个for循环实现,这里就不多说了,直接上代码,相信接触过java的人都看得懂。这里暴力法是针对Leetcode的3Sum来实现的。
import java.util.ArrayList;
import java.util.List;
/**
* 暴力查找
*
*/
public class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> resultList = new ArrayList<List<Integer>>();
List<Integer> tempList = null;
int arrayLength = nums.length;
if (arrayLength < 3) {
return resultList;
}
System.out.print("[");
nums = sortArray(nums);
for (int i = 0; i < nums.length; ++i) {
System.out.print(nums[i] + " ");
}
System.out.print("]");
System.out.println();
int firstNum = 0, secondNum = 0, thirdNum = 0;
for (int k = 0; k < arrayLength - 2; ++k) {
firstNum = nums[k];
for (int j = k + 1; j < arrayLength - 1; ++j) {
secondNum = nums[j];
for (int i = j + 1; i < arrayLength; ++i) {
thirdNum = nums[i];
if ((firstNum + secondNum + thirdNum) == 0) {
tempList = new ArrayList<Integer>();
tempList.add(firstNum);
tempList.add(secondNum);
tempList.add(thirdNum);
if (!isContains(resultList, tempList)) {
resultList.add(tempList);
}
}
}
}
}
return resultList;
}
/**
* 排序,采用直接选择排序,从小到大排序
*
* @param arrays
* @return
*/
private int[] sortArray(int[] arrays) {
if (arrays.length == 0 || arrays.length == 1) {
return arrays;
}
int length = arrays.length, temptemp;
for (int i = 0; i < length - 1; ++i) {
for (int k = i + 1; k < length; ++k) {
if (arrays[i] > arrays[k]) {
temptemp = arrays[k];
arrays[k] = arrays[i];
arrays[i] = temptemp;
}
}
}
return arrays;
}
/**
* 判断是否包涵
*
* @param list
* @param testNums
* @return
*/
private boolean isContains(List<List<Integer>> list, List<Integer> testNums) {
boolean result = false;
boolean temp = false;
List<Integer> tempNums;
for (int i = 0; i < list.size(); ++i) {
tempNums = list.get(i);
if (tempNums.size() != testNums.size()) {
continue;
}
for (int k = 0; k < tempNums.size(); ++k) {
if (tempNums.get(k) != testNums.get(k)) {
temp = true;
break;
}
}
if (!temp) {
result = true;
return result;
}
temp = false;
}
return result;
}
}
递归最后化为2sum实现
这种方法其实就是在递归到2Sum问题实现,算法思想上没有什么好解释的,看代码吧,这里我在代码里面加了大量的注释,就不在这里啰嗦了。
/**
* 递归链式查找,Ksun问题
*
*/
public class HalfSolution {
//默认是2sum问题
private int kValue = 2;
// 注入k值,动态实现K值问题
public void setK(int k) {
kValue = k < 2 ? 2 : k;
}
/**
* 外部调用方法,公有权限
* @param nums
* @param target
* @return
*/
public List<List<Integer>> threeSum(int[] nums,int target) {
return kSum(nums, kValue, target);
}
/**
* 做一些判断、排序之类的的事情
* @param nums
* @param k
* @param target
* @return
*/
private List<List<Integer>> kSum(int[] nums, int k, int target) {
//返回结果数据集合,这里这样写是因为Leetcode接口要求
List<List<Integer>> resultList = new ArrayList<List<Integer>>();
// 结果集里面的每个结果元素
ArrayList<Integer> tempList = null;
// 判断
if (nums.length < k) {
return resultList;
} else if (nums.length == k) {
int sumValue = 0;
tempList = new ArrayList<Integer>();
for (int i = 0; i < nums.length; ++i) {
sumValue += nums[i];
tempList.add(nums[i]);
}
if (sumValue == target) {
resultList.add(tempList);
}
return resultList;
}
// 数组排序
Arrays.sort(nums);
recusionSolution(nums, k, target, 0, tempList, resultList);
return resultList;
}
/**
* 主方法
* @param nums 数组
* @param k 几个值的和
* @param sum 目标值
* @param index 索引开始值
* @param tempList 盛放元素结果集
* @param resultList 最终结果集合
*/
private void recusionSolution(int[] nums, int k, int sum, int index,
ArrayList<Integer> tempList, List<List<Integer>> resultList) {
// 判断是不是2sum了,做一些指针的相应移动
if (k == 2) {
int first = index;
int tail = nums.length - 1;
int first_last_value = 0;
int tail_last_value = 0;
boolean isMatch = false;
while (first < tail) {
if (nums[first] + nums[tail] == sum) {
if (isMatch && first_last_value == nums[first]) {
first++;
continue;
} else if (isMatch && tail_last_value == nums[tail]) {
tail--;
continue;
} else if (isMatch && tail_last_value == nums[tail]
&& first_last_value == nums[first]) {
first++;
tail--;
continue;
}
ArrayList<Integer> tempList_copy = (ArrayList<Integer>) tempList
.clone();
tempList.add(nums[first]);
tempList.add(nums[tail]);
resultList.add(tempList);
tempList = tempList_copy;
isMatch = true;
first_last_value = nums[first];
tail_last_value = nums[tail];
first++;
tail--;
} else if (nums[first] + nums[tail] > sum) {
tail--;
} else if (nums[first] + nums[tail] < sum) {
first++;
}
}
} else {
//递归
for (int i = index; i < nums.length - k; ++i) {
if (k == kValue) {
tempList = new ArrayList<Integer>();
}
if (i > 0 && nums[i - 1] == nums[i]) {
continue;
}
tempList.add(nums[i]);
recusionSolution(nums, k - 1, sum - nums[i], i + 1, tempList,
resultList);
}
}
return;
}
}
hash实现法
hash实现法,其实和化解为2sum问题类似,只是在最后查找一个数字的时候要用到hash查找,一般就是采用hashMap,HashTable实现,这里不推荐Hashtable,HashTable内部采用了同步关键字,效率是没有hashmap高的。
算法实现:采用hash容器承载数据,再采用递归达到K==1的位置。
/**
* 哈希算法实现,类似于两个目标数查找
*
* @author cWX404529
*
*/
public class HashSolution {
// 默认是两个值
private int kValue = 2;
// 注入K值
public void setK(int k) {
kValue = k < 2 ? 2 : k;
}
public List<List<Integer>> threeSum(int[] nums,int target) {
return kSum(nums, kValue,target);
}
private List<List<Integer>> kSum(int[] nums, int k, int target) {
// 结果数据集合
List<List<Integer>> resultList = new ArrayList<List<Integer>>();
// 临时结果数据集合
ArrayList<Integer> tempList = new ArrayList<Integer>();
;
if (k <= 0 || nums.length < k) {
return resultList;
} else if (nums.length == k) {
int sumValue = 0;
for (int i = 0; i < nums.length; ++i) {
sumValue += nums[i];
tempList.add(nums[i]);
}
if (sumValue == target) {
resultList.add(tempList);
}
return resultList;
}
// 数组排序
Arrays.sort(nums);
// HashMap承载数据
HashMap<Integer, Integer> hashMap = new HashMap<Integer, Integer>();
for (int i = 0; i < nums.length; ++i) {
// 存储数据到hash容器
hashMap.put(i, nums[i]);
}
recusionSolution(nums, k, target, 0, tempList, resultList, hashMap);
return resultList;
}
/**
* tempList_1相当于数据栈,有数据的入栈和出栈,add()和remove()都是配对出现
* @param nums 源数组
* @param k K值
* @param sum 目标值
* @param index 开始索引值
* @param tempList 承载临时数据容器
* @param resultList 返回结果
* @param hashMap1 数组的hash容器实现
*/
private void recusionSolution(int[] nums, int k, int sum, int index,
ArrayList<Integer> tempList, List<List<Integer>> resultList,
HashMap<Integer, Integer> hashMap1) {
int last_value = nums[index] - 1;
// 剔除的数据再返回上一次递归的时候,要还原数据,这里装载剔除的数据
HashMap<Integer, Integer> temp_HashMap__1 = new HashMap<Integer, Integer>();
// K值等于1时采用hash判断是否有值
if (k == 1) {
if (hashMap1.containsValue(sum)) { // 调用Hash容器方法
tempList.add(sum);
// 谢天谢地这里是深复制,不然要自己实现。复制一份数据放入结果集中
ArrayList<Integer> tempList_1 = (ArrayList<Integer>) tempList
.clone();
resultList.add(tempList_1);
tempList.remove(kValue - k);
}
} else if (k > 1) {
for (int i = index; i < nums.length; ++i) {
if (k == kValue) {
tempList = new ArrayList<Integer>();
}
hashMap1.remove(i);
// 调用Hash容器方法
temp_HashMap__1.put(i, nums[i]);
// 这里判断前面一个数据是否相同,如果相同就不用再进入判读,直接跳过
if (last_value == nums[i]) {
last_value = nums[i];
continue;
}
last_value = nums[i];
tempList.add(nums[i]);
if (hashMap1.size() > 0) {
recusionSolution(nums, k - 1, sum - nums[i], i + 1,
tempList, resultList, hashMap1);
}
tempList.remove(kValue - k);
last_value = nums[i];
}
}
// 归还临时剔除的数据,为下次递归做准备
hashMap1.putAll(temp_HashMap__1);
return;
}
}
总结
整个Ksum算法实现并不复杂,实现起来只要逻辑清楚,能很快的写出来,这里要提一下的是,HashMap的Clone()方法是深复制(谢天谢地,不然要自己实现了),包括ArrayList也是,感兴趣的可以自己写demo测试一下。网上也看别人用的Hashable实现,但是我看他貌似没有考虑到数据重复的问题,这一点大家要知道,源数据是可以重复的。