题型一、一个数组中的多数之和,求其等于某个数的所有可能(不能重复)
相关题目:1、11、15、16、18
解题思路:这类题用暴力算法最为简单直接,但作为中等题肯定没有这么简单。使用暴力算法时,有m个数之和就会有m个循环嵌套,其时间复杂度为O(n^m),往往会超时。这类题最好的解法是使用排序加双指针:先将数组从小到大排序,若要求m个数之和,则设立(m-1)个for循环嵌套,第(m-1)个数作为头指针,第m个数作为尾指针,前面(m-1)个数为从数组头开始依次递增1的关系,依次往后遍历;而第m个数则是从数组尾开始往前遍历。这样能保证在当前的最里层循环中,m数之和是从最大开始的(因为第m个数是从尾部开始往前遍历),只要当这m数之和大于目标数时,第m数的指针就往前移使总和减小,直到小于或等于目标数时或第m数的指针等于第(m-1)数的指针时移动停止。
这样的算法使循环少了一层,并且节省了一部分不必要的计算的时间。总体的时间复杂度为O(n^(m-1))
例题:
//15、三数之和,目标数为0
//排序+双指针
/*
* 当我们需要枚举数组中的两个元素时,如果我们发现随着第一个元素的递增,第二个元素是递减的,
* 那么就可以使用双指针的方法,将枚举的时间复杂度从 O(N^2)减少至 O(N)
*/
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> list = new ArrayList<List<Integer>>();
//数组排序
Arrays.sort(nums);
//i为最左边的指针,必须保证该指针后面至少有两个元素
for(int i=0;i<nums.length-2;i++) {
//当i为0或者i与前面遍历过的元素不同时进入循环(防止重复)
if(i==0 || nums[i]!=nums[i-1]) {
//k为最右边的指针
int k = nums.length-1;
//j为中间指针,必须保证j后至少有一个元素
for(int j=i+1;j<nums.length-1;j++) {
if(j==i+1 || nums[j]!=nums[j-1]) {
//在k>j的前提下,当三者之和大于0时指针k左移(左移三者之和必减小)
while(k>j && (nums[i]+nums[j]+nums[k]>0)) {
k--;
}
//在k不等于j前提下,当三者之和为0时在list中加入结果
if(k!=j && (nums[i]+nums[j]+nums[k]==0)) {
List<Integer> temp = new ArrayList<Integer>();
temp.add(nums[i]);
temp.add(nums[j]);
temp.add(nums[k]);
list.add(temp);
}
}
}
}
}
return list;
}
但是目前我的算法还是不太行,有待优化。
每天一道LeetCode题,冲!!!
题型二、根据要求将数组某些元素从原位上删除,不能使用额外数组空间
相关题目:26、27
解题思路:这类题我刚拿上来,第一反应就是将要删除元素的后面元素都向前移动一位,以达到原位删除的目的。虽然这样的做法最终也AC了,但是用时相当多,因为除了外层的循环要遍历寻找指定的元素位置之外,内层还要有一个循环将该元素后面的元素前移一位。最好的解法是用双指针,快指针用于遍历寻找指定的元素位置,慢指针则首先指向数组头部,只当快指针找到要保留的元素时,将该元素保存到慢指针位置上(慢指针位置上的原本元素已被快指针先遍历过,确定了是保留还是舍弃),然后慢指针后移。这样的算法只需一个循环即可搞定,时间复杂度为O(n)
例题:
//27、删除数组中所有指定元素
//双指针
public int removeDuplicates(int[] nums) {
//不重复的元素总数
int len = 0;
//当数组长度大于0时进入,否则输出长度为0的数组
if(nums.length>0) {
//len为慢指针,保存不重复元素
len = 1;
//i为快指针
for(int i=1;i<nums.length;i++) {
/*
* 如果nums[i]!=nums[i-1],说明i指针所在元素与其之前的元素都不同,
* 则将nums[i]赋值给nums[len],并将len后移
*/
if(nums[i]!=nums[i-1]) {
nums[len] = nums[i];
len++;
}
}
}
return len;
}
每天一道LeetCode题,冲!!!
题型三、给定一个目标值,要求找出数组中所有相加的结果等于该值的组合
相关题目:39、40
解题思路:需要找出所有可行解的题目,可用暴力递归+回溯。将数组、当前指针所在数组下标、离目标值的差作为递归函数的实参,每递归一次,指针和离目标值的差就做相应变化。
例题:
//39
class Solution1 {
//全局变量是根据自己想要的变化而变化
List<List<Integer>> res = new ArrayList<>(); //记录答案,为防止答案被修改,答案为全局变量
List<Integer> path = new ArrayList<>(); //记录路径,为防止路径被修改,路径为全局变量
public List<List<Integer>> combinationSum(int[] candidates, int target) {
dfs(candidates,0, target);
return res;
}
/**
* 形参是根据路径的变化而变化
* @param c 数组长度
* @param u 当前遍历的数组下标
* @param target target和已通过路径之和的差
*/
public void dfs(int[] c, int u, int target) {
//不合法解,从该路径返回
if(target < 0) return ;
//当该路径找到合法解时就从该路径返回
if(target == 0)
{
res.add(new ArrayList<Integer>(path));
return ;
}
for(int i = u; i < c.length; i++){
//若当前元素小于或等于target时将该元素纳入路径
if( c[i] <= target)
{
path.add(c[i]);
dfs(c,i,target - c[i]); // 因为可以重复使用,所以还是i
path.remove(path.size()-1); //回溯,恢复现场
}
}
}
}
每天一道LeetCode题,冲!!!